Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32df083e87 | ||
|
|
58d563805f | ||
|
|
cfd8c42f07 | ||
|
|
0b2e766609 | ||
|
|
10f895b8b6 | ||
|
|
66abc05c46 | ||
|
|
8a8eec4247 | ||
|
|
abd3eacadf | ||
|
|
54a1c86d6a | ||
|
|
ba73988750 | ||
|
|
bdc80d8e82 | ||
|
|
9f013b7a51 | ||
|
|
e4fc33f331 | ||
|
|
4a49e52b6d | ||
|
|
7a293d768c | ||
|
|
72c2b3e916 | ||
|
|
12084af163 | ||
|
|
add33c5995 | ||
|
|
03303d73f0 | ||
|
|
18e1d3e1b8 | ||
|
|
820fa28543 | ||
|
|
f44585657c | ||
|
|
2586284dce | ||
|
|
ce048a507a | ||
|
|
2156f99c2a | ||
|
|
daf311f63a | ||
|
|
003de2d85e | ||
|
|
7bb9535f60 | ||
|
|
3b80473f2f | ||
|
|
99d2b731f5 | ||
|
|
515249d2c3 | ||
|
|
a40d957ad9 | ||
|
|
5a06503d4a | ||
|
|
49f7cc7704 | ||
|
|
024d090122 | ||
|
|
36c56e71b6 | ||
|
|
9e361727b8 | ||
|
|
6d4cb0f4e2 | ||
|
|
2847681f7b | ||
|
|
4c774601ec | ||
|
|
b3b02416a3 | ||
|
|
be1f3f32f0 | ||
|
|
66f517d5b2 | ||
|
|
c339bc3682 | ||
|
|
49f96d7f1e | ||
|
|
b49ba6bdfe | ||
|
|
58d9445139 | ||
|
|
86751985e9 | ||
|
|
b57a7c1d49 | ||
|
|
ac6968cc10 | ||
|
|
92ad4ca511 | ||
|
|
1899b49d25 | ||
|
|
aadf06c493 | ||
|
|
e9e6605862 | ||
|
|
ed13318209 | ||
|
|
e5ed8a10ed | ||
|
|
8b0769ce75 | ||
|
|
2229c24bf5 | ||
|
|
a6b19f40b2 | ||
|
|
d91edcffdf | ||
|
|
4c1d0127b6 |
67
.github/ionic-issue-bot.yml
vendored
@@ -56,14 +56,6 @@ closeAndLock:
|
||||
bug reports and feature requests. Please use our [forum](https://forum.ionicframework.com) for questions about the framework.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: appflow"
|
||||
message: >
|
||||
Thanks for the issue! This issue appears to be related to Ionic Appflow. We use this issue tracker exclusively for
|
||||
bug reports and feature requests. Please use the [Ionic Appflow Support Forum](https://ionic.zendesk.com/hc/en-us/requests/new)
|
||||
to report this issue.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: missing template"
|
||||
message: >
|
||||
@@ -145,65 +137,6 @@ noReproduction:
|
||||
lock: true
|
||||
dryRun: false
|
||||
|
||||
wrongRepo:
|
||||
repos:
|
||||
- label: "ionitron: capacitor"
|
||||
repo: capacitor
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Framework. It appears that this issue is associated with Capacitor.
|
||||
I am moving this issue to the Capacitor repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: v3"
|
||||
repo: ionic-v3
|
||||
message: >
|
||||
Thanks for the issue! We have moved the source code and issues for Ionic 3 into a separate repository.
|
||||
I am moving this issue to the repository for Ionic 3. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: cli"
|
||||
repo: ionic-cli
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Framework. It appears that this issue is associated with the Ionic CLI.
|
||||
I am moving this issue to the Ionic CLI repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: docs"
|
||||
repo: ionic-docs
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Framework. It appears that this issue is associated with the Ionic Documentation.
|
||||
I am moving this issue to the Ionic Docs repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: stencil"
|
||||
repo: stencil
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Framework. It appears that this issue is associated with Stencil.
|
||||
I am moving this issue to the Stencil repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
- label: "ionitron: native"
|
||||
repo: ionic-native
|
||||
message: >
|
||||
Thanks for the issue! We use this issue tracker exclusively for bug reports and feature requests
|
||||
associated with the Ionic Framework. It appears that this issue is associated with Ionic Native.
|
||||
I am moving this issue to the Ionic Native repository. Please track this issue over there.
|
||||
|
||||
|
||||
Thank you for using Ionic!
|
||||
close: true
|
||||
lock: true
|
||||
dryRun: false
|
||||
|
||||
screenshot:
|
||||
appId: 18001
|
||||
checkName: "build"
|
||||
|
||||
@@ -3,9 +3,9 @@ description: 'Build Ionic Angular Server'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -3,9 +3,9 @@ description: 'Build Ionic Angular'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -8,10 +8,10 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
10
.github/workflows/actions/build-core/action.yml
vendored
@@ -8,10 +8,10 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- name: Install Dependencies
|
||||
run: npm install
|
||||
working-directory: ./core
|
||||
@@ -31,4 +31,6 @@ runs:
|
||||
with:
|
||||
name: ionic-core
|
||||
output: core/CoreBuild.zip
|
||||
paths: core/dist core/components core/css core/hydrate core/loader core/src/components.d.ts core/api.txt
|
||||
# Include generated proxy files from Stencil output targets so
|
||||
# framework builds can detect when they need to be updated
|
||||
paths: core/dist core/components core/css core/hydrate core/loader core/src/components.d.ts core/api.txt packages/angular/src/directives/proxies.ts packages/angular/src/directives/proxies-list.ts packages/angular/standalone/src/directives/proxies.ts packages/vue/src/proxies.ts packages/react/src/components/proxies.ts packages/react/src/components/inner-proxies.ts packages/react/src/components/routing-proxies.ts
|
||||
|
||||
@@ -3,9 +3,9 @@ description: 'Build Ionic React Router'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -3,9 +3,9 @@ description: 'Build Ionic React'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -3,9 +3,9 @@ description: 'Builds Ionic Vue Router'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -3,9 +3,9 @@ description: 'Build Ionic Vue'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -10,7 +10,7 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: ${{ inputs.name }}
|
||||
path: ${{ inputs.path }}
|
||||
|
||||
@@ -19,9 +19,9 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
# Provenance requires npm 9.5.0+
|
||||
- name: Install latest npm
|
||||
run: npm install -g npm@latest
|
||||
|
||||
@@ -6,9 +6,9 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -3,9 +3,9 @@ description: 'Test Core Clean Build'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
|
||||
@@ -3,9 +3,9 @@ description: 'Test Core Lint'
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
working-directory: ./core
|
||||
|
||||
@@ -13,9 +13,9 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
@@ -62,7 +62,7 @@ runs:
|
||||
working-directory: ./core
|
||||
- name: Archive Updated Screenshots
|
||||
if: inputs.update == 'true' && steps.test-and-update.outputs.hasUpdatedScreenshots == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: updated-screenshots-${{ inputs.shard }}-${{ inputs.totalShards }}
|
||||
path: UpdatedScreenshots-${{ inputs.shard }}-${{ inputs.totalShards }}.zip
|
||||
|
||||
@@ -6,9 +6,9 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
working-directory: ./core
|
||||
|
||||
@@ -6,9 +6,9 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -6,9 +6,9 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -6,9 +6,9 @@ inputs:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
node-version: 24.x
|
||||
- uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
name: ionic-core
|
||||
|
||||
@@ -7,10 +7,10 @@ on:
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 22.x
|
||||
- uses: actions/download-artifact@v5
|
||||
node-version: 24.x
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
path: ./artifacts
|
||||
- name: Extract Archives
|
||||
|
||||
@@ -13,7 +13,7 @@ runs:
|
||||
- name: Create Archive
|
||||
run: zip -q -r ${{ inputs.output }} ${{ inputs.paths }}
|
||||
shell: bash
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ inputs.name }}
|
||||
path: ${{ inputs.output }}
|
||||
|
||||
2
.github/workflows/assign-issues.yml
vendored
@@ -13,6 +13,6 @@ jobs:
|
||||
- name: 'Auto-assign issue'
|
||||
uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0
|
||||
with:
|
||||
assignees: brandyscarney, ShaneK
|
||||
assignees: brandyscarney, thetaPC, ShaneK
|
||||
numOfAssignee: 1
|
||||
allowSelfAssign: false
|
||||
|
||||
30
.github/workflows/build.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
build-core:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-core
|
||||
with:
|
||||
ionicons-version: ${{ inputs.ionicons_npm_release_tag }}
|
||||
@@ -31,21 +31,21 @@ jobs:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-clean-build
|
||||
|
||||
test-core-lint:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-lint
|
||||
|
||||
test-core-spec:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-spec
|
||||
|
||||
test-core-screenshot:
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-screenshot
|
||||
with:
|
||||
shard: ${{ matrix.shard }}
|
||||
@@ -90,14 +90,14 @@ jobs:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-vue
|
||||
|
||||
build-vue-router:
|
||||
needs: [build-vue]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-vue-router
|
||||
|
||||
test-vue-e2e:
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
needs: [build-vue, build-vue-router]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-vue-e2e
|
||||
with:
|
||||
app: ${{ matrix.apps }}
|
||||
@@ -126,14 +126,14 @@ jobs:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-angular
|
||||
|
||||
build-angular-server:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-angular-server
|
||||
|
||||
test-angular-e2e:
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
needs: [build-angular, build-angular-server]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-angular-e2e
|
||||
with:
|
||||
app: ${{ matrix.apps }}
|
||||
@@ -162,14 +162,14 @@ jobs:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-react
|
||||
|
||||
build-react-router:
|
||||
needs: [build-react]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-react-router
|
||||
|
||||
test-react-router-e2e:
|
||||
@@ -180,7 +180,7 @@ jobs:
|
||||
needs: [build-react, build-react-router]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-react-router-e2e
|
||||
with:
|
||||
app: ${{ matrix.apps }}
|
||||
@@ -202,7 +202,7 @@ jobs:
|
||||
needs: [build-react, build-react-router]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-react-e2e
|
||||
with:
|
||||
app: ${{ matrix.apps }}
|
||||
|
||||
6
.github/workflows/codeql-analysis.yml
vendored
@@ -14,8 +14,8 @@ jobs:
|
||||
permissions:
|
||||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: github/codeql-action/init@v3
|
||||
- uses: actions/checkout@v5
|
||||
- uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: javascript
|
||||
- uses: github/codeql-action/analyze@v3
|
||||
- uses: github/codeql-action/analyze@v4
|
||||
|
||||
2
.github/workflows/conventional-commit.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
if: |
|
||||
!contains(github.event.pull_request.title, 'release') &&
|
||||
!contains(github.event.pull_request.title, 'chore')
|
||||
uses: amannn/action-semantic-pull-request@v5
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
2
.github/workflows/dev-build.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
outputs:
|
||||
dev-hash: ${{ steps.create-dev-hash.outputs.DEV_HASH }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
# A 1 is required before the timestamp
|
||||
# as lerna will fail when there is a leading 0
|
||||
# See https://github.com/lerna/lerna/issues/2840
|
||||
|
||||
2
.github/workflows/label.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/labeler@v6
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
|
||||
2
.github/workflows/nightly.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
outputs:
|
||||
nightly-hash: ${{ steps.create-nightly-hash.outputs.NIGHTLY_HASH }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
# A 1 is required before the timestamp
|
||||
# as lerna will fail when there is a leading 0
|
||||
# See https://github.com/lerna/lerna/issues/2840
|
||||
|
||||
16
.github/workflows/release-ionic.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
release-core:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/publish-npm
|
||||
with:
|
||||
scope: '@ionic/core'
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
needs: [release-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Restore @ionic/docs built cache
|
||||
uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
needs: [release-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Restore @ionic/core built cache
|
||||
uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
needs: [release-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Restore @ionic/core built cache
|
||||
uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
needs: [release-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Restore @ionic/core built cache
|
||||
uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
@@ -147,7 +147,7 @@ jobs:
|
||||
needs: [release-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Restore @ionic/core built cache
|
||||
uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
needs: [release-react]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Restore @ionic/core built cache
|
||||
uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
@@ -194,7 +194,7 @@ jobs:
|
||||
needs: [release-vue]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Restore @ionic/core built cache
|
||||
uses: ./.github/workflows/actions/download-archive
|
||||
with:
|
||||
|
||||
4
.github/workflows/release.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
needs: [release-ionic]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
token: ${{ secrets.IONITRON_TOKEN }}
|
||||
fetch-depth: 0
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
needs: [finalize-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
# Pull the latest version of the reference
|
||||
# branch instead of the revision that triggered
|
||||
# the workflow otherwise we won't get the commit
|
||||
|
||||
62
.github/workflows/stencil-nightly.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
build-core-with-stencil-nightly:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-core-stencil-prerelease
|
||||
with:
|
||||
stencil-version: ${{ inputs.npm_release_tag || 'nightly' }}
|
||||
@@ -35,21 +35,21 @@ jobs:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-clean-build
|
||||
|
||||
test-core-lint:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-lint
|
||||
|
||||
test-core-spec:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-spec
|
||||
with:
|
||||
stencil-version: ${{ inputs.npm_release_tag || 'nightly' }}
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-screenshot
|
||||
with:
|
||||
shard: ${{ matrix.shard }}
|
||||
@@ -100,14 +100,14 @@ jobs:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-vue
|
||||
|
||||
build-vue-router:
|
||||
needs: [build-vue]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-vue-router
|
||||
|
||||
test-vue-e2e:
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
needs: [build-vue, build-vue-router]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-vue-e2e
|
||||
with:
|
||||
app: ${{ matrix.apps }}
|
||||
@@ -136,14 +136,14 @@ jobs:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-angular
|
||||
|
||||
build-angular-server:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-angular-server
|
||||
|
||||
test-angular-e2e:
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
needs: [build-angular, build-angular-server]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-angular-e2e
|
||||
with:
|
||||
app: ${{ matrix.apps }}
|
||||
@@ -172,14 +172,14 @@ jobs:
|
||||
needs: [build-core-with-stencil-nightly]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-react
|
||||
|
||||
build-react-router:
|
||||
needs: [build-react]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-react-router
|
||||
|
||||
test-react-router-e2e:
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
needs: [build-react, build-react-router]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-react-router-e2e
|
||||
with:
|
||||
app: ${{ matrix.apps }}
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
needs: [build-react, build-react-router]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-react-e2e
|
||||
with:
|
||||
app: ${{ matrix.apps }}
|
||||
@@ -225,3 +225,35 @@ jobs:
|
||||
- name: Check build matrix status
|
||||
if: ${{ needs.test-react-e2e.result != 'success' }}
|
||||
run: exit 1
|
||||
|
||||
send-success-messages:
|
||||
needs: [test-core-clean-build, test-core-lint, test-core-spec, verify-screenshots, verify-test-vue-e2e, verify-test-angular-e2e, verify-test-react-router-e2e, verify-test-react-e2e]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !cancelled() && contains(needs.*.result, 'success') }}
|
||||
steps:
|
||||
- name: Notify success on Discord
|
||||
run: |
|
||||
curl -H "Content-Type:application/json" \
|
||||
-d '{"embeds": [{"title": "✅ Workflow ${{github.workflow}} #${{github.run_number}} finished successfully", "color": 65280, "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}]}' \
|
||||
${{secrets.DISCORD_NOTIFY_WEBHOOK}}
|
||||
- name: Notify success on Slack
|
||||
run: |
|
||||
curl -H "Content-Type:application/json" \
|
||||
-d '{"title": "✅ Workflow ${{github.workflow}} #${{github.run_number}} finished successfully", "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}' \
|
||||
${{secrets.SLACK_NOTIFY_SUCCESS_WEBHOOK}}
|
||||
|
||||
send-failure-messages:
|
||||
needs: [test-core-clean-build, test-core-lint, test-core-spec, verify-screenshots, verify-test-vue-e2e, verify-test-angular-e2e, verify-test-react-router-e2e, verify-test-react-e2e]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !cancelled() && contains(needs.*.result, 'failure') }}
|
||||
steps:
|
||||
- name: Notify failure on Discord
|
||||
run: |
|
||||
curl -H "Content-Type:application/json" \
|
||||
-d '{"content": "Alerting <@&1347593178580254761>!", "embeds": [{"title": "❌ Workflow ${{github.workflow}} #${{github.run_number}} failed", "color": 16711680, "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}]}' \
|
||||
${{secrets.DISCORD_NOTIFY_WEBHOOK}}
|
||||
- name: Notify failure on Slack
|
||||
run: |
|
||||
curl -H "Content-Type:application/json" \
|
||||
-d '{"title": "❌ Workflow ${{github.workflow}} #${{github.run_number}} failed", "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}' \
|
||||
${{secrets.SLACK_NOTIFY_FAILURE_WEBHOOK}}
|
||||
|
||||
6
.github/workflows/update-screenshots.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
build-core:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/build-core
|
||||
|
||||
test-core-screenshot:
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
needs: [build-core]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: ./.github/workflows/actions/test-core-screenshot
|
||||
with:
|
||||
shard: ${{ matrix.shard }}
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-core-screenshot]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
# Normally, we could just push with the
|
||||
# default GITHUB_TOKEN, but that will
|
||||
# not cause the build workflow
|
||||
|
||||
83
CHANGELOG.md
@@ -3,6 +3,89 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.7.9](https://github.com/ionic-team/ionic-framework/compare/v8.7.8...v8.7.9) (2025-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **accordion-group:** skip initial animation ([#30729](https://github.com/ionic-team/ionic-framework/issues/30729)) ([58d5638](https://github.com/ionic-team/ionic-framework/commit/58d563805fca1db88caeeb40a8f710ac30416d93)), closes [#30613](https://github.com/ionic-team/ionic-framework/issues/30613)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.8](https://github.com/ionic-team/ionic-framework/compare/v8.7.7...v8.7.8) (2025-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **checkbox, toggle:** fire ionFocus and ionBlur ([#30733](https://github.com/ionic-team/ionic-framework/issues/30733)) ([54a1c86](https://github.com/ionic-team/ionic-framework/commit/54a1c86d6a5d533b0c8c2d18edc62454a7c17bab))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.7](https://github.com/ionic-team/ionic-framework/compare/v8.7.6...v8.7.7) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **header:** ensure one banner role in condensed header ([#30718](https://github.com/ionic-team/ionic-framework/issues/30718)) ([12084af](https://github.com/ionic-team/ionic-framework/commit/12084af163ed811b9c6bda3c7850fc0c53c60c7b))
|
||||
* **header:** prevent flickering during iOS page transitions ([#30705](https://github.com/ionic-team/ionic-framework/issues/30705)) ([820fa28](https://github.com/ionic-team/ionic-framework/commit/820fa2854331722d22efd0e38a1936117477967a)), closes [#25326](https://github.com/ionic-team/ionic-framework/issues/25326)
|
||||
* **select:** improve screen reader announcement timing for validation errors ([#30723](https://github.com/ionic-team/ionic-framework/issues/30723)) ([03303d7](https://github.com/ionic-team/ionic-framework/commit/03303d73f0bfe2380ced7931525fc52fd8576367))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.6](https://github.com/ionic-team/ionic-framework/compare/v8.7.5...v8.7.6) (2025-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tabs:** respect stencil lifecycle order for tab selection ([#30702](https://github.com/ionic-team/ionic-framework/issues/30702)) ([7bb9535](https://github.com/ionic-team/ionic-framework/commit/7bb9535f601d2469ce60687a9c03f8b1cfe4aba4)), closes [#30611](https://github.com/ionic-team/ionic-framework/issues/30611)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.5](https://github.com/ionic-team/ionic-framework/compare/v8.7.4...v8.7.5) (2025-09-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modal:** allow sheet modals to skip focus trap ([#30689](https://github.com/ionic-team/ionic-framework/issues/30689)) ([a40d957](https://github.com/ionic-team/ionic-framework/commit/a40d957ad9c1897af365a91b45b00228a00d614c)), closes [#30684](https://github.com/ionic-team/ionic-framework/issues/30684)
|
||||
* **vue:** emit component-specific overlay events ([#30688](https://github.com/ionic-team/ionic-framework/issues/30688)) ([024d090](https://github.com/ionic-team/ionic-framework/commit/024d090122548e26ec2cdcfae4637dde8f288278)), closes [#30641](https://github.com/ionic-team/ionic-framework/issues/30641)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.4](https://github.com/ionic-team/ionic-framework/compare/v8.7.3...v8.7.4) (2025-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** improve error text accessibility ([#30635](https://github.com/ionic-team/ionic-framework/issues/30635)) ([c339bc3](https://github.com/ionic-team/ionic-framework/commit/c339bc36827b62ef871325869a9a5db9b17ac785))
|
||||
* **overlays,picker:** remove invalid aria-hidden attribute ([#30563](https://github.com/ionic-team/ionic-framework/issues/30563)) ([49f96d7](https://github.com/ionic-team/ionic-framework/commit/49f96d7f1e9050a95e3e33a821c0467ecc0bed64)), closes [#30040](https://github.com/ionic-team/ionic-framework/issues/30040)
|
||||
* **segment-view:** scroll and select the right item when the component is in RTL context; ([#30675](https://github.com/ionic-team/ionic-framework/issues/30675)) ([66f517d](https://github.com/ionic-team/ionic-framework/commit/66f517d5b2154fff00b294a78f4107f057a580c6)), closes [#30079](https://github.com/ionic-team/ionic-framework/issues/30079)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.3](https://github.com/ionic-team/ionic-framework/compare/v8.7.2...v8.7.3) (2025-08-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **checkbox:** add aria attributes to ignore checkbox icon ([#30633](https://github.com/ionic-team/ionic-framework/issues/30633)) ([e9e6605](https://github.com/ionic-team/ionic-framework/commit/e9e6605862a05a46d26c26a144ed1cf22133a2b7)), closes [#30231](https://github.com/ionic-team/ionic-framework/issues/30231)
|
||||
* **refresher:** prevent focus-related scroll jumps on refresh ([#30636](https://github.com/ionic-team/ionic-framework/issues/30636)) ([1899b49](https://github.com/ionic-team/ionic-framework/commit/1899b49d252abc6003f763cea8db2a51efa941ec))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.2](https://github.com/ionic-team/ionic-framework/compare/v8.7.1...v8.7.2) (2025-08-06)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,88 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [8.7.9](https://github.com/ionic-team/ionic-framework/compare/v8.7.8...v8.7.9) (2025-11-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **accordion-group:** skip initial animation ([#30729](https://github.com/ionic-team/ionic-framework/issues/30729)) ([58d5638](https://github.com/ionic-team/ionic-framework/commit/58d563805fca1db88caeeb40a8f710ac30416d93)), closes [#30613](https://github.com/ionic-team/ionic-framework/issues/30613)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.8](https://github.com/ionic-team/ionic-framework/compare/v8.7.7...v8.7.8) (2025-10-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **checkbox, toggle:** fire ionFocus and ionBlur ([#30733](https://github.com/ionic-team/ionic-framework/issues/30733)) ([54a1c86](https://github.com/ionic-team/ionic-framework/commit/54a1c86d6a5d533b0c8c2d18edc62454a7c17bab))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.7](https://github.com/ionic-team/ionic-framework/compare/v8.7.6...v8.7.7) (2025-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **header:** ensure one banner role in condensed header ([#30718](https://github.com/ionic-team/ionic-framework/issues/30718)) ([12084af](https://github.com/ionic-team/ionic-framework/commit/12084af163ed811b9c6bda3c7850fc0c53c60c7b))
|
||||
* **header:** prevent flickering during iOS page transitions ([#30705](https://github.com/ionic-team/ionic-framework/issues/30705)) ([820fa28](https://github.com/ionic-team/ionic-framework/commit/820fa2854331722d22efd0e38a1936117477967a)), closes [#25326](https://github.com/ionic-team/ionic-framework/issues/25326)
|
||||
* **select:** improve screen reader announcement timing for validation errors ([#30723](https://github.com/ionic-team/ionic-framework/issues/30723)) ([03303d7](https://github.com/ionic-team/ionic-framework/commit/03303d73f0bfe2380ced7931525fc52fd8576367))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.6](https://github.com/ionic-team/ionic-framework/compare/v8.7.5...v8.7.6) (2025-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tabs:** respect stencil lifecycle order for tab selection ([#30702](https://github.com/ionic-team/ionic-framework/issues/30702)) ([7bb9535](https://github.com/ionic-team/ionic-framework/commit/7bb9535f601d2469ce60687a9c03f8b1cfe4aba4)), closes [#30611](https://github.com/ionic-team/ionic-framework/issues/30611)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.5](https://github.com/ionic-team/ionic-framework/compare/v8.7.4...v8.7.5) (2025-09-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **modal:** allow sheet modals to skip focus trap ([#30689](https://github.com/ionic-team/ionic-framework/issues/30689)) ([a40d957](https://github.com/ionic-team/ionic-framework/commit/a40d957ad9c1897af365a91b45b00228a00d614c)), closes [#30684](https://github.com/ionic-team/ionic-framework/issues/30684)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.4](https://github.com/ionic-team/ionic-framework/compare/v8.7.3...v8.7.4) (2025-09-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **input:** improve error text accessibility ([#30635](https://github.com/ionic-team/ionic-framework/issues/30635)) ([c339bc3](https://github.com/ionic-team/ionic-framework/commit/c339bc36827b62ef871325869a9a5db9b17ac785))
|
||||
* **overlays,picker:** remove invalid aria-hidden attribute ([#30563](https://github.com/ionic-team/ionic-framework/issues/30563)) ([49f96d7](https://github.com/ionic-team/ionic-framework/commit/49f96d7f1e9050a95e3e33a821c0467ecc0bed64)), closes [#30040](https://github.com/ionic-team/ionic-framework/issues/30040)
|
||||
* **segment-view:** scroll and select the right item when the component is in RTL context; ([#30675](https://github.com/ionic-team/ionic-framework/issues/30675)) ([66f517d](https://github.com/ionic-team/ionic-framework/commit/66f517d5b2154fff00b294a78f4107f057a580c6)), closes [#30079](https://github.com/ionic-team/ionic-framework/issues/30079)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.3](https://github.com/ionic-team/ionic-framework/compare/v8.7.2...v8.7.3) (2025-08-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **checkbox:** add aria attributes to ignore checkbox icon ([#30633](https://github.com/ionic-team/ionic-framework/issues/30633)) ([e9e6605](https://github.com/ionic-team/ionic-framework/commit/e9e6605862a05a46d26c26a144ed1cf22133a2b7)), closes [#30231](https://github.com/ionic-team/ionic-framework/issues/30231)
|
||||
* **refresher:** prevent focus-related scroll jumps on refresh ([#30636](https://github.com/ionic-team/ionic-framework/issues/30636)) ([1899b49](https://github.com/ionic-team/ionic-framework/commit/1899b49d252abc6003f763cea8db2a51efa941ec))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [8.7.2](https://github.com/ionic-team/ionic-framework/compare/v8.7.1...v8.7.2) (2025-08-06)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Get Playwright
|
||||
FROM mcr.microsoft.com/playwright:v1.54.2
|
||||
FROM mcr.microsoft.com/playwright:v1.56.1
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /ionic
|
||||
|
||||
171
core/package-lock.json
generated
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "8.7.2",
|
||||
"version": "8.7.9",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "8.7.2",
|
||||
"version": "8.7.9",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.38.0",
|
||||
"ionicons": "^8.0.13",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.10.2",
|
||||
"@axe-core/playwright": "^4.11.0",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/haptics": "^7.0.0",
|
||||
"@capacitor/keyboard": "^7.0.0",
|
||||
@@ -22,13 +22,13 @@
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
"@ionic/prettier-config": "^2.0.0",
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^1.0.0",
|
||||
"@stencil/angular-output-target": "^0.10.0",
|
||||
"@stencil/react-output-target": "0.5.3",
|
||||
"@stencil/sass": "^3.0.9",
|
||||
"@stencil/vue-output-target": "0.11.8",
|
||||
"@stencil/vue-output-target": "0.10.8",
|
||||
"@types/jest": "^29.5.6",
|
||||
"@types/node": "^14.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
@@ -57,12 +57,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@axe-core/playwright": {
|
||||
"version": "4.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.10.2.tgz",
|
||||
"integrity": "sha512-6/b5BJjG6hDaRNtgzLIfKr5DfwyiLHO4+ByTLB0cJgWSM8Ll7KqtdblIS6bEkwSF642/Ex91vNqIl3GLXGlceg==",
|
||||
"version": "4.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.0.tgz",
|
||||
"integrity": "sha512-70vBT/Ylqpm65RQz2iCG2o0JJCEG/WCNyefTr2xcOcr1CoSee60gNQYUMZZ7YukoKkFLv26I/jjlsvwwp532oQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"axe-core": "~4.10.3"
|
||||
"axe-core": "~4.11.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"playwright-core": ">= 1.0.0"
|
||||
@@ -663,9 +663,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@capacitor/core": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.2.tgz",
|
||||
"integrity": "sha512-akCf9A1FUR8AWTtmgGjHEq6LmGsjA2U7igaJ9PxiCBfyxKqlDbuGHrlNdpvHEjV5tUPH3KYtkze6gtFcNKPU9A==",
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.4.tgz",
|
||||
"integrity": "sha512-xzjxpr+d2zwTpCaN0k+C6wKSZzWFAb9OVEUtmO72ihjr/NEDoLvsGl4WLfjWPcCO2zOy0b2X52tfRWjECFUjtw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -681,18 +681,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@capacitor/keyboard": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.2.tgz",
|
||||
"integrity": "sha512-9We5BY1mu+QWOReDukr+6HxA4Bh0mKBU0txFtwXJdjBohttMYWJzB+dQf4oHrX8odiU2Cm/BfDdAU2wV06Cyig==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.3.tgz",
|
||||
"integrity": "sha512-BIBKjmky5rOYNhvYhNeDi0MMvjwYZ6YF9JoCYcGKvKY+XLJKtezsEL78XfOlgWZBkbfR8uq3tzktY6PqgoYLKA==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@capacitor/core": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@capacitor/status-bar": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.2.tgz",
|
||||
"integrity": "sha512-fYYkkdzCbQV+MjZVnaQTFl5I4bddnFW8ZrPVxDjNoGVPTUG7H58Ij1+NcuNxHLXjJvZOoZeYJ3w3I16Wb2zssw==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.3.tgz",
|
||||
"integrity": "sha512-JyRpVnKwHij9hgPWolF6PK+HT3e2HSPjN11/h2OmKxq8GAdPGARFLv+97eZl0pvuvm0Kka/LpiLb5whXISBg7Q==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@capacitor/core": ">=7.0.0"
|
||||
@@ -1715,12 +1715,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.2.tgz",
|
||||
"integrity": "sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
|
||||
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright": "1.54.2"
|
||||
"playwright": "1.56.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -1905,19 +1905,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/angular-output-target": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-1.0.0.tgz",
|
||||
"integrity": "sha512-6/XtjEWlJS+40b3x2geCV43rjQKlHS/a1/62QjYGXBtHhMo92KyGwKns3ntmX6B4fdk/xAKehxD2WKtHIM3oxQ==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"license": "MIT",
|
||||
"version": "4.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.0.tgz",
|
||||
"integrity": "sha512-oC3QFKO0X1yXVvETgc8OLY525MNKhn9vISBrbtKnGoPlokJ6rI8Vk1RK22TevnNrHLI4SExNLbcDnqilKR35JQ==",
|
||||
"bin": {
|
||||
"stencil": "bin/stencil"
|
||||
},
|
||||
@@ -1960,9 +1959,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stencil/vue-output-target": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.11.8.tgz",
|
||||
"integrity": "sha512-R/kQoN15irgL7NJxWaUNSmwDLfoDBZjlYaXNnW3LHlF30TYfyez6pRgD7ZglSSTVktMtCXz6ZPhg0uq59VkhOw==",
|
||||
"version": "0.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.10.8.tgz",
|
||||
"integrity": "sha512-/a20LG29xqy/lxBqo6zc1LbyS20GW9xghypZ7vYdo5fQB8jHClAQDkn+c8aykQlre5TtHiODgvr/rRDUrQKwyg==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@stencil/core": ">=2.0.0 || >=3 || >= 4.0.0-beta.0 || >= 4.0.0",
|
||||
@@ -3034,9 +3033,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axe-core": {
|
||||
"version": "4.10.3",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
|
||||
"integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==",
|
||||
"version": "4.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz",
|
||||
"integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
@@ -3474,9 +3473,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz",
|
||||
"integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
@@ -8593,12 +8592,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.2.tgz",
|
||||
"integrity": "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
|
||||
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"playwright-core": "1.54.2"
|
||||
"playwright-core": "1.56.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -8611,9 +8610,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.2.tgz",
|
||||
"integrity": "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
|
||||
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
@@ -10653,12 +10652,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@axe-core/playwright": {
|
||||
"version": "4.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.10.2.tgz",
|
||||
"integrity": "sha512-6/b5BJjG6hDaRNtgzLIfKr5DfwyiLHO4+ByTLB0cJgWSM8Ll7KqtdblIS6bEkwSF642/Ex91vNqIl3GLXGlceg==",
|
||||
"version": "4.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.0.tgz",
|
||||
"integrity": "sha512-70vBT/Ylqpm65RQz2iCG2o0JJCEG/WCNyefTr2xcOcr1CoSee60gNQYUMZZ7YukoKkFLv26I/jjlsvwwp532oQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"axe-core": "~4.10.3"
|
||||
"axe-core": "~4.11.0"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
@@ -11102,9 +11101,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@capacitor/core": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.2.tgz",
|
||||
"integrity": "sha512-akCf9A1FUR8AWTtmgGjHEq6LmGsjA2U7igaJ9PxiCBfyxKqlDbuGHrlNdpvHEjV5tUPH3KYtkze6gtFcNKPU9A==",
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.4.tgz",
|
||||
"integrity": "sha512-xzjxpr+d2zwTpCaN0k+C6wKSZzWFAb9OVEUtmO72ihjr/NEDoLvsGl4WLfjWPcCO2zOy0b2X52tfRWjECFUjtw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
@@ -11118,16 +11117,16 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@capacitor/keyboard": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.2.tgz",
|
||||
"integrity": "sha512-9We5BY1mu+QWOReDukr+6HxA4Bh0mKBU0txFtwXJdjBohttMYWJzB+dQf4oHrX8odiU2Cm/BfDdAU2wV06Cyig==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-7.0.3.tgz",
|
||||
"integrity": "sha512-BIBKjmky5rOYNhvYhNeDi0MMvjwYZ6YF9JoCYcGKvKY+XLJKtezsEL78XfOlgWZBkbfR8uq3tzktY6PqgoYLKA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@capacitor/status-bar": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.2.tgz",
|
||||
"integrity": "sha512-fYYkkdzCbQV+MjZVnaQTFl5I4bddnFW8ZrPVxDjNoGVPTUG7H58Ij1+NcuNxHLXjJvZOoZeYJ3w3I16Wb2zssw==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-7.0.3.tgz",
|
||||
"integrity": "sha512-JyRpVnKwHij9hgPWolF6PK+HT3e2HSPjN11/h2OmKxq8GAdPGARFLv+97eZl0pvuvm0Kka/LpiLb5whXISBg7Q==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
@@ -11863,12 +11862,12 @@
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.2.tgz",
|
||||
"integrity": "sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz",
|
||||
"integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"playwright": "1.54.2"
|
||||
"playwright": "1.56.1"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
@@ -11977,16 +11976,16 @@
|
||||
}
|
||||
},
|
||||
"@stencil/angular-output-target": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/angular-output-target/-/angular-output-target-1.0.0.tgz",
|
||||
"integrity": "sha512-6/XtjEWlJS+40b3x2geCV43rjQKlHS/a1/62QjYGXBtHhMo92KyGwKns3ntmX6B4fdk/xAKehxD2WKtHIM3oxQ==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/core": {
|
||||
"version": "4.36.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz",
|
||||
"integrity": "sha512-PRFSpxNzX9Oi0Wfh02asztN9Sgev/MacfZwmd+VVyE6ZxW+a/kEpAYZhzGAmE+/aKVOGYuug7R9SulanYGxiDQ==",
|
||||
"version": "4.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.0.tgz",
|
||||
"integrity": "sha512-oC3QFKO0X1yXVvETgc8OLY525MNKhn9vISBrbtKnGoPlokJ6rI8Vk1RK22TevnNrHLI4SExNLbcDnqilKR35JQ==",
|
||||
"requires": {
|
||||
"@rollup/rollup-darwin-arm64": "4.34.9",
|
||||
"@rollup/rollup-darwin-x64": "4.34.9",
|
||||
@@ -12013,9 +12012,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@stencil/vue-output-target": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.11.8.tgz",
|
||||
"integrity": "sha512-R/kQoN15irgL7NJxWaUNSmwDLfoDBZjlYaXNnW3LHlF30TYfyez6pRgD7ZglSSTVktMtCXz6ZPhg0uq59VkhOw==",
|
||||
"version": "0.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.10.8.tgz",
|
||||
"integrity": "sha512-/a20LG29xqy/lxBqo6zc1LbyS20GW9xghypZ7vYdo5fQB8jHClAQDkn+c8aykQlre5TtHiODgvr/rRDUrQKwyg==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
@@ -12771,9 +12770,9 @@
|
||||
}
|
||||
},
|
||||
"axe-core": {
|
||||
"version": "4.10.3",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
|
||||
"integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==",
|
||||
"version": "4.11.0",
|
||||
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz",
|
||||
"integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==",
|
||||
"dev": true
|
||||
},
|
||||
"babel-jest": {
|
||||
@@ -13076,9 +13075,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz",
|
||||
"integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk-template": {
|
||||
@@ -16812,19 +16811,19 @@
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.2.tgz",
|
||||
"integrity": "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
|
||||
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.54.2"
|
||||
"playwright-core": "1.56.1"
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.54.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.2.tgz",
|
||||
"integrity": "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
|
||||
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "8.7.2",
|
||||
"version": "8.7.9",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -31,12 +31,12 @@
|
||||
"loader/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@stencil/core": "4.36.2",
|
||||
"@stencil/core": "4.38.0",
|
||||
"ionicons": "^8.0.13",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "^4.10.2",
|
||||
"@axe-core/playwright": "^4.11.0",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/haptics": "^7.0.0",
|
||||
"@capacitor/keyboard": "^7.0.0",
|
||||
@@ -44,13 +44,13 @@
|
||||
"@clack/prompts": "^0.11.0",
|
||||
"@ionic/eslint-config": "^0.3.0",
|
||||
"@ionic/prettier-config": "^2.0.0",
|
||||
"@playwright/test": "^1.54.2",
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@rollup/plugin-node-resolve": "^8.4.0",
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/angular-output-target": "^1.0.0",
|
||||
"@stencil/angular-output-target": "^0.10.0",
|
||||
"@stencil/react-output-target": "0.5.3",
|
||||
"@stencil/sass": "^3.0.9",
|
||||
"@stencil/vue-output-target": "0.11.8",
|
||||
"@stencil/vue-output-target": "0.10.8",
|
||||
"@types/jest": "^29.5.6",
|
||||
"@types/node": "^14.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
||||
|
||||
@@ -38,7 +38,40 @@ const enum AccordionState {
|
||||
})
|
||||
export class Accordion implements ComponentInterface {
|
||||
private accordionGroupEl?: HTMLIonAccordionGroupElement | null;
|
||||
private updateListener = () => this.updateState(false);
|
||||
private accordionGroupUpdateHandler = () => {
|
||||
/**
|
||||
* Determine if this update will cause an actual state change.
|
||||
* We only want to mark as "interacted" if the state is changing.
|
||||
*/
|
||||
const accordionGroup = this.accordionGroupEl;
|
||||
if (accordionGroup) {
|
||||
const value = accordionGroup.value;
|
||||
const accordionValue = this.value;
|
||||
const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue;
|
||||
const isExpanded = this.state === AccordionState.Expanded || this.state === AccordionState.Expanding;
|
||||
const stateWillChange = shouldExpand !== isExpanded;
|
||||
|
||||
/**
|
||||
* Only mark as interacted if:
|
||||
* 1. This is not the first update we've received with a defined value
|
||||
* 2. The state is actually changing (prevents redundant updates from enabling animations)
|
||||
*/
|
||||
if (this.hasReceivedFirstUpdate && stateWillChange) {
|
||||
this.hasInteracted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only count this as the first update if the group value is defined.
|
||||
* This prevents the initial undefined value from the group's componentDidLoad
|
||||
* from being treated as the first real update.
|
||||
*/
|
||||
if (value !== undefined) {
|
||||
this.hasReceivedFirstUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateState();
|
||||
};
|
||||
private contentEl: HTMLDivElement | undefined;
|
||||
private contentElWrapper: HTMLDivElement | undefined;
|
||||
private headerEl: HTMLDivElement | undefined;
|
||||
@@ -50,6 +83,25 @@ export class Accordion implements ComponentInterface {
|
||||
@State() state: AccordionState = AccordionState.Collapsed;
|
||||
@State() isNext = false;
|
||||
@State() isPrevious = false;
|
||||
/**
|
||||
* Tracks whether a user-initiated interaction has occurred.
|
||||
* Animations are disabled until the first interaction happens.
|
||||
* This prevents the accordion from animating when it's programmatically
|
||||
* set to an expanded or collapsed state on initial load.
|
||||
*/
|
||||
@State() hasInteracted = false;
|
||||
|
||||
/**
|
||||
* Tracks if this accordion has ever been expanded.
|
||||
* Used to prevent the first expansion from animating.
|
||||
*/
|
||||
private hasEverBeenExpanded = false;
|
||||
|
||||
/**
|
||||
* Tracks if this accordion has received its first update from the group.
|
||||
* Used to distinguish initial programmatic sets from user interactions.
|
||||
*/
|
||||
private hasReceivedFirstUpdate = false;
|
||||
|
||||
/**
|
||||
* The value of the accordion. Defaults to an autogenerated
|
||||
@@ -88,15 +140,15 @@ export class Accordion implements ComponentInterface {
|
||||
connectedCallback() {
|
||||
const accordionGroupEl = (this.accordionGroupEl = this.el?.closest('ion-accordion-group'));
|
||||
if (accordionGroupEl) {
|
||||
this.updateState(true);
|
||||
addEventListener(accordionGroupEl, 'ionValueChange', this.updateListener);
|
||||
this.updateState();
|
||||
addEventListener(accordionGroupEl, 'ionValueChange', this.accordionGroupUpdateHandler);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
const accordionGroupEl = this.accordionGroupEl;
|
||||
if (accordionGroupEl) {
|
||||
removeEventListener(accordionGroupEl, 'ionValueChange', this.updateListener);
|
||||
removeEventListener(accordionGroupEl, 'ionValueChange', this.accordionGroupUpdateHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,10 +264,16 @@ export class Accordion implements ComponentInterface {
|
||||
ionItem.appendChild(iconEl);
|
||||
};
|
||||
|
||||
private expandAccordion = (initialUpdate = false) => {
|
||||
private expandAccordion = () => {
|
||||
const { contentEl, contentElWrapper } = this;
|
||||
if (initialUpdate || contentEl === undefined || contentElWrapper === undefined) {
|
||||
|
||||
/**
|
||||
* If the content elements aren't available yet, just set the state.
|
||||
* This happens on initial render before the DOM is ready.
|
||||
*/
|
||||
if (contentEl === undefined || contentElWrapper === undefined) {
|
||||
this.state = AccordionState.Expanded;
|
||||
this.hasEverBeenExpanded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -227,6 +285,12 @@ export class Accordion implements ComponentInterface {
|
||||
cancelAnimationFrame(this.currentRaf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark that this accordion has been expanded at least once.
|
||||
* This allows subsequent expansions to animate.
|
||||
*/
|
||||
this.hasEverBeenExpanded = true;
|
||||
|
||||
if (this.shouldAnimate()) {
|
||||
raf(() => {
|
||||
this.state = AccordionState.Expanding;
|
||||
@@ -247,9 +311,14 @@ export class Accordion implements ComponentInterface {
|
||||
}
|
||||
};
|
||||
|
||||
private collapseAccordion = (initialUpdate = false) => {
|
||||
private collapseAccordion = () => {
|
||||
const { contentEl } = this;
|
||||
if (initialUpdate || contentEl === undefined) {
|
||||
|
||||
/**
|
||||
* If the content element isn't available yet, just set the state.
|
||||
* This happens on initial render before the DOM is ready.
|
||||
*/
|
||||
if (contentEl === undefined) {
|
||||
this.state = AccordionState.Collapsed;
|
||||
return;
|
||||
}
|
||||
@@ -291,6 +360,19 @@ export class Accordion implements ComponentInterface {
|
||||
* of what is set in the config.
|
||||
*/
|
||||
private shouldAnimate = () => {
|
||||
/**
|
||||
* Don't animate until after the first user interaction.
|
||||
* This prevents animations on initial load when accordions
|
||||
* start in an expanded or collapsed state programmatically.
|
||||
*
|
||||
* Additionally, don't animate the very first expansion even if
|
||||
* hasInteracted is true. This handles edge cases like React StrictMode
|
||||
* where effects run twice and might incorrectly mark as interacted.
|
||||
*/
|
||||
if (!this.hasInteracted || !this.hasEverBeenExpanded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof (window as any) === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
@@ -312,7 +394,7 @@ export class Accordion implements ComponentInterface {
|
||||
return true;
|
||||
};
|
||||
|
||||
private updateState = async (initialUpdate = false) => {
|
||||
private updateState = async () => {
|
||||
const accordionGroup = this.accordionGroupEl;
|
||||
const accordionValue = this.value;
|
||||
|
||||
@@ -325,10 +407,10 @@ export class Accordion implements ComponentInterface {
|
||||
const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue;
|
||||
|
||||
if (shouldExpand) {
|
||||
this.expandAccordion(initialUpdate);
|
||||
this.expandAccordion();
|
||||
this.isNext = this.isPrevious = false;
|
||||
} else {
|
||||
this.collapseAccordion(initialUpdate);
|
||||
this.collapseAccordion();
|
||||
|
||||
/**
|
||||
* When using popout or inset,
|
||||
@@ -386,6 +468,12 @@ export class Accordion implements ComponentInterface {
|
||||
|
||||
if (disabled || readonly) return;
|
||||
|
||||
/**
|
||||
* Mark that the user has interacted with the accordion.
|
||||
* This enables animations for all future state changes.
|
||||
*/
|
||||
this.hasInteracted = true;
|
||||
|
||||
if (accordionGroupEl) {
|
||||
/**
|
||||
* Because the accordion group may or may
|
||||
|
||||
@@ -200,6 +200,87 @@ it('should set default values if not provided', async () => {
|
||||
expect(accordion.classList.contains('accordion-collapsed')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not animate when initial value is set before load', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
});
|
||||
|
||||
const accordionGroup = page.doc.createElement('ion-accordion-group');
|
||||
accordionGroup.innerHTML = `
|
||||
<ion-accordion value="first">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="second">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
`;
|
||||
|
||||
accordionGroup.value = 'first';
|
||||
page.body.appendChild(accordionGroup);
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
const firstAccordion = accordionGroup.querySelector('ion-accordion[value="first"]')!;
|
||||
|
||||
expect(firstAccordion.classList.contains('accordion-expanded')).toEqual(true);
|
||||
expect(firstAccordion.classList.contains('accordion-expanding')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not animate when initial value is set after load', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
});
|
||||
|
||||
const accordionGroup = page.doc.createElement('ion-accordion-group');
|
||||
accordionGroup.innerHTML = `
|
||||
<ion-accordion value="first">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
<ion-accordion value="second">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
`;
|
||||
|
||||
page.body.appendChild(accordionGroup);
|
||||
await page.waitForChanges();
|
||||
|
||||
accordionGroup.value = 'first';
|
||||
await page.waitForChanges();
|
||||
|
||||
const firstAccordion = accordionGroup.querySelector('ion-accordion[value="first"]')!;
|
||||
|
||||
expect(firstAccordion.classList.contains('accordion-expanded')).toEqual(true);
|
||||
expect(firstAccordion.classList.contains('accordion-expanding')).toEqual(false);
|
||||
});
|
||||
|
||||
it('should not have animated class on first expansion', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Item, Accordion, AccordionGroup],
|
||||
html: `
|
||||
<ion-accordion-group>
|
||||
<ion-accordion value="first">
|
||||
<ion-item slot="header">Label</ion-item>
|
||||
<div slot="content">Content</div>
|
||||
</ion-accordion>
|
||||
</ion-accordion-group>
|
||||
`,
|
||||
});
|
||||
|
||||
const accordionGroup = page.body.querySelector('ion-accordion-group')!;
|
||||
const firstAccordion = page.body.querySelector('ion-accordion[value="first"]')!;
|
||||
|
||||
// First expansion should not have the animated class
|
||||
accordionGroup.value = 'first';
|
||||
await page.waitForChanges();
|
||||
|
||||
expect(firstAccordion.classList.contains('accordion-animated')).toEqual(false);
|
||||
expect(firstAccordion.classList.contains('accordion-expanded')).toEqual(true);
|
||||
});
|
||||
|
||||
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/27047
|
||||
it('should not have animated class when animated="false"', async () => {
|
||||
const page = await newSpecPage({
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -361,11 +361,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
target,
|
||||
};
|
||||
let fill = this.fill;
|
||||
/**
|
||||
* We check both undefined and null to
|
||||
* work around https://github.com/ionic-team/stencil/issues/3586.
|
||||
*/
|
||||
if (fill == null) {
|
||||
if (fill === undefined) {
|
||||
fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1004 B After Width: | Height: | Size: 968 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 29 KiB |
@@ -34,7 +34,6 @@ export class Checkbox implements ComponentInterface {
|
||||
private inputLabelId = `${this.inputId}-lbl`;
|
||||
private helperTextId = `${this.inputId}-helper-text`;
|
||||
private errorTextId = `${this.inputId}-error-text`;
|
||||
private focusEl?: HTMLElement;
|
||||
private inheritedAttributes: Attributes = {};
|
||||
|
||||
@Element() el!: HTMLIonCheckboxElement;
|
||||
@@ -147,9 +146,7 @@ export class Checkbox implements ComponentInterface {
|
||||
/** @internal */
|
||||
@Method()
|
||||
async setFocus() {
|
||||
if (this.focusEl) {
|
||||
this.focusEl.focus();
|
||||
}
|
||||
this.el.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,7 +166,6 @@ export class Checkbox implements ComponentInterface {
|
||||
private toggleChecked = (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
|
||||
this.setFocus();
|
||||
this.setChecked(!this.checked);
|
||||
this.indeterminate = false;
|
||||
};
|
||||
@@ -285,6 +281,9 @@ export class Checkbox implements ComponentInterface {
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
tabindex={disabled ? undefined : 0}
|
||||
onKeyDown={this.onKeyDown}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
onClick={this.onClick}
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
'in-item': hostContext('ion-item', el),
|
||||
@@ -296,7 +295,6 @@ export class Checkbox implements ComponentInterface {
|
||||
[`checkbox-alignment-${alignment}`]: alignment !== undefined,
|
||||
[`checkbox-label-placement-${labelPlacement}`]: true,
|
||||
})}
|
||||
onClick={this.onClick}
|
||||
>
|
||||
<label class="checkbox-wrapper" htmlFor={inputId}>
|
||||
{/*
|
||||
@@ -309,9 +307,6 @@ export class Checkbox implements ComponentInterface {
|
||||
disabled={disabled}
|
||||
id={inputId}
|
||||
onChange={this.toggleChecked}
|
||||
onFocus={() => this.onFocus()}
|
||||
onBlur={() => this.onBlur()}
|
||||
ref={(focusEl) => (this.focusEl = focusEl)}
|
||||
required={required}
|
||||
{...inheritedAttributes}
|
||||
/>
|
||||
@@ -328,7 +323,7 @@ export class Checkbox implements ComponentInterface {
|
||||
{this.renderHintText()}
|
||||
</div>
|
||||
<div class="native-wrapper">
|
||||
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container">
|
||||
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container" aria-hidden="true">
|
||||
{path}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
@@ -44,7 +44,10 @@ configs().forEach(({ title, screenshot, config }) => {
|
||||
});
|
||||
});
|
||||
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
/**
|
||||
* This behavior does not vary across modes/directions
|
||||
*/
|
||||
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
|
||||
test.describe(title('checkbox: ionChange'), () => {
|
||||
test('should fire ionChange when interacting with checkbox', async ({ page }) => {
|
||||
await page.setContent(
|
||||
@@ -133,4 +136,195 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
|
||||
expect(clickCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('checkbox: ionFocus'), () => {
|
||||
test('should not have visual regressions', async ({ page, pageUtils }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<style>
|
||||
#container {
|
||||
display: inline-block;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="container">
|
||||
<ion-checkbox>Unchecked</ion-checkbox>
|
||||
</div>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
await pageUtils.pressKeys('Tab');
|
||||
|
||||
const container = page.locator('#container');
|
||||
|
||||
await expect(container).toHaveScreenshot(screenshot(`checkbox-focus`));
|
||||
});
|
||||
|
||||
test('should not have visual regressions when interacting with checkbox in item', async ({ page, pageUtils }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item class="ion-focused">
|
||||
<ion-checkbox>Unchecked</ion-checkbox>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
// Test focus with keyboard navigation
|
||||
await pageUtils.pressKeys('Tab');
|
||||
|
||||
const item = page.locator('ion-item');
|
||||
|
||||
await expect(item).toHaveScreenshot(screenshot(`checkbox-in-item-focus`));
|
||||
});
|
||||
|
||||
test('should fire ionFocus when checkbox is focused', async ({ page, pageUtils }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionFocus = await page.spyOnEvent('ionFocus');
|
||||
|
||||
// Test focus with keyboard navigation
|
||||
await pageUtils.pressKeys('Tab');
|
||||
|
||||
expect(ionFocus).toHaveReceivedEventTimes(1);
|
||||
|
||||
// Reset focus
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
const checkboxBoundingBox = (await checkbox.boundingBox())!;
|
||||
await page.mouse.click(0, checkboxBoundingBox.height + 1);
|
||||
|
||||
// Test focus with click
|
||||
await checkbox.click();
|
||||
|
||||
expect(ionFocus).toHaveReceivedEventTimes(2);
|
||||
});
|
||||
|
||||
test('should fire ionFocus when interacting with checkbox in item', async ({ page, pageUtils }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionFocus = await page.spyOnEvent('ionFocus');
|
||||
|
||||
// Test focus with keyboard navigation
|
||||
await pageUtils.pressKeys('Tab');
|
||||
|
||||
expect(ionFocus).toHaveReceivedEventTimes(1);
|
||||
|
||||
// Verify that the event target is the checkbox and not the item
|
||||
const eventByKeyboard = ionFocus.events[0];
|
||||
expect((eventByKeyboard.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox');
|
||||
|
||||
// Reset focus
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
const checkboxBoundingBox = (await checkbox.boundingBox())!;
|
||||
await page.mouse.click(0, checkboxBoundingBox.height + 1);
|
||||
|
||||
// Test focus with click
|
||||
const item = page.locator('ion-item');
|
||||
await item.click();
|
||||
|
||||
expect(ionFocus).toHaveReceivedEventTimes(2);
|
||||
|
||||
// Verify that the event target is the checkbox and not the item
|
||||
const eventByClick = ionFocus.events[0];
|
||||
expect((eventByClick.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox');
|
||||
});
|
||||
|
||||
test('should not fire when programmatically setting a value', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionFocus = await page.spyOnEvent('ionFocus');
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
|
||||
await checkbox.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true));
|
||||
expect(ionFocus).not.toHaveReceivedEvent();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(title('checkbox: ionBlur'), () => {
|
||||
test('should fire ionBlur when checkbox is blurred', async ({ page, pageUtils }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionBlur = await page.spyOnEvent('ionBlur');
|
||||
|
||||
// Test blur with keyboard navigation
|
||||
// Focus the checkbox
|
||||
await pageUtils.pressKeys('Tab');
|
||||
// Blur the checkbox
|
||||
await pageUtils.pressKeys('Tab');
|
||||
|
||||
expect(ionBlur).toHaveReceivedEventTimes(1);
|
||||
|
||||
// Test blur with click
|
||||
const checkbox = page.locator('ion-checkbox');
|
||||
// Focus the checkbox
|
||||
await checkbox.click();
|
||||
// Blur the checkbox by clicking outside of it
|
||||
const checkboxBoundingBox = (await checkbox.boundingBox())!;
|
||||
await page.mouse.click(0, checkboxBoundingBox.height + 1);
|
||||
|
||||
expect(ionBlur).toHaveReceivedEventTimes(2);
|
||||
});
|
||||
|
||||
test('should fire ionBlur after interacting with checkbox in item', async ({ page, pageUtils }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-item>
|
||||
<ion-checkbox aria-label="checkbox" value="my-checkbox"></ion-checkbox>
|
||||
</ion-item>
|
||||
`,
|
||||
config
|
||||
);
|
||||
|
||||
const ionBlur = await page.spyOnEvent('ionBlur');
|
||||
|
||||
// Test blur with keyboard navigation
|
||||
// Focus the checkbox
|
||||
await pageUtils.pressKeys('Tab');
|
||||
// Blur the checkbox
|
||||
await pageUtils.pressKeys('Tab');
|
||||
|
||||
expect(ionBlur).toHaveReceivedEventTimes(1);
|
||||
|
||||
// Verify that the event target is the checkbox and not the item
|
||||
const event = ionBlur.events[0];
|
||||
expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox');
|
||||
|
||||
// Test blur with click
|
||||
const item = page.locator('ion-item');
|
||||
await item.click();
|
||||
// Blur the checkbox by clicking outside of it
|
||||
const itemBoundingBox = (await item.boundingBox())!;
|
||||
await page.mouse.click(0, itemBoundingBox.height + 1);
|
||||
|
||||
expect(ionBlur).toHaveReceivedEventTimes(2);
|
||||
|
||||
// Verify that the event target is the checkbox and not the item
|
||||
const eventByClick = ionBlur.events[0];
|
||||
expect((eventByClick.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
@@ -50,6 +50,20 @@
|
||||
<ion-checkbox checked style="width: 200px">Specified width</ion-checkbox><br />
|
||||
<ion-checkbox checked style="width: 100%">Full-width</ion-checkbox><br />
|
||||
</ion-content>
|
||||
|
||||
<script>
|
||||
document.addEventListener('ionBlur', (ev) => {
|
||||
console.log('ionBlur', ev);
|
||||
});
|
||||
|
||||
document.addEventListener('ionChange', (ev) => {
|
||||
console.log('ionChange', ev);
|
||||
});
|
||||
|
||||
document.addEventListener('ionFocus', (ev) => {
|
||||
console.log('ionFocus', ev);
|
||||
});
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -246,6 +246,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
<script>
|
||||
document.addEventListener('ionBlur', (ev) => {
|
||||
console.log('ionBlur', ev);
|
||||
});
|
||||
|
||||
document.addEventListener('ionChange', (ev) => {
|
||||
console.log('ionChange', ev);
|
||||
});
|
||||
|
||||
document.addEventListener('ionFocus', (ev) => {
|
||||
console.log('ionFocus', ev);
|
||||
});
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
@@ -3,6 +3,7 @@ import { Component, Element, Event, Host, Method, Prop, State, Watch, h, writeTa
|
||||
import { startFocusVisible } from '@utils/focus-visible';
|
||||
import { getElementRoot, raf, renderHiddenInput } from '@utils/helpers';
|
||||
import { printIonError, printIonWarning } from '@utils/logging';
|
||||
import { FOCUS_TRAP_DISABLE_CLASS } from '@utils/overlays';
|
||||
import { isRTL } from '@utils/rtl';
|
||||
import { createColorClasses } from '@utils/theme';
|
||||
import { caretDownSharp, caretUpSharp, chevronBack, chevronDown, chevronForward } from 'ionicons/icons';
|
||||
@@ -1598,7 +1599,7 @@ export class Datetime implements ComponentInterface {
|
||||
forcePresentation === 'time-date'
|
||||
? [this.renderTimePickerColumns(forcePresentation), this.renderDatePickerColumns(forcePresentation)]
|
||||
: [this.renderDatePickerColumns(forcePresentation), this.renderTimePickerColumns(forcePresentation)];
|
||||
return <ion-picker>{renderArray}</ion-picker>;
|
||||
return <ion-picker class={FOCUS_TRAP_DISABLE_CLASS}>{renderArray}</ion-picker>;
|
||||
}
|
||||
|
||||
private renderDatePickerColumns(forcePresentation: string) {
|
||||
|
||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@@ -39,6 +39,15 @@
|
||||
--opacity-scale: inherit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override styles applied during the page transition to prevent
|
||||
* header flickering.
|
||||
*/
|
||||
.header-collapse-fade.header-transitioning ion-toolbar {
|
||||
--background: transparent;
|
||||
--border-style: none;
|
||||
}
|
||||
|
||||
// iOS Header - Collapse Condense
|
||||
// --------------------------------------------------
|
||||
.header-collapse-condense {
|
||||
@@ -65,8 +74,6 @@
|
||||
* since it needs to blend in with the header above it.
|
||||
*/
|
||||
.header-collapse-condense ion-toolbar {
|
||||
--background: var(--ion-background-color, #fff);
|
||||
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@@ -93,6 +100,28 @@
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Large title toolbar should just use the content background
|
||||
* since it needs to blend in with the header above it.
|
||||
*/
|
||||
.header-collapse-condense ion-toolbar,
|
||||
/**
|
||||
* Override styles applied during the page transition to prevent
|
||||
* header flickering.
|
||||
*/
|
||||
.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar {
|
||||
--background: var(--ion-background-color, #fff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override styles applied during the page transition to prevent
|
||||
* header flickering.
|
||||
*/
|
||||
.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar {
|
||||
--border-style: none;
|
||||
--opacity-scale: 1;
|
||||
}
|
||||
|
||||
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,
|
||||
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
|
||||
opacity: 0;
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
handleToolbarIntersection,
|
||||
setHeaderActive,
|
||||
setToolbarBackgroundOpacity,
|
||||
getRoleType,
|
||||
} from './header.utils';
|
||||
|
||||
/**
|
||||
@@ -208,9 +209,10 @@ export class Header implements ComponentInterface {
|
||||
const { translucent, inheritedAttributes } = this;
|
||||
const mode = getIonMode(this);
|
||||
const collapse = this.collapse || 'none';
|
||||
const isCondensed = collapse === 'condense';
|
||||
|
||||
// banner role must be at top level, so remove role if inside a menu
|
||||
const roleType = hostContext('ion-menu', this.el) ? 'none' : 'banner';
|
||||
const roleType = getRoleType(hostContext('ion-menu', this.el), isCondensed, mode);
|
||||
|
||||
return (
|
||||
<Host
|
||||
|
||||
@@ -2,6 +2,8 @@ import { readTask, writeTask } from '@stencil/core';
|
||||
import { clamp } from '@utils/helpers';
|
||||
|
||||
const TRANSITION = 'all 0.2s ease-in-out';
|
||||
const ROLE_NONE = 'none';
|
||||
const ROLE_BANNER = 'banner';
|
||||
|
||||
interface HeaderIndex {
|
||||
el: HTMLIonHeaderElement;
|
||||
@@ -171,6 +173,7 @@ export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
|
||||
const ionTitles = toolbars.map((toolbar) => toolbar.ionTitleEl);
|
||||
|
||||
if (active) {
|
||||
headerEl.setAttribute('role', ROLE_BANNER);
|
||||
headerEl.classList.remove('header-collapse-condense-inactive');
|
||||
|
||||
ionTitles.forEach((ionTitle) => {
|
||||
@@ -179,6 +182,16 @@ export const setHeaderActive = (headerIndex: HeaderIndex, active = true) => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
/**
|
||||
* There can only be one banner landmark per page.
|
||||
* By default, all ion-headers have the banner role.
|
||||
* This causes an accessibility issue when using a
|
||||
* condensed header since there are two ion-headers
|
||||
* on the page at once (active and inactive).
|
||||
* To solve this, the role needs to be toggled
|
||||
* based on which header is active.
|
||||
*/
|
||||
headerEl.setAttribute('role', ROLE_NONE);
|
||||
headerEl.classList.add('header-collapse-condense-inactive');
|
||||
|
||||
/**
|
||||
@@ -244,3 +257,28 @@ export const handleHeaderFade = (scrollEl: HTMLElement, baseEl: HTMLElement, con
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the role type for the ion-header.
|
||||
*
|
||||
* @param isInsideMenu If ion-header is inside ion-menu.
|
||||
* @param isCondensed If ion-header has collapse="condense".
|
||||
* @param mode The current mode.
|
||||
* @returns 'none' if inside ion-menu or if condensed in md
|
||||
* mode, otherwise 'banner'.
|
||||
*/
|
||||
export const getRoleType = (isInsideMenu: boolean, isCondensed: boolean, mode: 'ios' | 'md') => {
|
||||
// If the header is inside a menu, it should not have the banner role.
|
||||
if (isInsideMenu) {
|
||||
return ROLE_NONE;
|
||||
}
|
||||
/**
|
||||
* Only apply role="none" to `md` mode condensed headers
|
||||
* since the large header is never shown.
|
||||
*/
|
||||
if (isCondensed && mode === 'md') {
|
||||
return ROLE_NONE;
|
||||
}
|
||||
// Default to banner role.
|
||||
return ROLE_BANNER;
|
||||
};
|
||||
|
||||
@@ -40,5 +40,45 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c
|
||||
|
||||
await expect(smallTitle).toHaveAttribute('aria-hidden', 'true');
|
||||
});
|
||||
|
||||
test('should only have the banner role on the active header', async ({ page }) => {
|
||||
await page.goto('/src/components/header/test/condense', config);
|
||||
const largeTitleHeader = page.locator('#largeTitleHeader');
|
||||
const smallTitleHeader = page.locator('#smallTitleHeader');
|
||||
const content = page.locator('ion-content');
|
||||
|
||||
await expect(largeTitleHeader).toHaveAttribute('role', 'banner');
|
||||
await expect(smallTitleHeader).toHaveAttribute('role', 'none');
|
||||
|
||||
await content.evaluate(async (el: HTMLIonContentElement) => {
|
||||
await el.scrollToBottom();
|
||||
});
|
||||
await page.locator('#largeTitleHeader.header-collapse-condense-inactive').waitFor();
|
||||
|
||||
await expect(largeTitleHeader).toHaveAttribute('role', 'none');
|
||||
await expect(smallTitleHeader).toHaveAttribute('role', 'banner');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
|
||||
test.describe(title('header: condense'), () => {
|
||||
test('should only have the banner role on the small header', async ({ page }) => {
|
||||
await page.goto('/src/components/header/test/condense', config);
|
||||
const largeTitleHeader = page.locator('#largeTitleHeader');
|
||||
const smallTitleHeader = page.locator('#smallTitleHeader');
|
||||
const content = page.locator('ion-content');
|
||||
|
||||
await expect(smallTitleHeader).toHaveAttribute('role', 'banner');
|
||||
await expect(largeTitleHeader).toHaveAttribute('role', 'none');
|
||||
|
||||
await content.evaluate(async (el: HTMLIonContentElement) => {
|
||||
await el.scrollToBottom();
|
||||
});
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(smallTitleHeader).toHaveAttribute('role', 'banner');
|
||||
await expect(largeTitleHeader).toHaveAttribute('role', 'none');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
h,
|
||||
} from '@stencil/core';
|
||||
import type { NotchController } from '@utils/forms';
|
||||
import { createNotchController } from '@utils/forms';
|
||||
import { createNotchController, checkInvalidState } from '@utils/forms';
|
||||
import type { Attributes } from '@utils/helpers';
|
||||
import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from '@utils/helpers';
|
||||
import { createSlotMutationController } from '@utils/slot-mutation-controller';
|
||||
@@ -79,8 +79,15 @@ export class Input implements ComponentInterface {
|
||||
*/
|
||||
@State() hasFocus = false;
|
||||
|
||||
/**
|
||||
* Track validation state for proper aria-live announcements
|
||||
*/
|
||||
@State() isInvalid = false;
|
||||
|
||||
@Element() el!: HTMLIonInputElement;
|
||||
|
||||
private validationObserver?: MutationObserver;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
@@ -406,6 +413,26 @@ export class Input implements ComponentInterface {
|
||||
() => this.labelSlot
|
||||
);
|
||||
|
||||
// Watch for class changes to update validation state
|
||||
if (Build.isBrowser && typeof MutationObserver !== 'undefined') {
|
||||
this.validationObserver = new MutationObserver(() => {
|
||||
const newIsInvalid = checkInvalidState(el);
|
||||
if (this.isInvalid !== newIsInvalid) {
|
||||
this.isInvalid = newIsInvalid;
|
||||
// Force a re-render to update aria-describedby immediately
|
||||
forceUpdate(this);
|
||||
}
|
||||
});
|
||||
|
||||
this.validationObserver.observe(el, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class'],
|
||||
});
|
||||
}
|
||||
|
||||
// Always set initial state
|
||||
this.isInvalid = checkInvalidState(el);
|
||||
|
||||
this.debounceChanged();
|
||||
if (Build.isBrowser) {
|
||||
document.dispatchEvent(
|
||||
@@ -451,6 +478,12 @@ export class Input implements ComponentInterface {
|
||||
this.notchController.destroy();
|
||||
this.notchController = undefined;
|
||||
}
|
||||
|
||||
// Clean up validation observer to prevent memory leaks
|
||||
if (this.validationObserver) {
|
||||
this.validationObserver.disconnect();
|
||||
this.validationObserver = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -626,22 +659,22 @@ export class Input implements ComponentInterface {
|
||||
* Renders the helper text or error text values
|
||||
*/
|
||||
private renderHintText() {
|
||||
const { helperText, errorText, helperTextId, errorTextId } = this;
|
||||
const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this;
|
||||
|
||||
return [
|
||||
<div id={helperTextId} class="helper-text">
|
||||
{helperText}
|
||||
<div id={helperTextId} class="helper-text" aria-live="polite">
|
||||
{!isInvalid ? helperText : null}
|
||||
</div>,
|
||||
<div id={errorTextId} class="error-text">
|
||||
{errorText}
|
||||
<div id={errorTextId} class="error-text" role="alert">
|
||||
{isInvalid ? errorText : null}
|
||||
</div>,
|
||||
];
|
||||
}
|
||||
|
||||
private getHintTextID(): string | undefined {
|
||||
const { el, helperText, errorText, helperTextId, errorTextId } = this;
|
||||
const { isInvalid, helperText, errorText, helperTextId, errorTextId } = this;
|
||||
|
||||
if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) {
|
||||
if (isInvalid && errorText) {
|
||||
return errorTextId;
|
||||
}
|
||||
|
||||
@@ -864,7 +897,7 @@ export class Input implements ComponentInterface {
|
||||
onCompositionstart={this.onCompositionStart}
|
||||
onCompositionend={this.onCompositionEnd}
|
||||
aria-describedby={this.getHintTextID()}
|
||||
aria-invalid={this.getHintTextID() === this.errorTextId}
|
||||
aria-invalid={this.isInvalid ? 'true' : undefined}
|
||||
{...this.inheritedAttributes}
|
||||
/>
|
||||
{this.clearInput && !readonly && !disabled && (
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.0 KiB |
284
core/src/components/input/test/validation/index.html
Normal file
@@ -0,0 +1,284 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Input - Validation</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
grid-row-gap: 20px;
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
|
||||
color: var(--ion-color-step-600);
|
||||
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.validation-info {
|
||||
margin: 20px;
|
||||
padding: 10px;
|
||||
background: var(--ion-color-light);
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Input - Validation Test</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<div class="validation-info">
|
||||
<h2>Screen Reader Testing Instructions:</h2>
|
||||
<ol>
|
||||
<li>Enable your screen reader (VoiceOver, NVDA, JAWS, etc.)</li>
|
||||
<li>Tab through the form fields</li>
|
||||
<li>When you tab away from an empty required field, the error should be announced immediately</li>
|
||||
<li>The error text should be announced BEFORE the next field is announced</li>
|
||||
<li>Test in Chrome, Safari, and Firefox to verify consistent behavior</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div>
|
||||
<h2>Required Email Field</h2>
|
||||
<ion-input
|
||||
id="email-input"
|
||||
type="email"
|
||||
label="Email"
|
||||
label-placement="floating"
|
||||
fill="outline"
|
||||
placeholder="Enter your email"
|
||||
helper-text="We'll never share your email"
|
||||
error-text="Please enter a valid email address"
|
||||
required
|
||||
></ion-input>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Required Name Field</h2>
|
||||
<ion-input
|
||||
id="name-input"
|
||||
type="text"
|
||||
label="Full Name"
|
||||
label-placement="floating"
|
||||
fill="outline"
|
||||
placeholder="Enter your full name"
|
||||
helper-text="First and last name"
|
||||
error-text="Name is required"
|
||||
required
|
||||
></ion-input>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Phone Number (Pattern Validation)</h2>
|
||||
<ion-input
|
||||
id="phone-input"
|
||||
type="tel"
|
||||
label="Phone"
|
||||
label-placement="floating"
|
||||
fill="outline"
|
||||
placeholder="(555) 555-5555"
|
||||
pattern="^\(\d{3}\) \d{3}-\d{4}$"
|
||||
helper-text="Format: (555) 555-5555"
|
||||
error-text="Please enter a valid phone number"
|
||||
required
|
||||
></ion-input>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Password (Min Length)</h2>
|
||||
<ion-input
|
||||
id="password-input"
|
||||
type="password"
|
||||
label="Password"
|
||||
label-placement="floating"
|
||||
fill="outline"
|
||||
placeholder="Enter password"
|
||||
minlength="8"
|
||||
helper-text="At least 8 characters"
|
||||
error-text="Password must be at least 8 characters"
|
||||
required
|
||||
></ion-input>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Age (Number Range)</h2>
|
||||
<ion-input
|
||||
id="age-input"
|
||||
type="number"
|
||||
label="Age"
|
||||
label-placement="floating"
|
||||
fill="outline"
|
||||
placeholder="Enter your age"
|
||||
min="18"
|
||||
max="120"
|
||||
helper-text="Must be 18 or older"
|
||||
error-text="Please enter a valid age (18-120)"
|
||||
required
|
||||
></ion-input>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Optional Field (No Validation)</h2>
|
||||
<ion-input
|
||||
id="optional-input"
|
||||
type="text"
|
||||
label="Optional Info"
|
||||
label-placement="floating"
|
||||
fill="outline"
|
||||
placeholder="This field is optional"
|
||||
helper-text="You can skip this field"
|
||||
></ion-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ion-padding">
|
||||
<ion-button id="submit-btn" expand="block" disabled>Submit Form</ion-button>
|
||||
<ion-button id="reset-btn" expand="block" fill="outline">Reset Form</ion-button>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
// Simple validation logic
|
||||
const inputs = document.querySelectorAll('ion-input');
|
||||
const submitBtn = document.getElementById('submit-btn');
|
||||
const resetBtn = document.getElementById('reset-btn');
|
||||
|
||||
// Track which fields have been touched
|
||||
const touchedFields = new Set();
|
||||
|
||||
// Validation functions
|
||||
const validators = {
|
||||
'email-input': (value) => {
|
||||
if (!value) return false;
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||
},
|
||||
'name-input': (value) => {
|
||||
return value && value.trim().length > 0;
|
||||
},
|
||||
'phone-input': (value) => {
|
||||
if (!value) return false;
|
||||
return /^\(\d{3}\) \d{3}-\d{4}$/.test(value);
|
||||
},
|
||||
'password-input': (value) => {
|
||||
return value && value.length >= 8;
|
||||
},
|
||||
'age-input': (value) => {
|
||||
if (!value) return false;
|
||||
const age = parseInt(value);
|
||||
return age >= 18 && age <= 120;
|
||||
},
|
||||
'optional-input': () => true, // Always valid
|
||||
};
|
||||
|
||||
function validateField(input) {
|
||||
const inputId = input.id;
|
||||
const value = input.value;
|
||||
const isValid = validators[inputId] ? validators[inputId](value) : true;
|
||||
|
||||
// Only show validation state if field has been touched
|
||||
if (touchedFields.has(inputId)) {
|
||||
if (isValid) {
|
||||
input.classList.remove('ion-invalid');
|
||||
input.classList.add('ion-valid');
|
||||
} else {
|
||||
input.classList.remove('ion-valid');
|
||||
input.classList.add('ion-invalid');
|
||||
}
|
||||
input.classList.add('ion-touched');
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
let allValid = true;
|
||||
inputs.forEach((input) => {
|
||||
if (input.id !== 'optional-input') {
|
||||
const isValid = validateField(input);
|
||||
if (!isValid) {
|
||||
allValid = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
submitBtn.disabled = !allValid;
|
||||
return allValid;
|
||||
}
|
||||
|
||||
// Add event listeners
|
||||
inputs.forEach((input) => {
|
||||
// Mark as touched on blur
|
||||
input.addEventListener('ionBlur', (e) => {
|
||||
touchedFields.add(input.id);
|
||||
validateField(input);
|
||||
validateForm();
|
||||
|
||||
const isInvalid = input.classList.contains('ion-invalid');
|
||||
if (isInvalid) {
|
||||
console.log('Field marked invalid:', input.label, input.errorText);
|
||||
}
|
||||
});
|
||||
|
||||
// Validate on input
|
||||
input.addEventListener('ionInput', (e) => {
|
||||
if (touchedFields.has(input.id)) {
|
||||
validateField(input);
|
||||
validateForm();
|
||||
}
|
||||
});
|
||||
|
||||
// Also validate on focus loss via native blur
|
||||
input.addEventListener('focusout', (e) => {
|
||||
// Small delay to ensure Ionic's classes are updated
|
||||
setTimeout(() => {
|
||||
touchedFields.add(input.id);
|
||||
validateField(input);
|
||||
validateForm();
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
|
||||
// Reset button
|
||||
resetBtn.addEventListener('click', () => {
|
||||
inputs.forEach((input) => {
|
||||
input.value = '';
|
||||
input.classList.remove('ion-valid', 'ion-invalid', 'ion-touched');
|
||||
});
|
||||
touchedFields.clear();
|
||||
submitBtn.disabled = true;
|
||||
});
|
||||
|
||||
// Submit button
|
||||
submitBtn.addEventListener('click', () => {
|
||||
if (validateForm()) {
|
||||
alert('Form submitted successfully!');
|
||||
}
|
||||
});
|
||||
|
||||
// Initial setup
|
||||
validateForm();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.3 KiB |
@@ -418,6 +418,9 @@ export class Menu implements ComponentInterface, MenuI {
|
||||
*/
|
||||
@Method()
|
||||
setOpen(shouldOpen: boolean, animated = true, role?: string): Promise<boolean> {
|
||||
// Blur the active element to prevent it from being kept focused inside an element that will be set with aria-hidden="true"
|
||||
(document.activeElement as HTMLElement)?.blur();
|
||||
|
||||
return menuController._setOpen(this, shouldOpen, animated, role);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,6 +95,12 @@ export const createSheetGesture = (
|
||||
const contentAnimation = animation.childAnimations.find((ani) => ani.id === 'contentAnimation');
|
||||
|
||||
const enableBackdrop = () => {
|
||||
// Respect explicit opt-out of focus trapping/backdrop interactions
|
||||
// If focusTrap is false or showBackdrop is false, do not enable the backdrop or re-enable focus trap
|
||||
const el = baseEl as HTMLIonModalElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
if (el.focusTrap === false || el.showBackdrop === false) {
|
||||
return;
|
||||
}
|
||||
baseEl.style.setProperty('pointer-events', 'auto');
|
||||
backdropEl.style.setProperty('pointer-events', 'auto');
|
||||
|
||||
@@ -235,7 +241,10 @@ export const createSheetGesture = (
|
||||
* ion-backdrop and .modal-wrapper always have pointer-events: auto
|
||||
* applied, so the modal content can still be interacted with.
|
||||
*/
|
||||
const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint;
|
||||
const shouldEnableBackdrop =
|
||||
currentBreakpoint > backdropBreakpoint &&
|
||||
(baseEl as HTMLIonModalElement & { focusTrap?: boolean }).focusTrap !== false &&
|
||||
(baseEl as HTMLIonModalElement & { showBackdrop?: boolean }).showBackdrop !== false;
|
||||
if (shouldEnableBackdrop) {
|
||||
enableBackdrop();
|
||||
} else {
|
||||
@@ -582,7 +591,10 @@ export const createSheetGesture = (
|
||||
* Backdrop should become enabled
|
||||
* after the backdropBreakpoint value
|
||||
*/
|
||||
const shouldEnableBackdrop = currentBreakpoint > backdropBreakpoint;
|
||||
const shouldEnableBackdrop =
|
||||
currentBreakpoint > backdropBreakpoint &&
|
||||
(baseEl as HTMLIonModalElement & { focusTrap?: boolean }).focusTrap !== false &&
|
||||
(baseEl as HTMLIonModalElement & { showBackdrop?: boolean }).showBackdrop !== false;
|
||||
if (shouldEnableBackdrop) {
|
||||
enableBackdrop();
|
||||
} else {
|
||||
|
||||
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 42 KiB |
@@ -3,7 +3,7 @@
|
||||
// Picker Column
|
||||
// --------------------------------------------------
|
||||
|
||||
button {
|
||||
.picker-column-option-button {
|
||||
@include padding(0);
|
||||
@include margin(0);
|
||||
|
||||
@@ -40,6 +40,6 @@ button {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
:host(.option-disabled) button {
|
||||
:host(.option-disabled) .picker-column-option-button {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -124,9 +124,9 @@ export class PickerColumnOption implements ComponentInterface {
|
||||
['option-disabled']: disabled,
|
||||
})}
|
||||
>
|
||||
<button tabindex="-1" aria-label={ariaLabel} disabled={disabled} onClick={() => this.onClick()}>
|
||||
<div class={'picker-column-option-button'} role="button" aria-label={ariaLabel} onClick={() => this.onClick()}>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</div>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
|
||||
test('should not have accessibility violations', async ({ page }) => {
|
||||
await page.goto(`/src/components/picker-column-option/test/a11y`, config);
|
||||
|
||||
const results = await new AxeBuilder({ page }).analyze();
|
||||
const results = await new AxeBuilder({ page }).disableRules('color-contrast').analyze();
|
||||
|
||||
expect(results.violations).toEqual([]);
|
||||
});
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |