diff --git a/.github/actions/publish-npm/action.yml b/.github/actions/publish-npm/action.yml index 0b5e9924719..d3e06d5fb01 100644 --- a/.github/actions/publish-npm/action.yml +++ b/.github/actions/publish-npm/action.yml @@ -22,7 +22,7 @@ runs: using: 'composite' steps: - name: 🟒 Configure Node for Publish - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ inputs.node-version }} registry-url: 'https://registry.npmjs.org' diff --git a/.github/ionic-issue-bot.yml b/.github/ionic-issue-bot.yml index c1e02a5f068..d98b2a53205 100644 --- a/.github/ionic-issue-bot.yml +++ b/.github/ionic-issue-bot.yml @@ -40,7 +40,7 @@ comment: If the requested feature is something you would find useful for your applications, please react to the original post with πŸ‘ (`+1`). If you would like to provide an additional use case for the feature, please post a comment. - + The team will review this feedback and make a final decision. Any decision will be posted on this thread, but please note that we may ultimately decide not to pursue this feature. @@ -83,6 +83,7 @@ stale: exemptLabels: - "good first issue" - "triage" + - "bug: external" - "type: bug" - "type: feature request" - "needs: investigation" diff --git a/.github/workflows/actions/build-angular-server/action.yml b/.github/workflows/actions/build-angular-server/action.yml index a90da3676c1..b5d37c5a9ac 100644 --- a/.github/workflows/actions/build-angular-server/action.yml +++ b/.github/workflows/actions/build-angular-server/action.yml @@ -3,7 +3,7 @@ description: 'Build Ionic Angular Server' runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/build-angular/action.yml b/.github/workflows/actions/build-angular/action.yml index fc7496de421..5224f9741e0 100644 --- a/.github/workflows/actions/build-angular/action.yml +++ b/.github/workflows/actions/build-angular/action.yml @@ -27,6 +27,10 @@ runs: run: npm run build shell: bash working-directory: ./packages/angular + - name: Clean core package.json + run: git checkout ./package.json + shell: bash + working-directory: ./core - name: πŸ” Check Diff run: git diff --exit-code shell: bash diff --git a/.github/workflows/actions/build-core-stencil-prerelease/action.yml b/.github/workflows/actions/build-core-stencil-prerelease/action.yml index 323ba6f7259..742c45a9b68 100644 --- a/.github/workflows/actions/build-core-stencil-prerelease/action.yml +++ b/.github/workflows/actions/build-core-stencil-prerelease/action.yml @@ -8,8 +8,8 @@ inputs: runs: using: 'composite' steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x @@ -29,4 +29,4 @@ runs: with: name: ionic-core output: core/CoreBuild.zip - paths: core/dist core/components core/src/foundations core/css core/themes core/hydrate core/loader core/src/components.d.ts + paths: core/dist core/components core/src/foundations core/css core/themes core/hydrate core/loader core/src/components.d.ts core/package.json diff --git a/.github/workflows/actions/build-core/action.yml b/.github/workflows/actions/build-core/action.yml index 2211059e817..0f712bda8f4 100644 --- a/.github/workflows/actions/build-core/action.yml +++ b/.github/workflows/actions/build-core/action.yml @@ -8,8 +8,8 @@ inputs: runs: using: 'composite' steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - name: πŸ•ΈοΈ Install Dependencies diff --git a/.github/workflows/actions/build-react-router/action.yml b/.github/workflows/actions/build-react-router/action.yml index a003fd83d6c..c8083494b0a 100644 --- a/.github/workflows/actions/build-react-router/action.yml +++ b/.github/workflows/actions/build-react-router/action.yml @@ -3,7 +3,7 @@ description: 'Build Ionic React Router' runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/build-react/action.yml b/.github/workflows/actions/build-react/action.yml index b65292f5dc7..5899335ad3e 100644 --- a/.github/workflows/actions/build-react/action.yml +++ b/.github/workflows/actions/build-react/action.yml @@ -3,7 +3,7 @@ description: 'Build Ionic React' runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive @@ -31,6 +31,10 @@ runs: run: npm run test.spec shell: bash working-directory: ./packages/react + - name: Clean core package.json + run: git checkout ./package.json + shell: bash + working-directory: ./core - name: πŸ” Check Diff run: git diff --exit-code shell: bash diff --git a/.github/workflows/actions/build-vue-router/action.yml b/.github/workflows/actions/build-vue-router/action.yml index b5bb65c6f89..9b07ce64973 100644 --- a/.github/workflows/actions/build-vue-router/action.yml +++ b/.github/workflows/actions/build-vue-router/action.yml @@ -3,7 +3,7 @@ description: 'Builds Ionic Vue Router' runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/build-vue/action.yml b/.github/workflows/actions/build-vue/action.yml index e660c31bbbd..5c7497ec359 100644 --- a/.github/workflows/actions/build-vue/action.yml +++ b/.github/workflows/actions/build-vue/action.yml @@ -3,7 +3,7 @@ description: 'Build Ionic Vue' runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive @@ -27,6 +27,10 @@ runs: run: npm run build shell: bash working-directory: ./packages/vue + - name: Clean core package.json + run: git checkout ./package.json + shell: bash + working-directory: ./core - name: πŸ” Check Diff run: git diff --exit-code shell: bash diff --git a/.github/workflows/actions/download-archive/action.yml b/.github/workflows/actions/download-archive/action.yml index e61c44a0910..2acddf9713f 100644 --- a/.github/workflows/actions/download-archive/action.yml +++ b/.github/workflows/actions/download-archive/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: name: ${{ inputs.name }} path: ${{ inputs.path }} diff --git a/.github/workflows/actions/test-angular-e2e/action.yml b/.github/workflows/actions/test-angular-e2e/action.yml index 9c3ac716d53..a4835a0210a 100644 --- a/.github/workflows/actions/test-angular-e2e/action.yml +++ b/.github/workflows/actions/test-angular-e2e/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive @@ -17,7 +17,7 @@ runs: - uses: ./.github/workflows/actions/download-archive with: name: ionic-angular - path: ./angular + path: ./packages/angular filename: AngularBuild.zip - uses: ./.github/workflows/actions/download-archive with: diff --git a/.github/workflows/actions/test-core-clean-build/action.yml b/.github/workflows/actions/test-core-clean-build/action.yml index b69a485b33f..96abc90121c 100644 --- a/.github/workflows/actions/test-core-clean-build/action.yml +++ b/.github/workflows/actions/test-core-clean-build/action.yml @@ -3,7 +3,7 @@ description: 'Test Core Clean Build' runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x @@ -12,6 +12,10 @@ runs: name: ionic-core path: ./core filename: CoreBuild.zip + - name: Clean core package.json + run: git checkout ./package.json + shell: bash + working-directory: ./core - name: πŸ” Check Diff run: | git diff --exit-code || { diff --git a/.github/workflows/actions/test-core-lint/action.yml b/.github/workflows/actions/test-core-lint/action.yml index 8e81bd6a635..f9f0011719a 100644 --- a/.github/workflows/actions/test-core-lint/action.yml +++ b/.github/workflows/actions/test-core-lint/action.yml @@ -3,13 +3,17 @@ description: 'Test Core Lint' runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - name: πŸ•ΈοΈ Install Dependencies run: npm ci working-directory: ./core shell: bash + - name: Clean core package.json + run: git checkout ./package.json + shell: bash + working-directory: ./core - name: πŸ–ŒοΈ Lint run: npm run lint shell: bash diff --git a/.github/workflows/actions/test-core-screenshot/action.yml b/.github/workflows/actions/test-core-screenshot/action.yml index e3c7d8771d5..1f8699e66d4 100644 --- a/.github/workflows/actions/test-core-screenshot/action.yml +++ b/.github/workflows/actions/test-core-screenshot/action.yml @@ -13,7 +13,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive @@ -30,6 +30,10 @@ runs: run: npm run test.e2e.docker.ci ${{ inputs.component }} -- --shard=${{ inputs.shard }}/${{ inputs.totalShards }} shell: bash working-directory: ./core + - name: Clean core package.json + run: git checkout ./package.json + shell: bash + working-directory: ./core - name: Test and Update id: test-and-update if: inputs.update == 'true' @@ -62,7 +66,7 @@ runs: working-directory: ./core - name: πŸ“¦ Archive Updated Screenshots if: inputs.update == 'true' && steps.test-and-update.outputs.hasUpdatedScreenshots == 'true' - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: updated-screenshots-${{ inputs.shard }}-${{ inputs.totalShards }} path: UpdatedScreenshots-${{ inputs.shard }}-${{ inputs.totalShards }}.zip diff --git a/.github/workflows/actions/test-core-spec/action.yml b/.github/workflows/actions/test-core-spec/action.yml index ee54ae604cf..2aab4b1be94 100644 --- a/.github/workflows/actions/test-core-spec/action.yml +++ b/.github/workflows/actions/test-core-spec/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - name: πŸ•ΈοΈ Install Dependencies diff --git a/.github/workflows/actions/test-react-e2e/action.yml b/.github/workflows/actions/test-react-e2e/action.yml index d27dd9f836e..a1bcbf7a4db 100644 --- a/.github/workflows/actions/test-react-e2e/action.yml +++ b/.github/workflows/actions/test-react-e2e/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/test-react-router-e2e/action.yml b/.github/workflows/actions/test-react-router-e2e/action.yml index b4e627ee896..034cfdce747 100644 --- a/.github/workflows/actions/test-react-router-e2e/action.yml +++ b/.github/workflows/actions/test-react-router-e2e/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/test-vue-e2e/action.yml b/.github/workflows/actions/test-vue-e2e/action.yml index c0adfd409b9..191cd193c8a 100644 --- a/.github/workflows/actions/test-vue-e2e/action.yml +++ b/.github/workflows/actions/test-vue-e2e/action.yml @@ -6,7 +6,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - uses: ./.github/workflows/actions/download-archive diff --git a/.github/workflows/actions/update-reference-screenshots/action.yml b/.github/workflows/actions/update-reference-screenshots/action.yml index ad41d5e724d..6ee56689b10 100644 --- a/.github/workflows/actions/update-reference-screenshots/action.yml +++ b/.github/workflows/actions/update-reference-screenshots/action.yml @@ -7,10 +7,10 @@ on: runs: using: 'composite' steps: - - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24.x - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: path: ./artifacts - name: πŸ”Ž Extract Archives @@ -21,6 +21,10 @@ runs: find . -type f -name 'UpdatedScreenshots-*.zip' -exec unzip -q -o -d ../ {} \; shell: bash working-directory: ./artifacts + - name: Clean core package.json + run: git checkout ./package.json + shell: bash + working-directory: ./core - name: πŸ“Έ Push Screenshots # Configure user as Ionitron # and push only the changed .png snapshots diff --git a/.github/workflows/actions/upload-archive/action.yml b/.github/workflows/actions/upload-archive/action.yml index 67465651c88..c09397345f9 100644 --- a/.github/workflows/actions/upload-archive/action.yml +++ b/.github/workflows/actions/upload-archive/action.yml @@ -13,7 +13,7 @@ runs: - name: πŸ—„οΈ Create Archive run: zip -q -r ${{ inputs.output }} ${{ inputs.paths }} shell: bash - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: ${{ inputs.name }} path: ${{ inputs.output }} diff --git a/.github/workflows/assign-issues.yml b/.github/workflows/assign-issues.yml index d06c1f52e10..d79a8c17c2c 100644 --- a/.github/workflows/assign-issues.yml +++ b/.github/workflows/assign-issues.yml @@ -11,7 +11,7 @@ jobs: issues: write steps: - name: 'Auto-assign issue' - uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0 + uses: pozil/auto-assign-issue@70adb98ca8b3941524e9ecde48e89067c4f96736 # v3.0.0 with: assignees: brandyscarney, thetaPC, ShaneK numOfAssignee: 1 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d3d52e048be..7772b2c432f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: build-core: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/test-core-clean-build test-core-lint: needs: [build-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/test-core-lint test-core-spec: needs: [build-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/build-vue build-vue-router: needs: [build-vue] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/build-angular build-angular-server: needs: [build-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/build-react build-react-router: needs: [build-react] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/test-react-e2e with: app: ${{ matrix.apps }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 2844a4f9b01..5e6715d02b1 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -14,7 +14,7 @@ jobs: permissions: security-events: write steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: github/codeql-action/init@v4 with: languages: javascript diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 2c459097e67..51806128a8e 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -13,7 +13,7 @@ jobs: outputs: dev-hash: ${{ steps.create-dev-hash.outputs.DEV_HASH }} steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # 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 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index dc2fa5860a9..2f64d9f9bd5 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -13,7 +13,7 @@ jobs: outputs: nightly-hash: ${{ steps.create-nightly-hash.outputs.NIGHTLY_HASH }} steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # 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 diff --git a/.github/workflows/release-ionic.yml b/.github/workflows/release-ionic.yml index c3f9e5740b6..70f1fb1956b 100644 --- a/.github/workflows/release-ionic.yml +++ b/.github/workflows/release-ionic.yml @@ -23,7 +23,7 @@ jobs: release-core: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/actions/publish-npm with: scope: '@ionic/core' @@ -48,7 +48,7 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Restore @ionic/docs built cache uses: ./.github/workflows/actions/download-archive with: @@ -67,7 +67,7 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: @@ -93,7 +93,7 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: @@ -118,7 +118,7 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: @@ -143,7 +143,7 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: @@ -163,7 +163,7 @@ jobs: needs: [release-react] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: @@ -188,7 +188,7 @@ jobs: needs: [release-vue] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a3bb4491cf5..df4429a806b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,7 +58,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ secrets.IONITRON_TOKEN }} fetch-depth: 0 @@ -89,7 +89,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Pull the latest version of the reference # branch instead of the revision that triggered # the workflow otherwise we won't get the commit diff --git a/.github/workflows/stencil-nightly.yml b/.github/workflows/stencil-nightly.yml index b00ac8c42a0..3e8d714ccc1 100644 --- a/.github/workflows/stencil-nightly.yml +++ b/.github/workflows/stencil-nightly.yml @@ -26,7 +26,7 @@ jobs: build-core-with-stencil-nightly: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/test-core-lint test-core-spec: needs: [build-core-with-stencil-nightly] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/build-vue build-vue-router: needs: [build-vue] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/build-angular build-angular-server: needs: [build-core-with-stencil-nightly] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/build-react build-react-router: needs: [build-react] runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: ./.github/workflows/actions/test-react-e2e with: app: ${{ matrix.apps }} diff --git a/.github/workflows/update-screenshots.yml b/.github/workflows/update-screenshots.yml index eefcddcb893..6efecbd8f95 100644 --- a/.github/workflows/update-screenshots.yml +++ b/.github/workflows/update-screenshots.yml @@ -26,7 +26,7 @@ jobs: build-core: runs-on: ubuntu-latest steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Normally, we could just push with the # default GITHUB_TOKEN, but that will # not cause the build workflow diff --git a/BREAKING.md b/BREAKING.md index ffecb8605f9..c47e098e609 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -19,9 +19,13 @@ This is a comprehensive list of the breaking changes introduced in the major ver - [Button](#version-9x-button) - [Card](#version-9x-card) - [Chip](#version-9x-chip) + - [Datetime](#version-9x-datetime) - [Grid](#version-9x-grid) + - [Input Otp](#version-9x-input-otp) - [Item Divider](#version-9x-item-divider) + - [Radio Group](#version-9x-radio-group) - [Spinner](#version-9x-spinner) + - [Textarea](#version-9x-textarea)

Global Styles

@@ -37,7 +41,35 @@ This is a comprehensive list of the breaking changes introduced in the major ver

Card

-- The `border-radius` of the `ios` and `md` card now defaults to `14px` and `12px` instead of `8px` and `4px`, respectively, in accordance with the iOS and Material Design 3 guidelines. To revert to the previous appearance, set the `shape` to `"soft"`, or override the `--border-radius` CSS variable to specify a different value. +- **ion-card**: The `border-radius` of the `ios` and `md` card now defaults to `14px` and `12px` instead of `8px` and `4px`, respectively, in accordance with the iOS and Material Design 3 guidelines. To revert to the previous appearance, set the `shape` to `"soft"`, or override the `--border-radius` CSS variable to specify a different value. + +- **ion-card-content**: The `ion-card-content` component has been updated to Shadow DOM. With this update, all card-related components now use Shadow DOM for style encapsulation. The default styles for heading elements inside `ion-card-content` have been removed. If you need custom styling for headings, you can add your own CSS targeting these elements. For example: + + ```css + ion-card-content h1 { + margin-top: 0; + margin-bottom: 2px; + + font-size: 1.5rem; + } + + ion-card-content h2 { + margin-top: 2px; + margin-bottom: 2px; + + font-size: 1rem; + } + + ion-card-content h3, + ion-card-content h4, + ion-card-content h5, + ion-card-content h6 { + margin-top: 2px; + margin-bottom: 2px; + + font-size: 0.875rem; + } + ```

Chip

@@ -50,19 +82,18 @@ This is a comprehensive list of the breaking changes introduced in the major ver - Specific theme classes (e.g., `ion-chip.md`) are no longer supported. Style modifications based on the active theme must be implemented using theme tokens rather than direct class targeting. - The `border-radius` of the `ios` and `md` chip now defaults to `10px` and `8px`, respectively, instead of `16px` in accordance with the iOS and Material Design 3 guidelines. To revert to the previous appearance, set the `shape` to `"round"`, or override the `IonChip.shape.round.border.radius` to specify a different value for global styles and `--ion-chip-shape-round-border-radius` for component-specific styles. -

Grid

+

Datetime

-- The properties `pull` and `push` have been deprecated and no longer work. A similar look can be achieved with the newly added property `order`. +- The `ion-buttons` component has been removed from the internal implementation of `ion-datetime` and is no longer required when passing custom buttons to the `slot="buttons"`. When providing custom buttons, use a `div` element instead of `ion-buttons`. While existing code using `ion-buttons` may continue to work visually, future updates to the `ion-buttons` component may cause any styles you rely on to break. -

Radio Group

+

Grid

-- Converted `ion-radio-group` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
-If you were targeting the internals of `ion-radio-group` in your CSS, you will need to target the `supporting-text`, `helper-text` or `error-text` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables.
-Additionally, the `radio-group-wrapper` div element has been removed, causing slotted elements to be direct children of the `ion-radio-group`. +- The properties `pull` and `push` have been deprecated and no longer work. A similar look can be achieved with the newly added property `order`.
Example 1: Swap two columns
**Version up to 8.x** + ```html @@ -72,7 +103,9 @@ Additionally, the `radio-group-wrapper` div element has been removed, causing sl ``` + **Version 9.x+** + ```html @@ -84,9 +117,11 @@ Additionally, the `radio-group-wrapper` div element has been removed, causing sl ```
Example 2: Reorder columns with specific sizes
+ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has `size="3" pull="9"`: **Version up to 8.x** + ```html @@ -95,7 +130,9 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has ` ``` + **Version 9.x+** + ```html @@ -104,7 +141,9 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has ` ``` +
Example 3: Push
+ ```html @@ -117,7 +156,9 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has ` ``` + **Version 9.x+** + ```html @@ -132,6 +173,7 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has ` ```
Example 4: Push and Pull
+ ```html @@ -144,7 +186,9 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has ` ``` + **Version 9.x+** + ```html @@ -158,6 +202,12 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has ` ``` +

Input Otp

+ +Converted `ion-input-otp` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM). + +If you were targeting the internals of `ion-input-otp` in your CSS, you will need to target the `group`, `container`, `native`, `separator` or `description` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables. +

Item Divider

- Component CSS variables have been removed. The component now utilizes the centralized Ionic Theming system. Global updates should be managed via the theme tokens file, while component-specific overrides are handled through localized CSS variables. @@ -173,6 +223,14 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has ` - `--inner-padding-start` is replaced by `IonItemDivider.inner.padding.start` for global styles and `--ion-item-divider-inner-padding-start` for component-specific overrides. - Specific theme classes (e.g., `ion-item-divider.md`) are no longer supported. Style modifications based on the active theme must be implemented using theme tokens rather than direct class targeting. +

Radio Group

+ +Converted `ion-radio-group` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM). + +If you were targeting the internals of `ion-radio-group` in your CSS, you will need to target the `supporting-text`, `helper-text` or `error-text` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables. + +Additionally, the `radio-group-wrapper` div element has been removed, causing slotted elements to be direct children of the `ion-radio-group`. +

Spinner

- Component CSS variables have been removed. The component now utilizes the centralized Ionic Theming system. Global updates should be managed via the theme tokens file, while component-specific overrides are handled through localized CSS variables. @@ -181,3 +239,9 @@ To reorder two columns where column 1 has `size="9" push="3"` and column 2 has ` - CSS classes now include the property name to improve clarity. - `.spinner-[spinner-name]` β†’ `.spinner-name-[spinner-name]` - Specific theme classes (e.g., `ion-spinner.md`) are no longer supported. Style modifications based on the active theme must be implemented using theme tokens rather than direct class targeting. + +

Textarea

+ +Converted `ion-textarea` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM). + +If you were targeting the internals of `ion-textarea` in your CSS, you will need to target the `wrapper`, `container`, `label`, `native`, `supporting-text`, `helper-text`, `error-text`, `counter`, or `bottom` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables. diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d8b1ae591..32f33d2cafe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,177 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.6](https://github.com/ionic-team/ionic-framework/compare/v8.8.5...v8.8.6) (2026-05-06) + + +### Bug Fixes + +* **action-sheet:** restore action-sheet-selected class on non-radio buttons ([#31109](https://github.com/ionic-team/ionic-framework/issues/31109)) ([c18502f](https://github.com/ionic-team/ionic-framework/commit/c18502f3efdec5440a11289235a93c62ce27ab89)), closes [#31090](https://github.com/ionic-team/ionic-framework/issues/31090) +* **datetime:** prevent hidden-state observer from tearing down ready class on initial entry ([#31108](https://github.com/ionic-team/ionic-framework/issues/31108)) ([30b479a](https://github.com/ionic-team/ionic-framework/commit/30b479a53acbc16961002df256bec358dc11e7fa)) +* **segment:** segment drag would set disabled segment button checked ([#31112](https://github.com/ionic-team/ionic-framework/issues/31112)) ([44be424](https://github.com/ionic-team/ionic-framework/commit/44be424221bee11ffbe91c4b1fa0a4d56fe1ecac)) + + + + + +## [8.8.5](https://github.com/ionic-team/ionic-framework/compare/v8.8.4...v8.8.5) (2026-04-29) + + +### Bug Fixes + +* **modal:** remove safe-area gap and flash in fullscreen modals ([#31092](https://github.com/ionic-team/ionic-framework/issues/31092)) ([f3cd39b](https://github.com/ionic-team/ionic-framework/commit/f3cd39b7fb291286374285c4a326ec6b9a8ea237)), closes [#31015](https://github.com/ionic-team/ionic-framework/issues/31015) +* **select:** select focused option on Enter in popover and modal interfaces ([#31093](https://github.com/ionic-team/ionic-framework/issues/31093)) ([fd79771](https://github.com/ionic-team/ionic-framework/commit/fd79771e5be77c9f38379a3a7b9ab44bb11ff325)), closes [#30561](https://github.com/ionic-team/ionic-framework/issues/30561) + + + + + +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + + +### Bug Fixes + +* **checkbox:** show labels after page navigation ([#31062](https://github.com/ionic-team/ionic-framework/issues/31062)) ([f4ac445](https://github.com/ionic-team/ionic-framework/commit/f4ac4459f8317bd5eeff7d4809f9cb0991c8efd9)), closes [#31052](https://github.com/ionic-team/ionic-framework/issues/31052) +* **datetime:** multiple month selected and flakiness display ([#31053](https://github.com/ionic-team/ionic-framework/issues/31053)) ([308aef5](https://github.com/ionic-team/ionic-framework/commit/308aef569d8c6ebc3ad2186bca6969da8e4b2a8d)) +* **tab-button:** update dark palette focused background color ([#31050](https://github.com/ionic-team/ionic-framework/issues/31050)) ([dec46b5](https://github.com/ionic-team/ionic-framework/commit/dec46b5d317080dd5d97dc056f0d8e6d4c8c45ac)) + + + + + +## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) + + +### Bug Fixes + +* **datetime:** scroll failing for adjacent days on ios ([#31033](https://github.com/ionic-team/ionic-framework/issues/31033)) ([3afd67f](https://github.com/ionic-team/ionic-framework/commit/3afd67f997521290a34c4448445ae227dc67f8f1)) +* **input-otp:** prevent deletion and paste when disabled or readonly ([#30983](https://github.com/ionic-team/ionic-framework/issues/30983)) ([81aa977](https://github.com/ionic-team/ionic-framework/commit/81aa977fde4867146adf67fcf72e24026574929c)), closes [#30913](https://github.com/ionic-team/ionic-framework/issues/30913) + + + + + +## [8.8.2](https://github.com/ionic-team/ionic-framework/compare/v8.8.1...v8.8.2) (2026-03-25) + + +### Bug Fixes + +* **angular:** forward generic type parameter on ModalOptions and PopoverOptions ([#31022](https://github.com/ionic-team/ionic-framework/issues/31022)) ([cbfe7cc](https://github.com/ionic-team/ionic-framework/commit/cbfe7cce3be345eacbf9fe29e74438a927c16679)), closes [#31012](https://github.com/ionic-team/ionic-framework/issues/31012) +* **checkbox:** re-evaluate label visibility when label is updated ([#30980](https://github.com/ionic-team/ionic-framework/issues/30980)) ([ce83407](https://github.com/ionic-team/ionic-framework/commit/ce83407e1debbe74f20d2d6dc2535a0ef3f974a0)) +* **datetime:** days keep in focus after changing the month ([#31021](https://github.com/ionic-team/ionic-framework/issues/31021)) ([5fdaba2](https://github.com/ionic-team/ionic-framework/commit/5fdaba2b021fe8b2b43a49eae7c687544c97d502)) + + + + + +## [8.8.1](https://github.com/ionic-team/ionic-framework/compare/v8.8.0...v8.8.1) (2026-03-06) + + +### Bug Fixes + +* **accordion:** update tabindex based on disabled state ([#30986](https://github.com/ionic-team/ionic-framework/issues/30986)) ([0e76a69](https://github.com/ionic-team/ionic-framework/commit/0e76a69370083702568825c29d63cf257d6b88f1)) +* **angular:** export RefresherPullEnd types ([#30991](https://github.com/ionic-team/ionic-framework/issues/30991)) ([72abcca](https://github.com/ionic-team/ionic-framework/commit/72abccaad8df3c1db004da28610fddd95ac93c02)) + + +### Features + +* **toast:** add wrapper and content parts (originally intended for 8.8.0 but omitted from that release) ([#30992](https://github.com/ionic-team/ionic-framework/issues/30992)) ([366f00e](https://github.com/ionic-team/ionic-framework/commit/366f00e25f06e28aa7166275445716c2d301e44a)), closes [#30735](https://github.com/ionic-team/ionic-framework/issues/30735) + + + + + +# [8.8.0](https://github.com/ionic-team/ionic-framework/compare/v8.7.18...v8.8.0) (2026-03-04) + + +### Features + +* **angular:** add custom injector support for modal and popover controllers ([#30899](https://github.com/ionic-team/ionic-framework/issues/30899)) ([822da42](https://github.com/ionic-team/ionic-framework/commit/822da428af86cd9b036b81515272321eb8fa586c)), closes [#30638](https://github.com/ionic-team/ionic-framework/issues/30638) +* **content:** add content-fullscreen class when fullscreen is true ([#30926](https://github.com/ionic-team/ionic-framework/issues/30926)) ([d74b11b](https://github.com/ionic-team/ionic-framework/commit/d74b11bc19d6268b256daf23ba6f107483c00320)) +* **datetime:** add header parts ([#30945](https://github.com/ionic-team/ionic-framework/issues/30945)) ([6ea186d](https://github.com/ionic-team/ionic-framework/commit/6ea186d96d80a94b774d4d0a51d536e0e5599935)) +* **datetime:** add wheel part to ion-picker-column ([#30934](https://github.com/ionic-team/ionic-framework/issues/30934)) ([0cf4c03](https://github.com/ionic-team/ionic-framework/commit/0cf4c03e298bb4f7eea71c966a1473765ebd6d7a)) +* **item-divider:** add inner and container parts ([#30928](https://github.com/ionic-team/ionic-framework/issues/30928)) ([5cdeb7f](https://github.com/ionic-team/ionic-framework/commit/5cdeb7fd357298f15e7ae29b14412d97bdc7c656)) +* **item-option:** add inner and container parts ([#30929](https://github.com/ionic-team/ionic-framework/issues/30929)) ([f8f7ffd](https://github.com/ionic-team/ionic-framework/commit/f8f7ffda318c0143d9bb5c79fe55b4ecb88e6ce3)) +* **item:** add inner and container parts ([#30927](https://github.com/ionic-team/ionic-framework/issues/30927)) ([a2c6559](https://github.com/ionic-team/ionic-framework/commit/a2c655923bb1cff51864949575e19028623c695d)) +* **list-header:** add inner part ([#30930](https://github.com/ionic-team/ionic-framework/issues/30930)) ([ef73476](https://github.com/ionic-team/ionic-framework/commit/ef73476e08670630907e775a38f9ed30a84e3f1f)) +* **modal:** add drag events for sheet and card modals ([#30962](https://github.com/ionic-team/ionic-framework/issues/30962)) ([d29ac71](https://github.com/ionic-team/ionic-framework/commit/d29ac713fad604c256fb385eb0c26eb9717e1ff4)), closes [#23955](https://github.com/ionic-team/ionic-framework/issues/23955) +* **range:** add classes and expose parts to allow individual styling of dual knobs ([#30941](https://github.com/ionic-team/ionic-framework/issues/30941)) ([5bcf921](https://github.com/ionic-team/ionic-framework/commit/5bcf92184118055483bf306ab9e319b8e3e61870)), closes [#29862](https://github.com/ionic-team/ionic-framework/issues/29862) +* **range:** add classes to the range when the value is at the min or max ([#30932](https://github.com/ionic-team/ionic-framework/issues/30932)) ([fac1a66](https://github.com/ionic-team/ionic-framework/commit/fac1a6673c88a531f1d79656be4eb544f235f819)) +* **refresher:** add ionPullStart and ionPullEnd events ([#30946](https://github.com/ionic-team/ionic-framework/issues/30946)) ([814c2e5](https://github.com/ionic-team/ionic-framework/commit/814c2e5ccd6d5bfda12bdf13a566cd66ff830d5b)), closes [#24524](https://github.com/ionic-team/ionic-framework/issues/24524) +* **segment-view:** add swipeGesture property to disable swiping ([#30948](https://github.com/ionic-team/ionic-framework/issues/30948)) ([46806bd](https://github.com/ionic-team/ionic-framework/commit/46806bd6e2af90a0b31fca68f508c06d3d281ec0)), closes [#30290](https://github.com/ionic-team/ionic-framework/issues/30290) +* **select:** add wrapper and bottom shadow parts ([#30951](https://github.com/ionic-team/ionic-framework/issues/30951)) ([5cea5ae](https://github.com/ionic-team/ionic-framework/commit/5cea5aeb44393edab7064e5980a1eb7e607d1b8d)) +* **select:** pass cancelText property to modal interface ([#30282](https://github.com/ionic-team/ionic-framework/issues/30282)) ([6e4f60a](https://github.com/ionic-team/ionic-framework/commit/6e4f60af4c188ae04028b444aa21118ae27c2ca7)) +* **textarea:** reflect disabled and readonly props ([#30910](https://github.com/ionic-team/ionic-framework/issues/30910)) ([55735df](https://github.com/ionic-team/ionic-framework/commit/55735df3fa62c8e259c56db3169f3d5459e71c0c)) + + + + + +## [8.7.18](https://github.com/ionic-team/ionic-framework/compare/v8.7.17...v8.7.18) (2026-02-25) + + +### Bug Fixes + +* **datetime:** stretch ion-buttons to fill space for ios ([#30963](https://github.com/ionic-team/ionic-framework/issues/30963)) ([d46b0b1](https://github.com/ionic-team/ionic-framework/commit/d46b0b15f6a652da6f863cf303e7ce06cfc820a8)) +* **many:** clear timeouts ([#30851](https://github.com/ionic-team/ionic-framework/issues/30851)) ([70b1237](https://github.com/ionic-team/ionic-framework/commit/70b1237823dd0cdab852486a6b2cbbfe0d0aaae9)), closes [#30860](https://github.com/ionic-team/ionic-framework/issues/30860) +* **modal, popover:** respect safe area insets on popovers and modals ([#30949](https://github.com/ionic-team/ionic-framework/issues/30949)) ([6490797](https://github.com/ionic-team/ionic-framework/commit/6490797851cede3bfda893a19b10f165259ec988)), closes [#28411](https://github.com/ionic-team/ionic-framework/issues/28411) +* **nav-controller:** reset direction state when navigation is canceled ([#30955](https://github.com/ionic-team/ionic-framework/issues/30955)) ([53172d1](https://github.com/ionic-team/ionic-framework/commit/53172d1a4035d5b510c230553aabd53dc1389e4b)) +* **radio-group:** prevent DOMException and NotFoundError when filtering radios ([#30958](https://github.com/ionic-team/ionic-framework/issues/30958)) ([682a17e](https://github.com/ionic-team/ionic-framework/commit/682a17ebb754da7714989623cf84b75e715e20e1)), closes [#30279](https://github.com/ionic-team/ionic-framework/issues/30279) [#30359](https://github.com/ionic-team/ionic-framework/issues/30359) +* **toast:** keep icon on the same line as long message in stacked layout ([#30923](https://github.com/ionic-team/ionic-framework/issues/30923)) ([442e3e9](https://github.com/ionic-team/ionic-framework/commit/442e3e983107a69cea4fb5587fb33da718eee8a3)), closes [#30908](https://github.com/ionic-team/ionic-framework/issues/30908) + + + + + +## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14) + + +### Bug Fixes + +* **input:** prevent Android TalkBack from focusing label separately ([#30895](https://github.com/ionic-team/ionic-framework/issues/30895)) ([ab733b7](https://github.com/ionic-team/ionic-framework/commit/ab733b71dd355d9486757f219fe09acaefeeefcc)) +* **input:** prevent placeholder from overlapping start slot during scroll assist ([#30896](https://github.com/ionic-team/ionic-framework/issues/30896)) ([3b3318d](https://github.com/ionic-team/ionic-framework/commit/3b3318da513b199128f3822bd8226797cd118b0f)) +* **tab-bar:** prevent keyboard controller memory leak on rapid mount/unmount ([#30906](https://github.com/ionic-team/ionic-framework/issues/30906)) ([f99d000](https://github.com/ionic-team/ionic-framework/commit/f99d0007a8ffc9c7d3d2636e912c37c12112b21d)) + + + + + +## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31) + + +### Bug Fixes + +* **modal:** prevent card modal animation on viewport resize when modal is closed ([#30894](https://github.com/ionic-team/ionic-framework/issues/30894)) ([e5634d4](https://github.com/ionic-team/ionic-framework/commit/e5634d45ee5fd32715f6e6b75e0448f74ee1f8f2)), closes [#30679](https://github.com/ionic-team/ionic-framework/issues/30679) + + + + + +## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23) + + +### Bug Fixes + +* **core:** use Capacitor safe-area CSS variables on older WebViews ([#30865](https://github.com/ionic-team/ionic-framework/issues/30865)) ([8573bf8](https://github.com/ionic-team/ionic-framework/commit/8573bf8083f75eda13c954a56731a6aac8ca5724)) +* **header:** show iOS condense header when app is in MD mode ([#30690](https://github.com/ionic-team/ionic-framework/issues/30690)) ([f83b000](https://github.com/ionic-team/ionic-framework/commit/f83b0005309400d674e43c497bdffbcb9d2c4d94)), closes [#29929](https://github.com/ionic-team/ionic-framework/issues/29929) +* **input-password-toggle:** improve screen reader announcements ([#30885](https://github.com/ionic-team/ionic-framework/issues/30885)) ([12ede4b](https://github.com/ionic-team/ionic-framework/commit/12ede4b79c8d5cffc2b014c7c8a0d2ef1d3bf90d)) +* **modal:** dismiss top-most overlay when multiple IDs match ([#30883](https://github.com/ionic-team/ionic-framework/issues/30883)) ([3b60a1d](https://github.com/ionic-team/ionic-framework/commit/3b60a1d68a1df1606ffee0bde7db7a206bac404a)), closes [#30030](https://github.com/ionic-team/ionic-framework/issues/30030) + + + + + +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + + +### Bug Fixes + +* **tabs:** select correct tab when routes have similar prefixes ([#30863](https://github.com/ionic-team/ionic-framework/issues/30863)) ([03fb422](https://github.com/ionic-team/ionic-framework/commit/03fb422bfa775e3e9dd695ea1857fa88d4245ecd)), closes [#30448](https://github.com/ionic-team/ionic-framework/issues/30448) + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package ionic-framework diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index d71c543621e..0b0ea102b40 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,171 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.8.6](https://github.com/ionic-team/ionic-framework/compare/v8.8.5...v8.8.6) (2026-05-06) + + +### Bug Fixes + +* **action-sheet:** restore action-sheet-selected class on non-radio buttons ([#31109](https://github.com/ionic-team/ionic-framework/issues/31109)) ([c18502f](https://github.com/ionic-team/ionic-framework/commit/c18502f3efdec5440a11289235a93c62ce27ab89)), closes [#31090](https://github.com/ionic-team/ionic-framework/issues/31090) +* **datetime:** prevent hidden-state observer from tearing down ready class on initial entry ([#31108](https://github.com/ionic-team/ionic-framework/issues/31108)) ([30b479a](https://github.com/ionic-team/ionic-framework/commit/30b479a53acbc16961002df256bec358dc11e7fa)) +* **segment:** segment drag would set disabled segment button checked ([#31112](https://github.com/ionic-team/ionic-framework/issues/31112)) ([44be424](https://github.com/ionic-team/ionic-framework/commit/44be424221bee11ffbe91c4b1fa0a4d56fe1ecac)) + + + + + +## [8.8.5](https://github.com/ionic-team/ionic-framework/compare/v8.8.4...v8.8.5) (2026-04-29) + + +### Bug Fixes + +* **modal:** remove safe-area gap and flash in fullscreen modals ([#31092](https://github.com/ionic-team/ionic-framework/issues/31092)) ([f3cd39b](https://github.com/ionic-team/ionic-framework/commit/f3cd39b7fb291286374285c4a326ec6b9a8ea237)), closes [#31015](https://github.com/ionic-team/ionic-framework/issues/31015) +* **select:** select focused option on Enter in popover and modal interfaces ([#31093](https://github.com/ionic-team/ionic-framework/issues/31093)) ([fd79771](https://github.com/ionic-team/ionic-framework/commit/fd79771e5be77c9f38379a3a7b9ab44bb11ff325)), closes [#30561](https://github.com/ionic-team/ionic-framework/issues/30561) + + + + + +## [8.8.4](https://github.com/ionic-team/ionic-framework/compare/v8.8.3...v8.8.4) (2026-04-15) + + +### Bug Fixes + +* **checkbox:** show labels after page navigation ([#31062](https://github.com/ionic-team/ionic-framework/issues/31062)) ([f4ac445](https://github.com/ionic-team/ionic-framework/commit/f4ac4459f8317bd5eeff7d4809f9cb0991c8efd9)), closes [#31052](https://github.com/ionic-team/ionic-framework/issues/31052) +* **datetime:** multiple month selected and flakiness display ([#31053](https://github.com/ionic-team/ionic-framework/issues/31053)) ([308aef5](https://github.com/ionic-team/ionic-framework/commit/308aef569d8c6ebc3ad2186bca6969da8e4b2a8d)) +* **tab-button:** update dark palette focused background color ([#31050](https://github.com/ionic-team/ionic-framework/issues/31050)) ([dec46b5](https://github.com/ionic-team/ionic-framework/commit/dec46b5d317080dd5d97dc056f0d8e6d4c8c45ac)) + + + + + +## [8.8.3](https://github.com/ionic-team/ionic-framework/compare/v8.8.2...v8.8.3) (2026-04-01) + + +### Bug Fixes + +* **datetime:** scroll failing for adjacent days on ios ([#31033](https://github.com/ionic-team/ionic-framework/issues/31033)) ([3afd67f](https://github.com/ionic-team/ionic-framework/commit/3afd67f997521290a34c4448445ae227dc67f8f1)) +* **input-otp:** prevent deletion and paste when disabled or readonly ([#30983](https://github.com/ionic-team/ionic-framework/issues/30983)) ([81aa977](https://github.com/ionic-team/ionic-framework/commit/81aa977fde4867146adf67fcf72e24026574929c)), closes [#30913](https://github.com/ionic-team/ionic-framework/issues/30913) + + + + + +## [8.8.2](https://github.com/ionic-team/ionic-framework/compare/v8.8.1...v8.8.2) (2026-03-25) + + +### Bug Fixes + +* **checkbox:** re-evaluate label visibility when label is updated ([#30980](https://github.com/ionic-team/ionic-framework/issues/30980)) ([ce83407](https://github.com/ionic-team/ionic-framework/commit/ce83407e1debbe74f20d2d6dc2535a0ef3f974a0)) +* **datetime:** days keep in focus after changing the month ([#31021](https://github.com/ionic-team/ionic-framework/issues/31021)) ([5fdaba2](https://github.com/ionic-team/ionic-framework/commit/5fdaba2b021fe8b2b43a49eae7c687544c97d502)) + + + + + +## [8.8.1](https://github.com/ionic-team/ionic-framework/compare/v8.8.0...v8.8.1) (2026-03-06) + + +### Bug Fixes + +* **accordion:** update tabindex based on disabled state ([#30986](https://github.com/ionic-team/ionic-framework/issues/30986)) ([0e76a69](https://github.com/ionic-team/ionic-framework/commit/0e76a69370083702568825c29d63cf257d6b88f1)) +* **angular:** export RefresherPullEnd types ([#30991](https://github.com/ionic-team/ionic-framework/issues/30991)) ([72abcca](https://github.com/ionic-team/ionic-framework/commit/72abccaad8df3c1db004da28610fddd95ac93c02)) + + +### Features + +* **toast:** add wrapper and content parts (originally intended for 8.8.0 but omitted from that release) ([#30992](https://github.com/ionic-team/ionic-framework/issues/30992)) ([366f00e](https://github.com/ionic-team/ionic-framework/commit/366f00e25f06e28aa7166275445716c2d301e44a)), closes [#30735](https://github.com/ionic-team/ionic-framework/issues/30735) + + + + + +# [8.8.0](https://github.com/ionic-team/ionic-framework/compare/v8.7.18...v8.8.0) (2026-03-04) + + +### Features + +* **content:** add content-fullscreen class when fullscreen is true ([#30926](https://github.com/ionic-team/ionic-framework/issues/30926)) ([d74b11b](https://github.com/ionic-team/ionic-framework/commit/d74b11bc19d6268b256daf23ba6f107483c00320)) +* **datetime:** add header parts ([#30945](https://github.com/ionic-team/ionic-framework/issues/30945)) ([6ea186d](https://github.com/ionic-team/ionic-framework/commit/6ea186d96d80a94b774d4d0a51d536e0e5599935)) +* **datetime:** add wheel part to ion-picker-column ([#30934](https://github.com/ionic-team/ionic-framework/issues/30934)) ([0cf4c03](https://github.com/ionic-team/ionic-framework/commit/0cf4c03e298bb4f7eea71c966a1473765ebd6d7a)) +* **item-divider:** add inner and container parts ([#30928](https://github.com/ionic-team/ionic-framework/issues/30928)) ([5cdeb7f](https://github.com/ionic-team/ionic-framework/commit/5cdeb7fd357298f15e7ae29b14412d97bdc7c656)) +* **item-option:** add inner and container parts ([#30929](https://github.com/ionic-team/ionic-framework/issues/30929)) ([f8f7ffd](https://github.com/ionic-team/ionic-framework/commit/f8f7ffda318c0143d9bb5c79fe55b4ecb88e6ce3)) +* **item:** add inner and container parts ([#30927](https://github.com/ionic-team/ionic-framework/issues/30927)) ([a2c6559](https://github.com/ionic-team/ionic-framework/commit/a2c655923bb1cff51864949575e19028623c695d)) +* **list-header:** add inner part ([#30930](https://github.com/ionic-team/ionic-framework/issues/30930)) ([ef73476](https://github.com/ionic-team/ionic-framework/commit/ef73476e08670630907e775a38f9ed30a84e3f1f)) +* **modal:** add drag events for sheet and card modals ([#30962](https://github.com/ionic-team/ionic-framework/issues/30962)) ([d29ac71](https://github.com/ionic-team/ionic-framework/commit/d29ac713fad604c256fb385eb0c26eb9717e1ff4)), closes [#23955](https://github.com/ionic-team/ionic-framework/issues/23955) +* **range:** add classes and expose parts to allow individual styling of dual knobs ([#30941](https://github.com/ionic-team/ionic-framework/issues/30941)) ([5bcf921](https://github.com/ionic-team/ionic-framework/commit/5bcf92184118055483bf306ab9e319b8e3e61870)), closes [#29862](https://github.com/ionic-team/ionic-framework/issues/29862) +* **range:** add classes to the range when the value is at the min or max ([#30932](https://github.com/ionic-team/ionic-framework/issues/30932)) ([fac1a66](https://github.com/ionic-team/ionic-framework/commit/fac1a6673c88a531f1d79656be4eb544f235f819)) +* **refresher:** add ionPullStart and ionPullEnd events ([#30946](https://github.com/ionic-team/ionic-framework/issues/30946)) ([814c2e5](https://github.com/ionic-team/ionic-framework/commit/814c2e5ccd6d5bfda12bdf13a566cd66ff830d5b)), closes [#24524](https://github.com/ionic-team/ionic-framework/issues/24524) +* **segment-view:** add swipeGesture property to disable swiping ([#30948](https://github.com/ionic-team/ionic-framework/issues/30948)) ([46806bd](https://github.com/ionic-team/ionic-framework/commit/46806bd6e2af90a0b31fca68f508c06d3d281ec0)), closes [#30290](https://github.com/ionic-team/ionic-framework/issues/30290) +* **select:** add wrapper and bottom shadow parts ([#30951](https://github.com/ionic-team/ionic-framework/issues/30951)) ([5cea5ae](https://github.com/ionic-team/ionic-framework/commit/5cea5aeb44393edab7064e5980a1eb7e607d1b8d)) +* **select:** pass cancelText property to modal interface ([#30282](https://github.com/ionic-team/ionic-framework/issues/30282)) ([6e4f60a](https://github.com/ionic-team/ionic-framework/commit/6e4f60af4c188ae04028b444aa21118ae27c2ca7)) +* **textarea:** reflect disabled and readonly props ([#30910](https://github.com/ionic-team/ionic-framework/issues/30910)) ([55735df](https://github.com/ionic-team/ionic-framework/commit/55735df3fa62c8e259c56db3169f3d5459e71c0c)) + + + + + +## [8.7.18](https://github.com/ionic-team/ionic-framework/compare/v8.7.17...v8.7.18) (2026-02-25) + + +### Bug Fixes + +* **datetime:** stretch ion-buttons to fill space for ios ([#30963](https://github.com/ionic-team/ionic-framework/issues/30963)) ([d46b0b1](https://github.com/ionic-team/ionic-framework/commit/d46b0b15f6a652da6f863cf303e7ce06cfc820a8)) +* **many:** clear timeouts ([#30851](https://github.com/ionic-team/ionic-framework/issues/30851)) ([70b1237](https://github.com/ionic-team/ionic-framework/commit/70b1237823dd0cdab852486a6b2cbbfe0d0aaae9)), closes [#30860](https://github.com/ionic-team/ionic-framework/issues/30860) +* **modal, popover:** respect safe area insets on popovers and modals ([#30949](https://github.com/ionic-team/ionic-framework/issues/30949)) ([6490797](https://github.com/ionic-team/ionic-framework/commit/6490797851cede3bfda893a19b10f165259ec988)), closes [#28411](https://github.com/ionic-team/ionic-framework/issues/28411) +* **radio-group:** prevent DOMException and NotFoundError when filtering radios ([#30958](https://github.com/ionic-team/ionic-framework/issues/30958)) ([682a17e](https://github.com/ionic-team/ionic-framework/commit/682a17ebb754da7714989623cf84b75e715e20e1)), closes [#30279](https://github.com/ionic-team/ionic-framework/issues/30279) [#30359](https://github.com/ionic-team/ionic-framework/issues/30359) +* **toast:** keep icon on the same line as long message in stacked layout ([#30923](https://github.com/ionic-team/ionic-framework/issues/30923)) ([442e3e9](https://github.com/ionic-team/ionic-framework/commit/442e3e983107a69cea4fb5587fb33da718eee8a3)), closes [#30908](https://github.com/ionic-team/ionic-framework/issues/30908) + + + + + +## [8.7.17](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.17) (2026-01-14) + + +### Bug Fixes + +* **input:** prevent Android TalkBack from focusing label separately ([#30895](https://github.com/ionic-team/ionic-framework/issues/30895)) ([ab733b7](https://github.com/ionic-team/ionic-framework/commit/ab733b71dd355d9486757f219fe09acaefeeefcc)) +* **input:** prevent placeholder from overlapping start slot during scroll assist ([#30896](https://github.com/ionic-team/ionic-framework/issues/30896)) ([3b3318d](https://github.com/ionic-team/ionic-framework/commit/3b3318da513b199128f3822bd8226797cd118b0f)) +* **tab-bar:** prevent keyboard controller memory leak on rapid mount/unmount ([#30906](https://github.com/ionic-team/ionic-framework/issues/30906)) ([f99d000](https://github.com/ionic-team/ionic-framework/commit/f99d0007a8ffc9c7d3d2636e912c37c12112b21d)) + + + + + +## [8.7.16](https://github.com/ionic-team/ionic-framework/compare/v8.7.15...v8.7.16) (2025-12-31) + + +### Bug Fixes + +* **modal:** prevent card modal animation on viewport resize when modal is closed ([#30894](https://github.com/ionic-team/ionic-framework/issues/30894)) ([e5634d4](https://github.com/ionic-team/ionic-framework/commit/e5634d45ee5fd32715f6e6b75e0448f74ee1f8f2)), closes [#30679](https://github.com/ionic-team/ionic-framework/issues/30679) + + + + + +## [8.7.15](https://github.com/ionic-team/ionic-framework/compare/v8.7.14...v8.7.15) (2025-12-23) + + +### Bug Fixes + +* **core:** use Capacitor safe-area CSS variables on older WebViews ([#30865](https://github.com/ionic-team/ionic-framework/issues/30865)) ([8573bf8](https://github.com/ionic-team/ionic-framework/commit/8573bf8083f75eda13c954a56731a6aac8ca5724)) +* **header:** show iOS condense header when app is in MD mode ([#30690](https://github.com/ionic-team/ionic-framework/issues/30690)) ([f83b000](https://github.com/ionic-team/ionic-framework/commit/f83b0005309400d674e43c497bdffbcb9d2c4d94)), closes [#29929](https://github.com/ionic-team/ionic-framework/issues/29929) +* **input-password-toggle:** improve screen reader announcements ([#30885](https://github.com/ionic-team/ionic-framework/issues/30885)) ([12ede4b](https://github.com/ionic-team/ionic-framework/commit/12ede4b79c8d5cffc2b014c7c8a0d2ef1d3bf90d)) +* **modal:** dismiss top-most overlay when multiple IDs match ([#30883](https://github.com/ionic-team/ionic-framework/issues/30883)) ([3b60a1d](https://github.com/ionic-team/ionic-framework/commit/3b60a1d68a1df1606ffee0bde7db7a206bac404a)), closes [#30030](https://github.com/ionic-team/ionic-framework/issues/30030) + + + + + +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + +**Note:** Version bump only for package @ionic/core + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/core diff --git a/core/Dockerfile b/core/Dockerfile index 50a4687701c..374f962fdbe 100644 --- a/core/Dockerfile +++ b/core/Dockerfile @@ -1,5 +1,5 @@ # Get Playwright -FROM mcr.microsoft.com/playwright:v1.56.1 +FROM mcr.microsoft.com/playwright:v1.58.2 # Set the working directory WORKDIR /ionic diff --git a/core/api.txt b/core/api.txt index f2af11afc9b..a0e6fa5def2 100644 --- a/core/api.txt +++ b/core/api.txt @@ -555,7 +555,7 @@ ion-card,css-prop,--color,ios ion-card,css-prop,--color,md ion-card,part,native -ion-card-content,none +ion-card-content,shadow ion-card-content,prop,mode,"ios" | "md",undefined,false,false ion-card-content,prop,theme,"ios" | "md" | "ionic",undefined,false,false @@ -881,9 +881,18 @@ ion-datetime,part,calendar-day ion-datetime,part,calendar-day active ion-datetime,part,calendar-day disabled ion-datetime,part,calendar-day today +ion-datetime,part,calendar-days-of-week +ion-datetime,part,calendar-header +ion-datetime,part,datetime-header +ion-datetime,part,datetime-selected-date +ion-datetime,part,datetime-title ion-datetime,part,month-year-button +ion-datetime,part,navigation-button +ion-datetime,part,next-button +ion-datetime,part,previous-button ion-datetime,part,time-button ion-datetime,part,time-button active +ion-datetime,part,wheel ion-datetime,part,wheel-item ion-datetime,part,wheel-item active @@ -1075,7 +1084,7 @@ ion-infinite-scroll-content,prop,theme,"ios" | "md" | "ionic",undefined,false,fa ion-input,scoped ion-input,prop,autocapitalize,string,'off',false,false -ion-input,prop,autocomplete,"name" | "url" | "off" | "on" | "additional-name" | "address-level1" | "address-level2" | "address-level3" | "address-level4" | "address-line1" | "address-line2" | "address-line3" | "bday-day" | "bday-month" | "bday-year" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-family-name" | "cc-given-name" | "cc-name" | "cc-number" | "cc-type" | "country" | "country-name" | "current-password" | "family-name" | "given-name" | "honorific-prefix" | "honorific-suffix" | "new-password" | "one-time-code" | "organization" | "postal-code" | "street-address" | "transaction-amount" | "transaction-currency" | "username" | "email" | "tel" | "tel-area-code" | "tel-country-code" | "tel-extension" | "tel-local" | "tel-national" | "nickname" | "organization-title" | "cc-additional-name" | "language" | "bday" | "sex" | "impp" | "photo",'off',false,false +ion-input,prop,autocomplete,"additional-name" | "address-level1" | "address-level2" | "address-level3" | "address-level4" | "address-line1" | "address-line2" | "address-line3" | "bday" | "bday-day" | "bday-month" | "bday-year" | "cc-additional-name" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-family-name" | "cc-given-name" | "cc-name" | "cc-number" | "cc-type" | "country" | "country-name" | "current-password" | "email" | "family-name" | "given-name" | "honorific-prefix" | "honorific-suffix" | "impp" | "language" | "name" | "new-password" | "nickname" | "off" | "on" | "one-time-code" | "organization" | "organization-title" | "photo" | "postal-code" | "sex" | "street-address" | "tel" | "tel-area-code" | "tel-country-code" | "tel-extension" | "tel-local" | "tel-national" | "transaction-amount" | "transaction-currency" | "url" | "username",'off',false,false ion-input,prop,autocorrect,"off" | "on",'off',false,false ion-input,prop,autofocus,boolean,false,false,false ion-input,prop,clearInput,boolean,false,false,false @@ -1170,18 +1179,20 @@ ion-input,css-prop,--placeholder-opacity,ionic ion-input,css-prop,--placeholder-opacity,ios ion-input,css-prop,--placeholder-opacity,md -ion-input-otp,scoped +ion-input-otp,shadow ion-input-otp,prop,autocapitalize,string,'off',false,false ion-input-otp,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-input-otp,prop,disabled,boolean,false,false,true ion-input-otp,prop,fill,"outline" | "solid" | undefined,'outline',false,false ion-input-otp,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false ion-input-otp,prop,length,number,4,false,false +ion-input-otp,prop,mode,"ios" | "md",undefined,false,false ion-input-otp,prop,pattern,string | undefined,undefined,false,false ion-input-otp,prop,readonly,boolean,false,false,true ion-input-otp,prop,separators,number[] | string | undefined,undefined,false,false ion-input-otp,prop,shape,"rectangular" | "round" | "soft",'round',false,false ion-input-otp,prop,size,"large" | "medium" | "small",'medium',false,false +ion-input-otp,prop,theme,"ios" | "md" | "ionic",undefined,false,false ion-input-otp,prop,type,"number" | "text",'number',false,false ion-input-otp,prop,value,null | number | string | undefined,'',false,false ion-input-otp,method,setFocus,setFocus(index?: number) => Promise @@ -1262,6 +1273,11 @@ ion-input-otp,css-prop,--separator-width,md ion-input-otp,css-prop,--width,ionic ion-input-otp,css-prop,--width,ios ion-input-otp,css-prop,--width,md +ion-input-otp,part,container +ion-input-otp,part,description +ion-input-otp,part,group +ion-input-otp,part,native +ion-input-otp,part,separator ion-input-password-toggle,shadow ion-input-password-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true @@ -1378,7 +1394,9 @@ ion-item,css-prop,--ripple-color,md ion-item,css-prop,--transition,ionic ion-item,css-prop,--transition,ios ion-item,css-prop,--transition,md +ion-item,part,container ion-item,part,detail-icon +ion-item,part,inner ion-item,part,native ion-item-divider,shadow @@ -1460,6 +1478,8 @@ ion-item-divider,css-prop,--ion-item-divider-thumbnail-margin-top ion-item-divider,css-prop,--ion-item-divider-thumbnail-start-slotted-margin-end ion-item-divider,css-prop,--ion-item-divider-thumbnail-start-slotted-margin-start ion-item-divider,css-prop,--ion-item-divider-thumbnail-width +ion-item-divider,part,container +ion-item-divider,part,inner ion-item-group,none ion-item-group,prop,mode,"ios" | "md",undefined,false,false @@ -1484,6 +1504,8 @@ ion-item-option,css-prop,--background,md ion-item-option,css-prop,--color,ionic ion-item-option,css-prop,--color,ios ion-item-option,css-prop,--color,md +ion-item-option,part,container +ion-item-option,part,inner ion-item-option,part,native ion-item-options,none @@ -1543,6 +1565,7 @@ ion-list-header,css-prop,--color,md ion-list-header,css-prop,--inner-border-width,ionic ion-list-header,css-prop,--inner-border-width,ios ion-list-header,css-prop,--inner-border-width,md +ion-list-header,part,inner ion-loading,scoped ion-loading,prop,animated,boolean,true,false,false @@ -1733,6 +1756,9 @@ ion-modal,method,setCurrentBreakpoint,setCurrentBreakpoint(breakpoint: number) = ion-modal,event,didDismiss,OverlayEventDetail,true ion-modal,event,didPresent,void,true ion-modal,event,ionBreakpointDidChange,ModalBreakpointChangeEventDetail,true +ion-modal,event,ionDragEnd,ModalDragEventDetail,true +ion-modal,event,ionDragMove,ModalDragEventDetail,true +ion-modal,event,ionDragStart,void,true ion-modal,event,ionModalDidDismiss,OverlayEventDetail,true ion-modal,event,ionModalDidPresent,void,true ion-modal,event,ionModalWillDismiss,OverlayEventDetail,true @@ -1784,7 +1810,7 @@ ion-nav,prop,animated,boolean,true,false,false ion-nav,prop,animation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-nav,prop,mode,"ios" | "md",undefined,false,false ion-nav,prop,root,Function | HTMLElement | ViewController | null | string | undefined,undefined,false,false -ion-nav,prop,rootParams,undefined | { [key: string]: any; },undefined,false,false +ion-nav,prop,rootParams,T | undefined,undefined,false,false ion-nav,prop,swipeGesture,boolean | undefined,undefined,false,false ion-nav,prop,theme,"ios" | "md" | "ionic",undefined,false,false ion-nav,method,canGoBack,canGoBack(view?: ViewController) => Promise @@ -1806,7 +1832,7 @@ ion-nav,event,ionNavWillChange,void,false ion-nav-link,none ion-nav-link,prop,component,Function | HTMLElement | ViewController | null | string | undefined,undefined,false,false -ion-nav-link,prop,componentProps,undefined | { [key: string]: any; },undefined,false,false +ion-nav-link,prop,componentProps,T | undefined,undefined,false,false ion-nav-link,prop,mode,"ios" | "md",undefined,false,false ion-nav-link,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-nav-link,prop,routerDirection,"back" | "forward" | "root",'forward',false,false @@ -1911,7 +1937,7 @@ ion-popover,prop,animated,boolean,true,false,false ion-popover,prop,arrow,boolean,true,false,false ion-popover,prop,backdropDismiss,boolean,true,false,false ion-popover,prop,component,Function | HTMLElement | null | string | undefined,undefined,false,false -ion-popover,prop,componentProps,undefined | { [key: string]: any; },undefined,false,false +ion-popover,prop,componentProps,T | undefined,undefined,false,false ion-popover,prop,dismissOnSelect,boolean,false,false,false ion-popover,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-popover,prop,event,any,undefined,false,false @@ -2106,11 +2132,28 @@ ion-range,css-prop,--pin-background,md ion-range,css-prop,--pin-color,ionic ion-range,css-prop,--pin-color,ios ion-range,css-prop,--pin-color,md +ion-range,part,activated ion-range,part,bar ion-range,part,bar-active +ion-range,part,focused +ion-range,part,hover ion-range,part,knob +ion-range,part,knob-a +ion-range,part,knob-b +ion-range,part,knob-handle +ion-range,part,knob-handle-a +ion-range,part,knob-handle-b +ion-range,part,knob-handle-lower +ion-range,part,knob-handle-upper +ion-range,part,knob-lower +ion-range,part,knob-upper ion-range,part,label ion-range,part,pin +ion-range,part,pin-a +ion-range,part,pin-b +ion-range,part,pin-lower +ion-range,part,pin-upper +ion-range,part,pressed ion-range,part,tick ion-range,part,tick-active @@ -2127,6 +2170,8 @@ ion-refresher,method,cancel,cancel() => Promise ion-refresher,method,complete,complete() => Promise ion-refresher,method,getProgress,getProgress() => Promise ion-refresher,event,ionPull,void,true +ion-refresher,event,ionPullEnd,RefresherPullEndEventDetail,true +ion-refresher,event,ionPullStart,void,true ion-refresher,event,ionRefresh,RefresherEventDetail,true ion-refresher,event,ionStart,void,true @@ -2210,7 +2255,7 @@ ion-row,css-prop,--ion-grid-gap ion-searchbar,scoped ion-searchbar,prop,animated,boolean,false,false,false ion-searchbar,prop,autocapitalize,string,'off',false,false -ion-searchbar,prop,autocomplete,"name" | "url" | "off" | "on" | "additional-name" | "address-level1" | "address-level2" | "address-level3" | "address-level4" | "address-line1" | "address-line2" | "address-line3" | "bday-day" | "bday-month" | "bday-year" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-family-name" | "cc-given-name" | "cc-name" | "cc-number" | "cc-type" | "country" | "country-name" | "current-password" | "family-name" | "given-name" | "honorific-prefix" | "honorific-suffix" | "new-password" | "one-time-code" | "organization" | "postal-code" | "street-address" | "transaction-amount" | "transaction-currency" | "username" | "email" | "tel" | "tel-area-code" | "tel-country-code" | "tel-extension" | "tel-local" | "tel-national" | "nickname" | "organization-title" | "cc-additional-name" | "language" | "bday" | "sex" | "impp" | "photo",'off',false,false +ion-searchbar,prop,autocomplete,"additional-name" | "address-level1" | "address-level2" | "address-level3" | "address-level4" | "address-line1" | "address-line2" | "address-line3" | "bday" | "bday-day" | "bday-month" | "bday-year" | "cc-additional-name" | "cc-csc" | "cc-exp" | "cc-exp-month" | "cc-exp-year" | "cc-family-name" | "cc-given-name" | "cc-name" | "cc-number" | "cc-type" | "country" | "country-name" | "current-password" | "email" | "family-name" | "given-name" | "honorific-prefix" | "honorific-suffix" | "impp" | "language" | "name" | "new-password" | "nickname" | "off" | "on" | "one-time-code" | "organization" | "organization-title" | "photo" | "postal-code" | "sex" | "street-address" | "tel" | "tel-area-code" | "tel-country-code" | "tel-extension" | "tel-local" | "tel-national" | "transaction-amount" | "transaction-currency" | "url" | "username",'off',false,false ion-searchbar,prop,autocorrect,"off" | "on",'off',false,false ion-searchbar,prop,cancelButtonIcon,string | undefined,undefined,false,false ion-searchbar,prop,cancelButtonText,string,'Cancel',false,false @@ -2387,9 +2432,11 @@ ion-segment-content,shadow ion-segment-view,shadow ion-segment-view,prop,disabled,boolean,false,false,false +ion-segment-view,prop,swipeGesture,boolean,true,false,false ion-segment-view,event,ionSegmentViewScroll,SegmentViewScrollEvent,true ion-select,shadow +ion-select,prop,cancelIcon,boolean,false,false,false ion-select,prop,cancelText,string,'Cancel',false,false ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean) | null | string | undefined,undefined,false,false @@ -2469,21 +2516,48 @@ ion-select,css-prop,--placeholder-opacity,md ion-select,css-prop,--ripple-color,ionic ion-select,css-prop,--ripple-color,ios ion-select,css-prop,--ripple-color,md +ion-select,css-prop,--select-text-gap,ionic +ion-select,css-prop,--select-text-gap,ios +ion-select,css-prop,--select-text-gap,md +ion-select,css-prop,--select-text-media-border-color,ionic +ion-select,css-prop,--select-text-media-border-color,ios +ion-select,css-prop,--select-text-media-border-color,md +ion-select,css-prop,--select-text-media-border-radius,ionic +ion-select,css-prop,--select-text-media-border-radius,ios +ion-select,css-prop,--select-text-media-border-radius,md +ion-select,css-prop,--select-text-media-border-style,ionic +ion-select,css-prop,--select-text-media-border-style,ios +ion-select,css-prop,--select-text-media-border-style,md +ion-select,css-prop,--select-text-media-border-width,ionic +ion-select,css-prop,--select-text-media-border-width,ios +ion-select,css-prop,--select-text-media-border-width,md +ion-select,css-prop,--select-text-media-height,ionic +ion-select,css-prop,--select-text-media-height,ios +ion-select,css-prop,--select-text-media-height,md +ion-select,css-prop,--select-text-media-width,ionic +ion-select,css-prop,--select-text-media-width,ios +ion-select,css-prop,--select-text-media-width,md +ion-select,part,bottom ion-select,part,container ion-select,part,error-text ion-select,part,helper-text ion-select,part,icon +ion-select,part,inner ion-select,part,label ion-select,part,placeholder ion-select,part,supporting-text ion-select,part,text +ion-select,part,wrapper ion-select-modal,scoped +ion-select-modal,prop,cancelIcon,boolean,false,false,false +ion-select-modal,prop,cancelText,string,'Close',false,false ion-select-modal,prop,header,string | undefined,undefined,false,false ion-select-modal,prop,multiple,boolean | undefined,undefined,false,false ion-select-modal,prop,options,SelectModalOption[],[],false,false ion-select-option,shadow +ion-select-option,prop,description,string | undefined,undefined,false,false ion-select-option,prop,disabled,boolean,false,false,false ion-select-option,prop,mode,"ios" | "md",undefined,false,false ion-select-option,prop,theme,"ios" | "md" | "ionic",undefined,false,false @@ -2553,6 +2627,7 @@ ion-tab,method,setActive,setActive() => Promise ion-tab-bar,shadow ion-tab-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-tab-bar,prop,expand,"compact" | "full",'full',false,false +ion-tab-bar,prop,hideOnScroll,boolean,false,false,false ion-tab-bar,prop,mode,"ios" | "md",undefined,false,false ion-tab-bar,prop,selectedTab,string | undefined,undefined,false,false ion-tab-bar,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false @@ -2629,7 +2704,7 @@ ion-text,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "second ion-text,prop,mode,"ios" | "md",undefined,false,false ion-text,prop,theme,"ios" | "md" | "ionic",undefined,false,false -ion-textarea,scoped +ion-textarea,shadow ion-textarea,prop,autoGrow,boolean,false,false,true ion-textarea,prop,autocapitalize,string,'none',false,false ion-textarea,prop,autofocus,boolean,false,false,false @@ -2639,7 +2714,7 @@ ion-textarea,prop,cols,number | undefined,undefined,false,true ion-textarea,prop,counter,boolean,false,false,false ion-textarea,prop,counterFormatter,((inputLength: number, maxLength: number) => string) | undefined,undefined,false,false ion-textarea,prop,debounce,number | undefined,undefined,false,false -ion-textarea,prop,disabled,boolean,false,false,false +ion-textarea,prop,disabled,boolean,false,false,true ion-textarea,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false ion-textarea,prop,errorText,string | undefined,undefined,false,false ion-textarea,prop,fill,"outline" | "solid" | undefined,undefined,false,false @@ -2652,8 +2727,8 @@ ion-textarea,prop,minlength,number | undefined,undefined,false,false ion-textarea,prop,mode,"ios" | "md",undefined,false,false ion-textarea,prop,name,string,this.inputId,false,false ion-textarea,prop,placeholder,string | undefined,undefined,false,false -ion-textarea,prop,readonly,boolean,false,false,false -ion-textarea,prop,required,boolean,false,false,false +ion-textarea,prop,readonly,boolean,false,false,true +ion-textarea,prop,required,boolean,false,false,true ion-textarea,prop,rows,number | undefined,undefined,false,false ion-textarea,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false ion-textarea,prop,size,"large" | "medium" | "small" | undefined,'medium',false,false @@ -2721,6 +2796,15 @@ ion-textarea,css-prop,--placeholder-font-weight,md ion-textarea,css-prop,--placeholder-opacity,ionic ion-textarea,css-prop,--placeholder-opacity,ios ion-textarea,css-prop,--placeholder-opacity,md +ion-textarea,part,bottom +ion-textarea,part,container +ion-textarea,part,counter +ion-textarea,part,error-text +ion-textarea,part,helper-text +ion-textarea,part,label +ion-textarea,part,native +ion-textarea,part,supporting-text +ion-textarea,part,wrapper ion-thumbnail,shadow ion-thumbnail,prop,mode,"ios" | "md",undefined,false,false @@ -2827,9 +2911,11 @@ ion-toast,css-prop,--width,md ion-toast,part,button ion-toast,part,button cancel ion-toast,part,container +ion-toast,part,content ion-toast,part,header ion-toast,part,icon ion-toast,part,message +ion-toast,part,wrapper ion-toggle,shadow ion-toggle,prop,alignment,"center" | "start" | undefined,undefined,false,false @@ -2896,6 +2982,7 @@ ion-toolbar,shadow ion-toolbar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-toolbar,prop,mode,"ios" | "md",undefined,false,false ion-toolbar,prop,theme,"ios" | "md" | "ionic",undefined,false,false +ion-toolbar,prop,titlePlacement,"center" | "end" | "start" | undefined,undefined,false,false ion-toolbar,css-prop,--background,ionic ion-toolbar,css-prop,--background,ios ion-toolbar,css-prop,--background,md diff --git a/core/package-lock.json b/core/package-lock.json index d273935ea14..25e7b64662b 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,29 +1,28 @@ { "name": "@ionic/core", - "version": "8.7.13", + "version": "8.8.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "8.7.13", + "version": "8.8.6", "license": "MIT", "dependencies": { "@phosphor-icons/core": "^2.1.1", - "@stencil/core": "4.38.0", + "@stencil/core": "4.43.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" }, "devDependencies": { - "@axe-core/playwright": "^4.11.0", + "@axe-core/playwright": "^4.11.1", "@capacitor/core": "^8.0.0", "@capacitor/haptics": "^8.0.0", "@capacitor/keyboard": "^8.0.0", "@capacitor/status-bar": "^8.0.0", - "@clack/prompts": "^0.11.0", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^2.0.0", - "@playwright/test": "^1.56.1", + "@playwright/test": "^1.58.2", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", "@stencil/angular-output-target": "^0.10.0", @@ -45,8 +44,8 @@ "fs-extra": "^9.0.1", "jest": "^29.7.0", "jest-cli": "^29.7.0", - "outsystems-design-tokens": "^1.3.4", - "playwright-core": "^1.56.1", + "outsystems-design-tokens": "^1.3.8", + "playwright-core": "^1.58.2", "prettier": "^2.8.8", "rollup": "^2.26.4", "sass": "^1.33.0", @@ -64,11 +63,13 @@ "dev": true }, "node_modules/@axe-core/playwright": { - "version": "4.11.0", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.1.tgz", + "integrity": "sha512-mKEfoUIB1MkVTht0BGZFXtSAEKXMJoDkyV5YZ9jbBmZCcWDz71tegNsdTkIN8zc/yMi5Gm2kx7Z5YQ9PfWNAWw==", "dev": true, "license": "MPL-2.0", "dependencies": { - "axe-core": "~4.11.0" + "axe-core": "~4.11.1" }, "peerDependencies": { "playwright-core": ">= 1.0.0" @@ -97,7 +98,6 @@ "version": "7.16.12", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -714,20 +714,19 @@ } }, "node_modules/@capacitor/core": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.0.0.tgz", - "integrity": "sha512-250HTVd/W/KdMygoqaedisvNbHbpbQTN2Hy/8ZYGm1nAqE0Fx7sGss4l0nDg33STxEdDhtVRoL2fIaaiukKseA==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-8.3.1.tgz", + "integrity": "sha512-UF8ItlHguU1Z6GXfPTeT2gakf+ctNI8pAS1kwSBQlsJMlfD4OPoto/SmKnOxKCQvnF4WRcdWeg6C0zREUNaAQg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.1.0" } }, "node_modules/@capacitor/haptics": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-8.0.0.tgz", - "integrity": "sha512-DY1IUOjke1T4ITl7mFHQIKCaJJyHYAYRYHG9bVApU7PDOZiMVGMp48Yjzdqjya+wv/AHS5mDabSTUmhJ5uDvBA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@capacitor/haptics/-/haptics-8.0.2.tgz", + "integrity": "sha512-c2hZzRR5Fk1tbTvhG1jhh2XBAf3EhnIerMIb2sl7Mt41Gxx1fhBJFDa0/BI1IbY4loVepyyuqNC9820/GZuoWQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -735,9 +734,9 @@ } }, "node_modules/@capacitor/keyboard": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.0.tgz", - "integrity": "sha512-ycPW6iQyFwzDK95jihesj5EGiyyGSfbBqNek11iNp9tBOB7zDeYkUA2S/vPpOETt3dhP6pWr7a9gNVGuEfj11g==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@capacitor/keyboard/-/keyboard-8.0.3.tgz", + "integrity": "sha512-27Bv5/2w1Ss2njguBgTS98O0Bb8DRJhAARyzXYib0JlT/n6BrJw/EZ0CokM4C8GFUjFDjJnEKF1Ie01buTMEXQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -745,15 +744,16 @@ } }, "node_modules/@capacitor/status-bar": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-8.0.0.tgz", - "integrity": "sha512-aIj3bc7z8lfPgOen8HlrBrkfnxpFnh21OCx6jCUx4Mvv+B6eEkUQ49b32DOddgVfr+igRHLX2SYi7duqIsNDXg==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@capacitor/status-bar/-/status-bar-8.0.2.tgz", + "integrity": "sha512-WXs8YB8B9eEaPZz+bcdY6t2nForF1FLoj/JU0Dl9RRgQnddnS98FEEyDooQhaY7wivr000j4+SC1FyeJkrFO7A==", "dev": true, "license": "MIT", "peerDependencies": { "@capacitor/core": ">=8.0.0" } }, +<<<<<<< HEAD "node_modules/@clack/core": { "version": "0.5.0", "dev": true, @@ -1215,6 +1215,8 @@ "node": ">=18" } }, +======= +>>>>>>> origin/next "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, @@ -1399,7 +1401,6 @@ "version": "4.33.0", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "4.33.0", "@typescript-eslint/types": "4.33.0", @@ -2360,11 +2361,13 @@ "license": "MIT" }, "node_modules/@playwright/test": { - "version": "1.56.1", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.56.1" + "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -2549,9 +2552,10 @@ } }, "node_modules/@stencil/core": { - "version": "4.38.0", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.43.0.tgz", + "integrity": "sha512-6Uj2Z3lzLuufYAE7asZ6NLKgSwsB9uxl84Eh34PASnUjfj32GkrP4DtKK7fNeh1WFGGyffsTDka3gwtl+4reUg==", "license": "MIT", - "peer": true, "bin": { "stencil": "bin/stencil" }, @@ -2981,7 +2985,6 @@ "version": "6.7.2", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.7.2", "@typescript-eslint/types": "6.7.2", @@ -3207,6 +3210,7 @@ "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.25", @@ -3221,6 +3225,7 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.12" }, @@ -3233,7 +3238,8 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@vue/compiler-dom": { "version": "3.5.25", @@ -3241,6 +3247,7 @@ "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-core": "3.5.25", "@vue/shared": "3.5.25" @@ -3252,6 +3259,7 @@ "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.25", @@ -3269,7 +3277,8 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@vue/compiler-sfc/node_modules/postcss": { "version": "8.5.6", @@ -3291,6 +3300,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3306,6 +3316,7 @@ "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.25", "@vue/shared": "3.5.25" @@ -3317,6 +3328,7 @@ "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/shared": "3.5.25" } @@ -3327,6 +3339,7 @@ "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/reactivity": "3.5.25", "@vue/shared": "3.5.25" @@ -3338,6 +3351,7 @@ "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/reactivity": "3.5.25", "@vue/runtime-core": "3.5.25", @@ -3351,6 +3365,7 @@ "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-ssr": "3.5.25", "@vue/shared": "3.5.25" @@ -3364,7 +3379,8 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz", "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@zeit/schemas": { "version": "2.21.0", @@ -3399,7 +3415,6 @@ "version": "7.4.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3633,7 +3648,9 @@ } }, "node_modules/axe-core": { - "version": "4.11.0", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", "dev": true, "license": "MPL-2.0", "engines": { @@ -4696,7 +4713,8 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/debug": { "version": "2.6.9", @@ -5107,7 +5125,6 @@ "version": "7.32.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -8585,6 +8602,7 @@ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } @@ -8950,6 +8968,7 @@ } ], "license": "MIT", + "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -9148,9 +9167,9 @@ } }, "node_modules/outsystems-design-tokens": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/outsystems-design-tokens/-/outsystems-design-tokens-1.3.6.tgz", - "integrity": "sha512-j+m64nl3hfCDRniU47WwlGWDvvdCRI2tQ9DpFtG4l5SLxf1tMTtbuHalrmfKCDWdx3W8eguyF7GKR6mkE7Z37g==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/outsystems-design-tokens/-/outsystems-design-tokens-1.3.8.tgz", + "integrity": "sha512-hT2r9Crd7rn1ewG9hzx7bUA5AjxEdzFsaxzUJ11DqBW86aHmc2ImsCUkaavMlADmZ1g/ZHxV/eO5Bmy3lWKpmA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -9432,11 +9451,13 @@ } }, "node_modules/playwright": { - "version": "1.56.1", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.56.1" + "playwright-core": "1.58.2" }, "bin": { "playwright": "cli.js" @@ -9449,12 +9470,11 @@ } }, "node_modules/playwright-core": { - "version": "1.56.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", - "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -9476,7 +9496,6 @@ "version": "7.0.35", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -9582,7 +9601,6 @@ "version": "0.36.2", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "postcss": ">=5.0.0" } @@ -9633,7 +9651,6 @@ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -10017,7 +10034,6 @@ "version": "2.35.1", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -10338,6 +10354,7 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -11097,8 +11114,7 @@ }, "node_modules/tslib": { "version": "2.1.0", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tsutils": { "version": "3.21.0", diff --git a/core/package.json b/core/package.json index 1e99261352e..563478ed3aa 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "8.7.13", + "version": "8.8.6", "description": "Base components for Ionic", "engines": { "node": ">= 16" @@ -35,20 +35,19 @@ ], "dependencies": { "@phosphor-icons/core": "^2.1.1", - "@stencil/core": "4.38.0", + "@stencil/core": "4.43.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" }, "devDependencies": { - "@axe-core/playwright": "^4.11.0", + "@axe-core/playwright": "^4.11.1", "@capacitor/core": "^8.0.0", "@capacitor/haptics": "^8.0.0", "@capacitor/keyboard": "^8.0.0", "@capacitor/status-bar": "^8.0.0", - "@clack/prompts": "^0.11.0", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^2.0.0", - "@playwright/test": "^1.56.1", + "@playwright/test": "^1.58.2", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", "@stencil/angular-output-target": "^0.10.0", @@ -70,8 +69,8 @@ "fs-extra": "^9.0.1", "jest": "^29.7.0", "jest-cli": "^29.7.0", - "outsystems-design-tokens": "^1.3.4", - "playwright-core": "^1.56.1", + "outsystems-design-tokens": "^1.3.8", + "playwright-core": "^1.58.2", "prettier": "^2.8.8", "rollup": "^2.26.4", "sass": "^1.33.0", @@ -110,8 +109,7 @@ "docker.build": "docker build -t ionic-playwright .", "test.e2e.docker": "npm run docker.build && node ./scripts/docker.mjs", "test.e2e.docker.update-snapshots": "npm run test.e2e.docker -- --update-snapshots='changed'", - "test.e2e.docker.ci": "npm run docker.build && CI=true node ./scripts/docker.mjs", - "test.e2e.script": "node scripts/testing/e2e-script.mjs" + "test.e2e.docker.ci": "npm run docker.build && CI=true node ./scripts/docker.mjs" }, "author": "Ionic Team", "license": "MIT", diff --git a/core/scripts/testing/e2e-script.mjs b/core/scripts/testing/e2e-script.mjs deleted file mode 100644 index 794447bf61c..00000000000 --- a/core/scripts/testing/e2e-script.mjs +++ /dev/null @@ -1,260 +0,0 @@ -// The purpose of this script is to provide a way run the E2E tests -// without having the developer to manually run multiple commands based -// on the desired end result. -// E.g. update the local ground truths for a specific component or -// open the Playwright report after running the E2E tests. - -import { - intro, - outro, - confirm, - spinner, - isCancel, - cancel, - text, - log, -} from '@clack/prompts'; -import { exec, spawn } from 'child_process'; -import fs from 'node:fs'; -import { setTimeout as sleep } from 'node:timers/promises'; -import util from 'node:util'; -import color from 'picocolors'; - -async function main() { - const execAsync = util.promisify(exec); - const cleanUpFiles = async () => { - // Clean up the local ground truths. - const cleanUp = spinner(); - - // Inform the user that the local ground truths are being cleaned up. - cleanUp.start('Restoring local ground truths'); - - // Reset the local ground truths. - await execAsync('git reset -- src/**/*-linux.png').catch((error) => { - cleanUp.stop('Failed to reset local ground truths'); - console.error(error); - return process.exit(0); - }); - - // Restore the local ground truths. - await execAsync('git restore -- src/**/*-linux.png').catch((error) => { - cleanUp.stop('Failed to restore local ground truths'); - console.error(error); - return process.exit(0); - }); - - // Inform the user that the local ground truths have been cleaned up. - cleanUp.stop('Local ground truths have been restored to their original state in order to avoid committing them.'); - }; - - intro(color.inverse(' Update Local Ground Truths')); - - // Ask user for the component name they want to test. - const componentValue = await text({ - message: 'Enter the component or path you want to test (e.g. chip, src/components/chip)', - placeholder: 'Empty for all components', - }); - - // User cancelled the operation with `Ctrl+C` or `CMD+C`. - if (isCancel(componentValue)) { - cancel('Operation cancelled'); - return process.exit(0); - } - - // Ask user if they want to update their local ground truths. - const shouldUpdateTruths = await confirm({ - message: 'Do you want to update your local ground truths?', - }); - - // User cancelled the operation with `Ctrl+C` or `CMD+C`. - if (isCancel(shouldUpdateTruths)) { - cancel('Operation cancelled'); - return process.exit(0); - } - - if (shouldUpdateTruths) { - const defaultBaseBranch = 'main'; - - // Ask user for the base branch. - let baseBranch = await text({ - message: 'Enter the base branch name:', - placeholder: `default: ${defaultBaseBranch}`, - }) - - // User cancelled the operation with `Ctrl+C` or `CMD+C`. - if (isCancel(baseBranch)) { - cancel('Operation cancelled'); - return process.exit(0); - } - - // User didn't provide a base branch. - if (!baseBranch) { - baseBranch = defaultBaseBranch; - } - - /** - * The provided base branch needs to be fetched. - * This ensures that the local base branch is up-to-date with the - * remote base branch. Otherwise, there might be errors stating that - * certain files don't exist in the local base branch. - */ - const fetchBaseBranch = spinner(); - - // Inform the user that the base branch is being fetched. - fetchBaseBranch.start(`Fetching "${baseBranch}" to have the latest changes`); - - // Fetch the base branch. - await execAsync(`git fetch origin ${baseBranch}`).catch((error) => { - fetchBaseBranch.stop(`Failed to fetch "${baseBranch}"`); - console.error(error); - return process.exit(0); - }); - - // Inform the user that the base branch has been fetched. - fetchBaseBranch.stop(`Fetched "${baseBranch}"`); - - - const updateGroundTruth = spinner(); - - // Inform the user that the local ground truths are being updated. - updateGroundTruth.start('Updating local ground truths'); - - // Check if user provided an existing file or directory. - const isValidLocation = fs.existsSync(componentValue); - - // User provided an existing file or directory. - if (isValidLocation) { - const stats = fs.statSync(componentValue); - - // User provided a file as the component. - // ex: `componentValue` = `src/components/chip/test/basic/chip.e2e.ts` - if (stats.isFile()) { - // Update the local ground truths for the provided path. - await execAsync(`git checkout origin/${baseBranch} -- ${componentValue}-snapshots/*-linux.png`).catch((error) => { - updateGroundTruth.stop('Failed to update local ground truths'); - console.error(error); - return process.exit(0); - }); - } - - // User provided a directory as the component. - // ex: `componentValue` = `src/components/chip` - if (stats.isDirectory()) { - // Update the local ground truths for the provided directory. - await execAsync(`git checkout origin/${baseBranch} -- ${componentValue}/test/*/*.e2e.ts-snapshots/*-linux.png`).catch((error) => { - updateGroundTruth.stop('Failed to update local ground truths'); - console.error(error); - return process.exit(0); - }); - } - } - // User provided a component name as the component. - // ex: `componentValue` = `chip` - else if (componentValue) { - // Update the local ground truths for the provided component. - await execAsync(`git checkout origin/${baseBranch} -- src/components/${componentValue}/test/*/${componentValue}.e2e.ts-snapshots/*-linux.png`).catch((error) => { - updateGroundTruth.stop('Failed to update local ground truths'); - console.error(error); - return process.exit(0); - }); - } - // User provided an empty string. - else { - // Update the local ground truths for all components. - await execAsync(`git checkout origin/${baseBranch} -- src/components/*/test/*/*.e2e.ts-snapshots/*-linux.png`).catch((error) => { - updateGroundTruth.stop('Failed to update local ground truths'); - console.error(error); - return process.exit(0); - }); - } - - // Inform the user that the local ground truths have been updated. - updateGroundTruth.stop('Updated local ground truths'); - } - - const buildCore = spinner(); - - // Inform the user that the core is being built. - buildCore.start('Building core'); - - /** - * Build core - * Otherwise, the uncommitted changes will not be reflected in the tests because: - * - popping the stash doesn't trigger a re-render even if `npm start` is running - * - app is not running the `npm start` command - */ - await execAsync('npm run build').catch((error) => { - // Clean up the local ground truths. - cleanUpFiles(); - - buildCore.stop('Failed to build core'); - console.error(error); - return process.exit(0); - }); - - buildCore.stop('Built core'); - - const runE2ETests = spinner(); - - // Inform the user that the E2E tests are being run. - runE2ETests.start('Running E2E tests'); - - // User provided a component value. - if (componentValue) { - await execAsync(`npm run test.e2e.docker.ci ${componentValue}`).catch((error) => { - // Clean up the local ground truths. - cleanUpFiles(); - - runE2ETests.stop('Failed to run E2E tests'); - console.error(error); - return process.exit(0); - }); - } else { - await execAsync('npm run test.e2e.docker.ci').catch((error) => { - // Clean up the local ground truths. - cleanUpFiles(); - - runE2ETests.stop('Failed to run E2E tests'); - console.error(error); - return process.exit(0); - }); - } - - runE2ETests.stop('Ran E2E tests'); - - // Clean up the local ground truths. - await cleanUpFiles(); - - // Ask user if they want to open the Playwright report. - const shouldOpenReport = await confirm({ - message: 'Do you want to open the Playwright report?', - }); - - // User cancelled the operation with `Ctrl+C` or `CMD+C`. - if (isCancel(shouldOpenReport)) { - cancel('Operation cancelled'); - return process.exit(0); - } - - // User chose to open the Playwright report. - if (shouldOpenReport) { - // Use spawn to display the server information and the key to quit the server. - spawn('npx', ['playwright', 'show-report'], { - stdio: 'inherit', - }); - } else { - // Inform the user that the Playwright report can be opened by running the following command. - log.info('If you change your mind, you can open the Playwright report by running the following command:'); - log.info(color.bold('npx playwright show-report')); - } - - if (shouldOpenReport) { - outro("You're all set! Don't forget to quit serving the Playwright report when you're done."); - } else { - outro("You're all set!"); - } - - await sleep(1000); -} - -main().catch(console.error); diff --git a/core/scripts/vercel-build.sh b/core/scripts/vercel-build.sh new file mode 100755 index 00000000000..99c83b22adc --- /dev/null +++ b/core/scripts/vercel-build.sh @@ -0,0 +1,360 @@ +#!/bin/bash +# +# Vercel preview build script +# +# Builds core component tests (same as before) plus framework test apps +# (Angular, React, Vue) so they're all accessible from a single preview URL. +# +# Core tests: /src/components/{name}/test/{scenario} +# Angular test app: /angular/ +# React test app: /react/ +# Vue test app: /vue/ +# +set -e + +# Vercel places core/ at /vercel/path1 (bind mount). The full repo clone +# lives at /vercel/path0. We can't rely on `..` to reach it, so we search. +CORE_DIR=$(pwd) +OUTPUT_DIR="${CORE_DIR}/../_vercel_output" + +# Find the actual repo root (the directory containing packages/) +REPO_ROOT="" +for candidate in /vercel/path0 /vercel/path1 "${CORE_DIR}/.."; do + if [ -d "${candidate}/packages" ]; then + REPO_ROOT="${candidate}" + break + fi +done + +echo "=== Ionic Framework Preview Build ===" +echo "Core dir: ${CORE_DIR}" +echo "Repo root: ${REPO_ROOT:-NOT FOUND}" +if [ -z "${REPO_ROOT}" ]; then + echo "(This is expected in some Vercel configs -- framework test apps will be skipped)" +fi + +rm -rf "${OUTPUT_DIR}" +mkdir -p "${OUTPUT_DIR}" + +# Step 1 - Build Core (dependencies already installed by Vercel installCommand) +echo "" +echo "--- Step 1: Building Core ---" +npm run build + +# Copy core files to output. The test HTML files use relative paths like +# ../../../../../dist/ionic/ionic.esm.js so the directory structure must +# be preserved exactly. +echo "Copying core output..." +cp -r "${CORE_DIR}/src" "${OUTPUT_DIR}/src" +cp -r "${CORE_DIR}/dist" "${OUTPUT_DIR}/dist" +cp -r "${CORE_DIR}/css" "${OUTPUT_DIR}/css" +mkdir -p "${OUTPUT_DIR}/scripts" +cp -r "${CORE_DIR}/scripts/testing" "${OUTPUT_DIR}/scripts/testing" + +# Generate directory index pages so users can browse core test pages. +# Creates an index.html in every directory under src/components/ that +# doesn't already have one. Only includes child directories that eventually +# lead to a test page (an index.html). Prunes snapshot dirs and dead ends. +echo "Generating directory indexes for core tests..." +generate_dir_index() { + local dir="$1" + local url_path="$2" + # Skip if an index.html already exists (it's an actual test page) + [ -f "${dir}/index.html" ] && return + + # Absolute hrefs based on url_path. Vercel does not redirect to add trailing + # slashes, so a relative href like "basic/" from a URL without a trailing + # slash resolves against the parent directory and breaks navigation. + local parent_path="${url_path%/}" + parent_path="${parent_path%/*}/" + + local entries="" + for child in "${dir}"/*/; do + [ -d "${child}" ] || continue + local name=$(basename "${child}") + # Skip snapshot directories and hidden dirs + case "${name}" in *-snapshots|.*) continue ;; esac + # Only include if there's at least one index.html somewhere underneath + find "${child}" -name "index.html" -print -quit | grep -q . || continue + entries="${entries}${name}/\n" + done + + [ -z "${entries}" ] && return + + cat > "${dir}/index.html" << IDXEOF + + + + + + Index of ${url_path} + + + +

Index of ${url_path}

+ ../ +$(echo -e "${entries}") + + +IDXEOF +} + +# Walk all directories under src/ (bottom-up so parent indexes reflect pruned children) +find "${OUTPUT_DIR}/src" -depth -type d | while IFS= read -r dir; do + url_path="${dir#${OUTPUT_DIR}}" + generate_dir_index "${dir}" "${url_path}/" +done + +# Vercel mounts core/ at a separate path (path1) from the repo clone (path0). +# Framework packages reference core via relative paths (../../core/css etc.), +# which resolve to path0/core/ -- not path1/ where we just built. +# Symlink path0/core -> path1 so those references find the build outputs. +if [ -n "${REPO_ROOT}" ] && [ "${CORE_DIR}" != "${REPO_ROOT}/core" ] && [ -d "${REPO_ROOT}/core" ]; then + echo "Linking ${REPO_ROOT}/core -> ${CORE_DIR} (so framework builds find core outputs)" + rm -rf "${REPO_ROOT}/core" + ln -s "${CORE_DIR}" "${REPO_ROOT}/core" +fi + +# Check if the full repo is available +if [ -z "${REPO_ROOT}" ]; then + echo "" + echo "WARNING: Could not find repo root (no directory with packages/ found)" + echo "Only core tests will be deployed (framework test apps require the full repo)." + + # Generate landing page and exit -- core tests are still useful + cat > "${OUTPUT_DIR}/index.html" << 'LANDING_EOF' + +Ionic Framework - Preview +

Ionic Framework Preview

Core tests only. Browse to /src/components/{name}/test/{scenario}/

+ +LANDING_EOF + + echo "=== Preview build complete (core only) ===" + exit 0 +fi + +# Step 2 - Build Framework Packages (parallel) +echo "" +echo "--- Step 2: Building Framework Packages ---" + +build_angular_pkgs() { + (cd "${REPO_ROOT}/packages/angular" && npm install && npm run sync && npm run build) || return 1 + (cd "${REPO_ROOT}/packages/angular-server" && npm install && npm run build) || return 1 +} + +build_react_pkgs() { + (cd "${REPO_ROOT}/packages/react" && npm install && npm run sync && npm run build) || return 1 + (cd "${REPO_ROOT}/packages/react-router" && npm install && npm run build) || return 1 +} + +build_vue_pkgs() { + (cd "${REPO_ROOT}/packages/vue" && npm install && npm run sync && npm run build) || return 1 + (cd "${REPO_ROOT}/packages/vue-router" && npm install && npm run build) || return 1 +} + +build_angular_pkgs > /tmp/vercel-angular-pkg.log 2>&1 & +PID_ANG=$! +build_react_pkgs > /tmp/vercel-react-pkg.log 2>&1 & +PID_REACT=$! +build_vue_pkgs > /tmp/vercel-vue-pkg.log 2>&1 & +PID_VUE=$! + +ANG_PKG_OK=true; REACT_PKG_OK=true; VUE_PKG_OK=true +wait $PID_ANG || { echo "Angular packages failed:"; tail -30 /tmp/vercel-angular-pkg.log; ANG_PKG_OK=false; } +wait $PID_REACT || { echo "React packages failed:"; tail -30 /tmp/vercel-react-pkg.log; REACT_PKG_OK=false; } +wait $PID_VUE || { echo "Vue packages failed:"; tail -30 /tmp/vercel-vue-pkg.log; VUE_PKG_OK=false; } + +if ! $ANG_PKG_OK || ! $REACT_PKG_OK || ! $VUE_PKG_OK; then + echo "ERROR: Some framework package builds failed." + echo "Core tests will still be deployed. Skipping failed framework test apps." +else + echo "All framework packages built." +fi + +# Step 3 - Build Framework Test Apps (parallel) +echo "" +echo "--- Step 3: Building Framework Test Apps ---" + +# Find the best available app version for a given package. +# Scans the apps/ directory and picks the newest version (reverse version sort). +pick_app() { + local apps_dir="$1/apps" + [ -d "${apps_dir}" ] || return 1 + local app + app=$(ls -1d "${apps_dir}"/*/ 2>/dev/null | xargs -n1 basename | sort -V -r | head -1) + [ -n "${app}" ] && echo "${app}" && return 0 + return 1 +} + +build_angular_test() { + local APP + APP=$(pick_app "${REPO_ROOT}/packages/angular/test") || { + echo "[angular] No test app found, skipping." + return 0 + } + echo "[angular] Building ${APP}..." + + cd "${REPO_ROOT}/packages/angular/test" + ./build.sh "${APP}" + cd "build/${APP}" + npm install + npm run sync + # --base-href sets so Angular Router works under the sub-path + npm run build -- --base-href /angular/ + + # Output path assumes the 'browser' builder. If migrated to 'application' builder, update this. + if [ ! -d "dist/test-app/browser" ]; then + echo "[angular] ERROR: Expected output at dist/test-app/browser/ not found." + return 1 + fi + mkdir -p "${OUTPUT_DIR}/angular" + cp -r dist/test-app/browser/* "${OUTPUT_DIR}/angular/" + echo "[angular] Done." +} + +build_react_test() { + local APP + APP=$(pick_app "${REPO_ROOT}/packages/react/test") || { + echo "[react] No test app found, skipping." + return 0 + } + echo "[react] Building ${APP}..." + + cd "${REPO_ROOT}/packages/react/test" + ./build.sh "${APP}" + cd "build/${APP}" + npm install + npm run sync + # --base sets Vite's base URL; import.meta.env.BASE_URL is read by IonReactRouter basename + npx vite build --base /react/ + + mkdir -p "${OUTPUT_DIR}/react" + cp -r dist/* "${OUTPUT_DIR}/react/" + echo "[react] Done." +} + +build_vue_test() { + local APP + APP=$(pick_app "${REPO_ROOT}/packages/vue/test") || { + echo "[vue] No test app found, skipping." + return 0 + } + echo "[vue] Building ${APP}..." + + cd "${REPO_ROOT}/packages/vue/test" + ./build.sh "${APP}" + cd "build/${APP}" + npm install + npm run sync + # Vue Router already reads import.meta.env.BASE_URL which Vite sets from --base + npx vite build --base /vue/ + + mkdir -p "${OUTPUT_DIR}/vue" + cp -r dist/* "${OUTPUT_DIR}/vue/" + echo "[vue] Done." +} + +# TODO: Add build_react_router_test() when reactrouter6-* apps are added to +# packages/react-router/test/apps/ + +TEST_FAILED="" + +if $ANG_PKG_OK; then + build_angular_test > /tmp/vercel-angular-test.log 2>&1 & + PID_ANG_TEST=$! +fi +if $REACT_PKG_OK; then + build_react_test > /tmp/vercel-react-test.log 2>&1 & + PID_REACT_TEST=$! +fi +if $VUE_PKG_OK; then + build_vue_test > /tmp/vercel-vue-test.log 2>&1 & + PID_VUE_TEST=$! +fi + +if $ANG_PKG_OK; then + wait $PID_ANG_TEST || { echo "Angular test app failed:"; tail -30 /tmp/vercel-angular-test.log; TEST_FAILED="${TEST_FAILED} angular"; } +fi +if $REACT_PKG_OK; then + wait $PID_REACT_TEST || { echo "React test app failed:"; tail -30 /tmp/vercel-react-test.log; TEST_FAILED="${TEST_FAILED} react"; } +fi +if $VUE_PKG_OK; then + wait $PID_VUE_TEST || { echo "Vue test app failed:"; tail -30 /tmp/vercel-vue-test.log; TEST_FAILED="${TEST_FAILED} vue"; } +fi + +if [ -n "${TEST_FAILED}" ]; then + echo "" + echo "WARNING: Some test app builds failed:${TEST_FAILED}" + echo "Core tests and successful framework apps will still be deployed." +fi + +# Step 4 - Landing Page +echo "" +echo "--- Step 4: Generating landing page ---" + +cat > "${OUTPUT_DIR}/index.html" << 'LANDING_EOF' + + + + + + Ionic Framework - Preview + + + + + + +LANDING_EOF + +echo "" +echo "=== Preview build complete ===" +ls -la "${OUTPUT_DIR}" diff --git a/core/setupJest.js b/core/setupJest.js index f2eb0e70a31..77ea127c680 100644 --- a/core/setupJest.js +++ b/core/setupJest.js @@ -8,7 +8,9 @@ expect.extend({ throw new Error('expected toHaveShadowPart to be called on an element with a shadow root'); } - const shadowPart = received.shadowRoot.querySelector(`[part="${part}"]`); + // Use attribute selector with ~= to match space-separated part values + // e.g., [part~="knob"] matches elements with part="knob" or part="knob knob-a" + const shadowPart = received.shadowRoot.querySelector(`[part~="${part}"]`); const pass = shadowPart !== null; const message = `expected ${received.tagName.toLowerCase()} to have shadow part "${part}"`; diff --git a/core/src/components.d.ts b/core/src/components.d.ts index fb7e5f9cff6..1c737c6faac 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -22,7 +22,7 @@ import { SpinnerTypes } from "./components/spinner/spinner-configs"; import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface"; import { InputOtpChangeEventDetail, InputOtpCompleteEventDetail, InputOtpInputEventDetail } from "./components/input-otp/input-otp-interface"; import { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface"; -import { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface"; +import { ModalBreakpointChangeEventDetail, ModalDragEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface"; import { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface"; import { ViewController } from "./components/nav/view-controller"; import { PickerChangeEventDetail } from "./components/picker/picker-interfaces"; @@ -32,7 +32,7 @@ import { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAct import { IonProgressBarShape } from "./components/progress-bar/progress-bar.interfaces"; import { RadioGroupChangeEventDetail, RadioGroupCompareFn } from "./components/radio-group/radio-group-interface"; import { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue } from "./components/range/range-interface"; -import { RefresherEventDetail } from "./components/refresher/refresher-interface"; +import { RefresherEventDetail, RefresherPullEndEventDetail } from "./components/refresher/refresher-interface"; import { ItemReorderEventDetail, ReorderEndEventDetail, ReorderMoveEventDetail } from "./components/reorder-group/reorder-group-interface"; import { NavigationHookCallback } from "./components/route/route-interface"; import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface"; @@ -64,7 +64,7 @@ export { SpinnerTypes } from "./components/spinner/spinner-configs"; export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface"; export { InputOtpChangeEventDetail, InputOtpCompleteEventDetail, InputOtpInputEventDetail } from "./components/input-otp/input-otp-interface"; export { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface"; -export { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface"; +export { ModalBreakpointChangeEventDetail, ModalDragEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface"; export { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface"; export { ViewController } from "./components/nav/view-controller"; export { PickerChangeEventDetail } from "./components/picker/picker-interfaces"; @@ -74,7 +74,7 @@ export { PopoverSize, PositionAlign, PositionReference, PositionSide, TriggerAct export { IonProgressBarShape } from "./components/progress-bar/progress-bar.interfaces"; export { RadioGroupChangeEventDetail, RadioGroupCompareFn } from "./components/radio-group/radio-group-interface"; export { PinFormatter, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue } from "./components/range/range-interface"; -export { RefresherEventDetail } from "./components/refresher/refresher-interface"; +export { RefresherEventDetail, RefresherPullEndEventDetail } from "./components/refresher/refresher-interface"; export { ItemReorderEventDetail, ReorderEndEventDetail, ReorderMoveEventDetail } from "./components/reorder-group/reorder-group-interface"; export { NavigationHookCallback } from "./components/route/route-interface"; export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface"; @@ -604,7 +604,7 @@ export namespace Components { */ "expand"?: 'full' | 'block'; /** - * Set to `"clear"` for a transparent button that resembles a flat button, to `"outline"` for a transparent button with a border, or to `"solid"` for a button with a filled background. The default fill is `"solid"` except inside of a toolbar, where the default is `"clear"`. + * Set to `"clear"` for a transparent button that resembles a flat button, to `"outline"` for a transparent button with a border, or to `"solid"` for a button with a filled background. The default fill is `"solid"` except when inside of a buttons or datetime component, where the default fill is `"clear"`. */ "fill"?: 'clear' | 'outline' | 'solid' | 'default'; /** @@ -1783,6 +1783,10 @@ export namespace Components { * @default 4 */ "length": number; + /** + * The mode determines the platform behaviors of the component. + */ + "mode"?: "ios" | "md"; /** * A regex pattern string for allowed characters. Defaults based on type. For numbers (`type="number"`): `"[\p{N}]"` For text (`type="text"`): `"[\p{L}\p{N}]"` */ @@ -1811,6 +1815,10 @@ export namespace Components { * @default 'medium' */ "size": 'small' | 'medium' | 'large'; + /** + * The theme determines the visual appearance of the component. + */ + "theme"?: "ios" | "md" | "ionic"; /** * The type of input allowed in the input boxes. * @default 'number' @@ -3086,6 +3094,7 @@ export namespace Components { * The theme determines the visual appearance of the component. */ "theme"?: "ios" | "md" | "ionic"; + "updateRadiosTabindex": () => Promise; /** * the value of the radio group. */ @@ -3205,7 +3214,7 @@ export namespace Components { */ "mode"?: "ios" | "md"; /** - * How much to multiply the pull speed by. To slow the pull animation down, pass a number less than `1`. To speed up the pull, pass a number greater than `1`. The default value is `1` which is equal to the speed of the cursor. If a negative value is passed in, the factor will be `1` instead. For example: If the value passed is `1.2` and the content is dragged by `10` pixels, instead of `10` pixels the content will be pulled by `12` pixels (an increase of 20 percent). If the value passed is `0.8`, the dragged amount will be `8` pixels, less than the amount the cursor has moved. Does not apply when the refresher content uses a spinner, enabling the native refresher. + * How much to multiply the pull speed by. To slow the pull animation down, pass a number less than `1`. To speed up the pull, pass a number greater than `1`. The default value is `1` which is equal to the speed of the cursor. If a negative value is passed in, the factor will be `1` instead. For example, If the value passed is `1.2` and the content is dragged by `10` pixels, instead of `10` pixels, the content will be pulled by `12` pixels (an increase of 20 percent). If the value passed is `0.8`, the dragged amount will be `8` pixels, less than the amount the cursor has moved. Does not apply when the refresher content uses a spinner, enabling the native refresher. * @default 1 */ "pullFactor": number; @@ -3664,8 +3673,18 @@ export namespace Components { * @param smoothScroll : Whether to animate the scroll transition. */ "setContent": (id: string, smoothScroll?: boolean) => Promise; + /** + * If `true`, users will be able to swipe the segment view to navigate between segment contents. + * @default true + */ + "swipeGesture": boolean; } interface IonSelect { + /** + * If `true`, the cancel button will display an icon instead of the `cancelText`. Only applies when `interface` is set to `"modal"`. Has no effect on `"action-sheet"`, `"alert"`, or `"popover"` interfaces. When `cancelIcon` is `true`, the `cancelText` property is ignored for display but is used as the accessible label for the icon button. + * @default false + */ + "cancelIcon": boolean; /** * The text to display on the cancel button. * @default 'Cancel' @@ -3782,6 +3801,16 @@ export namespace Components { "value"?: any | null; } interface IonSelectModal { + /** + * If `true`, the cancel button will display a close icon instead of the `cancelText`. When `cancelIcon` is `true`, `cancelText` is not displayed visually but is still used as the accessible label (`aria-label`) for the button. + * @default false + */ + "cancelIcon": boolean; + /** + * The text to display on the cancel button. + * @default 'Close' + */ + "cancelText": string; "header"?: string; "multiple"?: boolean; /** @@ -3790,6 +3819,10 @@ export namespace Components { "options": SelectModalOption[]; } interface IonSelectOption { + /** + * Text that is placed underneath the option text to provide additional details about the option. + */ + "description"?: string; /** * If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons. * @default false @@ -3943,6 +3976,11 @@ export namespace Components { * @default 'full' */ "expand": 'compact' | 'full'; + /** + * If `true`, the tab bar will be hidden when the user scrolls down and shown when the user scrolls up. Only applies when the theme is `"ionic"` and `expand` is `"compact"`. + * @default false + */ + "hideOnScroll": boolean; /** * The mode determines the platform behaviors of the component. */ @@ -4436,6 +4474,10 @@ export namespace Components { * The theme determines the visual appearance of the component. */ "theme"?: "ios" | "md" | "ionic"; + /** + * Where to place the title relative to the other toolbar content. `"start"`: The title will appear to the left of the toolbar content in LTR and to the right in RTL. `"center"`: The title will appear in the center of the toolbar. `"end"`: The title will appear to the right of the toolbar content in LTR and to the left in RTL. Only applies in the `ionic` theme. + */ + "titlePlacement"?: 'start' | 'center' | 'end'; } } export interface IonAccordionGroupCustomEvent extends CustomEvent { @@ -5216,6 +5258,9 @@ declare global { "willDismiss": OverlayEventDetail; "didDismiss": OverlayEventDetail; "ionMount": void; + "ionDragStart": void; + "ionDragMove": ModalDragEventDetail; + "ionDragEnd": ModalDragEventDetail; } interface HTMLIonModalElement extends Components.IonModal, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLIonModalElement, ev: IonModalCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -5436,6 +5481,8 @@ declare global { "ionRefresh": RefresherEventDetail; "ionPull": void; "ionStart": void; + "ionPullStart": void; + "ionPullEnd": RefresherPullEndEventDetail; } interface HTMLIonRefresherElement extends Components.IonRefresher, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLIonRefresherElement, ev: IonRefresherCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -5983,6 +6030,8 @@ declare global { } } declare namespace LocalJSX { + type OneOf = { [P in K]: PropT } & { [P in `attr:${K}` | `prop:${K}`]?: never } | { [P in `attr:${K}`]: AttrT } & { [P in K | `prop:${K}`]?: never } | { [P in `prop:${K}`]: PropT } & { [P in K | `attr:${K}`]?: never }; + interface IonAccordion { /** * If `true`, the accordion cannot be interacted with. @@ -6543,7 +6592,7 @@ declare namespace LocalJSX { */ "expand"?: 'full' | 'block'; /** - * Set to `"clear"` for a transparent button that resembles a flat button, to `"outline"` for a transparent button with a border, or to `"solid"` for a button with a filled background. The default fill is `"solid"` except inside of a toolbar, where the default is `"clear"`. + * Set to `"clear"` for a transparent button that resembles a flat button, to `"outline"` for a transparent button with a border, or to `"solid"` for a button with a filled background. The default fill is `"solid"` except when inside of a buttons or datetime component, where the default fill is `"clear"`. */ "fill"?: 'clear' | 'outline' | 'solid' | 'default'; /** @@ -7741,6 +7790,10 @@ declare namespace LocalJSX { * @default 'outline' */ "fill"?: 'outline' | 'solid'; + /** + * The `id` of a `
` element to associate this element with. + */ + "form"?: string; /** * A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. For numbers (type="number"): "numeric" For text (type="text"): "text" */ @@ -7750,6 +7803,14 @@ declare namespace LocalJSX { * @default 4 */ "length"?: number; + /** + * The mode determines the platform behaviors of the component. + */ + "mode"?: "ios" | "md"; + /** + * The name of the element, used when submitting an HTML form. + */ + "name"?: string; /** * Emitted when the input group loses focus. */ @@ -7793,6 +7854,10 @@ declare namespace LocalJSX { * @default 'medium' */ "size"?: 'small' | 'medium' | 'large'; + /** + * The theme determines the visual appearance of the component. + */ + "theme"?: "ios" | "md" | "ionic"; /** * The type of input allowed in the input boxes. * @default 'number' @@ -8409,6 +8474,18 @@ declare namespace LocalJSX { * Emitted after the modal breakpoint has changed. */ "onIonBreakpointDidChange"?: (event: IonModalCustomEvent) => void; + /** + * Event that is emitted when the sheet modal or card modal gesture ends. + */ + "onIonDragEnd"?: (event: IonModalCustomEvent) => void; + /** + * Event that is emitted when the sheet modal or card modal gesture moves. + */ + "onIonDragMove"?: (event: IonModalCustomEvent) => void; + /** + * Event that is emitted when the sheet modal or card modal gesture starts. + */ + "onIonDragStart"?: (event: IonModalCustomEvent) => void; /** * Emitted after the modal has dismissed. */ @@ -9149,16 +9226,25 @@ declare namespace LocalJSX { * Emitted while the user is pulling down the content and exposing the refresher. */ "onIonPull"?: (event: IonRefresherCustomEvent) => void; + /** + * Emitted when the refresher has returned to the inactive state after a pull gesture. This fires whether the refresh completed successfully or was canceled. + */ + "onIonPullEnd"?: (event: IonRefresherCustomEvent) => void; + /** + * Emitted when the user begins to start pulling down. + */ + "onIonPullStart"?: (event: IonRefresherCustomEvent) => void; /** * Emitted when the user lets go of the content and has pulled down further than the `pullMin` or pulls the content down and exceeds the pullMax. Updates the refresher state to `refreshing`. The `complete()` method should be called when the async operation has completed. */ "onIonRefresh"?: (event: IonRefresherCustomEvent) => void; /** * Emitted when the user begins to start pulling down. + * @deprecated Use `ionPullStart` instead. */ "onIonStart"?: (event: IonRefresherCustomEvent) => void; /** - * How much to multiply the pull speed by. To slow the pull animation down, pass a number less than `1`. To speed up the pull, pass a number greater than `1`. The default value is `1` which is equal to the speed of the cursor. If a negative value is passed in, the factor will be `1` instead. For example: If the value passed is `1.2` and the content is dragged by `10` pixels, instead of `10` pixels the content will be pulled by `12` pixels (an increase of 20 percent). If the value passed is `0.8`, the dragged amount will be `8` pixels, less than the amount the cursor has moved. Does not apply when the refresher content uses a spinner, enabling the native refresher. + * How much to multiply the pull speed by. To slow the pull animation down, pass a number less than `1`. To speed up the pull, pass a number greater than `1`. The default value is `1` which is equal to the speed of the cursor. If a negative value is passed in, the factor will be `1` instead. For example, If the value passed is `1.2` and the content is dragged by `10` pixels, instead of `10` pixels, the content will be pulled by `12` pixels (an increase of 20 percent). If the value passed is `0.8`, the dragged amount will be `8` pixels, less than the amount the cursor has moved. Does not apply when the refresher content uses a spinner, enabling the native refresher. * @default 1 */ "pullFactor"?: number; @@ -9655,8 +9741,18 @@ declare namespace LocalJSX { * Emitted when the segment view is scrolled. */ "onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent) => void; + /** + * If `true`, users will be able to swipe the segment view to navigate between segment contents. + * @default true + */ + "swipeGesture"?: boolean; } interface IonSelect { + /** + * If `true`, the cancel button will display an icon instead of the `cancelText`. Only applies when `interface` is set to `"modal"`. Has no effect on `"action-sheet"`, `"alert"`, or `"popover"` interfaces. When `cancelIcon` is `true`, the `cancelText` property is ignored for display but is used as the accessible label for the icon button. + * @default false + */ + "cancelIcon"?: boolean; /** * The text to display on the cancel button. * @default 'Cancel' @@ -9792,6 +9888,16 @@ declare namespace LocalJSX { "value"?: any | null; } interface IonSelectModal { + /** + * If `true`, the cancel button will display a close icon instead of the `cancelText`. When `cancelIcon` is `true`, `cancelText` is not displayed visually but is still used as the accessible label (`aria-label`) for the button. + * @default false + */ + "cancelIcon"?: boolean; + /** + * The text to display on the cancel button. + * @default 'Close' + */ + "cancelText"?: string; "header"?: string; "multiple"?: boolean; /** @@ -9800,6 +9906,10 @@ declare namespace LocalJSX { "options"?: SelectModalOption[]; } interface IonSelectOption { + /** + * Text that is placed underneath the option text to provide additional details about the option. + */ + "description"?: string; /** * If `true`, the user cannot interact with the select option. This property does not apply when `interface="action-sheet"` as `ion-action-sheet` does not allow for disabled buttons. * @default false @@ -9956,6 +10066,11 @@ declare namespace LocalJSX { * @default 'full' */ "expand"?: 'compact' | 'full'; + /** + * If `true`, the tab bar will be hidden when the user scrolls down and shown when the user scrolls up. Only applies when the theme is `"ionic"` and `expand` is `"compact"`. + * @default false + */ + "hideOnScroll"?: boolean; /** * The mode determines the platform behaviors of the component. */ @@ -10131,6 +10246,10 @@ declare namespace LocalJSX { * The fill for the item. If `"solid"` the item will have a background. If `"outline"` the item will be transparent with a border. Only available when the theme is `"md"`. */ "fill"?: 'outline' | 'solid'; + /** + * The `id` of a `` element to associate this element with. + */ + "form"?: string; /** * Text that is placed under the textarea and displayed when no error is detected. */ @@ -10489,210 +10608,1013 @@ declare namespace LocalJSX { * The theme determines the visual appearance of the component. */ "theme"?: "ios" | "md" | "ionic"; + /** + * Where to place the title relative to the other toolbar content. `"start"`: The title will appear to the left of the toolbar content in LTR and to the right in RTL. `"center"`: The title will appear in the center of the toolbar. `"end"`: The title will appear to the right of the toolbar content in LTR and to the left in RTL. Only applies in the `ionic` theme. + */ + "titlePlacement"?: 'start' | 'center' | 'end'; + } + + interface IonAccordionAttributes { + "value": string; + "disabled": boolean; + "readonly": boolean; + "toggleIcon": string; + "toggleIconSlot": 'start' | 'end'; + } + interface IonAccordionGroupAttributes { + "animated": boolean; + "multiple": boolean; + "value": string | string[] | null; + "disabled": boolean; + "readonly": boolean; + "expand": 'compact' | 'inset'; + "shape": 'soft' | 'round' | 'rectangular'; + } + interface IonActionSheetAttributes { + "overlayIndex": number; + "hasController": boolean; + "keyboardClose": boolean; + "cssClass": string | string[]; + "backdropDismiss": boolean; + "header": string; + "subHeader": string; + "translucent": boolean; + "animated": boolean; + "isOpen": boolean; + "trigger": string | undefined; + } + interface IonAlertAttributes { + "overlayIndex": number; + "hasController": boolean; + "keyboardClose": boolean; + "cssClass": string | string[]; + "header": string; + "subHeader": string; + "message": string | IonicSafeString; + "backdropDismiss": boolean; + "translucent": boolean; + "animated": boolean; + "isOpen": boolean; + "trigger": string | undefined; + } + interface IonAvatarAttributes { + "size": 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge'; + "shape": 'soft' | 'round' | 'rectangular'; + "disabled": boolean; + } + interface IonBackButtonAttributes { + "color": Color; + "defaultHref": string; + "disabled": boolean; + "icon": string | null; + "text": string | null; + "type": 'submit' | 'reset' | 'button'; + } + interface IonBackdropAttributes { + "visible": boolean; + "tappable": boolean; + "stopPropagation": boolean; + } + interface IonBadgeAttributes { + "color": Color; + "hue": IonBadgeHue; + "shape": IonBadgeShape; + "size": IonBadgeSize; + "vertical": IonBadgeVerticalPosition; + } + interface IonBreadcrumbAttributes { + "collapsed": boolean; + "last": boolean; + "showCollapsedIndicator": boolean; + "color": Color; + "active": boolean; + "disabled": boolean; + "download": string | undefined; + "href": string | undefined; + "rel": string | undefined; + "separator": boolean | undefined; + "target": string | undefined; + "routerDirection": RouterDirection; + } + interface IonBreadcrumbsAttributes { + "color": Color; + "maxItems": number; + "itemsBeforeCollapse": number; + "itemsAfterCollapse": number; + } + interface IonButtonAttributes { + "color": Color; + "buttonType": string; + "disabled": boolean; + "expand": 'full' | 'block'; + "fill": 'clear' | 'outline' | 'solid' | 'default'; + "routerDirection": RouterDirection; + "download": string | undefined; + "href": string | undefined; + "rel": string | undefined; + "shape": 'soft' | 'round' | 'rectangular'; + "size": 'small' | 'default' | 'medium' | 'large'; + "strong": boolean; + "target": string | undefined; + "type": 'submit' | 'reset' | 'button'; + "form": string | HTMLFormElement; + } + interface IonButtonsAttributes { + "collapse": boolean; + } + interface IonCardAttributes { + "color": Color; + "button": boolean; + "type": 'submit' | 'reset' | 'button'; + "disabled": boolean; + "download": string | undefined; + "href": string | undefined; + "rel": string | undefined; + "routerDirection": RouterDirection; + "shape": 'soft' | 'round' | 'rectangular'; + "target": string | undefined; + } + interface IonCardHeaderAttributes { + "color": Color; + "translucent": boolean; + } + interface IonCardSubtitleAttributes { + "color": Color; + } + interface IonCardTitleAttributes { + "color": Color; + } + interface IonCheckboxAttributes { + "color": Color; + "name": string; + "checked": boolean; + "indeterminate": boolean; + "disabled": boolean; + "errorText": string; + "helperText": string; + "value": string; + "labelPlacement": 'start' | 'end' | 'fixed' | 'stacked'; + "justify": 'start' | 'end' | 'space-between'; + "alignment": 'start' | 'center'; + "required": boolean; + "shape": 'soft' | 'rectangular'; + "size": 'small'; + } + interface IonChipAttributes { + "color": Color; + "outline": boolean; + "fill": IonChipFill; + "disabled": boolean; + "hue": IonChipHue; + "shape": IonChipShape; + "size": IonChipSize; + } + interface IonColAttributes { + "offset": string; + "offsetXs": string; + "offsetSm": string; + "offsetMd": string; + "offsetLg": string; + "offsetXl": string; + "order": string; + "orderXs": string; + "orderSm": string; + "orderMd": string; + "orderLg": string; + "orderXl": string; + "pull": string; + "pullXs": string; + "pullSm": string; + "pullMd": string; + "pullLg": string; + "pullXl": string; + "push": string; + "pushXs": string; + "pushSm": string; + "pushMd": string; + "pushLg": string; + "pushXl": string; + "size": string; + "sizeXs": string; + "sizeSm": string; + "sizeMd": string; + "sizeLg": string; + "sizeXl": string; + } + interface IonContentAttributes { + "color": Color; + "fullscreen": boolean; + "fixedSlotPlacement": 'after' | 'before'; + "forceOverscroll": boolean; + "scrollX": boolean; + "scrollY": boolean; + "scrollEvents": boolean; + } + interface IonDatetimeAttributes { + "color": Color; + "name": string; + "disabled": boolean; + "readonly": boolean; + "showAdjacentDays": boolean; + "min": string; + "max": string; + "presentation": DatetimePresentation; + "cancelText": string; + "doneText": string; + "clearText": string; + "yearValues": string; + "monthValues": string; + "dayValues": string; + "hourValues": string; + "minuteValues": string; + "locale": string; + "firstDayOfWeek": number; + "multiple": boolean; + "value": string | string[] | null; + "showDefaultTitle": boolean; + "showDefaultButtons": boolean; + "showClearButton": boolean; + "showDefaultTimeLabel": boolean; + "hourCycle": DatetimeHourCycle; + "size": 'cover' | 'fixed'; + "preferWheel": boolean; + } + interface IonDatetimeButtonAttributes { + "color": Color; + "disabled": boolean; + "datetime": string; + } + interface IonDividerAttributes { + "spacing": 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge'; + "inset": boolean; + } + interface IonFabAttributes { + "horizontal": 'start' | 'end' | 'center'; + "vertical": 'top' | 'bottom' | 'center'; + "edge": boolean; + "activated": boolean; + } + interface IonFabButtonAttributes { + "color": Color; + "activated": boolean; + "disabled": boolean; + "download": string | undefined; + "href": string | undefined; + "rel": string | undefined; + "routerDirection": RouterDirection; + "target": string | undefined; + "show": boolean; + "translucent": boolean; + "type": 'submit' | 'reset' | 'button'; + "size": 'small'; + "closeIcon": string; + } + interface IonFabListAttributes { + "activated": boolean; + "side": 'start' | 'end' | 'top' | 'bottom'; + } + interface IonFooterAttributes { + "collapse": 'fade'; + "translucent": boolean; + } + interface IonGridAttributes { + "fixed": boolean; + } + interface IonHeaderAttributes { + "collapse": 'condense' | 'fade'; + "divider": boolean; + "translucent": boolean; + } + interface IonImgAttributes { + "alt": string; + "src": string; + } + interface IonInfiniteScrollAttributes { + "threshold": string; + "disabled": boolean; + "position": 'top' | 'bottom'; + "preserveRerenderScrollPosition": boolean; + } + interface IonInfiniteScrollContentAttributes { + "loadingSpinner": SpinnerTypes | null; + "loadingText": string | IonicSafeString; + } + interface IonInputAttributes { + "color": Color; + "autocapitalize": string; + "autocomplete": AutocompleteTypes; + "autocorrect": 'on' | 'off'; + "autofocus": boolean; + "clearInput": boolean; + "clearInputIcon": string; + "clearOnEdit": boolean; + "counter": boolean; + "debounce": number; + "disabled": boolean; + "enterkeyhint": 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + "errorText": string; + "fill": 'outline' | 'solid'; + "inputmode": 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; + "helperText": string; + "label": string; + "labelPlacement": 'start' | 'end' | 'floating' | 'stacked' | 'fixed'; + "max": string; + "maxlength": number; + "min": string; + "minlength": number; + "multiple": boolean; + "name": string; + "pattern": string; + "placeholder": string; + "readonly": boolean; + "required": boolean; + "shape": 'soft' | 'round' | 'rectangular'; + "spellcheck": boolean; + "step": string; + "size": 'medium' | 'large' | 'xlarge'; + "type": TextFieldTypes; + "value": string; + } + interface IonInputOtpAttributes { + "autocapitalize": string; + "color": Color; + "disabled": boolean; + "fill": 'outline' | 'solid'; + "inputmode": 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; + "length": number; + "pattern": string; + "readonly": boolean; + "separators": 'all' | string | number[]; + "shape": 'round' | 'rectangular' | 'soft'; + "size": 'small' | 'medium' | 'large'; + "type": 'text' | 'number'; + "value": string; + } + interface IonInputPasswordToggleAttributes { + "color": Color; + "showIcon": string; + "hideIcon": string; + "type": TextFieldTypes; + } + interface IonItemAttributes { + "color": Color; + "button": boolean; + "detail": boolean; + "detailIcon": string; + "disabled": boolean; + "download": string | undefined; + "href": string | undefined; + "rel": string | undefined; + "lines": 'full' | 'inset' | 'none'; + "routerDirection": RouterDirection; + "target": string | undefined; + "type": 'submit' | 'reset' | 'button'; + } + interface IonItemDividerAttributes { + "color": Color; + "sticky": boolean; + } + interface IonItemOptionAttributes { + "color": Color; + "disabled": boolean; + "download": string | undefined; + "expandable": boolean; + "href": string | undefined; + "hue": 'bold' | 'subtle'; + "rel": string | undefined; + "target": string | undefined; + "type": 'submit' | 'reset' | 'button'; + "shape": 'soft' | 'round' | 'rectangular'; + } + interface IonItemOptionsAttributes { + "side": Side; + } + interface IonItemSlidingAttributes { + "disabled": boolean; + } + interface IonLabelAttributes { + "color": Color; + "position": 'fixed' | 'stacked' | 'floating'; + } + interface IonListAttributes { + "lines": 'full' | 'inset' | 'none'; + "inset": boolean; + "shape": 'soft' | 'round' | 'rectangular'; + } + interface IonListHeaderAttributes { + "color": Color; + "lines": 'full' | 'inset' | 'none'; + } + interface IonLoadingAttributes { + "overlayIndex": number; + "hasController": boolean; + "keyboardClose": boolean; + "message": string | IonicSafeString; + "cssClass": string | string[]; + "duration": number; + "backdropDismiss": boolean; + "showBackdrop": boolean; + "spinner": SpinnerTypes | null; + "translucent": boolean; + "animated": boolean; + "isOpen": boolean; + "trigger": string | undefined; + } + interface IonMenuAttributes { + "contentId": string; + "menuId": string; + "type": MenuType; + "disabled": boolean; + "side": Side; + "swipeGesture": boolean; + "maxEdgeStart": number; + } + interface IonMenuButtonAttributes { + "color": Color; + "disabled": boolean; + "menu": string; + "autoHide": boolean; + "type": 'submit' | 'reset' | 'button'; + } + interface IonMenuToggleAttributes { + "menu": string; + "autoHide": boolean; + } + interface IonModalAttributes { + "hasController": boolean; + "overlayIndex": number; + "keyboardClose": boolean; + "expandToScroll": boolean; + "initialBreakpoint": number; + "backdropBreakpoint": number; + "handle": boolean; + "handleBehavior": ModalHandleBehavior; + "component": ComponentRef; + "cssClass": string | string[]; + "backdropDismiss": boolean; + "showBackdrop": boolean; + "animated": boolean; + "isOpen": boolean; + "trigger": string | undefined; + "keepContentsMounted": boolean; + "focusTrap": boolean; + "canDismiss": boolean | ((data?: any, role?: string) => Promise); + "shape": 'soft' | 'round' | 'rectangular'; + } + interface IonNavAttributes { + "swipeGesture": boolean; + "animated": boolean; + "root": NavComponent; + } + interface IonNavLinkAttributes { + "component": NavComponent; + "routerDirection": RouterDirection; + } + interface IonNoteAttributes { + "color": Color; + } + interface IonPickerColumnAttributes { + "disabled": boolean; + "value": string; + "color": Color; + "numericInput": boolean; + } + interface IonPickerColumnOptionAttributes { + "disabled": boolean; + "value": string; + "color": Color; + } + interface IonPickerLegacyAttributes { + "overlayIndex": number; + "hasController": boolean; + "keyboardClose": boolean; + "cssClass": string | string[]; + "duration": number; + "showBackdrop": boolean; + "backdropDismiss": boolean; + "animated": boolean; + "isOpen": boolean; + "trigger": string | undefined; + } + interface IonPopoverAttributes { + "hasController": boolean; + "overlayIndex": number; + "component": ComponentRef; + "keyboardClose": boolean; + "cssClass": string | string[]; + "backdropDismiss": boolean; + "event": string; + "showBackdrop": boolean; + "translucent": boolean; + "animated": boolean; + "triggerAction": TriggerAction; + "trigger": string | undefined; + "size": PopoverSize; + "dismissOnSelect": boolean; + "reference": PositionReference; + "side": PositionSide; + "alignment": PositionAlign; + "arrow": boolean; + "isOpen": boolean; + "keyboardEvents": boolean; + "focusTrap": boolean; + "keepContentsMounted": boolean; + } + interface IonProgressBarAttributes { + "type": 'determinate' | 'indeterminate'; + "reversed": boolean; + "value": number; + "buffer": number; + "color": Color; + "shape": IonProgressBarShape; + } + interface IonRadioAttributes { + "color": Color; + "name": string; + "disabled": boolean; + "value": string; + "labelPlacement": 'start' | 'end' | 'fixed' | 'stacked'; + "justify": 'start' | 'end' | 'space-between'; + "alignment": 'start' | 'center'; + } + interface IonRadioGroupAttributes { + "allowEmptySelection": boolean; + "compareWith": string | RadioGroupCompareFn | null; + "name": string; + "value": string; + "helperText": string; + "errorText": string; + } + interface IonRangeAttributes { + "color": Color; + "debounce": number; + "name": string; + "label": string; + "dualKnobs": boolean; + "min": number; + "max": number; + "pin": boolean; + "snaps": boolean; + "step": number; + "ticks": boolean; + "activeBarStart": number; + "disabled": boolean; + "value": RangeValue; + "labelPlacement": 'start' | 'end' | 'fixed' | 'stacked'; + } + interface IonRefresherAttributes { + "pullMin": number; + "pullMax": number; + "closeDuration": string; + "snapbackDuration": string; + "pullFactor": number; + "disabled": boolean; + } + interface IonRefresherContentAttributes { + "pullingIcon": SpinnerTypes | string | null; + "pullingText": string | IonicSafeString; + "refreshingSpinner": SpinnerTypes | null; + "refreshingText": string | IonicSafeString; + } + interface IonReorderGroupAttributes { + "disabled": boolean; + } + interface IonRippleEffectAttributes { + "type": 'bounded' | 'unbounded'; + } + interface IonRouteAttributes { + "url": string; + "component": string; + } + interface IonRouteRedirectAttributes { + "from": string; + "to": string | undefined | null; + } + interface IonRouterAttributes { + "root": string; + "useHash": boolean; + } + interface IonRouterLinkAttributes { + "color": Color; + "href": string | undefined; + "rel": string | undefined; + "routerDirection": RouterDirection; + "target": string | undefined; + } + interface IonRouterOutletAttributes { + "mode": "ios" | "md"; + "animated": boolean; + } + interface IonSearchbarAttributes { + "color": Color; + "animated": boolean; + "autocapitalize": string; + "autocomplete": AutocompleteTypes; + "autocorrect": 'on' | 'off'; + "cancelButtonIcon": string; + "cancelButtonText": string; + "clearIcon": string; + "debounce": number; + "disabled": boolean; + "inputmode": 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; + "enterkeyhint": 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + "maxlength": number; + "minlength": number; + "name": string; + "placeholder": string; + "searchIcon": string; + "showCancelButton": 'never' | 'focus' | 'always'; + "showClearButton": 'never' | 'focus' | 'always'; + "spellcheck": boolean; + "type": 'text' | 'password' | 'email' | 'number' | 'search' | 'tel' | 'url'; + "value": string | null; + "shape": 'soft' | 'round' | 'rectangular'; + "size": 'small' | 'medium' | 'large'; + } + interface IonSegmentAttributes { + "color": Color; + "disabled": boolean; + "scrollable": boolean; + "swipeGesture": boolean; + "value": string; + "selectOnFocus": boolean; + } + interface IonSegmentButtonAttributes { + "contentId": string; + "disabled": boolean; + "layout": SegmentButtonLayout; + "type": 'submit' | 'reset' | 'button'; + "value": string; + } + interface IonSegmentViewAttributes { + "disabled": boolean; + "swipeGesture": boolean; + } + interface IonSelectAttributes { + "cancelText": string; + "cancelIcon": boolean; + "color": Color; + "compareWith": string | SelectCompareFn | null; + "disabled": boolean; + "fill": 'outline' | 'solid'; + "errorText": string; + "helperText": string; + "interface": SelectInterface; + "interfaceOptions": string; + "justify": 'start' | 'end' | 'space-between'; + "label": string; + "labelPlacement": 'start' | 'end' | 'floating' | 'stacked' | 'fixed'; + "multiple": boolean; + "name": string; + "okText": string; + "placeholder": string; + "selectedText": string | null; + "toggleIcon": string; + "expandedIcon": string; + "required": boolean; + "shape": 'soft' | 'round' | 'rectangular'; + "size": 'small' | 'medium' | 'large'; + "value": string; + } + interface IonSelectModalAttributes { + "header": string; + "cancelText": string; + "cancelIcon": boolean; + "multiple": boolean; + } + interface IonSelectOptionAttributes { + "disabled": boolean; + "value": string; + "description": string; + } + interface IonSelectPopoverAttributes { + "header": string; + "subHeader": string; + "message": string; + "multiple": boolean; + } + interface IonSkeletonTextAttributes { + "animated": boolean; + } + interface IonSpinnerAttributes { + "color": Color; + "duration": number; + "name": SpinnerTypes; + "paused": boolean; + "size": SpinnerSize; + } + interface IonSplitPaneAttributes { + "contentId": string; + "disabled": boolean; + "when": string; + } + interface IonTabAttributes { + "active": boolean; + "tab": string; + "component": ComponentRef; + } + interface IonTabBarAttributes { + "color": Color; + "selectedTab": string; + "hideOnScroll": boolean; + "translucent": boolean; + "expand": 'compact' | 'full'; + "shape": 'soft' | 'round' | 'rectangular'; + } + interface IonTabButtonAttributes { + "disabled": boolean; + "download": string | undefined; + "href": string | undefined; + "rel": string | undefined; + "layout": TabButtonLayout; + "selected": boolean; + "shape": 'soft' | 'round' | 'rectangular'; + "tab": string; + "target": string | undefined; + } + interface IonTabsAttributes { + "useRouter": boolean; + } + interface IonTextAttributes { + "color": Color; + } + interface IonTextareaAttributes { + "color": Color; + "autocapitalize": string; + "autofocus": boolean; + "clearOnEdit": boolean; + "debounce": number; + "disabled": boolean; + "fill": 'outline' | 'solid'; + "inputmode": 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'; + "enterkeyhint": 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + "maxlength": number; + "minlength": number; + "name": string; + "placeholder": string; + "readonly": boolean; + "required": boolean; + "spellcheck": boolean; + "cols": number; + "rows": number; + "wrap": 'hard' | 'soft' | 'off'; + "autoGrow": boolean; + "value": string | null; + "counter": boolean; + "errorText": string; + "helperText": string; + "label": string; + "labelPlacement": 'start' | 'end' | 'floating' | 'stacked' | 'fixed'; + "shape": 'soft' | 'round' | 'rectangular'; + "size": 'small' | 'medium' | 'large'; + } + interface IonTitleAttributes { + "color": Color; + "size": 'large' | 'small'; + } + interface IonToastAttributes { + "overlayIndex": number; + "hasController": boolean; + "color": Color; + "cssClass": string | string[]; + "duration": number; + "header": string; + "hue": 'bold' | 'subtle'; + "layout": ToastLayout; + "message": string | IonicSafeString; + "keyboardClose": boolean; + "position": ToastPosition; + "positionAnchor": HTMLElement | string; + "shape": 'soft' | 'round' | 'rectangular'; + "translucent": boolean; + "animated": boolean; + "icon": string; + "swipeGesture": ToastSwipeGestureDirection; + "isOpen": boolean; + "trigger": string | undefined; + } + interface IonToggleAttributes { + "color": Color; + "name": string; + "checked": boolean; + "disabled": boolean; + "errorText": string; + "helperText": string; + "value": string | null; + "enableOnOffLabels": boolean | undefined; + "labelPlacement": 'start' | 'end' | 'fixed' | 'stacked'; + "justify": 'start' | 'end' | 'space-between'; + "alignment": 'start' | 'center'; + "required": boolean; + } + interface IonToolbarAttributes { + "color": Color; + "titlePlacement": 'start' | 'center' | 'end'; } + interface IntrinsicElements { - "ion-accordion": IonAccordion; - "ion-accordion-group": IonAccordionGroup; - "ion-action-sheet": IonActionSheet; - "ion-alert": IonAlert; + "ion-accordion": Omit & { [K in keyof IonAccordion & keyof IonAccordionAttributes]?: IonAccordion[K] } & { [K in keyof IonAccordion & keyof IonAccordionAttributes as `attr:${K}`]?: IonAccordionAttributes[K] } & { [K in keyof IonAccordion & keyof IonAccordionAttributes as `prop:${K}`]?: IonAccordion[K] }; + "ion-accordion-group": Omit & { [K in keyof IonAccordionGroup & keyof IonAccordionGroupAttributes]?: IonAccordionGroup[K] } & { [K in keyof IonAccordionGroup & keyof IonAccordionGroupAttributes as `attr:${K}`]?: IonAccordionGroupAttributes[K] } & { [K in keyof IonAccordionGroup & keyof IonAccordionGroupAttributes as `prop:${K}`]?: IonAccordionGroup[K] }; + "ion-action-sheet": Omit & { [K in keyof IonActionSheet & keyof IonActionSheetAttributes]?: IonActionSheet[K] } & { [K in keyof IonActionSheet & keyof IonActionSheetAttributes as `attr:${K}`]?: IonActionSheetAttributes[K] } & { [K in keyof IonActionSheet & keyof IonActionSheetAttributes as `prop:${K}`]?: IonActionSheet[K] } & OneOf<"overlayIndex", IonActionSheet["overlayIndex"], IonActionSheetAttributes["overlayIndex"]>; + "ion-alert": Omit & { [K in keyof IonAlert & keyof IonAlertAttributes]?: IonAlert[K] } & { [K in keyof IonAlert & keyof IonAlertAttributes as `attr:${K}`]?: IonAlertAttributes[K] } & { [K in keyof IonAlert & keyof IonAlertAttributes as `prop:${K}`]?: IonAlert[K] } & OneOf<"overlayIndex", IonAlert["overlayIndex"], IonAlertAttributes["overlayIndex"]>; "ion-app": IonApp; - "ion-avatar": IonAvatar; - "ion-back-button": IonBackButton; - "ion-backdrop": IonBackdrop; - "ion-badge": IonBadge; - "ion-breadcrumb": IonBreadcrumb; - "ion-breadcrumbs": IonBreadcrumbs; - "ion-button": IonButton; - "ion-buttons": IonButtons; - "ion-card": IonCard; + "ion-avatar": Omit & { [K in keyof IonAvatar & keyof IonAvatarAttributes]?: IonAvatar[K] } & { [K in keyof IonAvatar & keyof IonAvatarAttributes as `attr:${K}`]?: IonAvatarAttributes[K] } & { [K in keyof IonAvatar & keyof IonAvatarAttributes as `prop:${K}`]?: IonAvatar[K] }; + "ion-back-button": Omit & { [K in keyof IonBackButton & keyof IonBackButtonAttributes]?: IonBackButton[K] } & { [K in keyof IonBackButton & keyof IonBackButtonAttributes as `attr:${K}`]?: IonBackButtonAttributes[K] } & { [K in keyof IonBackButton & keyof IonBackButtonAttributes as `prop:${K}`]?: IonBackButton[K] }; + "ion-backdrop": Omit & { [K in keyof IonBackdrop & keyof IonBackdropAttributes]?: IonBackdrop[K] } & { [K in keyof IonBackdrop & keyof IonBackdropAttributes as `attr:${K}`]?: IonBackdropAttributes[K] } & { [K in keyof IonBackdrop & keyof IonBackdropAttributes as `prop:${K}`]?: IonBackdrop[K] }; + "ion-badge": Omit & { [K in keyof IonBadge & keyof IonBadgeAttributes]?: IonBadge[K] } & { [K in keyof IonBadge & keyof IonBadgeAttributes as `attr:${K}`]?: IonBadgeAttributes[K] } & { [K in keyof IonBadge & keyof IonBadgeAttributes as `prop:${K}`]?: IonBadge[K] }; + "ion-breadcrumb": Omit & { [K in keyof IonBreadcrumb & keyof IonBreadcrumbAttributes]?: IonBreadcrumb[K] } & { [K in keyof IonBreadcrumb & keyof IonBreadcrumbAttributes as `attr:${K}`]?: IonBreadcrumbAttributes[K] } & { [K in keyof IonBreadcrumb & keyof IonBreadcrumbAttributes as `prop:${K}`]?: IonBreadcrumb[K] } & OneOf<"last", IonBreadcrumb["last"], IonBreadcrumbAttributes["last"]> & OneOf<"showCollapsedIndicator", IonBreadcrumb["showCollapsedIndicator"], IonBreadcrumbAttributes["showCollapsedIndicator"]>; + "ion-breadcrumbs": Omit & { [K in keyof IonBreadcrumbs & keyof IonBreadcrumbsAttributes]?: IonBreadcrumbs[K] } & { [K in keyof IonBreadcrumbs & keyof IonBreadcrumbsAttributes as `attr:${K}`]?: IonBreadcrumbsAttributes[K] } & { [K in keyof IonBreadcrumbs & keyof IonBreadcrumbsAttributes as `prop:${K}`]?: IonBreadcrumbs[K] }; + "ion-button": Omit & { [K in keyof IonButton & keyof IonButtonAttributes]?: IonButton[K] } & { [K in keyof IonButton & keyof IonButtonAttributes as `attr:${K}`]?: IonButtonAttributes[K] } & { [K in keyof IonButton & keyof IonButtonAttributes as `prop:${K}`]?: IonButton[K] }; + "ion-buttons": Omit & { [K in keyof IonButtons & keyof IonButtonsAttributes]?: IonButtons[K] } & { [K in keyof IonButtons & keyof IonButtonsAttributes as `attr:${K}`]?: IonButtonsAttributes[K] } & { [K in keyof IonButtons & keyof IonButtonsAttributes as `prop:${K}`]?: IonButtons[K] }; + "ion-card": Omit & { [K in keyof IonCard & keyof IonCardAttributes]?: IonCard[K] } & { [K in keyof IonCard & keyof IonCardAttributes as `attr:${K}`]?: IonCardAttributes[K] } & { [K in keyof IonCard & keyof IonCardAttributes as `prop:${K}`]?: IonCard[K] }; "ion-card-content": IonCardContent; - "ion-card-header": IonCardHeader; - "ion-card-subtitle": IonCardSubtitle; - "ion-card-title": IonCardTitle; - "ion-checkbox": IonCheckbox; - "ion-chip": IonChip; - "ion-col": IonCol; - "ion-content": IonContent; - "ion-datetime": IonDatetime; - "ion-datetime-button": IonDatetimeButton; - "ion-divider": IonDivider; - "ion-fab": IonFab; - "ion-fab-button": IonFabButton; - "ion-fab-list": IonFabList; - "ion-footer": IonFooter; - "ion-grid": IonGrid; - "ion-header": IonHeader; - "ion-img": IonImg; - "ion-infinite-scroll": IonInfiniteScroll; - "ion-infinite-scroll-content": IonInfiniteScrollContent; - "ion-input": IonInput; - "ion-input-otp": IonInputOtp; - "ion-input-password-toggle": IonInputPasswordToggle; - "ion-item": IonItem; - "ion-item-divider": IonItemDivider; + "ion-card-header": Omit & { [K in keyof IonCardHeader & keyof IonCardHeaderAttributes]?: IonCardHeader[K] } & { [K in keyof IonCardHeader & keyof IonCardHeaderAttributes as `attr:${K}`]?: IonCardHeaderAttributes[K] } & { [K in keyof IonCardHeader & keyof IonCardHeaderAttributes as `prop:${K}`]?: IonCardHeader[K] }; + "ion-card-subtitle": Omit & { [K in keyof IonCardSubtitle & keyof IonCardSubtitleAttributes]?: IonCardSubtitle[K] } & { [K in keyof IonCardSubtitle & keyof IonCardSubtitleAttributes as `attr:${K}`]?: IonCardSubtitleAttributes[K] } & { [K in keyof IonCardSubtitle & keyof IonCardSubtitleAttributes as `prop:${K}`]?: IonCardSubtitle[K] }; + "ion-card-title": Omit & { [K in keyof IonCardTitle & keyof IonCardTitleAttributes]?: IonCardTitle[K] } & { [K in keyof IonCardTitle & keyof IonCardTitleAttributes as `attr:${K}`]?: IonCardTitleAttributes[K] } & { [K in keyof IonCardTitle & keyof IonCardTitleAttributes as `prop:${K}`]?: IonCardTitle[K] }; + "ion-checkbox": Omit & { [K in keyof IonCheckbox & keyof IonCheckboxAttributes]?: IonCheckbox[K] } & { [K in keyof IonCheckbox & keyof IonCheckboxAttributes as `attr:${K}`]?: IonCheckboxAttributes[K] } & { [K in keyof IonCheckbox & keyof IonCheckboxAttributes as `prop:${K}`]?: IonCheckbox[K] }; + "ion-chip": Omit & { [K in keyof IonChip & keyof IonChipAttributes]?: IonChip[K] } & { [K in keyof IonChip & keyof IonChipAttributes as `attr:${K}`]?: IonChipAttributes[K] } & { [K in keyof IonChip & keyof IonChipAttributes as `prop:${K}`]?: IonChip[K] }; + "ion-col": Omit & { [K in keyof IonCol & keyof IonColAttributes]?: IonCol[K] } & { [K in keyof IonCol & keyof IonColAttributes as `attr:${K}`]?: IonColAttributes[K] } & { [K in keyof IonCol & keyof IonColAttributes as `prop:${K}`]?: IonCol[K] }; + "ion-content": Omit & { [K in keyof IonContent & keyof IonContentAttributes]?: IonContent[K] } & { [K in keyof IonContent & keyof IonContentAttributes as `attr:${K}`]?: IonContentAttributes[K] } & { [K in keyof IonContent & keyof IonContentAttributes as `prop:${K}`]?: IonContent[K] }; + "ion-datetime": Omit & { [K in keyof IonDatetime & keyof IonDatetimeAttributes]?: IonDatetime[K] } & { [K in keyof IonDatetime & keyof IonDatetimeAttributes as `attr:${K}`]?: IonDatetimeAttributes[K] } & { [K in keyof IonDatetime & keyof IonDatetimeAttributes as `prop:${K}`]?: IonDatetime[K] }; + "ion-datetime-button": Omit & { [K in keyof IonDatetimeButton & keyof IonDatetimeButtonAttributes]?: IonDatetimeButton[K] } & { [K in keyof IonDatetimeButton & keyof IonDatetimeButtonAttributes as `attr:${K}`]?: IonDatetimeButtonAttributes[K] } & { [K in keyof IonDatetimeButton & keyof IonDatetimeButtonAttributes as `prop:${K}`]?: IonDatetimeButton[K] }; + "ion-divider": Omit & { [K in keyof IonDivider & keyof IonDividerAttributes]?: IonDivider[K] } & { [K in keyof IonDivider & keyof IonDividerAttributes as `attr:${K}`]?: IonDividerAttributes[K] } & { [K in keyof IonDivider & keyof IonDividerAttributes as `prop:${K}`]?: IonDivider[K] }; + "ion-fab": Omit & { [K in keyof IonFab & keyof IonFabAttributes]?: IonFab[K] } & { [K in keyof IonFab & keyof IonFabAttributes as `attr:${K}`]?: IonFabAttributes[K] } & { [K in keyof IonFab & keyof IonFabAttributes as `prop:${K}`]?: IonFab[K] }; + "ion-fab-button": Omit & { [K in keyof IonFabButton & keyof IonFabButtonAttributes]?: IonFabButton[K] } & { [K in keyof IonFabButton & keyof IonFabButtonAttributes as `attr:${K}`]?: IonFabButtonAttributes[K] } & { [K in keyof IonFabButton & keyof IonFabButtonAttributes as `prop:${K}`]?: IonFabButton[K] }; + "ion-fab-list": Omit & { [K in keyof IonFabList & keyof IonFabListAttributes]?: IonFabList[K] } & { [K in keyof IonFabList & keyof IonFabListAttributes as `attr:${K}`]?: IonFabListAttributes[K] } & { [K in keyof IonFabList & keyof IonFabListAttributes as `prop:${K}`]?: IonFabList[K] }; + "ion-footer": Omit & { [K in keyof IonFooter & keyof IonFooterAttributes]?: IonFooter[K] } & { [K in keyof IonFooter & keyof IonFooterAttributes as `attr:${K}`]?: IonFooterAttributes[K] } & { [K in keyof IonFooter & keyof IonFooterAttributes as `prop:${K}`]?: IonFooter[K] }; + "ion-grid": Omit & { [K in keyof IonGrid & keyof IonGridAttributes]?: IonGrid[K] } & { [K in keyof IonGrid & keyof IonGridAttributes as `attr:${K}`]?: IonGridAttributes[K] } & { [K in keyof IonGrid & keyof IonGridAttributes as `prop:${K}`]?: IonGrid[K] }; + "ion-header": Omit & { [K in keyof IonHeader & keyof IonHeaderAttributes]?: IonHeader[K] } & { [K in keyof IonHeader & keyof IonHeaderAttributes as `attr:${K}`]?: IonHeaderAttributes[K] } & { [K in keyof IonHeader & keyof IonHeaderAttributes as `prop:${K}`]?: IonHeader[K] }; + "ion-img": Omit & { [K in keyof IonImg & keyof IonImgAttributes]?: IonImg[K] } & { [K in keyof IonImg & keyof IonImgAttributes as `attr:${K}`]?: IonImgAttributes[K] } & { [K in keyof IonImg & keyof IonImgAttributes as `prop:${K}`]?: IonImg[K] }; + "ion-infinite-scroll": Omit & { [K in keyof IonInfiniteScroll & keyof IonInfiniteScrollAttributes]?: IonInfiniteScroll[K] } & { [K in keyof IonInfiniteScroll & keyof IonInfiniteScrollAttributes as `attr:${K}`]?: IonInfiniteScrollAttributes[K] } & { [K in keyof IonInfiniteScroll & keyof IonInfiniteScrollAttributes as `prop:${K}`]?: IonInfiniteScroll[K] }; + "ion-infinite-scroll-content": Omit & { [K in keyof IonInfiniteScrollContent & keyof IonInfiniteScrollContentAttributes]?: IonInfiniteScrollContent[K] } & { [K in keyof IonInfiniteScrollContent & keyof IonInfiniteScrollContentAttributes as `attr:${K}`]?: IonInfiniteScrollContentAttributes[K] } & { [K in keyof IonInfiniteScrollContent & keyof IonInfiniteScrollContentAttributes as `prop:${K}`]?: IonInfiniteScrollContent[K] }; + "ion-input": Omit & { [K in keyof IonInput & keyof IonInputAttributes]?: IonInput[K] } & { [K in keyof IonInput & keyof IonInputAttributes as `attr:${K}`]?: IonInputAttributes[K] } & { [K in keyof IonInput & keyof IonInputAttributes as `prop:${K}`]?: IonInput[K] }; + "ion-input-otp": Omit & { [K in keyof IonInputOtp & keyof IonInputOtpAttributes]?: IonInputOtp[K] } & { [K in keyof IonInputOtp & keyof IonInputOtpAttributes as `attr:${K}`]?: IonInputOtpAttributes[K] } & { [K in keyof IonInputOtp & keyof IonInputOtpAttributes as `prop:${K}`]?: IonInputOtp[K] }; + "ion-input-password-toggle": Omit & { [K in keyof IonInputPasswordToggle & keyof IonInputPasswordToggleAttributes]?: IonInputPasswordToggle[K] } & { [K in keyof IonInputPasswordToggle & keyof IonInputPasswordToggleAttributes as `attr:${K}`]?: IonInputPasswordToggleAttributes[K] } & { [K in keyof IonInputPasswordToggle & keyof IonInputPasswordToggleAttributes as `prop:${K}`]?: IonInputPasswordToggle[K] }; + "ion-item": Omit & { [K in keyof IonItem & keyof IonItemAttributes]?: IonItem[K] } & { [K in keyof IonItem & keyof IonItemAttributes as `attr:${K}`]?: IonItemAttributes[K] } & { [K in keyof IonItem & keyof IonItemAttributes as `prop:${K}`]?: IonItem[K] }; + "ion-item-divider": Omit & { [K in keyof IonItemDivider & keyof IonItemDividerAttributes]?: IonItemDivider[K] } & { [K in keyof IonItemDivider & keyof IonItemDividerAttributes as `attr:${K}`]?: IonItemDividerAttributes[K] } & { [K in keyof IonItemDivider & keyof IonItemDividerAttributes as `prop:${K}`]?: IonItemDivider[K] }; "ion-item-group": IonItemGroup; - "ion-item-option": IonItemOption; - "ion-item-options": IonItemOptions; - "ion-item-sliding": IonItemSliding; - "ion-label": IonLabel; - "ion-list": IonList; - "ion-list-header": IonListHeader; - "ion-loading": IonLoading; - "ion-menu": IonMenu; - "ion-menu-button": IonMenuButton; - "ion-menu-toggle": IonMenuToggle; - "ion-modal": IonModal; - "ion-nav": IonNav; - "ion-nav-link": IonNavLink; - "ion-note": IonNote; + "ion-item-option": Omit & { [K in keyof IonItemOption & keyof IonItemOptionAttributes]?: IonItemOption[K] } & { [K in keyof IonItemOption & keyof IonItemOptionAttributes as `attr:${K}`]?: IonItemOptionAttributes[K] } & { [K in keyof IonItemOption & keyof IonItemOptionAttributes as `prop:${K}`]?: IonItemOption[K] }; + "ion-item-options": Omit & { [K in keyof IonItemOptions & keyof IonItemOptionsAttributes]?: IonItemOptions[K] } & { [K in keyof IonItemOptions & keyof IonItemOptionsAttributes as `attr:${K}`]?: IonItemOptionsAttributes[K] } & { [K in keyof IonItemOptions & keyof IonItemOptionsAttributes as `prop:${K}`]?: IonItemOptions[K] }; + "ion-item-sliding": Omit & { [K in keyof IonItemSliding & keyof IonItemSlidingAttributes]?: IonItemSliding[K] } & { [K in keyof IonItemSliding & keyof IonItemSlidingAttributes as `attr:${K}`]?: IonItemSlidingAttributes[K] } & { [K in keyof IonItemSliding & keyof IonItemSlidingAttributes as `prop:${K}`]?: IonItemSliding[K] }; + "ion-label": Omit & { [K in keyof IonLabel & keyof IonLabelAttributes]?: IonLabel[K] } & { [K in keyof IonLabel & keyof IonLabelAttributes as `attr:${K}`]?: IonLabelAttributes[K] } & { [K in keyof IonLabel & keyof IonLabelAttributes as `prop:${K}`]?: IonLabel[K] }; + "ion-list": Omit & { [K in keyof IonList & keyof IonListAttributes]?: IonList[K] } & { [K in keyof IonList & keyof IonListAttributes as `attr:${K}`]?: IonListAttributes[K] } & { [K in keyof IonList & keyof IonListAttributes as `prop:${K}`]?: IonList[K] }; + "ion-list-header": Omit & { [K in keyof IonListHeader & keyof IonListHeaderAttributes]?: IonListHeader[K] } & { [K in keyof IonListHeader & keyof IonListHeaderAttributes as `attr:${K}`]?: IonListHeaderAttributes[K] } & { [K in keyof IonListHeader & keyof IonListHeaderAttributes as `prop:${K}`]?: IonListHeader[K] }; + "ion-loading": Omit & { [K in keyof IonLoading & keyof IonLoadingAttributes]?: IonLoading[K] } & { [K in keyof IonLoading & keyof IonLoadingAttributes as `attr:${K}`]?: IonLoadingAttributes[K] } & { [K in keyof IonLoading & keyof IonLoadingAttributes as `prop:${K}`]?: IonLoading[K] } & OneOf<"overlayIndex", IonLoading["overlayIndex"], IonLoadingAttributes["overlayIndex"]>; + "ion-menu": Omit & { [K in keyof IonMenu & keyof IonMenuAttributes]?: IonMenu[K] } & { [K in keyof IonMenu & keyof IonMenuAttributes as `attr:${K}`]?: IonMenuAttributes[K] } & { [K in keyof IonMenu & keyof IonMenuAttributes as `prop:${K}`]?: IonMenu[K] }; + "ion-menu-button": Omit & { [K in keyof IonMenuButton & keyof IonMenuButtonAttributes]?: IonMenuButton[K] } & { [K in keyof IonMenuButton & keyof IonMenuButtonAttributes as `attr:${K}`]?: IonMenuButtonAttributes[K] } & { [K in keyof IonMenuButton & keyof IonMenuButtonAttributes as `prop:${K}`]?: IonMenuButton[K] }; + "ion-menu-toggle": Omit & { [K in keyof IonMenuToggle & keyof IonMenuToggleAttributes]?: IonMenuToggle[K] } & { [K in keyof IonMenuToggle & keyof IonMenuToggleAttributes as `attr:${K}`]?: IonMenuToggleAttributes[K] } & { [K in keyof IonMenuToggle & keyof IonMenuToggleAttributes as `prop:${K}`]?: IonMenuToggle[K] }; + "ion-modal": Omit & { [K in keyof IonModal & keyof IonModalAttributes]?: IonModal[K] } & { [K in keyof IonModal & keyof IonModalAttributes as `attr:${K}`]?: IonModalAttributes[K] } & { [K in keyof IonModal & keyof IonModalAttributes as `prop:${K}`]?: IonModal[K] } & OneOf<"overlayIndex", IonModal["overlayIndex"], IonModalAttributes["overlayIndex"]>; + "ion-nav": Omit & { [K in keyof IonNav & keyof IonNavAttributes]?: IonNav[K] } & { [K in keyof IonNav & keyof IonNavAttributes as `attr:${K}`]?: IonNavAttributes[K] } & { [K in keyof IonNav & keyof IonNavAttributes as `prop:${K}`]?: IonNav[K] }; + "ion-nav-link": Omit & { [K in keyof IonNavLink & keyof IonNavLinkAttributes]?: IonNavLink[K] } & { [K in keyof IonNavLink & keyof IonNavLinkAttributes as `attr:${K}`]?: IonNavLinkAttributes[K] } & { [K in keyof IonNavLink & keyof IonNavLinkAttributes as `prop:${K}`]?: IonNavLink[K] }; + "ion-note": Omit & { [K in keyof IonNote & keyof IonNoteAttributes]?: IonNote[K] } & { [K in keyof IonNote & keyof IonNoteAttributes as `attr:${K}`]?: IonNoteAttributes[K] } & { [K in keyof IonNote & keyof IonNoteAttributes as `prop:${K}`]?: IonNote[K] }; "ion-picker": IonPicker; - "ion-picker-column": IonPickerColumn; - "ion-picker-column-option": IonPickerColumnOption; - "ion-picker-legacy": IonPickerLegacy; + "ion-picker-column": Omit & { [K in keyof IonPickerColumn & keyof IonPickerColumnAttributes]?: IonPickerColumn[K] } & { [K in keyof IonPickerColumn & keyof IonPickerColumnAttributes as `attr:${K}`]?: IonPickerColumnAttributes[K] } & { [K in keyof IonPickerColumn & keyof IonPickerColumnAttributes as `prop:${K}`]?: IonPickerColumn[K] }; + "ion-picker-column-option": Omit & { [K in keyof IonPickerColumnOption & keyof IonPickerColumnOptionAttributes]?: IonPickerColumnOption[K] } & { [K in keyof IonPickerColumnOption & keyof IonPickerColumnOptionAttributes as `attr:${K}`]?: IonPickerColumnOptionAttributes[K] } & { [K in keyof IonPickerColumnOption & keyof IonPickerColumnOptionAttributes as `prop:${K}`]?: IonPickerColumnOption[K] }; + "ion-picker-legacy": Omit & { [K in keyof IonPickerLegacy & keyof IonPickerLegacyAttributes]?: IonPickerLegacy[K] } & { [K in keyof IonPickerLegacy & keyof IonPickerLegacyAttributes as `attr:${K}`]?: IonPickerLegacyAttributes[K] } & { [K in keyof IonPickerLegacy & keyof IonPickerLegacyAttributes as `prop:${K}`]?: IonPickerLegacy[K] } & OneOf<"overlayIndex", IonPickerLegacy["overlayIndex"], IonPickerLegacyAttributes["overlayIndex"]>; "ion-picker-legacy-column": IonPickerLegacyColumn; - "ion-popover": IonPopover; - "ion-progress-bar": IonProgressBar; - "ion-radio": IonRadio; - "ion-radio-group": IonRadioGroup; - "ion-range": IonRange; - "ion-refresher": IonRefresher; - "ion-refresher-content": IonRefresherContent; + "ion-popover": Omit & { [K in keyof IonPopover & keyof IonPopoverAttributes]?: IonPopover[K] } & { [K in keyof IonPopover & keyof IonPopoverAttributes as `attr:${K}`]?: IonPopoverAttributes[K] } & { [K in keyof IonPopover & keyof IonPopoverAttributes as `prop:${K}`]?: IonPopover[K] } & OneOf<"overlayIndex", IonPopover["overlayIndex"], IonPopoverAttributes["overlayIndex"]>; + "ion-progress-bar": Omit & { [K in keyof IonProgressBar & keyof IonProgressBarAttributes]?: IonProgressBar[K] } & { [K in keyof IonProgressBar & keyof IonProgressBarAttributes as `attr:${K}`]?: IonProgressBarAttributes[K] } & { [K in keyof IonProgressBar & keyof IonProgressBarAttributes as `prop:${K}`]?: IonProgressBar[K] }; + "ion-radio": Omit & { [K in keyof IonRadio & keyof IonRadioAttributes]?: IonRadio[K] } & { [K in keyof IonRadio & keyof IonRadioAttributes as `attr:${K}`]?: IonRadioAttributes[K] } & { [K in keyof IonRadio & keyof IonRadioAttributes as `prop:${K}`]?: IonRadio[K] }; + "ion-radio-group": Omit & { [K in keyof IonRadioGroup & keyof IonRadioGroupAttributes]?: IonRadioGroup[K] } & { [K in keyof IonRadioGroup & keyof IonRadioGroupAttributes as `attr:${K}`]?: IonRadioGroupAttributes[K] } & { [K in keyof IonRadioGroup & keyof IonRadioGroupAttributes as `prop:${K}`]?: IonRadioGroup[K] }; + "ion-range": Omit & { [K in keyof IonRange & keyof IonRangeAttributes]?: IonRange[K] } & { [K in keyof IonRange & keyof IonRangeAttributes as `attr:${K}`]?: IonRangeAttributes[K] } & { [K in keyof IonRange & keyof IonRangeAttributes as `prop:${K}`]?: IonRange[K] }; + "ion-refresher": Omit & { [K in keyof IonRefresher & keyof IonRefresherAttributes]?: IonRefresher[K] } & { [K in keyof IonRefresher & keyof IonRefresherAttributes as `attr:${K}`]?: IonRefresherAttributes[K] } & { [K in keyof IonRefresher & keyof IonRefresherAttributes as `prop:${K}`]?: IonRefresher[K] }; + "ion-refresher-content": Omit & { [K in keyof IonRefresherContent & keyof IonRefresherContentAttributes]?: IonRefresherContent[K] } & { [K in keyof IonRefresherContent & keyof IonRefresherContentAttributes as `attr:${K}`]?: IonRefresherContentAttributes[K] } & { [K in keyof IonRefresherContent & keyof IonRefresherContentAttributes as `prop:${K}`]?: IonRefresherContent[K] }; "ion-reorder": IonReorder; - "ion-reorder-group": IonReorderGroup; - "ion-ripple-effect": IonRippleEffect; - "ion-route": IonRoute; - "ion-route-redirect": IonRouteRedirect; - "ion-router": IonRouter; - "ion-router-link": IonRouterLink; - "ion-router-outlet": IonRouterOutlet; + "ion-reorder-group": Omit & { [K in keyof IonReorderGroup & keyof IonReorderGroupAttributes]?: IonReorderGroup[K] } & { [K in keyof IonReorderGroup & keyof IonReorderGroupAttributes as `attr:${K}`]?: IonReorderGroupAttributes[K] } & { [K in keyof IonReorderGroup & keyof IonReorderGroupAttributes as `prop:${K}`]?: IonReorderGroup[K] }; + "ion-ripple-effect": Omit & { [K in keyof IonRippleEffect & keyof IonRippleEffectAttributes]?: IonRippleEffect[K] } & { [K in keyof IonRippleEffect & keyof IonRippleEffectAttributes as `attr:${K}`]?: IonRippleEffectAttributes[K] } & { [K in keyof IonRippleEffect & keyof IonRippleEffectAttributes as `prop:${K}`]?: IonRippleEffect[K] }; + "ion-route": Omit & { [K in keyof IonRoute & keyof IonRouteAttributes]?: IonRoute[K] } & { [K in keyof IonRoute & keyof IonRouteAttributes as `attr:${K}`]?: IonRouteAttributes[K] } & { [K in keyof IonRoute & keyof IonRouteAttributes as `prop:${K}`]?: IonRoute[K] } & OneOf<"component", IonRoute["component"], IonRouteAttributes["component"]>; + "ion-route-redirect": Omit & { [K in keyof IonRouteRedirect & keyof IonRouteRedirectAttributes]?: IonRouteRedirect[K] } & { [K in keyof IonRouteRedirect & keyof IonRouteRedirectAttributes as `attr:${K}`]?: IonRouteRedirectAttributes[K] } & { [K in keyof IonRouteRedirect & keyof IonRouteRedirectAttributes as `prop:${K}`]?: IonRouteRedirect[K] } & OneOf<"from", IonRouteRedirect["from"], IonRouteRedirectAttributes["from"]> & OneOf<"to", IonRouteRedirect["to"], IonRouteRedirectAttributes["to"]>; + "ion-router": Omit & { [K in keyof IonRouter & keyof IonRouterAttributes]?: IonRouter[K] } & { [K in keyof IonRouter & keyof IonRouterAttributes as `attr:${K}`]?: IonRouterAttributes[K] } & { [K in keyof IonRouter & keyof IonRouterAttributes as `prop:${K}`]?: IonRouter[K] }; + "ion-router-link": Omit & { [K in keyof IonRouterLink & keyof IonRouterLinkAttributes]?: IonRouterLink[K] } & { [K in keyof IonRouterLink & keyof IonRouterLinkAttributes as `attr:${K}`]?: IonRouterLinkAttributes[K] } & { [K in keyof IonRouterLink & keyof IonRouterLinkAttributes as `prop:${K}`]?: IonRouterLink[K] }; + "ion-router-outlet": Omit & { [K in keyof IonRouterOutlet & keyof IonRouterOutletAttributes]?: IonRouterOutlet[K] } & { [K in keyof IonRouterOutlet & keyof IonRouterOutletAttributes as `attr:${K}`]?: IonRouterOutletAttributes[K] } & { [K in keyof IonRouterOutlet & keyof IonRouterOutletAttributes as `prop:${K}`]?: IonRouterOutlet[K] }; "ion-row": IonRow; - "ion-searchbar": IonSearchbar; - "ion-segment": IonSegment; - "ion-segment-button": IonSegmentButton; + "ion-searchbar": Omit & { [K in keyof IonSearchbar & keyof IonSearchbarAttributes]?: IonSearchbar[K] } & { [K in keyof IonSearchbar & keyof IonSearchbarAttributes as `attr:${K}`]?: IonSearchbarAttributes[K] } & { [K in keyof IonSearchbar & keyof IonSearchbarAttributes as `prop:${K}`]?: IonSearchbar[K] }; + "ion-segment": Omit & { [K in keyof IonSegment & keyof IonSegmentAttributes]?: IonSegment[K] } & { [K in keyof IonSegment & keyof IonSegmentAttributes as `attr:${K}`]?: IonSegmentAttributes[K] } & { [K in keyof IonSegment & keyof IonSegmentAttributes as `prop:${K}`]?: IonSegment[K] }; + "ion-segment-button": Omit & { [K in keyof IonSegmentButton & keyof IonSegmentButtonAttributes]?: IonSegmentButton[K] } & { [K in keyof IonSegmentButton & keyof IonSegmentButtonAttributes as `attr:${K}`]?: IonSegmentButtonAttributes[K] } & { [K in keyof IonSegmentButton & keyof IonSegmentButtonAttributes as `prop:${K}`]?: IonSegmentButton[K] }; "ion-segment-content": IonSegmentContent; - "ion-segment-view": IonSegmentView; - "ion-select": IonSelect; - "ion-select-modal": IonSelectModal; - "ion-select-option": IonSelectOption; - "ion-select-popover": IonSelectPopover; - "ion-skeleton-text": IonSkeletonText; - "ion-spinner": IonSpinner; - "ion-split-pane": IonSplitPane; - "ion-tab": IonTab; - "ion-tab-bar": IonTabBar; - "ion-tab-button": IonTabButton; - "ion-tabs": IonTabs; - "ion-text": IonText; - "ion-textarea": IonTextarea; + "ion-segment-view": Omit & { [K in keyof IonSegmentView & keyof IonSegmentViewAttributes]?: IonSegmentView[K] } & { [K in keyof IonSegmentView & keyof IonSegmentViewAttributes as `attr:${K}`]?: IonSegmentViewAttributes[K] } & { [K in keyof IonSegmentView & keyof IonSegmentViewAttributes as `prop:${K}`]?: IonSegmentView[K] }; + "ion-select": Omit & { [K in keyof IonSelect & keyof IonSelectAttributes]?: IonSelect[K] } & { [K in keyof IonSelect & keyof IonSelectAttributes as `attr:${K}`]?: IonSelectAttributes[K] } & { [K in keyof IonSelect & keyof IonSelectAttributes as `prop:${K}`]?: IonSelect[K] }; + "ion-select-modal": Omit & { [K in keyof IonSelectModal & keyof IonSelectModalAttributes]?: IonSelectModal[K] } & { [K in keyof IonSelectModal & keyof IonSelectModalAttributes as `attr:${K}`]?: IonSelectModalAttributes[K] } & { [K in keyof IonSelectModal & keyof IonSelectModalAttributes as `prop:${K}`]?: IonSelectModal[K] }; + "ion-select-option": Omit & { [K in keyof IonSelectOption & keyof IonSelectOptionAttributes]?: IonSelectOption[K] } & { [K in keyof IonSelectOption & keyof IonSelectOptionAttributes as `attr:${K}`]?: IonSelectOptionAttributes[K] } & { [K in keyof IonSelectOption & keyof IonSelectOptionAttributes as `prop:${K}`]?: IonSelectOption[K] }; + "ion-select-popover": Omit & { [K in keyof IonSelectPopover & keyof IonSelectPopoverAttributes]?: IonSelectPopover[K] } & { [K in keyof IonSelectPopover & keyof IonSelectPopoverAttributes as `attr:${K}`]?: IonSelectPopoverAttributes[K] } & { [K in keyof IonSelectPopover & keyof IonSelectPopoverAttributes as `prop:${K}`]?: IonSelectPopover[K] }; + "ion-skeleton-text": Omit & { [K in keyof IonSkeletonText & keyof IonSkeletonTextAttributes]?: IonSkeletonText[K] } & { [K in keyof IonSkeletonText & keyof IonSkeletonTextAttributes as `attr:${K}`]?: IonSkeletonTextAttributes[K] } & { [K in keyof IonSkeletonText & keyof IonSkeletonTextAttributes as `prop:${K}`]?: IonSkeletonText[K] }; + "ion-spinner": Omit & { [K in keyof IonSpinner & keyof IonSpinnerAttributes]?: IonSpinner[K] } & { [K in keyof IonSpinner & keyof IonSpinnerAttributes as `attr:${K}`]?: IonSpinnerAttributes[K] } & { [K in keyof IonSpinner & keyof IonSpinnerAttributes as `prop:${K}`]?: IonSpinner[K] }; + "ion-split-pane": Omit & { [K in keyof IonSplitPane & keyof IonSplitPaneAttributes]?: IonSplitPane[K] } & { [K in keyof IonSplitPane & keyof IonSplitPaneAttributes as `attr:${K}`]?: IonSplitPaneAttributes[K] } & { [K in keyof IonSplitPane & keyof IonSplitPaneAttributes as `prop:${K}`]?: IonSplitPane[K] }; + "ion-tab": Omit & { [K in keyof IonTab & keyof IonTabAttributes]?: IonTab[K] } & { [K in keyof IonTab & keyof IonTabAttributes as `attr:${K}`]?: IonTabAttributes[K] } & { [K in keyof IonTab & keyof IonTabAttributes as `prop:${K}`]?: IonTab[K] } & OneOf<"tab", IonTab["tab"], IonTabAttributes["tab"]>; + "ion-tab-bar": Omit & { [K in keyof IonTabBar & keyof IonTabBarAttributes]?: IonTabBar[K] } & { [K in keyof IonTabBar & keyof IonTabBarAttributes as `attr:${K}`]?: IonTabBarAttributes[K] } & { [K in keyof IonTabBar & keyof IonTabBarAttributes as `prop:${K}`]?: IonTabBar[K] }; + "ion-tab-button": Omit & { [K in keyof IonTabButton & keyof IonTabButtonAttributes]?: IonTabButton[K] } & { [K in keyof IonTabButton & keyof IonTabButtonAttributes as `attr:${K}`]?: IonTabButtonAttributes[K] } & { [K in keyof IonTabButton & keyof IonTabButtonAttributes as `prop:${K}`]?: IonTabButton[K] }; + "ion-tabs": Omit & { [K in keyof IonTabs & keyof IonTabsAttributes]?: IonTabs[K] } & { [K in keyof IonTabs & keyof IonTabsAttributes as `attr:${K}`]?: IonTabsAttributes[K] } & { [K in keyof IonTabs & keyof IonTabsAttributes as `prop:${K}`]?: IonTabs[K] }; + "ion-text": Omit & { [K in keyof IonText & keyof IonTextAttributes]?: IonText[K] } & { [K in keyof IonText & keyof IonTextAttributes as `attr:${K}`]?: IonTextAttributes[K] } & { [K in keyof IonText & keyof IonTextAttributes as `prop:${K}`]?: IonText[K] }; + "ion-textarea": Omit & { [K in keyof IonTextarea & keyof IonTextareaAttributes]?: IonTextarea[K] } & { [K in keyof IonTextarea & keyof IonTextareaAttributes as `attr:${K}`]?: IonTextareaAttributes[K] } & { [K in keyof IonTextarea & keyof IonTextareaAttributes as `prop:${K}`]?: IonTextarea[K] }; "ion-thumbnail": IonThumbnail; - "ion-title": IonTitle; - "ion-toast": IonToast; - "ion-toggle": IonToggle; - "ion-toolbar": IonToolbar; + "ion-title": Omit & { [K in keyof IonTitle & keyof IonTitleAttributes]?: IonTitle[K] } & { [K in keyof IonTitle & keyof IonTitleAttributes as `attr:${K}`]?: IonTitleAttributes[K] } & { [K in keyof IonTitle & keyof IonTitleAttributes as `prop:${K}`]?: IonTitle[K] }; + "ion-toast": Omit & { [K in keyof IonToast & keyof IonToastAttributes]?: IonToast[K] } & { [K in keyof IonToast & keyof IonToastAttributes as `attr:${K}`]?: IonToastAttributes[K] } & { [K in keyof IonToast & keyof IonToastAttributes as `prop:${K}`]?: IonToast[K] } & OneOf<"overlayIndex", IonToast["overlayIndex"], IonToastAttributes["overlayIndex"]>; + "ion-toggle": Omit & { [K in keyof IonToggle & keyof IonToggleAttributes]?: IonToggle[K] } & { [K in keyof IonToggle & keyof IonToggleAttributes as `attr:${K}`]?: IonToggleAttributes[K] } & { [K in keyof IonToggle & keyof IonToggleAttributes as `prop:${K}`]?: IonToggle[K] }; + "ion-toolbar": Omit & { [K in keyof IonToolbar & keyof IonToolbarAttributes]?: IonToolbar[K] } & { [K in keyof IonToolbar & keyof IonToolbarAttributes as `attr:${K}`]?: IonToolbarAttributes[K] } & { [K in keyof IonToolbar & keyof IonToolbarAttributes as `prop:${K}`]?: IonToolbar[K] }; } } export { LocalJSX as JSX }; declare module "@stencil/core" { export namespace JSX { interface IntrinsicElements { - "ion-accordion": LocalJSX.IonAccordion & JSXBase.HTMLAttributes; - "ion-accordion-group": LocalJSX.IonAccordionGroup & JSXBase.HTMLAttributes; - "ion-action-sheet": LocalJSX.IonActionSheet & JSXBase.HTMLAttributes; - "ion-alert": LocalJSX.IonAlert & JSXBase.HTMLAttributes; - "ion-app": LocalJSX.IonApp & JSXBase.HTMLAttributes; - "ion-avatar": LocalJSX.IonAvatar & JSXBase.HTMLAttributes; - "ion-back-button": LocalJSX.IonBackButton & JSXBase.HTMLAttributes; - "ion-backdrop": LocalJSX.IonBackdrop & JSXBase.HTMLAttributes; - "ion-badge": LocalJSX.IonBadge & JSXBase.HTMLAttributes; - "ion-breadcrumb": LocalJSX.IonBreadcrumb & JSXBase.HTMLAttributes; - "ion-breadcrumbs": LocalJSX.IonBreadcrumbs & JSXBase.HTMLAttributes; - "ion-button": LocalJSX.IonButton & JSXBase.HTMLAttributes; - "ion-buttons": LocalJSX.IonButtons & JSXBase.HTMLAttributes; - "ion-card": LocalJSX.IonCard & JSXBase.HTMLAttributes; - "ion-card-content": LocalJSX.IonCardContent & JSXBase.HTMLAttributes; - "ion-card-header": LocalJSX.IonCardHeader & JSXBase.HTMLAttributes; - "ion-card-subtitle": LocalJSX.IonCardSubtitle & JSXBase.HTMLAttributes; - "ion-card-title": LocalJSX.IonCardTitle & JSXBase.HTMLAttributes; - "ion-checkbox": LocalJSX.IonCheckbox & JSXBase.HTMLAttributes; - "ion-chip": LocalJSX.IonChip & JSXBase.HTMLAttributes; - "ion-col": LocalJSX.IonCol & JSXBase.HTMLAttributes; - "ion-content": LocalJSX.IonContent & JSXBase.HTMLAttributes; - "ion-datetime": LocalJSX.IonDatetime & JSXBase.HTMLAttributes; - "ion-datetime-button": LocalJSX.IonDatetimeButton & JSXBase.HTMLAttributes; - "ion-divider": LocalJSX.IonDivider & JSXBase.HTMLAttributes; - "ion-fab": LocalJSX.IonFab & JSXBase.HTMLAttributes; - "ion-fab-button": LocalJSX.IonFabButton & JSXBase.HTMLAttributes; - "ion-fab-list": LocalJSX.IonFabList & JSXBase.HTMLAttributes; - "ion-footer": LocalJSX.IonFooter & JSXBase.HTMLAttributes; - "ion-grid": LocalJSX.IonGrid & JSXBase.HTMLAttributes; - "ion-header": LocalJSX.IonHeader & JSXBase.HTMLAttributes; - "ion-img": LocalJSX.IonImg & JSXBase.HTMLAttributes; - "ion-infinite-scroll": LocalJSX.IonInfiniteScroll & JSXBase.HTMLAttributes; - "ion-infinite-scroll-content": LocalJSX.IonInfiniteScrollContent & JSXBase.HTMLAttributes; - "ion-input": LocalJSX.IonInput & JSXBase.HTMLAttributes; - "ion-input-otp": LocalJSX.IonInputOtp & JSXBase.HTMLAttributes; - "ion-input-password-toggle": LocalJSX.IonInputPasswordToggle & JSXBase.HTMLAttributes; - "ion-item": LocalJSX.IonItem & JSXBase.HTMLAttributes; - "ion-item-divider": LocalJSX.IonItemDivider & JSXBase.HTMLAttributes; - "ion-item-group": LocalJSX.IonItemGroup & JSXBase.HTMLAttributes; - "ion-item-option": LocalJSX.IonItemOption & JSXBase.HTMLAttributes; - "ion-item-options": LocalJSX.IonItemOptions & JSXBase.HTMLAttributes; - "ion-item-sliding": LocalJSX.IonItemSliding & JSXBase.HTMLAttributes; - "ion-label": LocalJSX.IonLabel & JSXBase.HTMLAttributes; - "ion-list": LocalJSX.IonList & JSXBase.HTMLAttributes; - "ion-list-header": LocalJSX.IonListHeader & JSXBase.HTMLAttributes; - "ion-loading": LocalJSX.IonLoading & JSXBase.HTMLAttributes; - "ion-menu": LocalJSX.IonMenu & JSXBase.HTMLAttributes; - "ion-menu-button": LocalJSX.IonMenuButton & JSXBase.HTMLAttributes; - "ion-menu-toggle": LocalJSX.IonMenuToggle & JSXBase.HTMLAttributes; - "ion-modal": LocalJSX.IonModal & JSXBase.HTMLAttributes; - "ion-nav": LocalJSX.IonNav & JSXBase.HTMLAttributes; - "ion-nav-link": LocalJSX.IonNavLink & JSXBase.HTMLAttributes; - "ion-note": LocalJSX.IonNote & JSXBase.HTMLAttributes; - "ion-picker": LocalJSX.IonPicker & JSXBase.HTMLAttributes; - "ion-picker-column": LocalJSX.IonPickerColumn & JSXBase.HTMLAttributes; - "ion-picker-column-option": LocalJSX.IonPickerColumnOption & JSXBase.HTMLAttributes; - "ion-picker-legacy": LocalJSX.IonPickerLegacy & JSXBase.HTMLAttributes; - "ion-picker-legacy-column": LocalJSX.IonPickerLegacyColumn & JSXBase.HTMLAttributes; - "ion-popover": LocalJSX.IonPopover & JSXBase.HTMLAttributes; - "ion-progress-bar": LocalJSX.IonProgressBar & JSXBase.HTMLAttributes; - "ion-radio": LocalJSX.IonRadio & JSXBase.HTMLAttributes; - "ion-radio-group": LocalJSX.IonRadioGroup & JSXBase.HTMLAttributes; - "ion-range": LocalJSX.IonRange & JSXBase.HTMLAttributes; - "ion-refresher": LocalJSX.IonRefresher & JSXBase.HTMLAttributes; - "ion-refresher-content": LocalJSX.IonRefresherContent & JSXBase.HTMLAttributes; - "ion-reorder": LocalJSX.IonReorder & JSXBase.HTMLAttributes; - "ion-reorder-group": LocalJSX.IonReorderGroup & JSXBase.HTMLAttributes; - "ion-ripple-effect": LocalJSX.IonRippleEffect & JSXBase.HTMLAttributes; - "ion-route": LocalJSX.IonRoute & JSXBase.HTMLAttributes; - "ion-route-redirect": LocalJSX.IonRouteRedirect & JSXBase.HTMLAttributes; - "ion-router": LocalJSX.IonRouter & JSXBase.HTMLAttributes; - "ion-router-link": LocalJSX.IonRouterLink & JSXBase.HTMLAttributes; - "ion-router-outlet": LocalJSX.IonRouterOutlet & JSXBase.HTMLAttributes; - "ion-row": LocalJSX.IonRow & JSXBase.HTMLAttributes; - "ion-searchbar": LocalJSX.IonSearchbar & JSXBase.HTMLAttributes; - "ion-segment": LocalJSX.IonSegment & JSXBase.HTMLAttributes; - "ion-segment-button": LocalJSX.IonSegmentButton & JSXBase.HTMLAttributes; - "ion-segment-content": LocalJSX.IonSegmentContent & JSXBase.HTMLAttributes; - "ion-segment-view": LocalJSX.IonSegmentView & JSXBase.HTMLAttributes; - "ion-select": LocalJSX.IonSelect & JSXBase.HTMLAttributes; - "ion-select-modal": LocalJSX.IonSelectModal & JSXBase.HTMLAttributes; - "ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes; - "ion-select-popover": LocalJSX.IonSelectPopover & JSXBase.HTMLAttributes; - "ion-skeleton-text": LocalJSX.IonSkeletonText & JSXBase.HTMLAttributes; - "ion-spinner": LocalJSX.IonSpinner & JSXBase.HTMLAttributes; - "ion-split-pane": LocalJSX.IonSplitPane & JSXBase.HTMLAttributes; - "ion-tab": LocalJSX.IonTab & JSXBase.HTMLAttributes; - "ion-tab-bar": LocalJSX.IonTabBar & JSXBase.HTMLAttributes; - "ion-tab-button": LocalJSX.IonTabButton & JSXBase.HTMLAttributes; - "ion-tabs": LocalJSX.IonTabs & JSXBase.HTMLAttributes; - "ion-text": LocalJSX.IonText & JSXBase.HTMLAttributes; - "ion-textarea": LocalJSX.IonTextarea & JSXBase.HTMLAttributes; - "ion-thumbnail": LocalJSX.IonThumbnail & JSXBase.HTMLAttributes; - "ion-title": LocalJSX.IonTitle & JSXBase.HTMLAttributes; - "ion-toast": LocalJSX.IonToast & JSXBase.HTMLAttributes; - "ion-toggle": LocalJSX.IonToggle & JSXBase.HTMLAttributes; - "ion-toolbar": LocalJSX.IonToolbar & JSXBase.HTMLAttributes; + "ion-accordion": LocalJSX.IntrinsicElements["ion-accordion"] & JSXBase.HTMLAttributes; + "ion-accordion-group": LocalJSX.IntrinsicElements["ion-accordion-group"] & JSXBase.HTMLAttributes; + "ion-action-sheet": LocalJSX.IntrinsicElements["ion-action-sheet"] & JSXBase.HTMLAttributes; + "ion-alert": LocalJSX.IntrinsicElements["ion-alert"] & JSXBase.HTMLAttributes; + "ion-app": LocalJSX.IntrinsicElements["ion-app"] & JSXBase.HTMLAttributes; + "ion-avatar": LocalJSX.IntrinsicElements["ion-avatar"] & JSXBase.HTMLAttributes; + "ion-back-button": LocalJSX.IntrinsicElements["ion-back-button"] & JSXBase.HTMLAttributes; + "ion-backdrop": LocalJSX.IntrinsicElements["ion-backdrop"] & JSXBase.HTMLAttributes; + "ion-badge": LocalJSX.IntrinsicElements["ion-badge"] & JSXBase.HTMLAttributes; + "ion-breadcrumb": LocalJSX.IntrinsicElements["ion-breadcrumb"] & JSXBase.HTMLAttributes; + "ion-breadcrumbs": LocalJSX.IntrinsicElements["ion-breadcrumbs"] & JSXBase.HTMLAttributes; + "ion-button": LocalJSX.IntrinsicElements["ion-button"] & JSXBase.HTMLAttributes; + "ion-buttons": LocalJSX.IntrinsicElements["ion-buttons"] & JSXBase.HTMLAttributes; + "ion-card": LocalJSX.IntrinsicElements["ion-card"] & JSXBase.HTMLAttributes; + "ion-card-content": LocalJSX.IntrinsicElements["ion-card-content"] & JSXBase.HTMLAttributes; + "ion-card-header": LocalJSX.IntrinsicElements["ion-card-header"] & JSXBase.HTMLAttributes; + "ion-card-subtitle": LocalJSX.IntrinsicElements["ion-card-subtitle"] & JSXBase.HTMLAttributes; + "ion-card-title": LocalJSX.IntrinsicElements["ion-card-title"] & JSXBase.HTMLAttributes; + "ion-checkbox": LocalJSX.IntrinsicElements["ion-checkbox"] & JSXBase.HTMLAttributes; + "ion-chip": LocalJSX.IntrinsicElements["ion-chip"] & JSXBase.HTMLAttributes; + "ion-col": LocalJSX.IntrinsicElements["ion-col"] & JSXBase.HTMLAttributes; + "ion-content": LocalJSX.IntrinsicElements["ion-content"] & JSXBase.HTMLAttributes; + "ion-datetime": LocalJSX.IntrinsicElements["ion-datetime"] & JSXBase.HTMLAttributes; + "ion-datetime-button": LocalJSX.IntrinsicElements["ion-datetime-button"] & JSXBase.HTMLAttributes; + "ion-divider": LocalJSX.IntrinsicElements["ion-divider"] & JSXBase.HTMLAttributes; + "ion-fab": LocalJSX.IntrinsicElements["ion-fab"] & JSXBase.HTMLAttributes; + "ion-fab-button": LocalJSX.IntrinsicElements["ion-fab-button"] & JSXBase.HTMLAttributes; + "ion-fab-list": LocalJSX.IntrinsicElements["ion-fab-list"] & JSXBase.HTMLAttributes; + "ion-footer": LocalJSX.IntrinsicElements["ion-footer"] & JSXBase.HTMLAttributes; + "ion-grid": LocalJSX.IntrinsicElements["ion-grid"] & JSXBase.HTMLAttributes; + "ion-header": LocalJSX.IntrinsicElements["ion-header"] & JSXBase.HTMLAttributes; + "ion-img": LocalJSX.IntrinsicElements["ion-img"] & JSXBase.HTMLAttributes; + "ion-infinite-scroll": LocalJSX.IntrinsicElements["ion-infinite-scroll"] & JSXBase.HTMLAttributes; + "ion-infinite-scroll-content": LocalJSX.IntrinsicElements["ion-infinite-scroll-content"] & JSXBase.HTMLAttributes; + "ion-input": LocalJSX.IntrinsicElements["ion-input"] & JSXBase.HTMLAttributes; + "ion-input-otp": LocalJSX.IntrinsicElements["ion-input-otp"] & JSXBase.HTMLAttributes; + "ion-input-password-toggle": LocalJSX.IntrinsicElements["ion-input-password-toggle"] & JSXBase.HTMLAttributes; + "ion-item": LocalJSX.IntrinsicElements["ion-item"] & JSXBase.HTMLAttributes; + "ion-item-divider": LocalJSX.IntrinsicElements["ion-item-divider"] & JSXBase.HTMLAttributes; + "ion-item-group": LocalJSX.IntrinsicElements["ion-item-group"] & JSXBase.HTMLAttributes; + "ion-item-option": LocalJSX.IntrinsicElements["ion-item-option"] & JSXBase.HTMLAttributes; + "ion-item-options": LocalJSX.IntrinsicElements["ion-item-options"] & JSXBase.HTMLAttributes; + "ion-item-sliding": LocalJSX.IntrinsicElements["ion-item-sliding"] & JSXBase.HTMLAttributes; + "ion-label": LocalJSX.IntrinsicElements["ion-label"] & JSXBase.HTMLAttributes; + "ion-list": LocalJSX.IntrinsicElements["ion-list"] & JSXBase.HTMLAttributes; + "ion-list-header": LocalJSX.IntrinsicElements["ion-list-header"] & JSXBase.HTMLAttributes; + "ion-loading": LocalJSX.IntrinsicElements["ion-loading"] & JSXBase.HTMLAttributes; + "ion-menu": LocalJSX.IntrinsicElements["ion-menu"] & JSXBase.HTMLAttributes; + "ion-menu-button": LocalJSX.IntrinsicElements["ion-menu-button"] & JSXBase.HTMLAttributes; + "ion-menu-toggle": LocalJSX.IntrinsicElements["ion-menu-toggle"] & JSXBase.HTMLAttributes; + "ion-modal": LocalJSX.IntrinsicElements["ion-modal"] & JSXBase.HTMLAttributes; + "ion-nav": LocalJSX.IntrinsicElements["ion-nav"] & JSXBase.HTMLAttributes; + "ion-nav-link": LocalJSX.IntrinsicElements["ion-nav-link"] & JSXBase.HTMLAttributes; + "ion-note": LocalJSX.IntrinsicElements["ion-note"] & JSXBase.HTMLAttributes; + "ion-picker": LocalJSX.IntrinsicElements["ion-picker"] & JSXBase.HTMLAttributes; + "ion-picker-column": LocalJSX.IntrinsicElements["ion-picker-column"] & JSXBase.HTMLAttributes; + "ion-picker-column-option": LocalJSX.IntrinsicElements["ion-picker-column-option"] & JSXBase.HTMLAttributes; + "ion-picker-legacy": LocalJSX.IntrinsicElements["ion-picker-legacy"] & JSXBase.HTMLAttributes; + "ion-picker-legacy-column": LocalJSX.IntrinsicElements["ion-picker-legacy-column"] & JSXBase.HTMLAttributes; + "ion-popover": LocalJSX.IntrinsicElements["ion-popover"] & JSXBase.HTMLAttributes; + "ion-progress-bar": LocalJSX.IntrinsicElements["ion-progress-bar"] & JSXBase.HTMLAttributes; + "ion-radio": LocalJSX.IntrinsicElements["ion-radio"] & JSXBase.HTMLAttributes; + "ion-radio-group": LocalJSX.IntrinsicElements["ion-radio-group"] & JSXBase.HTMLAttributes; + "ion-range": LocalJSX.IntrinsicElements["ion-range"] & JSXBase.HTMLAttributes; + "ion-refresher": LocalJSX.IntrinsicElements["ion-refresher"] & JSXBase.HTMLAttributes; + "ion-refresher-content": LocalJSX.IntrinsicElements["ion-refresher-content"] & JSXBase.HTMLAttributes; + "ion-reorder": LocalJSX.IntrinsicElements["ion-reorder"] & JSXBase.HTMLAttributes; + "ion-reorder-group": LocalJSX.IntrinsicElements["ion-reorder-group"] & JSXBase.HTMLAttributes; + "ion-ripple-effect": LocalJSX.IntrinsicElements["ion-ripple-effect"] & JSXBase.HTMLAttributes; + "ion-route": LocalJSX.IntrinsicElements["ion-route"] & JSXBase.HTMLAttributes; + "ion-route-redirect": LocalJSX.IntrinsicElements["ion-route-redirect"] & JSXBase.HTMLAttributes; + "ion-router": LocalJSX.IntrinsicElements["ion-router"] & JSXBase.HTMLAttributes; + "ion-router-link": LocalJSX.IntrinsicElements["ion-router-link"] & JSXBase.HTMLAttributes; + "ion-router-outlet": LocalJSX.IntrinsicElements["ion-router-outlet"] & JSXBase.HTMLAttributes; + "ion-row": LocalJSX.IntrinsicElements["ion-row"] & JSXBase.HTMLAttributes; + "ion-searchbar": LocalJSX.IntrinsicElements["ion-searchbar"] & JSXBase.HTMLAttributes; + "ion-segment": LocalJSX.IntrinsicElements["ion-segment"] & JSXBase.HTMLAttributes; + "ion-segment-button": LocalJSX.IntrinsicElements["ion-segment-button"] & JSXBase.HTMLAttributes; + "ion-segment-content": LocalJSX.IntrinsicElements["ion-segment-content"] & JSXBase.HTMLAttributes; + "ion-segment-view": LocalJSX.IntrinsicElements["ion-segment-view"] & JSXBase.HTMLAttributes; + "ion-select": LocalJSX.IntrinsicElements["ion-select"] & JSXBase.HTMLAttributes; + "ion-select-modal": LocalJSX.IntrinsicElements["ion-select-modal"] & JSXBase.HTMLAttributes; + "ion-select-option": LocalJSX.IntrinsicElements["ion-select-option"] & JSXBase.HTMLAttributes; + "ion-select-popover": LocalJSX.IntrinsicElements["ion-select-popover"] & JSXBase.HTMLAttributes; + "ion-skeleton-text": LocalJSX.IntrinsicElements["ion-skeleton-text"] & JSXBase.HTMLAttributes; + "ion-spinner": LocalJSX.IntrinsicElements["ion-spinner"] & JSXBase.HTMLAttributes; + "ion-split-pane": LocalJSX.IntrinsicElements["ion-split-pane"] & JSXBase.HTMLAttributes; + "ion-tab": LocalJSX.IntrinsicElements["ion-tab"] & JSXBase.HTMLAttributes; + "ion-tab-bar": LocalJSX.IntrinsicElements["ion-tab-bar"] & JSXBase.HTMLAttributes; + "ion-tab-button": LocalJSX.IntrinsicElements["ion-tab-button"] & JSXBase.HTMLAttributes; + "ion-tabs": LocalJSX.IntrinsicElements["ion-tabs"] & JSXBase.HTMLAttributes; + "ion-text": LocalJSX.IntrinsicElements["ion-text"] & JSXBase.HTMLAttributes; + "ion-textarea": LocalJSX.IntrinsicElements["ion-textarea"] & JSXBase.HTMLAttributes; + "ion-thumbnail": LocalJSX.IntrinsicElements["ion-thumbnail"] & JSXBase.HTMLAttributes; + "ion-title": LocalJSX.IntrinsicElements["ion-title"] & JSXBase.HTMLAttributes; + "ion-toast": LocalJSX.IntrinsicElements["ion-toast"] & JSXBase.HTMLAttributes; + "ion-toggle": LocalJSX.IntrinsicElements["ion-toggle"] & JSXBase.HTMLAttributes; + "ion-toolbar": LocalJSX.IntrinsicElements["ion-toolbar"] & JSXBase.HTMLAttributes; } } } diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Chrome-linux.png index f87c858ac77..3fce761ee2c 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Firefox-linux.png index 2d7ce9a8ef6..1d1ab6ec0aa 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Safari-linux.png index 0588a962752..1fb9ab7a938 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-expanded-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Chrome-linux.png index 49b2c248d8a..cef9f546adb 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Firefox-linux.png index e24ab1469d3..589022e296a 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Safari-linux.png index 87cf310460f..f0ca7a7e061 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-compact-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Chrome-linux.png index 6e3678b90a5..14d4329a95c 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Firefox-linux.png index fd1362a0af0..a925225677b 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Safari-linux.png index 1f68539047d..39380b80b64 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-expanded-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Chrome-linux.png index 17034dc0fff..d80a088ae2d 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Firefox-linux.png index 317d3cc8d9d..f56169eb633 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Safari-linux.png index 072bd8296ee..c476288dc70 100644 Binary files a/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion-group/test/expand/accordion-group.e2e.ts-snapshots/accordion-group-expand-inset-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png index 5e0e5e266f3..d342fabe6b1 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png index cc3c71e6860..aa2de569a9c 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png index ba16b6bbd45..a569b929379 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Chrome-linux.png index 62857a2cb13..1a8d5c00709 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Firefox-linux.png index 2e5aee67edd..03bb7b1d6e6 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Safari-linux.png index 0b2108a9a4b..d1d0d216cfa 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-round-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png index b3bb878fae3..b581d65cf42 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png index dca1c54f88e..4f9bf1d155d 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Safari-linux.png index fa3a5572236..a1721c811de 100644 Binary files a/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion-group/test/shape/accordion-group.e2e.ts-snapshots/accordion-group-shape-soft-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png index 70c28875cac..d00f2f7c63b 100644 Binary files a/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png index ef6a63fdd25..76ad15af97a 100644 Binary files a/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png index 6531a2cc5d5..6cafb113a70 100644 Binary files a/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion-group/test/states/accordion-group.e2e.ts-snapshots/accordion-group-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/accordion.ionic.scss b/core/src/components/accordion/accordion.ionic.scss index 5ec75eb92d7..8ba094ba666 100644 --- a/core/src/components/accordion/accordion.ionic.scss +++ b/core/src/components/accordion/accordion.ionic.scss @@ -28,6 +28,11 @@ z-index: 3; } +// Hide the divider for the last accordion item +:host(.accordion-last)::after { + display: none; +} + :host(.accordion-animated) { transition: all 300ms cubic-bezier(0.25, 0.8, 0.5, 1); } @@ -51,6 +56,7 @@ --padding-end: #{globals.$ion-space-400}; --padding-bottom: #{globals.$ion-space-300}; --padding-start: #{globals.$ion-space-400}; + --background-activated: #{globals.$ion-bg-neutral-subtlest-press}; @include globals.typography(globals.$ion-heading-h6-medium); } diff --git a/core/src/components/accordion/accordion.tsx b/core/src/components/accordion/accordion.tsx index 4f76c035e7f..dcfa11029ac 100644 --- a/core/src/components/accordion/accordion.tsx +++ b/core/src/components/accordion/accordion.tsx @@ -87,6 +87,7 @@ export class Accordion implements ComponentInterface { @State() state: AccordionState = AccordionState.Collapsed; @State() isNext = false; @State() isPrevious = false; + @State() isLast = false; /** * Tracks whether a user-initiated interaction has occurred. * Animations are disabled until the first interaction happens. @@ -431,6 +432,9 @@ export class Accordion implements ComponentInterface { const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue; + const nextAccordion = this.getNextSibling(); + this.isLast = nextAccordion === undefined; + if (shouldExpand) { this.expandAccordion(); this.isNext = this.isPrevious = false; @@ -444,7 +448,6 @@ export class Accordion implements ComponentInterface { * applied. Check to see if the * next or previous accordion is selected. */ - const nextAccordion = this.getNextSibling(); const nextAccordionValue = nextAccordion?.value; if (nextAccordionValue !== undefined) { @@ -525,6 +528,7 @@ export class Accordion implements ComponentInterface { 'accordion-next': this.isNext, 'accordion-previous': this.isPrevious, + 'accordion-last': this.isLast, 'accordion-disabled': disabled, 'accordion-readonly': readonly, @@ -533,6 +537,7 @@ export class Accordion implements ComponentInterface { 'in-accordion-group-expand-inset': hostContext('.accordion-group-expand-inset', this.el), }} + tabindex={disabled ? '-1' : undefined} >
this.toggleExpanded()} diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-ios-ltr-Mobile-Chrome-linux.png index 6af04302d06..c00d05d75c2 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-ios-rtl-Mobile-Chrome-linux.png index aa8da582448..135832db904 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Chrome-linux.png index d414438c4dd..9453467642d 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Chrome-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Chrome-linux.png index 42a3db59769..dac7aa36332 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Chrome-linux.png index f87c858ac77..3fce761ee2c 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Firefox-linux.png index 2d7ce9a8ef6..1d1ab6ec0aa 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Safari-linux.png index 0588a962752..1fb9ab7a938 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Chrome-linux.png index af88b20f71d..82c1a08d3fc 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Firefox-linux.png index e8e47710fc1..895e3adb2c0 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Firefox-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Safari-linux.png index 712e2565cf1..64b931868fc 100644 Binary files a/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Safari-linux.png and b/core/src/components/accordion/test/basic/accordion.e2e.ts-snapshots/accordion-basic-text-ionic-md-rtl-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Chrome-linux.png index 246f81ff1ab..affcb48df2c 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Firefox-linux.png index ba3dc9161e1..0c1f394093f 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Safari-linux.png index d06c23ace97..eae882c6d63 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ios-ltr-Mobile-Chrome-linux.png index 6b45adf776d..7961c4d4f7d 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-md-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-md-ltr-Mobile-Chrome-linux.png index 7fbb4ff95be..08b7ffb5634 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-one-open-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Chrome-linux.png index d1112303eae..63db497757f 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Firefox-linux.png index 280594aef39..6ce667543e4 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Safari-linux.png index 4b50d4aea48..d760bea3553 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ios-ltr-Mobile-Chrome-linux.png index ac7f64d88e6..8b8e697bbda 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-md-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-md-ltr-Mobile-Chrome-linux.png index 34f3ceb71d8..73a2c4f04e5 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-two-open-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Chrome-linux.png index 4580f75861b..2f593a98948 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Firefox-linux.png index 3d4f1511760..cc6982c98f5 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Safari-linux.png index b6fe5447e94..b8ac4165b65 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ios-ltr-Mobile-Chrome-linux.png index dfea291091b..fcaf3a013e5 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-md-ltr-Mobile-Chrome-linux.png b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-md-ltr-Mobile-Chrome-linux.png index bbf3f68c083..4c3b4ecdbdd 100644 Binary files a/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/accordion/test/multiple/accordion.e2e.ts-snapshots/accordion-zero-open-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts b/core/src/components/accordion/test/nested/accordion.e2e.ts index a0a13226496..8b861cf39ff 100644 --- a/core/src/components/accordion/test/nested/accordion.e2e.ts +++ b/core/src/components/accordion/test/nested/accordion.e2e.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; import { configs, test } from '@utils/test/playwright'; -configs({ directions: ['ltr'] }).forEach(({ config, screenshot, title }) => { +configs({ directions: ['ltr'], modes: ['md', 'ios', 'ionic-md'] }).forEach(({ config, screenshot, title }) => { test.describe(title('accordion: nested'), () => { test('parent and child should not be disabled', async ({ page }) => { await page.goto(`/src/components/accordion/test/nested`, config); diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..cc2226f4a90 Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..0d328069396 Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 00000000000..4cc155173fc Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-child-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..8673bcdbc3f Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..ce47f8aa1f5 Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 00000000000..ef8f9981a57 Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-nested-enabled-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..8c81ad2e667 Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..9dad80dbbf9 Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 00000000000..55e81d2a158 Binary files /dev/null and b/core/src/components/accordion/test/nested/accordion.e2e.ts-snapshots/accordion-parent-disabled-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/accordion/test/nested/index.html b/core/src/components/accordion/test/nested/index.html index 1588b27aead..783740c6634 100644 --- a/core/src/components/accordion/test/nested/index.html +++ b/core/src/components/accordion/test/nested/index.html @@ -11,7 +11,7 @@ + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const pickerColumn = datetime.locator('ion-picker-column').first(); + + const backgroundColor = await pickerColumn.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + expect(backgroundColor).toBe('rgb(255, 0, 0)'); + }); + + test('should be able to customize wheel part within the month/year picker', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30420', + }); + + await page.setContent( + ` + + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const monthYearButton = datetime.locator('.calendar-month-year-toggle'); + + await monthYearButton.click(); + + const pickerColumn = datetime.locator('ion-picker-column').first(); + + const backgroundColor = await pickerColumn.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + expect(backgroundColor).toBe('rgb(255, 165, 0)'); + }); + + test('should be able to customize wheel part within the time picker', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30420', + }); + + await page.setContent( + ` + + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const timeButton = datetime.locator('.time-body'); + + await timeButton.click(); + + const pickerColumn = page.locator('ion-picker-column').first(); + + const backgroundColor = await pickerColumn.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + expect(backgroundColor).toBe('rgb(0, 128, 0)'); + }); + + test('should be able to customize wheel part when focused', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30420', + }); + + await page.setContent( + ` + + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const pickerColumn = datetime.locator('ion-picker-column').first(); + + await pickerColumn.click({ position: { x: 10, y: 10 } }); + + const backgroundColor = await pickerColumn.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + expect(backgroundColor).toBe('rgb(0, 0, 255)'); + }); + + test('should be able to customize datetime header parts', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30083', + }); + + await page.setContent( + ` + + + Select Date + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const header = datetime.locator('.datetime-header'); + const title = datetime.locator('.datetime-title'); + const selectedDate = datetime.locator('.datetime-selected-date'); + + const headerBackgroundColor = await header.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + const titleBackgroundColor = await title.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + const selectedDateBackgroundColor = await selectedDate.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + expect(headerBackgroundColor).toBe('rgb(255, 165, 0)'); + expect(titleBackgroundColor).toBe('rgb(255, 192, 203)'); + expect(selectedDateBackgroundColor).toBe('rgb(238, 130, 238)'); + }); + + test('should be able to customize calendar header part', async ({ page }) => { + await page.setContent( + ` + + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const header = datetime.locator('.calendar-header'); + + const backgroundColor = await header.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + expect(backgroundColor).toBe('rgb(255, 165, 0)'); + }); + + test('should be able to customize month/year picker part', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/26596', + }); + + await page.setContent( + ` + + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const monthYearButton = datetime.locator('.calendar-month-year-toggle'); + + const backgroundColor = await monthYearButton.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + expect(backgroundColor).toBe('rgb(173, 216, 230)'); + }); + + test('should be able to customize navigation button parts', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30830', + }); + + await page.setContent( + ` + + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const prevButton = datetime.locator('.calendar-next-prev ion-button').first(); + const nextButton = datetime.locator('.calendar-next-prev ion-button').last(); + + const prevBackgroundColor = await prevButton.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + const prevColor = await prevButton.evaluate((el) => { + return window.getComputedStyle(el).color; + }); + + const nextBackgroundColor = await nextButton.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + const nextColor = await nextButton.evaluate((el) => { + return window.getComputedStyle(el).color; + }); + + // Verify the navigation-button part applies the styles + expect(prevBackgroundColor).toBe('rgb(178, 34, 34)'); + expect(nextBackgroundColor).toBe('rgb(178, 34, 34)'); + // Verify the previous-button part applies the styles + expect(prevColor).toBe('rgb(0, 0, 255)'); + // Verify the next-button part applies the styles + expect(nextColor).toBe('rgb(0, 128, 0)'); + }); + + test('should be able to customize days of the week part', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30830', + }); + + await page.setContent( + ` + + + `, + config + ); + + const datetime = page.locator('ion-datetime'); + const daysOfWeek = datetime.locator('.calendar-days-of-week'); + + const backgroundColor = await daysOfWeek.evaluate((el) => { + return window.getComputedStyle(el).backgroundColor; + }); + + expect(backgroundColor).toBe('rgb(0, 128, 0)'); + }); + }); }); /** diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Chrome-linux.png index b1e6ab0a387..26de6765222 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Firefox-linux.png index a0a669e8a0d..dca70dfaa8f 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Safari-linux.png index ea20012f266..a495362b363 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-calendar-days-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Chrome-linux.png index 693362fd43c..a8a9a921814 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Firefox-linux.png index da87fa2f0dd..c3c46b4ec6c 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Safari-linux.png index 2b0eba53ffb..d05caa3c7d1 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-md-ltr-Mobile-Firefox-linux.png index b5883c2dc74..85ebffb1433 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-calendar-day-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Chrome-linux.png index 1b15a66a57a..7ab80e99b61 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Firefox-linux.png index c663807d933..eb69fae6967 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Safari-linux.png index 2db5b9382d6..f165423c2c8 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-md-ltr-Mobile-Firefox-linux.png index d0a468a1e9e..777dc1526bb 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-focus-selected-calendar-day-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-time-button-active-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-time-button-active-ios-ltr-Mobile-Safari-linux.png index 7c7e296086a..f4bc6529a2b 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-time-button-active-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-time-button-active-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-time-picker-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-time-picker-ios-ltr-Mobile-Safari-linux.png index c779ad81e99..3516195baf6 100644 Binary files a/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-time-picker-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/custom/datetime.e2e.ts-snapshots/datetime-custom-time-picker-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/custom/index.html b/core/src/components/datetime/test/custom/index.html index 09895d9c591..0266e149616 100644 --- a/core/src/components/datetime/test/custom/index.html +++ b/core/src/components/datetime/test/custom/index.html @@ -74,6 +74,13 @@ color: rgb(128, 30, 171); } + /* Targets the month/year picker and the wheel style datetime */ + .custom-grid-wheel::part(wheel):focus, + /* Targets the time picker */ + ion-picker-column:focus { + background-color: rgba(138, 238, 69, 0.37); + } + /* * Custom Datetime Day Parts * ------------------------------------------- @@ -127,6 +134,46 @@ background-color: rgb(154 209 98 / 0.2); color: #9ad162; } + + /* + * Custom Datetime Header Parts + * ------------------------------------------- + */ + + #custom-grid::part(calendar-header), + #custom-title::part(datetime-header) { + background-color: orange; + } + + #custom-grid::part(month-year-button) { + background-color: lightblue; + color: rgb(128, 30, 171); + } + + #custom-grid::part(navigation-button) { + background-color: firebrick; + } + + #custom-grid::part(previous-button) { + color: white; + } + + #custom-grid::part(next-button) { + color: black; + } + + #custom-grid::part(calendar-days-of-week) { + background-color: #9ad162; + color: white; + } + + #custom-title::part(datetime-title) { + background-color: pink; + } + + #custom-title::part(datetime-selected-date) { + background-color: violet; + } @@ -156,6 +203,11 @@

Wheel Style

Grid Style

+
+ + Select Date + +
diff --git a/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Chrome-linux.png index ab04f17c5a4..7c6fff731b3 100644 Binary files a/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Firefox-linux.png index 760a43893f0..678ef2710e9 100644 Binary files a/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Safari-linux.png index 3334ccd407d..cdac617a5d5 100644 Binary files a/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/disabled/datetime.e2e.ts-snapshots/datetime-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ionic-md-ltr-light-Mobile-Chrome-linux.png index d06b0e19602..fdeea6ffd8c 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Chrome-linux.png index 1c67770de6e..1d4c36ec83c 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Firefox-linux.png index d144284e5da..82f3e79ef5b 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Safari-linux.png index 43962440779..007758afa97 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ionic-md-ltr-light-Mobile-Chrome-linux.png index 7f11a7e52c8..925d0caa096 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Chrome-linux.png index 6371fecccdc..759adee777f 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Firefox-linux.png index 5b886a794b7..190caa00f04 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Safari-linux.png index 790d149e4c8..81810e34551 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-date-time-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ionic-md-ltr-light-Mobile-Chrome-linux.png index eece8be526d..6e1ebff1f70 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Chrome-linux.png index a38eb2edef6..29b81e864c6 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Firefox-linux.png index 7baeb7d5a73..4a296b077bd 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Safari-linux.png index 38983245cea..9d3a0638c1e 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-cover-time-date-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ionic-md-ltr-light-Mobile-Chrome-linux.png index 3b5af726698..404a3fdd179 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Chrome-linux.png index 04bda71a4ca..fc9f9fdb09a 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Firefox-linux.png index a99bca2ed94..63ca406a82d 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Safari-linux.png index 05447926b57..5cdb3c291fd 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ionic-md-ltr-light-Mobile-Chrome-linux.png index 0387680c4a4..a6ddf147912 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Chrome-linux.png index 5683bcf38c4..2fca3b624cd 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Firefox-linux.png index 652ab035b7d..e768f73dbb6 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Safari-linux.png index e50d885e445..eab1c63d0d1 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-date-time-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ionic-md-ltr-light-Mobile-Chrome-linux.png index ac6a121d1a8..dbe41ad0f71 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Chrome-linux.png index 5e4a2ee7219..1eee2850af3 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Firefox-linux.png index 228a75eee1e..71024eacfa8 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Safari-linux.png index c7f1c7006f6..95a3e6977a4 100644 Binary files a/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/display/datetime.e2e.ts-snapshots/datetime-display-time-date-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Chrome-linux.png index cb3f7e55c76..7a019537e28 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Firefox-linux.png index 37957b7becc..fe23689cbe4 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Safari-linux.png index b00b348b831..c099f1ec9f2 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Chrome-linux.png index 230bbecfb45..d0757e1578b 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Firefox-linux.png index 92a83b85b1b..4f185349885 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Safari-linux.png index 98e5c289ad2..d93cff8faa6 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-ltr-Mobile-Chrome-linux.png index 61b8477311c..e00022d8dc2 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-ltr-Mobile-Firefox-linux.png index a389cd62f2e..c90659bcda1 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-rtl-Mobile-Chrome-linux.png index 48a0fcec1b3..a84cd778cb6 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-rtl-Mobile-Firefox-linux.png index 510b62027c9..1ff656b4917 100644 Binary files a/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/first-day-of-week/datetime.e2e.ts-snapshots/datetime-day-of-week-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Chrome-linux.png index 4cc405bd53f..31a74fe973c 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Firefox-linux.png index cc1c5ca41cb..15b9d765a04 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Safari-linux.png index fa99291a336..aaeea72a7e2 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-array-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Chrome-linux.png index 6d767977315..89bdc37e20e 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Firefox-linux.png index 8628e1cd64d..1455a169446 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Safari-linux.png index 6e480314469..3b08bfce4d0 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-callback-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Chrome-linux.png index 74b305490f9..c2089238eb4 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Firefox-linux.png index 1e689d045ae..15da68faaea 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Safari-linux.png index 4b645645d20..c727986ef83 100644 Binary files a/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/highlighted-dates/datetime.e2e.ts-snapshots/datetime-highlightedDates-single-color-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Chrome-linux.png index 17bcaf03468..d1a4b6d5794 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Firefox-linux.png index ff94bd1eecf..2e90e5b54cf 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Safari-linux.png index 2f70f1f5c43..0b9ce287594 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Chrome-linux.png index 718a7974a3a..3fd798a81a6 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Chrome-linux.png index b9f35631e4e..bdc55b3ef69 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Chrome-linux.png index 354074ce47f..aa271975a52 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-month-year-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Chrome-linux.png index 5dcade52180..a39799fc95d 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Chrome-linux.png index 6081313f604..ca3a9d53f4e 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-en-US-time-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Chrome-linux.png index f83d75e7978..f1f532ecc86 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Firefox-linux.png index bc70789048d..7464d99be2e 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Safari-linux.png index 385d2c67951..a6555d200f0 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Chrome-linux.png index 35f035bcded..0385732624b 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Chrome-linux.png index b0e4bba755b..552e10959b7 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Chrome-linux.png index ba57d08494b..d46aee41cd4 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-month-year-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Chrome-linux.png index 71c9e27339f..b87eb80c6cd 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Chrome-linux.png index defbc8af5d7..42c2a1968fd 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-es-ES-time-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Chrome-linux.png index 6d07fa93f2d..d5cc615d9f6 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Firefox-linux.png index af6cad4a418..6983760521b 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Safari-linux.png index 2770cb15bb3..130ef9c9293 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Chrome-linux.png index 89af655f419..ced474124bb 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Chrome-linux.png index 0057425d7c6..b9029cac810 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Chrome-linux.png index 5714d63c701..7ff4caeab5a 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-month-year-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Chrome-linux.png index 71c9e27339f..b87eb80c6cd 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Chrome-linux.png index defbc8af5d7..42c2a1968fd 100644 Binary files a/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/locale/datetime.e2e.ts-snapshots/datetime-locale-ja-JP-time-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Chrome-linux.png index 5b5af5eb521..19c0c0d6f77 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Firefox-linux.png index f90c54390c7..94c62e7ac44 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Safari-linux.png index 67bb944a2f9..d35621c98d4 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Chrome-linux.png index f0700d64fa7..867421c74d8 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Firefox-linux.png index 41add5cc9e1..05e86411978 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Safari-linux.png index e46fa3a3daa..4c5dbeb33ab 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-multipleDefaultValues-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Chrome-linux.png index 2a025403827..796dc8ce9af 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Firefox-linux.png index 578b9af39b6..c715a9d0bc3 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Safari-linux.png index 0beaa85b3d3..5ef5163ebd8 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Chrome-linux.png index 1d1a062ed3d..1feb16194a5 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Firefox-linux.png index cbd385bbf36..c1c67ad78dc 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Safari-linux.png index 9568c38ffb9..1deb34e0f36 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-singleDefaultValue-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Chrome-linux.png index fef210e677c..6dabf2f56c8 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Firefox-linux.png index 3db6bd30540..844939936af 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Safari-linux.png index d6d0d945d0e..d86b091c42a 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Chrome-linux.png index 64c6c8c0407..25eb73a3174 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Firefox-linux.png index 5192e619dc8..cc0ad932585 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Safari-linux.png index f96487b49ab..8edc989786c 100644 Binary files a/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/multiple/datetime.e2e.ts-snapshots/datetime-multiple-withHeader-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Chrome-linux.png index 5f26e1d763b..c1bfc8a0ac6 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Firefox-linux.png index 4fdf457d5ee..d8227f6ce86 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Safari-linux.png index b63a780aaba..0e063adcfc0 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Chrome-linux.png index 905bb2a8359..c9c25757f44 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Firefox-linux.png index 9b774c1bacb..975dcd883b3 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Safari-linux.png index 8ec52491b1f..928dc1e5807 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-base-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Chrome-linux.png index c65115da4c9..cff6ab08b20 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Firefox-linux.png index 874a208465e..5be30c8a964 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Safari-linux.png index 2b3797ad362..cf7fafa36da 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Chrome-linux.png index 99823a97be0..841af622f4b 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Firefox-linux.png index d9d62a91434..aabc8990888 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Safari-linux.png index f80d5660e77..55d85be6d70 100644 Binary files a/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/position/datetime.e2e.ts-snapshots/datetime-position-popover-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Chrome-linux.png index 7cfa461ac63..ea32e26aaa1 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Firefox-linux.png index 560ca42aa4f..ad6f76c951d 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Safari-linux.png index 79cc5eebb44..696079c64e5 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Chrome-linux.png index e8b55be8120..03e89be54a0 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Firefox-linux.png index 992acde7d7c..13de50068fb 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Safari-linux.png index f2c03460187..876d433432a 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Chrome-linux.png index 6279df12003..68dbbf8780c 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Firefox-linux.png index b51c06b48c7..ffdd1182770 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Safari-linux.png index cdd2c7e6505..d521c5e2e8f 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Chrome-linux.png index 8abdc47a042..0407df1e818 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Firefox-linux.png index bcb391a8e92..acdd19f206b 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Safari-linux.png index 2f541195673..28ae7e30ea9 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-date-time-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Chrome-linux.png index 26b0353de03..a1d02aa2cd2 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Firefox-linux.png index ad5c5a02dc7..c1487741209 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Safari-linux.png index 1a282451443..31a76849712 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Chrome-linux.png index 735713c51a0..6786f570342 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Firefox-linux.png index 8276d3f7e35..8bec0ade32f 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Safari-linux.png index 0e0532ffac9..1d79bc4992b 100644 Binary files a/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/datetime/test/presentation/datetime.e2e.ts-snapshots/datetime-presentation-time-date-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Chrome-linux.png index 99b9bdf1ede..5a25aa39131 100644 Binary files a/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Firefox-linux.png index b9b5e2a7010..78a8cc8a325 100644 Binary files a/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Safari-linux.png index 6cc24531723..daab39946fe 100644 Binary files a/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/readonly/datetime.e2e.ts-snapshots/datetime-readonly-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts index b7a3e0528f3..73b9985b13e 100644 --- a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts +++ b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts @@ -55,7 +55,11 @@ configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr'] }).forEach(({ ti const datetime = page.locator('#display'); await expect(datetime).toHaveScreenshot(screenshot(`datetime-show-adjacent-days-display`)); }); + }); +}); +configs({ directions: ['ltr', 'rtl'] }).forEach(({ title, config }) => { + test.describe(title('datetime: show adjacent days'), () => { test('should return the same date format on current month days and on adjacent days', async ({ page }) => { await page.setContent( ` @@ -125,5 +129,51 @@ configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr'] }).forEach(({ ti value: '2022-11-22T16:22:00', }); }); + + test('should navigate to previous month via swipe and then select adjacent day from prior month', async ({ + page, + }) => { + await page.setContent( + ` + + `, + config + ); + + // Wait for the datetime to be ready. + await page.locator('.datetime-ready').waitFor(); + const ionChange = await page.spyOnEvent('ionChange'); + const calendarMonthYear = page.locator('ion-datetime .calendar-month-year'); + const calendarBody = page.locator('ion-datetime .calendar-body'); + + // Wait for the month to be visible. + await expect(calendarMonthYear).toHaveText('February 2026'); + + // Scroll to the previous month. + await calendarBody.evaluate((el: HTMLElement) => { + const rtl = document.documentElement.dir === 'rtl'; + el.scrollLeft += rtl ? el.clientWidth * 2 : -el.clientWidth * 2; + }); + + // Wait for the month to change. + await page.waitForChanges(); + await expect(calendarMonthYear).toHaveText('January 2026'); + + // Select the adjacent day from the prior month. + const dec31Adjacent = page.locator( + '.calendar-day-adjacent-day[data-month="12"][data-year="2025"][data-day="31"]' + ); + await dec31Adjacent.click(); + + // Wait for the month to change. + await page.waitForChanges(); + + // Wait for the month to be visible. + await expect(calendarMonthYear).toHaveText('December 2025'); + await ionChange.next(); + await expect(ionChange).toHaveReceivedEventDetail({ + value: '2025-12-31T16:22:00', + }); + }); }); }); diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Chrome-linux.png index 1f4d7a23706..188db8e6fa1 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Firefox-linux.png index b901962f864..8c0d2c5b2c9 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Safari-linux.png index 44736664e1d..1307eb210bb 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-custom-calendar-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png index 8541a491d08..372356e2f19 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Chrome-linux.png index 046db126345..d47e1238565 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Firefox-linux.png index 86f65d91799..615e04b903d 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Safari-linux.png index c7c7a08d841..e00cb6a1b7a 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-date-range-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ionic-md-ltr-light-Mobile-Chrome-linux.png index fa901211fe8..4c78ad0a28f 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Chrome-linux.png index 0c1ccf53afd..a2cb169c476 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Firefox-linux.png index d67765af393..d64174d157b 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Safari-linux.png index bb7ae7a5a39..17bf60b7c28 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-md-ltr-Mobile-Firefox-linux.png index 9de6935f65a..d77203c2d39 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-display-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ionic-md-ltr-light-Mobile-Chrome-linux.png index 6b053d1c72a..c4cc45a299f 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Chrome-linux.png index d068b5bf9b8..c1f97d5edf5 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Firefox-linux.png index 94b2614cdf2..76075382d1e 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Safari-linux.png index df3c27c3c93..4476f9ced91 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-md-ltr-Mobile-Firefox-linux.png index 971a4be2a60..f6b38e60a97 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png index 47f357edd3e..4cffb48e10e 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Chrome-linux.png index f187be03f48..139d936ff35 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Firefox-linux.png index f3086f96207..079b8d5dd8d 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Safari-linux.png index 8b1f3522c12..69ee05da8fa 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-md-ltr-Mobile-Firefox-linux.png index 92ae494ccda..8e6d4039bc9 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-month-disabled-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png index 1a2ba2142d5..5dd84c0252e 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Chrome-linux.png index db61f894d1e..a6cb70ba2d3 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Firefox-linux.png index 89759eefd67..3f869ec9d3a 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Safari-linux.png index 6c739f0d9fd..233d026bd7e 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-md-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-md-ltr-Mobile-Firefox-linux.png index 23aff3d1d1d..6aff06f2b49 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-md-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-specific-date-disabled-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png index 2c9b1f872a6..6d30907dabf 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Chrome-linux.png index c7198f834b7..2fd579efb64 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Firefox-linux.png index 2e38980b08e..a7ef1a2a0ea 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Safari-linux.png index 4d1679eb43d..8a3d8803d0c 100644 Binary files a/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/datetime/test/show-adjacent-days/datetime.e2e.ts-snapshots/datetime-show-adjacent-days-weekends-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/fab-list/fab-list.tsx b/core/src/components/fab-list/fab-list.tsx index ff5346dadcd..0504c3eab7f 100644 --- a/core/src/components/fab-list/fab-list.tsx +++ b/core/src/components/fab-list/fab-list.tsx @@ -14,6 +14,7 @@ import { getIonTheme } from '../../global/ionic-global'; }) export class FabList implements ComponentInterface { @Element() el!: HTMLIonFabElement; + private activateTimeouts: ReturnType[] = []; /** * If `true`, the fab list will show all fab buttons in the list. @@ -22,12 +23,15 @@ export class FabList implements ComponentInterface { @Watch('activated') protected activatedChanged(activated: boolean) { + this.activateTimeouts.forEach(clearTimeout); + this.activateTimeouts = []; + const fabs = Array.from(this.el.querySelectorAll('ion-fab-button')); // if showing the fabs add a timeout, else show immediately const timeout = activated ? 30 : 0; fabs.forEach((fab, i) => { - setTimeout(() => (fab.show = activated), i * timeout); + this.activateTimeouts.push(setTimeout(() => (fab.show = activated), i * timeout)); }); } @@ -36,6 +40,11 @@ export class FabList implements ComponentInterface { */ @Prop() side: 'start' | 'end' | 'top' | 'bottom' = 'bottom'; + disconnectedCallback() { + this.activateTimeouts.forEach(clearTimeout); + this.activateTimeouts = []; + } + render() { const theme = getIonTheme(this); return ( diff --git a/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-ios-ltr-Mobile-Chrome-linux.png index a5bbe5f4601..542174f6bc7 100644 Binary files a/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-ios-rtl-Mobile-Chrome-linux.png index 2d2bf4fa000..429d5df8358 100644 Binary files a/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-md-ltr-Mobile-Chrome-linux.png b/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-md-ltr-Mobile-Chrome-linux.png index 0b78ed2431e..7a34f6fd1df 100644 Binary files a/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-md-rtl-Mobile-Chrome-linux.png b/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-md-rtl-Mobile-Chrome-linux.png index ab1f37b0518..47d8129365f 100644 Binary files a/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/fab/test/basic/fab.e2e.ts-snapshots/fab-basic-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/fab/test/states/fab.e2e.ts-snapshots/fab-states-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/fab/test/states/fab.e2e.ts-snapshots/fab-states-ios-ltr-Mobile-Chrome-linux.png index 94ba10a87c7..000cf668d4f 100644 Binary files a/core/src/components/fab/test/states/fab.e2e.ts-snapshots/fab-states-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/fab/test/states/fab.e2e.ts-snapshots/fab-states-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/fab/test/states/fab.e2e.ts-snapshots/fab-states-md-ltr-Mobile-Chrome-linux.png b/core/src/components/fab/test/states/fab.e2e.ts-snapshots/fab-states-md-ltr-Mobile-Chrome-linux.png index 7b669c4514c..997c00c81bd 100644 Binary files a/core/src/components/fab/test/states/fab.e2e.ts-snapshots/fab-states-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/fab/test/states/fab.e2e.ts-snapshots/fab-states-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/footer/footer.tsx b/core/src/components/footer/footer.tsx index 0fb75cac82f..2039bf1c95d 100644 --- a/core/src/components/footer/footer.tsx +++ b/core/src/components/footer/footer.tsx @@ -25,6 +25,7 @@ export class Footer implements ComponentInterface { private scrollEl?: HTMLElement; private contentScrollCallback?: () => void; private keyboardCtrl: KeyboardController | null = null; + private keyboardCtrlPromise: Promise | null = null; @State() private keyboardVisible = false; @@ -55,7 +56,7 @@ export class Footer implements ComponentInterface { } async connectedCallback() { - this.keyboardCtrl = await createKeyboardController(async (keyboardOpen, waitForResize) => { + const promise = createKeyboardController(async (keyboardOpen, waitForResize) => { /** * If the keyboard is hiding, then we need to wait * for the webview to resize. Otherwise, the footer @@ -67,11 +68,32 @@ export class Footer implements ComponentInterface { this.keyboardVisible = keyboardOpen; // trigger re-render by updating state }); + this.keyboardCtrlPromise = promise; + + const keyboardCtrl = await promise; + + /** + * Only assign if this is still the current promise. + * Otherwise, a new connectedCallback has started or + * disconnectedCallback was called, so destroy this instance. + */ + if (this.keyboardCtrlPromise === promise) { + this.keyboardCtrl = keyboardCtrl; + this.keyboardCtrlPromise = null; + } else { + keyboardCtrl.destroy(); + } } disconnectedCallback() { + if (this.keyboardCtrlPromise) { + this.keyboardCtrlPromise.then((ctrl) => ctrl.destroy()); + this.keyboardCtrlPromise = null; + } + if (this.keyboardCtrl) { this.keyboardCtrl.destroy(); + this.keyboardCtrl = null; } } diff --git a/core/src/components/grid/test/offsets-pull-push/grid.e2e.ts-snapshots/grid-offsets-md-ltr-Mobile-Chrome-linux.png b/core/src/components/grid/test/offsets-pull-push/grid.e2e.ts-snapshots/grid-offsets-md-ltr-Mobile-Chrome-linux.png index 740477f379a..1f12db2d4f7 100644 Binary files a/core/src/components/grid/test/offsets-pull-push/grid.e2e.ts-snapshots/grid-offsets-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/grid/test/offsets-pull-push/grid.e2e.ts-snapshots/grid-offsets-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/grid/test/offsets-pull-push/grid.e2e.ts-snapshots/grid-offsets-md-rtl-Mobile-Chrome-linux.png b/core/src/components/grid/test/offsets-pull-push/grid.e2e.ts-snapshots/grid-offsets-md-rtl-Mobile-Chrome-linux.png index 4cad1160b1b..140e0624bd6 100644 Binary files a/core/src/components/grid/test/offsets-pull-push/grid.e2e.ts-snapshots/grid-offsets-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/grid/test/offsets-pull-push/grid.e2e.ts-snapshots/grid-offsets-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/grid/test/offsets/grid.e2e.ts-snapshots/grid-offsets-md-ltr-Mobile-Chrome-linux.png b/core/src/components/grid/test/offsets/grid.e2e.ts-snapshots/grid-offsets-md-ltr-Mobile-Chrome-linux.png index 4bb3c0460e7..f7d75c3e428 100644 Binary files a/core/src/components/grid/test/offsets/grid.e2e.ts-snapshots/grid-offsets-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/grid/test/offsets/grid.e2e.ts-snapshots/grid-offsets-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/grid/test/offsets/grid.e2e.ts-snapshots/grid-offsets-md-rtl-Mobile-Chrome-linux.png b/core/src/components/grid/test/offsets/grid.e2e.ts-snapshots/grid-offsets-md-rtl-Mobile-Chrome-linux.png index c91494aca96..e6ef703ec9d 100644 Binary files a/core/src/components/grid/test/offsets/grid.e2e.ts-snapshots/grid-offsets-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/grid/test/offsets/grid.e2e.ts-snapshots/grid-offsets-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/grid/test/padding/grid.e2e.ts-snapshots/grid-padding-md-ltr-Mobile-Chrome-linux.png b/core/src/components/grid/test/padding/grid.e2e.ts-snapshots/grid-padding-md-ltr-Mobile-Chrome-linux.png index 1c7b35266d8..1216197bc36 100644 Binary files a/core/src/components/grid/test/padding/grid.e2e.ts-snapshots/grid-padding-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/grid/test/padding/grid.e2e.ts-snapshots/grid-padding-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/grid/test/padding/grid.e2e.ts-snapshots/grid-padding-md-rtl-Mobile-Chrome-linux.png b/core/src/components/grid/test/padding/grid.e2e.ts-snapshots/grid-padding-md-rtl-Mobile-Chrome-linux.png index 435827a3b59..435ac076ac9 100644 Binary files a/core/src/components/grid/test/padding/grid.e2e.ts-snapshots/grid-padding-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/grid/test/padding/grid.e2e.ts-snapshots/grid-padding-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/grid/test/sizes/grid.e2e.ts-snapshots/grid-sizes-md-ltr-Mobile-Chrome-linux.png b/core/src/components/grid/test/sizes/grid.e2e.ts-snapshots/grid-sizes-md-ltr-Mobile-Chrome-linux.png index 3efc57a76db..09c16d9e1ab 100644 Binary files a/core/src/components/grid/test/sizes/grid.e2e.ts-snapshots/grid-sizes-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/grid/test/sizes/grid.e2e.ts-snapshots/grid-sizes-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/grid/test/sizes/grid.e2e.ts-snapshots/grid-sizes-md-rtl-Mobile-Chrome-linux.png b/core/src/components/grid/test/sizes/grid.e2e.ts-snapshots/grid-sizes-md-rtl-Mobile-Chrome-linux.png index 052d8f15827..f613b3dfdb8 100644 Binary files a/core/src/components/grid/test/sizes/grid.e2e.ts-snapshots/grid-sizes-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/grid/test/sizes/grid.e2e.ts-snapshots/grid-sizes-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/header/header.md.scss b/core/src/components/header/header.md.scss index 49d700192eb..1d670924ff6 100644 --- a/core/src/components/header/header.md.scss +++ b/core/src/components/header/header.md.scss @@ -8,7 +8,7 @@ box-shadow: $header-md-box-shadow; } -.header-collapse-condense { +.header-md.header-collapse-condense { display: none; } diff --git a/core/src/components/header/test/condense-modal/header.e2e.ts b/core/src/components/header/test/condense-modal/header.e2e.ts new file mode 100644 index 00000000000..72cc6a7b706 --- /dev/null +++ b/core/src/components/header/test/condense-modal/header.e2e.ts @@ -0,0 +1,71 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This test verifies that collapsible headers with mode="ios" work correctly + * when both iOS and MD stylesheets are loaded. The bug occurred because + * `.header-collapse-condense { display: none }` in the MD stylesheet was not + * scoped to `.header-md`, causing it to hide iOS condense headers when both + * stylesheets were present. + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('header: condense with iOS mode override'), () => { + test('should show iOS condense header when both MD and iOS styles are loaded', async ({ page }) => { + test.info().annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/29929', + }); + + // Include both an MD header and an iOS modal to force both stylesheets to load + await page.setContent( + ` + + + + MD Header + + + + + + + + Header + + + + + + Large Header + + +

Content

+
+
+ `, + config + ); + + const modal = page.locator('ion-modal'); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await modal.evaluate((el: HTMLIonModalElement) => el.present()); + await ionModalDidPresent.next(); + + const largeTitleHeader = modal.locator('#largeTitleHeader'); + + // The large title header should be visible, not hidden by MD styles + await expect(largeTitleHeader).toBeVisible(); + + // Verify it has the iOS mode class + await expect(largeTitleHeader).toHaveClass(/header-ios/); + + // Verify it does NOT have display: none applied + // This would fail if the MD stylesheet's unscoped .header-collapse-condense rule applies + const display = await largeTitleHeader.evaluate((el) => { + return window.getComputedStyle(el).display; + }); + expect(display).not.toBe('none'); + }); + }); +}); diff --git a/core/src/components/img/img.tsx b/core/src/components/img/img.tsx index 12d4a4e4748..87c9668e58e 100644 --- a/core/src/components/img/img.tsx +++ b/core/src/components/img/img.tsx @@ -19,6 +19,7 @@ import { getIonTheme } from '../../global/ionic-global'; export class Img implements ComponentInterface { private io?: IntersectionObserver; private inheritedAttributes: Attributes = {}; + private loadTimeout: ReturnType | undefined; @Element() el!: HTMLElement; @@ -59,7 +60,17 @@ export class Img implements ComponentInterface { this.addIO(); } + disconnectedCallback() { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + } + } + private addIO() { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + this.loadTimeout = undefined; + } if (this.src === undefined) { return; } @@ -85,7 +96,7 @@ export class Img implements ComponentInterface { this.io.observe(this.el); } else { // fall back to setTimeout for Safari and IE - setTimeout(() => this.load(), 200); + this.loadTimeout = setTimeout(() => this.load(), 200); } } diff --git a/core/src/components/input-otp/input-otp.common.scss b/core/src/components/input-otp/input-otp.common.scss index 6af7c8cee63..74ae4ea2821 100644 --- a/core/src/components/input-otp/input-otp.common.scss +++ b/core/src/components/input-otp/input-otp.common.scss @@ -94,10 +94,13 @@ background: var(--background); color: var(--color); + font-family: inherit; font-size: inherit; text-align: center; appearance: none; + + box-sizing: border-box; } :host(.has-focus) .native-input { diff --git a/core/src/components/input-otp/input-otp.native.scss b/core/src/components/input-otp/input-otp.native.scss index daf2b3ff877..f2fe5b5ec89 100644 --- a/core/src/components/input-otp/input-otp.native.scss +++ b/core/src/components/input-otp/input-otp.native.scss @@ -19,6 +19,8 @@ --highlight-color-valid: #{ion-color(success, base)}; --highlight-color-invalid: #{ion-color(danger, base)}; + font-family: $font-family-base; + font-size: dynamic-font(14px); } diff --git a/core/src/components/input-otp/input-otp.tsx b/core/src/components/input-otp/input-otp.tsx index 3e8684505ab..440f75c6e4c 100644 --- a/core/src/components/input-otp/input-otp.tsx +++ b/core/src/components/input-otp/input-otp.tsx @@ -1,5 +1,6 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Component, Element, Event, Fragment, Host, Prop, State, h, Watch } from '@stencil/core'; +import { AttachInternals, Component, Element, Event, Fragment, Host, Prop, State, h, Watch } from '@stencil/core'; +import { reportValidityToElementInternals } from '@utils/forms'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes } from '@utils/helpers'; import { printIonWarning } from '@utils/logging'; @@ -16,6 +17,16 @@ import type { InputOtpInputEventDetail, } from './input-otp-interface'; +/** + * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component. + * @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the visual appearance of the component. + * + * @part group - The container element that wraps all input boxes. + * @part container - The wrapper element for each individual input box. + * @part native - The native input element. + * @part separator - The separator element displayed between input boxes. + * @part description - The container element for the description text. + */ @Component({ tag: 'ion-input-otp', styleUrls: { @@ -23,7 +34,8 @@ import type { md: 'input-otp.md.scss', ionic: 'input-otp.ionic.scss', }, - scoped: true, + shadow: true, + formAssociated: true, }) export class InputOTP implements ComponentInterface { private inheritedAttributes: Attributes = {}; @@ -47,6 +59,8 @@ export class InputOTP implements ComponentInterface { @Element() el!: HTMLIonInputOtpElement; + @AttachInternals() internals!: ElementInternals; + @State() private inputValues: string[] = []; @State() hasFocus = false; @State() private previousInputValues: string[] = []; @@ -69,6 +83,14 @@ export class InputOTP implements ComponentInterface { */ @Prop({ reflect: true }) disabled = false; + /** + * Update element internals when disabled prop changes + */ + @Watch('disabled') + protected disabledChanged() { + this.updateElementInternals(); + } + /** * The fill for the input boxes. If `"solid"` the input boxes will have a background. If * `"outline"` the input boxes will be transparent with a border. @@ -197,6 +219,7 @@ export class InputOTP implements ComponentInterface { valueChanged() { this.initializeValues(); this.updateTabIndexes(); + this.updateElementInternals(); } /** @@ -272,6 +295,7 @@ export class InputOTP implements ComponentInterface { componentDidLoad() { this.updateTabIndexes(); + this.updateElementInternals(); } /** @@ -356,6 +380,50 @@ export class InputOTP implements ComponentInterface { } } + /** + * Gets the value of the input group as a string for form submission. + * Returns an empty string if the value is null or undefined. + */ + private getValue(): string { + return this.value != null ? this.value.toString() : ''; + } + + /** + * Called when the form state is restored. + * Restores the component's value. + */ + formStateRestoreCallback(value: string) { + this.value = value; + } + + /** + * Called when the form is reset. + * Resets the component's value. + */ + formResetCallback() { + this.value = ''; + } + + /** + * Updates the form value and reports validity state to the browser via + * ElementInternals. This should be called when the component loads, when + * the disabled prop changes, and when the value changes to ensure the form + * value stays in sync and validation state is updated. + */ + private updateElementInternals() { + // Disabled form controls should not be included in form data + // Pass null to setFormValue when disabled to exclude it from form submission + const value = this.disabled ? null : this.getValue(); + // ElementInternals may not be fully available in test environments + // so we need to check if the method exists before calling it + if (typeof this.internals.setFormValue === 'function') { + this.internals.setFormValue(value); + } + // Use the first input element for validity reporting since all inputs + // share the same validation state + reportValidityToElementInternals(this.inputRefs[0] ?? null, this.internals); + } + /** * Emits an `ionChange` event. * This API should be called for user committed changes. @@ -536,10 +604,21 @@ export class InputOTP implements ComponentInterface { * - Tab: Allows normal tab navigation between components */ private onKeyDown = (index: number) => (event: KeyboardEvent) => { - const { length } = this; + const { disabled, length, readonly } = this; const rtl = isRTL(this.el); const input = event.target as HTMLInputElement; + if (disabled) { + return; + } + + if (readonly) { + if (event.key === 'Backspace' || event.key === 'Delete') { + event.preventDefault(); + return; + } + } + // Meta shortcuts are used to copy, paste, and select text // We don't want to handle these keys here const metaShortcuts = ['a', 'c', 'v', 'x', 'r', 'z', 'y']; @@ -604,11 +683,15 @@ export class InputOTP implements ComponentInterface { * 5. Single character replacement */ private onInput = (index: number) => (event: InputEvent) => { - const { length, validKeyPattern } = this; + const { disabled, length, readonly, validKeyPattern } = this; const input = event.target as HTMLInputElement; const value = input.value; const previousValue = this.previousInputValues[index] || ''; + if (disabled || readonly) { + return; + } + // 1. Autofill handling // If the length of the value increases by more than 1 from the previous // value, treat this as autofill. This is to prevent the case where the @@ -736,10 +819,14 @@ export class InputOTP implements ComponentInterface { * the next empty input after pasting. */ private onPaste = (event: ClipboardEvent) => { - const { inputRefs, length, validKeyPattern } = this; + const { disabled, inputRefs, length, readonly, validKeyPattern } = this; event.preventDefault(); + if (disabled || readonly) { + return; + } + const pastedText = event.clipboardData?.getData('text'); // If there is no pasted text, still emit the input change event @@ -817,12 +904,19 @@ export class InputOTP implements ComponentInterface { 'input-otp-readonly': readonly, })} > -
+
{Array.from({ length }).map((_, index) => ( <> -
+
- {this.showSeparator(index) &&
} + {this.showSeparator(index) &&
} ))}
@@ -851,6 +945,7 @@ export class InputOTP implements ComponentInterface { 'input-otp-description': true, 'input-otp-description-hidden': !hasDescription, }} + part="description" >
diff --git a/core/src/components/input-otp/test/basic/input-otp.e2e.ts b/core/src/components/input-otp/test/basic/input-otp.e2e.ts index 2067a000209..306e72f873d 100644 --- a/core/src/components/input-otp/test/basic/input-otp.e2e.ts +++ b/core/src/components/input-otp/test/basic/input-otp.e2e.ts @@ -687,6 +687,31 @@ configs({ modes: ['ios'] }).forEach(({ title, config }) => { const firstInput = page.locator('ion-input-otp input').first(); await expect(firstInput).toBeFocused(); }); + + test('should allow arrow key navigation when readonly is true', async ({ page }) => { + await page.setContent(`Description`, config); + + const inputOtp = page.locator('ion-input-otp'); + const inputBoxes = page.locator('ion-input-otp input'); + await inputBoxes.nth(1).focus(); + + const isRTL = await page.evaluate(() => document.dir === 'rtl'); + if (isRTL) { + await page.keyboard.press('ArrowLeft'); + await expect(inputBoxes.nth(2)).toBeFocused(); + + await page.keyboard.press('ArrowRight'); + await expect(inputBoxes.nth(1)).toBeFocused(); + } else { + await page.keyboard.press('ArrowRight'); + await expect(inputBoxes.nth(2)).toBeFocused(); + + await page.keyboard.press('ArrowLeft'); + await expect(inputBoxes.nth(1)).toBeFocused(); + } + + await verifyInputValues(inputOtp, ['1', '2', '3', '4']); + }); }); test.describe(title('input-otp: backspace functionality'), () => { @@ -737,6 +762,21 @@ configs({ modes: ['ios'] }).forEach(({ title, config }) => { const inputOtp = page.locator('ion-input-otp'); await verifyInputValues(inputOtp, ['1', '3', '', '']); }); + + test('should not modify values with backspace or delete when readonly is true', async ({ page }) => { + await page.setContent(`Description`, config); + + const inputOtp = page.locator('ion-input-otp'); + const ionInput = await page.spyOnEvent('ionInput'); + + await page.keyboard.press('Tab'); + await page.keyboard.press('Backspace'); + await page.keyboard.press('Delete'); + await page.keyboard.type('5'); + + await verifyInputValues(inputOtp, ['1', '2', '3', '4']); + await expect(ionInput).not.toHaveReceivedEvent(); + }); }); test.describe(title('input-otp: paste functionality'), () => { @@ -828,6 +868,28 @@ configs({ modes: ['ios'] }).forEach(({ title, config }) => { await verifyInputValues(inputOtp, ['Ψ£', 'Ψ¨', 'Ψ¬', 'Ψ―', '1', '2']); }); + + test('should not paste text when disabled is true', async ({ page }) => { + await page.setContent(`Description`, config); + + const firstInput = page.locator('ion-input-otp input').first(); + await firstInput.focus(); + await simulatePaste(firstInput, '5678'); + + const inputOtp = page.locator('ion-input-otp'); + await verifyInputValues(inputOtp, ['1', '2', '3', '4']); + }); + + test('should not paste text when readonly is true', async ({ page }) => { + await page.setContent(`Description`, config); + + const firstInput = page.locator('ion-input-otp input').first(); + await firstInput.focus(); + await simulatePaste(firstInput, '5678'); + + const inputOtp = page.locator('ion-input-otp'); + await verifyInputValues(inputOtp, ['1', '2', '3', '4']); + }); }); }); diff --git a/core/src/components/input-otp/test/custom/input-otp.e2e.ts b/core/src/components/input-otp/test/custom/input-otp.e2e.ts new file mode 100644 index 00000000000..fda8263c3b0 --- /dev/null +++ b/core/src/components/input-otp/test/custom/input-otp.e2e.ts @@ -0,0 +1,163 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('input-otp: custom'), () => { + test('should allow styling the group part', async ({ page }) => { + await page.setContent( + ` + + + Description + `, + config + ); + + const inputOtp = await page.locator('ion-input-otp'); + const group = await inputOtp.evaluate((el: HTMLIonInputOtpElement) => { + const groupEl = el.shadowRoot?.querySelector('[part="group"]') as HTMLElement | null; + if (!groupEl) { + return ''; + } + return getComputedStyle(groupEl).backgroundColor; + }); + + expect(group).toBe('rgb(255, 0, 0)'); + }); + + test('should allow styling the container part', async ({ page }) => { + await page.setContent( + ` + + + Description + `, + config + ); + + const inputOtp = await page.locator('ion-input-otp'); + const container = await inputOtp.evaluate((el: HTMLIonInputOtpElement) => { + const containerEl = el.shadowRoot?.querySelector('[part="container"]') as HTMLElement | null; + if (!containerEl) { + return ''; + } + return getComputedStyle(containerEl).backgroundColor; + }); + + expect(container).toBe('rgb(0, 128, 0)'); + }); + + test('should allow styling the native part', async ({ page }) => { + await page.setContent( + ` + + + Description + `, + config + ); + + const inputOtp = await page.locator('ion-input-otp'); + + // Focus the first native input and then find the inactive and + // active native elements to verify the correct styles are applied + const { inactiveNative, activeNative } = await inputOtp.evaluate((el: HTMLIonInputOtpElement) => { + const nativeElements = el.shadowRoot?.querySelectorAll('[part="native"]') as + | NodeListOf + | undefined; + + if (!nativeElements || nativeElements.length === 0) { + return { inactiveNative: '', activeNative: '' }; + } + + // Focus the first native input + const firstNative = nativeElements[0] as HTMLInputElement; + firstNative.focus(); + + // Find the focused element. If the focused element is not + // a native input, use the first native input. + const activeNativeEl = + Array.from(nativeElements).find((nativeEl) => nativeEl === document.activeElement) || firstNative; + + // Find the first non-focused element + const inactiveNativeEl = Array.from(nativeElements).find((nativeEl) => nativeEl !== activeNativeEl); + + return { + inactiveNative: inactiveNativeEl ? getComputedStyle(inactiveNativeEl).backgroundColor : '', + activeNative: getComputedStyle(activeNativeEl).backgroundColor, + }; + }); + + expect(inactiveNative).toBe('rgb(0, 0, 255)'); + expect(activeNative).toBe('rgb(255, 0, 0)'); + }); + + test('should allow styling the separator part', async ({ page }) => { + await page.setContent( + ` + + + Description + `, + config + ); + + const inputOtp = await page.locator('ion-input-otp'); + const separator = await inputOtp.evaluate( + (el: HTMLIonInputOtpElement) => + getComputedStyle(el.shadowRoot?.querySelector('[part="separator"]') as HTMLElement).backgroundColor + ); + + expect(separator).toBe('rgb(0, 128, 0)'); + }); + + test('should allow styling the description part', async ({ page }) => { + await page.setContent( + ` + + + Description + `, + config + ); + + const inputOtp = await page.locator('ion-input-otp'); + const description = await inputOtp.evaluate((el: HTMLIonInputOtpElement) => { + const descriptionEl = el.shadowRoot?.querySelector('[part="description"]') as HTMLElement | null; + if (!descriptionEl) { + return ''; + } + return getComputedStyle(descriptionEl).color; + }); + + expect(description).toBe('rgb(0, 0, 255)'); + }); + }); +}); diff --git a/core/src/components/input-otp/test/form/index.html b/core/src/components/input-otp/test/form/index.html new file mode 100644 index 00000000000..f19f65d81fb --- /dev/null +++ b/core/src/components/input-otp/test/form/index.html @@ -0,0 +1,174 @@ + + + + + Input OTP - Form + + + + + + + + + + + + + + + Input OTP - Form + + + + + + + Input OTP + + +
+ + + +
+ + +
+

 

+

OTP Value:

+

Form Data:

+

+        
+
+ + +
+ + diff --git a/core/src/components/input-otp/test/form/input-otp.e2e.ts b/core/src/components/input-otp/test/form/input-otp.e2e.ts new file mode 100644 index 00000000000..c7e501a2450 --- /dev/null +++ b/core/src/components/input-otp/test/form/input-otp.e2e.ts @@ -0,0 +1,133 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('input-otp: form'), () => { + test('should set formData when submit button is clicked', async ({ page }) => { + await page.setContent( + ` +
+ Description + +
+ + `, + config + ); + + const inputOtp = page.locator('ion-input-otp'); + const submitButton = page.locator('button[type="submit"]'); + const firstInput = page.locator('ion-input-otp input').first(); + + // Type into the first input box - this will fill all 4 boxes + await firstInput.focus(); + await page.keyboard.type('1234'); + + // Wait for any async operations to complete + await page.waitForChanges(); + + // Submit the form and then wait for any async operations to complete + await submitButton.click(); + await page.waitForChanges(); + + // Verify that the form's validation passed + const formValidity = await page.evaluate(() => { + const form = document.querySelector('form'); + return form ? form.checkValidity() : null; + }); + expect(formValidity).toBe(true); + + // Verify that the input-otp's value is set + const inputOtpValue = await inputOtp.evaluate((el: HTMLIonInputOtpElement) => { + return el.value ?? ''; + }); + expect(inputOtpValue).toBe('1234'); + + // Verify that the formData is set + const formData = await page.evaluate(() => { + const form = document.querySelector('form'); + if (!form) { + return null; + } + const formData = new FormData(form); + const entries: Record = {}; + for (const [key, value] of formData.entries()) { + entries[key] = value.toString(); + } + return entries; + }); + expect(formData).toBeDefined(); + expect(formData?.['otp']).toBe('1234'); + }); + + test('should reset formData when reset button is clicked', async ({ page }) => { + await page.setContent( + ` +
+ + + +
+ + `, + config + ); + + const inputOtp = page.locator('ion-input-otp'); + const submitButton = page.locator('button[type="submit"]'); + const resetButton = page.locator('button[type="reset"]'); + const firstInput = page.locator('ion-input-otp input').first(); + + // Type into the first input box - this will fill all 4 boxes + await firstInput.focus(); + await page.keyboard.type('1234'); + + // Wait for any async operations to complete + await page.waitForChanges(); + + // Submit the form and then wait for any async operations to complete + await submitButton.click(); + await page.waitForChanges(); + + // Reset the form and then wait for any async operations to complete + await resetButton.click(); + await page.waitForChanges(); + + // Verify that the input-otp's value is cleared + const inputOtpValue = await inputOtp.evaluate((el: HTMLIonInputOtpElement) => { + return el.value ?? ''; + }); + expect(inputOtpValue).toBe(''); + + // Verify that the formData is cleared + const formData = await page.evaluate(() => { + const form = document.querySelector('form'); + if (!form) { + return null; + } + const formData = new FormData(form); + const entries: Record = {}; + for (const [key, value] of formData.entries()) { + entries[key] = value.toString(); + } + return entries; + }); + expect(formData?.['otp']).toBe(''); + }); + }); +}); diff --git a/core/src/components/input-password-toggle/input-password-toggle.tsx b/core/src/components/input-password-toggle/input-password-toggle.tsx index 5badee21602..2cf3678d2a4 100644 --- a/core/src/components/input-password-toggle/input-password-toggle.tsx +++ b/core/src/components/input-password-toggle/input-password-toggle.tsx @@ -169,9 +169,8 @@ export class InputPasswordToggle implements ComponentInterface { color={color} fill="clear" shape="round" - aria-checked={isPasswordVisible ? 'true' : 'false'} aria-label={isPasswordVisible ? 'Hide password' : 'Show password'} - role="switch" + aria-pressed={isPasswordVisible ? 'true' : 'false'} type="button" onPointerDown={(ev) => { /** diff --git a/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts b/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts index 0493509dc24..da9846ef9d8 100644 --- a/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts +++ b/core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts @@ -22,7 +22,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { }); test.describe(title('input password toggle: aria attributes'), () => { - test('should inherit aria attributes to inner button on load', async ({ page }) => { + test('should have correct aria attributes on load', async ({ page }) => { await page.setContent( ` @@ -35,9 +35,9 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { const nativeButton = page.locator('ion-input-password-toggle button'); await expect(nativeButton).toHaveAttribute('aria-label', 'Show password'); - await expect(nativeButton).toHaveAttribute('aria-checked', 'false'); + await expect(nativeButton).toHaveAttribute('aria-pressed', 'false'); }); - test('should inherit aria attributes to inner button after toggle', async ({ page }) => { + test('should update aria attributes after toggle', async ({ page }) => { await page.setContent( ` @@ -51,7 +51,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => { await nativeButton.click(); await expect(nativeButton).toHaveAttribute('aria-label', 'Hide password'); - await expect(nativeButton).toHaveAttribute('aria-checked', 'true'); + await expect(nativeButton).toHaveAttribute('aria-pressed', 'true'); }); }); }); diff --git a/core/src/components/input/input.common.scss b/core/src/components/input/input.common.scss index 6cb8e8605d9..1cc609846a5 100644 --- a/core/src/components/input/input.common.scss +++ b/core/src/components/input/input.common.scss @@ -139,6 +139,12 @@ // The input cover is not clickable when the input is disabled .cloned-input { position: absolute; + top: 0; + bottom: 0; + + // Reset height since absolute positioning with top/bottom handles sizing + height: auto; + max-height: none; pointer-events: none; } diff --git a/core/src/components/input/input.ionic.scss b/core/src/components/input/input.ionic.scss index 626bbaf935b..83355427f3c 100644 --- a/core/src/components/input/input.ionic.scss +++ b/core/src/components/input/input.ionic.scss @@ -266,15 +266,15 @@ @include globals.typography(globals.$ion-body-sm-medium); } -.input-bottom .helper-text, -.input-bottom .counter { - color: globals.$ion-primitives-neutral-800; -} - .input-bottom .error-text { color: globals.$ion-text-danger; } +.input-bottom .helper-text, +.input-bottom .counter { + color: globals.$ion-text-subtlest; +} + :host(.has-focus.ion-valid) .helper-text { color: var(--highlight-color-valid); } diff --git a/core/src/components/input/input.tsx b/core/src/components/input/input.tsx index 94829b29bdd..0075f5691c7 100644 --- a/core/src/components/input/input.tsx +++ b/core/src/components/input/input.tsx @@ -53,6 +53,7 @@ export class Input implements ComponentInterface { private inputId = `ion-input-${inputIds++}`; private helperTextId = `${this.inputId}-helper-text`; private errorTextId = `${this.inputId}-error-text`; + private labelTextId = `${this.inputId}-label`; private inheritedAttributes: Attributes = {}; private isComposing = false; private slotMutationController?: SlotMutationController; @@ -428,7 +429,12 @@ export class Input implements ComponentInterface { connectedCallback() { const { el } = this; - this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => forceUpdate(this)); + this.slotMutationController = createSlotMutationController(el, ['label', 'start', 'end'], () => { + this.setSlottedLabelId(); + forceUpdate(this); + }); + + this.setSlottedLabelId(); this.notchController = createNotchController( el, () => this.notchSpacerEl, @@ -803,7 +809,7 @@ export class Input implements ComponentInterface { } private renderLabel() { - const { label } = this; + const { label, labelTextId } = this; return (
- {label === undefined ? :
{label}
} + {label === undefined ? ( + + ) : ( +
+ {label} +
+ )}
); } @@ -825,6 +840,33 @@ export class Input implements ComponentInterface { return this.el.querySelector('[slot="label"]'); } + /** + * Ensures the slotted label element has an ID for aria-labelledby. + * If no ID exists, we assign one using our generated labelTextId. + */ + private setSlottedLabelId() { + const slottedLabel = this.labelSlot; + if (slottedLabel && !slottedLabel.id) { + slottedLabel.id = this.labelTextId; + } + } + + /** + * Returns the ID to use for aria-labelledby on the native input, + * or undefined if aria-label is explicitly set (to avoid conflicts). + */ + private getLabelledById(): string | undefined { + if (this.inheritedAttributes['aria-label']) { + return undefined; + } + + if (this.label !== undefined) { + return this.labelTextId; + } + + return this.labelSlot?.id || undefined; + } + /** * Returns `true` if label content is provided * either by a prop or a content. If you want @@ -1026,6 +1068,7 @@ export class Input implements ComponentInterface { onCompositionend={this.onCompositionEnd} aria-describedby={this.getHintTextID()} aria-invalid={this.isInvalid ? 'true' : undefined} + aria-labelledby={this.getLabelledById()} {...this.inheritedAttributes} /> {clearInput && !readonly && !disabled && ( diff --git a/core/src/components/input/test/a11y/input.e2e.ts b/core/src/components/input/test/a11y/input.e2e.ts index 6a40385c925..21ce46c52b2 100644 --- a/core/src/components/input/test/a11y/input.e2e.ts +++ b/core/src/components/input/test/a11y/input.e2e.ts @@ -57,6 +57,104 @@ configs({ directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ title, }); }); +configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => { + test.describe(title('input: label a11y for Android TalkBack'), () => { + /** + * Android TalkBack treats visible text elements as separate focusable items. + * These tests verify that the label is hidden from a11y tree (aria-hidden) + * while remaining associated with the input via aria-labelledby. + */ + test('label text wrapper should be hidden from accessibility tree when using label prop', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + const labelTextWrapper = page.locator('ion-input .label-text-wrapper'); + await expect(labelTextWrapper).toHaveAttribute('aria-hidden', 'true'); + }); + + test('label text wrapper should be hidden from accessibility tree when using label slot', async ({ page }) => { + await page.setContent( + ` + +
Email
+
+ `, + config + ); + + const labelTextWrapper = page.locator('ion-input .label-text-wrapper'); + await expect(labelTextWrapper).toHaveAttribute('aria-hidden', 'true'); + }); + + test('native input should have aria-labelledby pointing to label text when using label prop', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + const nativeInput = page.locator('ion-input input'); + const labelText = page.locator('ion-input .label-text'); + + const labelTextId = await labelText.getAttribute('id'); + expect(labelTextId).not.toBeNull(); + await expect(nativeInput).toHaveAttribute('aria-labelledby', labelTextId!); + }); + + test('native input should have aria-labelledby pointing to slotted label when using label slot', async ({ + page, + }) => { + await page.setContent( + ` + +
Email
+
+ `, + config + ); + + const nativeInput = page.locator('ion-input input'); + const slottedLabel = page.locator('ion-input [slot="label"]'); + + const slottedLabelId = await slottedLabel.getAttribute('id'); + expect(slottedLabelId).not.toBeNull(); + await expect(nativeInput).toHaveAttribute('aria-labelledby', slottedLabelId!); + }); + + test('should not add aria-labelledby when aria-label is provided on host', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + const nativeInput = page.locator('ion-input input'); + + await expect(nativeInput).toHaveAttribute('aria-label', 'Custom Label'); + await expect(nativeInput).not.toHaveAttribute('aria-labelledby'); + }); + + test('should not add aria-hidden to label wrapper when no label is present', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + const labelTextWrapper = page.locator('ion-input .label-text-wrapper'); + + await expect(labelTextWrapper).not.toHaveAttribute('aria-hidden', 'true'); + }); + }); +}); + configs({ directions: ['ltr'] }).forEach(({ title, config, screenshot }) => { test.describe(title('input: font scaling'), () => { test('should scale text on larger font sizes', async ({ page }) => { diff --git a/core/src/components/item-divider/item-divider.tsx b/core/src/components/item-divider/item-divider.tsx index be520226478..c2d3d32f150 100644 --- a/core/src/components/item-divider/item-divider.tsx +++ b/core/src/components/item-divider/item-divider.tsx @@ -10,6 +10,9 @@ import type { Color } from '../../interface'; * @slot - Content is placed between the named slots if provided without a slot. * @slot start - Content is placed to the left of the divider text in LTR, and to the right in RTL. * @slot end - Content is placed to the right of the divider text in LTR, and to the left in RTL. + * + * @part inner - The inner wrapper element that arranges the divider content. + * @part container - The wrapper element that contains the default slot. */ @Component({ tag: 'ion-item-divider', @@ -46,8 +49,8 @@ export class ItemDivider implements ComponentInterface { })} > -
-
+
+
diff --git a/core/src/components/item-divider/test/custom/item-divider.e2e.ts b/core/src/components/item-divider/test/custom/item-divider.e2e.ts new file mode 100644 index 00000000000..497d607a2b8 --- /dev/null +++ b/core/src/components/item-divider/test/custom/item-divider.e2e.ts @@ -0,0 +1,57 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => { + test.describe(title('item-divider: custom'), () => { + test.describe('CSS shadow parts', () => { + test('should be able to customize inner part', async ({ page }) => { + await page.setContent( + ` + + + Divider + `, + config + ); + + const divider = page.locator('ion-item-divider'); + const backgroundColor = await divider.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const inner = shadowRoot?.querySelector('.item-divider-inner'); + return inner ? window.getComputedStyle(inner).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(255, 0, 0)'); + }); + + test('should be able to customize container part', async ({ page }) => { + await page.setContent( + ` + + + Divider + `, + config + ); + + const divider = page.locator('ion-item-divider'); + const backgroundColor = await divider.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const container = shadowRoot?.querySelector('.item-divider-wrapper'); + return container ? window.getComputedStyle(container).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(0, 128, 0)'); + }); + }); + }); +}); diff --git a/core/src/components/item-option/item-option.tsx b/core/src/components/item-option/item-option.tsx index e734b9cac98..e1d801bbe08 100644 --- a/core/src/components/item-option/item-option.tsx +++ b/core/src/components/item-option/item-option.tsx @@ -18,6 +18,8 @@ import type { Color } from '../../interface'; * @slot end - Content is placed to the right of the option text in LTR, and to the left in RTL. * * @part native - The native HTML button or anchor element that wraps all child elements. + * @part inner - The inner wrapper element that arranges the option content. + * @part container - The container element that wraps the start, icon-only, default, and end slots. */ @Component({ tag: 'ion-item-option', @@ -148,9 +150,9 @@ export class ItemOption implements ComponentInterface, AnchorInterface, ButtonIn })} > - + -
+
diff --git a/core/src/components/item-option/test/custom/item-option.e2e.ts b/core/src/components/item-option/test/custom/item-option.e2e.ts new file mode 100644 index 00000000000..b1a44d7dc6f --- /dev/null +++ b/core/src/components/item-option/test/custom/item-option.e2e.ts @@ -0,0 +1,80 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => { + test.describe(title('item-option: custom'), () => { + test.describe('CSS shadow parts', () => { + test('should be able to customize native part', async ({ page }) => { + await page.setContent( + ` + + + Option + `, + config + ); + + const itemOption = page.locator('ion-item-option'); + const backgroundColor = await itemOption.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const native = shadowRoot?.querySelector('.button-native'); + return native ? window.getComputedStyle(native).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(255, 0, 0)'); + }); + + test('should be able to customize inner part', async ({ page }) => { + await page.setContent( + ` + + + Option + `, + config + ); + + const itemOption = page.locator('ion-item-option'); + const backgroundColor = await itemOption.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const inner = shadowRoot?.querySelector('.button-inner'); + return inner ? window.getComputedStyle(inner).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(0, 128, 0)'); + }); + + test('should be able to customize container part', async ({ page }) => { + await page.setContent( + ` + + + Option + `, + config + ); + + const itemOption = page.locator('ion-item-option'); + const backgroundColor = await itemOption.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const container = shadowRoot?.querySelector('.horizontal-wrapper'); + return container ? window.getComputedStyle(container).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(0, 0, 255)'); + }); + }); + }); +}); diff --git a/core/src/components/item-sliding/item-sliding.tsx b/core/src/components/item-sliding/item-sliding.tsx index fc4d4ce2644..1d70cbc12d4 100644 --- a/core/src/components/item-sliding/item-sliding.tsx +++ b/core/src/components/item-sliding/item-sliding.tsx @@ -27,6 +27,7 @@ const enum SlidingState { SwipeEnd = 1 << 5, SwipeStart = 1 << 6, + AnimatingFullSwipe = 1 << 7, } let openSlidingItem: HTMLIonItemSlidingElement | undefined; @@ -47,6 +48,7 @@ export class ItemSliding implements ComponentInterface { private optsWidthLeftSide = 0; private sides = ItemSide.None; private tmr?: ReturnType; + private animationAbortController?: AbortController; private leftOptions?: HTMLIonItemOptionsElement; private rightOptions?: HTMLIonItemOptionsElement; private optsDirty = true; @@ -113,6 +115,15 @@ export class ItemSliding implements ComponentInterface { this.gesture = undefined; } + if (this.tmr !== undefined) { + clearTimeout(this.tmr); + this.tmr = undefined; + } + + // Abort any in-progress animation. The abort handler rejects the pending + // promise, causing animateFullSwipe's finally block to run cleanup. + this.animationAbortController?.abort(); + this.item = null; this.leftOptions = this.rightOptions = undefined; @@ -153,6 +164,10 @@ export class ItemSliding implements ComponentInterface { */ @Method() async open(side: Side | undefined) { + if ((this.state & SlidingState.AnimatingFullSwipe) !== 0) { + return; + } + /** * It is possible for the item to be added to the DOM * after the item-sliding component was created. As a result, @@ -216,6 +231,9 @@ export class ItemSliding implements ComponentInterface { */ @Method() async close() { + if ((this.state & SlidingState.AnimatingFullSwipe) !== 0) { + return; + } this.setOpenAmount(0, true); } @@ -248,6 +266,135 @@ export class ItemSliding implements ComponentInterface { } } + /** + * Check if the given item options element contains at least one expandable, non-disabled option. + */ + private hasExpandableOptions(options: HTMLIonItemOptionsElement | undefined): boolean { + if (!options) return false; + + const optionElements = options.querySelectorAll('ion-item-option'); + return Array.from(optionElements).some((option: any) => { + return option.expandable === true && !option.disabled; + }); + } + + /** + * Returns a Promise that resolves after `ms` milliseconds, or rejects if the + * given AbortSignal is fired before the timer expires. + */ + private delay(ms: number, signal: AbortSignal): Promise { + return new Promise((resolve, reject) => { + const id = setTimeout(resolve, ms); + signal.addEventListener( + 'abort', + () => { + clearTimeout(id); + reject(new DOMException('Animation cancelled', 'AbortError')); + }, + { once: true } + ); + }); + } + + /** + * Animate the item to a specific position using CSS transitions. + * Returns a Promise that resolves when the animation completes, or rejects if + * the given AbortSignal is fired. + */ + private animateToPosition(position: number, duration: number, signal: AbortSignal): Promise { + return new Promise((resolve, reject) => { + if (!this.item) { + return resolve(); + } + + this.item.style.transition = `transform ${duration}ms ease-out`; + this.item.style.transform = `translate3d(${-position}px, 0, 0)`; + + const id = setTimeout(resolve, duration); + signal.addEventListener( + 'abort', + () => { + clearTimeout(id); + reject(new DOMException('Animation cancelled', 'AbortError')); + }, + { once: true } + ); + }); + } + + /** + * Calculate the swipe threshold distance required to trigger a full swipe animation. + * Returns the maximum options width plus a margin to ensure it's achievable. + */ + private getSwipeThreshold(direction: 'start' | 'end'): number { + const maxWidth = direction === 'end' ? this.optsWidthRightSide : this.optsWidthLeftSide; + return maxWidth + SWIPE_MARGIN; + } + + /** + * Animate the item through a full swipe sequence: off-screen β†’ trigger action β†’ return. + * This is used when an expandable option is swiped beyond the threshold. + */ + private async animateFullSwipe(direction: 'start' | 'end') { + const abortController = new AbortController(); + this.animationAbortController = abortController; + const { signal } = abortController; + + // Prevent interruption during animation + if (this.gesture) { + this.gesture.enable(false); + } + + try { + const options = direction === 'end' ? this.rightOptions : this.leftOptions; + + // Trigger expandable state without moving the item + // Set state directly so expandable option fills its container, starting from + // the exact position where the user released, without any visual snap. + this.state = + direction === 'end' + ? SlidingState.End | SlidingState.SwipeEnd | SlidingState.AnimatingFullSwipe + : SlidingState.Start | SlidingState.SwipeStart | SlidingState.AnimatingFullSwipe; + + await this.delay(100, signal); + + // Animate off-screen while maintaining the expanded state + const offScreenDistance = direction === 'end' ? window.innerWidth : -window.innerWidth; + await this.animateToPosition(offScreenDistance, 250, signal); + + // Trigger action + if (options) { + options.fireSwipeEvent(); + } + + // Small delay before returning + await this.delay(300, signal); + + // Return to closed state + await this.animateToPosition(0, 250, signal); + } catch { + // Animation was aborted (e.g. component disconnected). finally handles cleanup. + } finally { + this.animationAbortController = undefined; + + // Reset state + if (this.item) { + this.item.style.transition = ''; + this.item.style.transform = ''; + } + this.openAmount = 0; + this.state = SlidingState.Disabled; + + if (openSlidingItem === this.el) { + openSlidingItem = undefined; + } + + if (this.gesture) { + this.gesture.enable(!this.disabled); + } + } + } + private async updateOptions() { const options = this.el.querySelectorAll('ion-item-options'); @@ -370,6 +517,27 @@ export class ItemSliding implements ComponentInterface { resetContentScrollY(contentEl, initialContentScrollY); } + // Check for full swipe conditions with expandable options + const rawSwipeDistance = Math.abs(gesture.deltaX); + const direction = gesture.deltaX < 0 ? 'end' : 'start'; + const options = direction === 'end' ? this.rightOptions : this.leftOptions; + const hasExpandable = this.hasExpandableOptions(options); + + const shouldTriggerFullSwipe = + hasExpandable && + (rawSwipeDistance > this.getSwipeThreshold(direction) || + (Math.abs(gesture.velocityX) > 0.5 && + rawSwipeDistance > (direction === 'end' ? this.optsWidthRightSide : this.optsWidthLeftSide) * 0.5)); + + if (shouldTriggerFullSwipe) { + this.animateFullSwipe(direction).catch(() => { + if (this.gesture) { + this.gesture.enable(!this.disabled); + } + }); + return; + } + const velocity = gesture.velocityX; let restingPoint = this.openAmount > 0 ? this.optsWidthRightSide : -this.optsWidthLeftSide; diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts index 794dff39a5b..de0ad68de65 100644 --- a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts @@ -1,6 +1,12 @@ import { expect } from '@playwright/test'; import { configs, dragElementBy, test } from '@utils/test/playwright'; +import { + DRAG_DISTANCE_SINGLE_OPTION, + DRAG_DISTANCE_MULTIPLE_OPTIONS, + DRAG_STEPS_UNDER_FULL_SWIPE, +} from '../test.utils'; + /** * item-sliding doesn't have mode-specific styling, * but the child components, item-options and item-option, do. @@ -23,9 +29,9 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? -150 : 150; + const dragByX = config.direction === 'rtl' ? -DRAG_DISTANCE_SINGLE_OPTION : DRAG_DISTANCE_SINGLE_OPTION; - await dragElementBy(item, page, dragByX); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot('item-sliding-start')); @@ -42,9 +48,10 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? 150 : -150; + const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); + await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot('item-sliding-end')); }); @@ -61,7 +68,16 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co await page.goto(`/src/components/item-sliding/test/basic`, config); const item = page.locator('#item2'); - await dragElementBy(item, page, -150); + await dragElementBy( + item, + page, + -DRAG_DISTANCE_MULTIPLE_OPTIONS, + 0, + undefined, + undefined, + true, + DRAG_STEPS_UNDER_FULL_SWIPE + ); await page.waitForChanges(); // item-sliding doesn't have an easy way to tell whether it's fully open so just screenshot it @@ -140,8 +156,8 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf const direction = config.direction; const item = page.locator('ion-item-sliding'); - const dragByX = direction == 'rtl' ? -150 : 150; - await dragElementBy(item, page, dragByX); + const dragByX = direction == 'rtl' ? -DRAG_DISTANCE_MULTIPLE_OPTIONS : DRAG_DISTANCE_MULTIPLE_OPTIONS; + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-safe-area-left`)); @@ -181,8 +197,8 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf const direction = config.direction; const item = page.locator('ion-item-sliding'); - const dragByX = direction == 'rtl' ? 150 : -150; - await dragElementBy(item, page, dragByX); + const dragByX = direction == 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); await expect(item).toHaveScreenshot(screenshot(`item-sliding-safe-area-right`)); diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ionic-md-ltr-light-Mobile-Chrome-linux.png index 1c75edd1410..7f4b322311f 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ionic-md-rtl-light-Mobile-Chrome-linux.png index 6b23ed6d6dd..9625029fcfd 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-ltr-Mobile-Chrome-linux.png index 41faee08368..303faec5a19 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-ltr-Mobile-Firefox-linux.png index cb08138d6c8..09680f09bca 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-rtl-Mobile-Chrome-linux.png index 63b06652799..4ef66dbd849 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-md-ltr-Mobile-Chrome-linux.png index e973b27865f..901ae056a43 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-md-rtl-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-md-rtl-Mobile-Chrome-linux.png index 9a7b90611c5..a4b92467dbd 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-end-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-gesture-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-gesture-md-ltr-Mobile-Chrome-linux.png index e973b27865f..901ae056a43 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-gesture-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-gesture-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-left-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-left-ionic-md-ltr-light-Mobile-Chrome-linux.png index 528ebde36b1..7863a16c3c4 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-left-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-left-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-left-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-left-ionic-md-rtl-light-Mobile-Chrome-linux.png index bcf3e12e56c..bcec2812a0a 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-left-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-left-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-right-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-right-ionic-md-ltr-light-Mobile-Chrome-linux.png index 5c8fbc2477a..c1b14b72174 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-right-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-right-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-right-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-right-ionic-md-rtl-light-Mobile-Chrome-linux.png index 4d6ec42cb6a..2e3e088deb1 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-right-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-safe-area-right-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ionic-md-ltr-light-Mobile-Chrome-linux.png index 78f35487820..a4cd8a08042 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ionic-md-rtl-light-Mobile-Chrome-linux.png index 923b3d6564e..2f9b5feecb2 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ios-ltr-Mobile-Chrome-linux.png index 63d95d47632..91980276159 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ios-rtl-Mobile-Chrome-linux.png index 908ac75b835..902a4735b75 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-md-ltr-Mobile-Chrome-linux.png index 330bf66877a..a31c87381f0 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-md-rtl-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-md-rtl-Mobile-Chrome-linux.png index a2d6fb99f16..e5d9af21647 100644 Binary files a/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/basic/item-sliding.e2e.ts-snapshots/item-sliding-start-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/full-swipe/index.html b/core/src/components/item-sliding/test/full-swipe/index.html new file mode 100644 index 00000000000..1fface1300c --- /dev/null +++ b/core/src/components/item-sliding/test/full-swipe/index.html @@ -0,0 +1,131 @@ + + + + + Item Sliding - Full Swipe + + + + + + + + + + + + + + Item Sliding - Full Swipe + + + + +
+

Full Swipe - Expandable Options

+
+ + + + + Expandable End (Swipe Left) + + + Delete + + + + + + + Expandable Start (Swipe Right) + + + Archive + + + + + + + Expandable Both Sides + + + Archive + + + Delete + + + + +
+

Non-Expandable Options (No Full Swipe)

+
+ + + + + Non-Expandable (Should Show Options) + + + Edit + + + + + + + Multiple Non-Expandable Options + + + Edit + Share + Delete + + + + +
+

Mixed Scenarios

+
+ + + + + Expandable + Other Options + + + Edit + Delete + + + +
+
+ + + diff --git a/core/src/components/item-sliding/test/full-swipe/item-sliding.e2e.ts b/core/src/components/item-sliding/test/full-swipe/item-sliding.e2e.ts new file mode 100644 index 00000000000..38d09885389 --- /dev/null +++ b/core/src/components/item-sliding/test/full-swipe/item-sliding.e2e.ts @@ -0,0 +1,191 @@ +import { expect } from '@playwright/test'; +import { configs, dragElementBy, test } from '@utils/test/playwright'; + +/** + * Full swipe animation behavior is mode-independent but + * child components (ion-item-options, ion-item-option) have + * mode-specific styling, so we test across all modes. + * + * When an item has at least one expandable option and the user swipes + * beyond the threshold (or with sufficient velocity), the item slides + * off-screen, fires ionSwipe, and returns to its closed position. + */ + +// Full animation cycle duration (100ms expand + 250ms off-screen + 300ms delay + 250ms return) +const FULL_ANIMATION_MS = 1100; + +configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr', 'rtl'] }).forEach(({ title, config }) => { + test.describe(title('item-sliding: full swipe'), () => { + test('should fire ionSwipe when expandable option is swiped fully (end side)', async ({ page }) => { + await page.setContent( + ` + + + Expandable End (Swipe Left) + + + Delete + + + `, + config + ); + + const ionSwipe = await page.spyOnEvent('ionSwipe'); + const item = page.locator('ion-item-sliding'); + const dragByX = config.direction === 'rtl' ? 190 : -190; + + await dragElementBy(item, page, dragByX); + await ionSwipe.next(); + + expect(ionSwipe).toHaveReceivedEventTimes(1); + }); + + test('should fire ionSwipe when expandable option is swiped fully (start side)', async ({ page }) => { + await page.setContent( + ` + + + Expandable Start (Swipe Right) + + + Archive + + + `, + config + ); + + const ionSwipe = await page.spyOnEvent('ionSwipe'); + const item = page.locator('ion-item-sliding'); + const dragByX = config.direction === 'rtl' ? -190 : 190; + + await dragElementBy(item, page, dragByX); + await ionSwipe.next(); + + expect(ionSwipe).toHaveReceivedEventTimes(1); + }); + + test('should return to closed state after full swipe animation completes', async ({ page }) => { + await page.setContent( + ` + + + Expandable End (Swipe Left) + + + Delete + + + `, + config + ); + + const item = page.locator('ion-item-sliding'); + const dragByX = config.direction === 'rtl' ? 190 : -190; + + await dragElementBy(item, page, dragByX); + await page.waitForTimeout(FULL_ANIMATION_MS); + + const openAmount = await item.evaluate((el: HTMLIonItemSlidingElement) => el.getOpenAmount()); + expect(openAmount).toBe(0); + }); + + test('should NOT trigger full swipe animation for non-expandable options', async ({ page }) => { + await page.setContent( + ` + + + Non-Expandable (Should Show Options) + + + Edit + + + `, + config + ); + + const ionSwipe = await page.spyOnEvent('ionSwipe'); + const item = page.locator('ion-item-sliding'); + const dragByX = config.direction === 'rtl' ? 180 : -180; + + await dragElementBy(item, page, dragByX); + + await ionSwipe.next(); + await page.waitForChanges(); + + // The full swipe animation closes the item (openAmount === 0) after completing. + // For a non-expandable item, no animation runs and the item stays open at optsWidth. + const openAmount = await item.evaluate((el: HTMLIonItemSlidingElement) => el.getOpenAmount()); + expect(Math.abs(openAmount)).toBeGreaterThan(0); + }); + + test('should fire ionSwipe when non-expandable options are swiped past the threshold', async ({ page }) => { + await page.setContent( + ` + + + Non-Expandable (Should Show Options) + + + Edit + + + `, + config + ); + + const ionSwipe = await page.spyOnEvent('ionSwipe'); + const item = page.locator('ion-item-sliding'); + const dragByX = config.direction === 'rtl' ? 190 : -190; + + await dragElementBy(item, page, dragByX); + await ionSwipe.next(); + + expect(ionSwipe).toHaveReceivedEventTimes(1); + }); + }); +}); + +/** + * Velocity-based trigger: a fast short swipe should trigger the full animation + * even if the raw distance alone wouldn't exceed the threshold. + * This behavior does not vary across modes. + */ +configs({ modes: ['md'], directions: ['ltr', 'rtl'] }).forEach(({ title, config }) => { + test.describe(title('item-sliding: full swipe velocity'), () => { + test('should trigger full swipe animation with fast velocity', async ({ page }) => { + await page.setContent( + ` + + + Expandable End (Swipe Left) + + + Delete + + + `, + config + ); + + const ionSwipe = await page.spyOnEvent('ionSwipe'); + const item = page.locator('ion-item-sliding'); + const box = (await item.boundingBox())!; + + // Few steps = high velocity gesture + const startX = config.direction === 'rtl' ? box.x + 30 : box.x + box.width - 10; + const endX = config.direction === 'rtl' ? box.x + box.width - 10 : box.x + 30; + const startY = box.y + box.height / 2; + + await page.mouse.move(startX, startY); + await page.mouse.down(); + await page.mouse.move(endX, startY, { steps: 3 }); + await page.mouse.up(); + await ionSwipe.next(); + + expect(ionSwipe).toHaveReceivedEventTimes(1); + }); + }); +}); diff --git a/core/src/components/item-sliding/test/hue/item-sliding.e2e.ts-snapshots/item-option-hue-bold-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/hue/item-sliding.e2e.ts-snapshots/item-option-hue-bold-ionic-md-ltr-light-Mobile-Chrome-linux.png index 41b0dbd4152..0f6c7710d8f 100644 Binary files a/core/src/components/item-sliding/test/hue/item-sliding.e2e.ts-snapshots/item-option-hue-bold-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/hue/item-sliding.e2e.ts-snapshots/item-option-hue-bold-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/hue/item-sliding.e2e.ts-snapshots/item-option-hue-subtle-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/hue/item-sliding.e2e.ts-snapshots/item-option-hue-subtle-ionic-md-ltr-light-Mobile-Chrome-linux.png index b52a42391b9..839df4e7e6e 100644 Binary files a/core/src/components/item-sliding/test/hue/item-sliding.e2e.ts-snapshots/item-option-hue-subtle-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/hue/item-sliding.e2e.ts-snapshots/item-option-hue-subtle-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts index 910baa4216f..4716a3054c5 100644 --- a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts @@ -1,6 +1,8 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS, DRAG_STEPS_UNDER_FULL_SWIPE } from '../test.utils'; + /** * item-sliding doesn't have mode-specific styling, * but the child components, item-options and item-option, do. @@ -10,12 +12,13 @@ import { configs, test, dragElementBy } from '@utils/test/playwright'; */ configs({ modes: ['ionic-md', 'ios', 'md'] }).forEach(({ title, screenshot, config }) => { test.describe(title('item-sliding: icons'), () => { - test('should not have visual regressions', async ({ page }) => { + test.beforeEach(async ({ page }) => { await page.goto(`/src/components/item-sliding/test/icons`, config); + }); - const itemIDs = ['iconsOnly', 'iconsStart', 'iconsEnd', 'iconsTop', 'iconsBottom']; - for (const itemID of itemIDs) { - const item = page.locator(`#${itemID}`); + ['iconsOnly', 'iconsStart', 'iconsEnd', 'iconsTop', 'iconsBottom'].forEach((position) => { + test(`${position} - should not have visual regressions`, async ({ page }) => { + const item = page.locator(`#${position}`); /** * Negative dragByX value to drag element from the right to the left @@ -23,15 +26,15 @@ configs({ modes: ['ionic-md', 'ios', 'md'] }).forEach(({ title, screenshot, conf * Positive dragByX value to drag element from the left to the right * to reveal the options on the left side. */ - const dragByX = config.direction === 'rtl' ? 150 : -150; + const dragByX = config.direction === 'rtl' ? DRAG_DISTANCE_MULTIPLE_OPTIONS : -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); // Convert camelCase ids to kebab-case for screenshot file names - const itemIDKebab = itemID.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); - await expect(item).toHaveScreenshot(screenshot(`item-sliding-${itemIDKebab}`)); - } + const positionKebab = position.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + await expect(item).toHaveScreenshot(screenshot(`item-sliding-${positionKebab}`)); + }); }); }); }); diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-bottom-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-bottom-ionic-md-ltr-light-Mobile-Chrome-linux.png index 578bc704890..4b1c73b2393 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-bottom-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-bottom-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-bottom-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-bottom-ionic-md-rtl-light-Mobile-Chrome-linux.png index 18c79b3d0b3..81b2b0fec36 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-bottom-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-bottom-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-end-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-end-ionic-md-ltr-light-Mobile-Chrome-linux.png index 19012aa7324..43cb58c0bdc 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-end-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-end-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-end-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-end-ionic-md-rtl-light-Mobile-Chrome-linux.png index f048d5cf475..fdd66329ea6 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-end-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-end-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ionic-md-ltr-light-Mobile-Chrome-linux.png index e22bd29a671..5a0881cbeb2 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ionic-md-rtl-light-Mobile-Chrome-linux.png index 2c0ea67f084..33a7c7154b8 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ios-ltr-Mobile-Firefox-linux.png index dad017915f6..b41011409c9 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-start-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-top-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-top-ionic-md-ltr-light-Mobile-Chrome-linux.png index 3b24c98fb30..33c1a8bbea2 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-top-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-top-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-top-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-top-ionic-md-rtl-light-Mobile-Chrome-linux.png index b5d0da4993e..a3a6c11b827 100644 Binary files a/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-top-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/icons/item-sliding.e2e.ts-snapshots/item-sliding-icons-top-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts index 53772e7f02a..9fc0b5e9446 100644 --- a/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/scroll-target/item-sliding.e2e.ts @@ -1,5 +1,8 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; + +import { DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; + /** * This behavior does not vary across modes/directions */ @@ -15,7 +18,15 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => expect(await scrollEl.evaluate((el: HTMLElement) => el.scrollTop)).toEqual(0); - await dragElementBy(itemSlidingEl, page, -150, 0, undefined, undefined, false); + /** + * No need to increase steps to prevent the full swipe threshold + * from being crossed because: + * - we are not testing swipe behavior here + * - increasing steps is only in Webkit since it accumulates velocity + * faster than other browsers, and this test is skipped in Webkit, so + * the default step count is safe to use + */ + await dragElementBy(itemSlidingEl, page, -DRAG_DISTANCE_MULTIPLE_OPTIONS, 0, undefined, undefined, false); /** * Do not use scrollToBottom() or other scrolling methods diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts index 6010e8e5266..624196217a0 100644 --- a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts @@ -1,30 +1,33 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS, DRAG_STEPS_UNDER_FULL_SWIPE } from '../test.utils'; + /** * The shapes on the `item-option` do not vary by direction * when they are not being dragged. */ configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { test.describe(title('item-sliding: shapes'), () => { - test('should not have visual regressions when not expanded', async ({ page }) => { + test.beforeEach(async ({ page }) => { await page.goto(`/src/components/item-sliding/test/shapes`, config); + }); - const itemIDs = ['round', 'soft', 'rectangular']; - for (const itemID of itemIDs) { - const item = page.locator(`#${itemID}`); + ['round', 'soft', 'rectangular'].forEach((shape) => { + test(`${shape} - should not have visual regressions when not expanded`, async ({ page }) => { + const item = page.locator(`#${shape}`); /** * Negative dragByX value to drag element from the right to the left * to reveal the options on the right side. */ - const dragByX = -150; + const dragByX = -DRAG_DISTANCE_MULTIPLE_OPTIONS; - await dragElementBy(item, page, dragByX); + await dragElementBy(item, page, dragByX, 0, undefined, undefined, true, DRAG_STEPS_UNDER_FULL_SWIPE); await page.waitForChanges(); - await expect(item).toHaveScreenshot(screenshot(`item-sliding-${itemID}`)); - } + await expect(item).toHaveScreenshot(screenshot(`item-sliding-${shape}`)); + }); }); }); }); diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png index 4644464ef2c..98f6a510462 100644 Binary files a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-round-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-round-ionic-md-ltr-light-Mobile-Chrome-linux.png index 3b24c98fb30..33c1a8bbea2 100644 Binary files a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-round-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-round-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png index 6e2778c607b..8b906e61e69 100644 Binary files a/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item-sliding/test/shapes/item-sliding.e2e.ts-snapshots/item-sliding-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts index 01c40bf2110..f30660803db 100644 --- a/core/src/components/item-sliding/test/states/item-sliding.e2e.ts +++ b/core/src/components/item-sliding/test/states/item-sliding.e2e.ts @@ -1,6 +1,8 @@ import { expect } from '@playwright/test'; import { configs, test, dragElementBy } from '@utils/test/playwright'; +import { DRAG_DISTANCE_MULTIPLE_OPTIONS } from '../test.utils'; + /** * This behavior does not vary across modes */ @@ -16,8 +18,13 @@ configs({ modes: ['ionic-md', 'md', 'ios'], directions: ['ltr'] }).forEach(({ ti * Negative dragByX value to drag element from the right to the left * to reveal the options on the right side. */ - const dragByX = -150; + const dragByX = -DRAG_DISTANCE_MULTIPLE_OPTIONS; + /** + * No need to increase steps to prevent the full swipe threshold from + * being crossed because the option is disabled, so the option will + * never expand fully regardless of drag speed. + */ await dragElementBy(item, page, dragByX); await page.waitForChanges(); diff --git a/core/src/components/item-sliding/test/test.utils.ts b/core/src/components/item-sliding/test/test.utils.ts index 7bda6511efe..b18803b4af0 100644 --- a/core/src/components/item-sliding/test/test.utils.ts +++ b/core/src/components/item-sliding/test/test.utils.ts @@ -1,6 +1,21 @@ import { expect } from '@playwright/test'; import type { E2EPage, ScreenshotFn } from '@utils/test/playwright'; +/** + * Drag distances that reveal options without crossing the full swipe + * threshold (`optsWidth` + `SWIPE_MARGIN`). A narrower options panel + * requires a shorter drag. + */ +export const DRAG_DISTANCE_SINGLE_OPTION = 100; +export const DRAG_DISTANCE_MULTIPLE_OPTIONS = 150; + +/** + * The number of drag steps used when revealing options. A higher step + * count slows the drag velocity, keeping it below the full swipe + * threshold in WebKit. See `dragElementBy` for more details. + */ +export const DRAG_STEPS_UNDER_FULL_SWIPE = 15; + /** * Warning: This function will fail when in RTL mode. * TODO(FW-3711): Remove the `directions` config when this issue preventing diff --git a/core/src/components/item/item.tsx b/core/src/components/item/item.tsx index 69ab55e785a..d25f473f586 100644 --- a/core/src/components/item/item.tsx +++ b/core/src/components/item/item.tsx @@ -21,6 +21,8 @@ import type { RouterDirection } from '../router/utils/interface'; * @slot end - Content is placed to the right of the item text in LTR, and to the left in RTL. * * @part native - The native HTML button, anchor or div element that wraps all child elements. + * @part inner - The inner wrapper element that arranges the item content. + * @part container - The wrapper element that contains the default slot. * @part detail-icon - The chevron icon for the item. Only applies when `detail="true"`. */ @Component({ @@ -431,8 +433,8 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac {...clickFn} > -
-
+
+
diff --git a/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-ios-ltr-Mobile-Chrome-linux.png index bed9b30e91f..e077b477e74 100644 Binary files a/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-ios-rtl-Mobile-Chrome-linux.png index 94330a022c5..ad5ca9457d2 100644 Binary files a/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-md-ltr-Mobile-Chrome-linux.png index 2cb14a2c1f1..928c0327097 100644 Binary files a/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-md-rtl-Mobile-Chrome-linux.png index 4d1fd4e3666..d9ed200f691 100644 Binary files a/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/basic/item.e2e.ts-snapshots/item-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-ios-ltr-Mobile-Chrome-linux.png index 322e5dddf9d..ac4affe029c 100644 Binary files a/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-ios-rtl-Mobile-Chrome-linux.png index 1aa1ddbc4af..e8583d89378 100644 Binary files a/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-md-ltr-Mobile-Chrome-linux.png index ee2f6ecb8f1..838a941b085 100644 Binary files a/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-md-rtl-Mobile-Chrome-linux.png index 4e088395f44..293ff4b9a25 100644 Binary files a/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/buttons/item.e2e.ts-snapshots/item-buttons-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/css-variables/item.e2e.ts b/core/src/components/item/test/css-variables/item.e2e.ts deleted file mode 100644 index 7f24b1724cd..00000000000 --- a/core/src/components/item/test/css-variables/item.e2e.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { expect } from '@playwright/test'; -import { configs, test } from '@utils/test/playwright'; - -/** - * This behavior does not vary across directions - */ -configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { - test.describe(title('item: CSS variables'), () => { - test('should not have visual regressions', async ({ page }) => { - await page.goto(`/src/components/item/test/css-variables`, config); - - await page.setIonViewport(); - - await expect(page).toHaveScreenshot(screenshot(`item-css-vars-diff`)); - }); - }); -}); diff --git a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Chrome-linux.png deleted file mode 100644 index e5deac11796..00000000000 Binary files a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Chrome-linux.png and /dev/null differ diff --git a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Firefox-linux.png deleted file mode 100644 index de756ae34fc..00000000000 Binary files a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Firefox-linux.png and /dev/null differ diff --git a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Safari-linux.png deleted file mode 100644 index 763572ddc84..00000000000 Binary files a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-ios-ltr-Mobile-Safari-linux.png and /dev/null differ diff --git a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Chrome-linux.png deleted file mode 100644 index 10e9806c874..00000000000 Binary files a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Chrome-linux.png and /dev/null differ diff --git a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Firefox-linux.png deleted file mode 100644 index 24b45b3112e..00000000000 Binary files a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Firefox-linux.png and /dev/null differ diff --git a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Safari-linux.png deleted file mode 100644 index dbcf1f27b7a..00000000000 Binary files a/core/src/components/item/test/css-variables/item.e2e.ts-snapshots/item-css-vars-diff-md-ltr-Mobile-Safari-linux.png and /dev/null differ diff --git a/core/src/components/item/test/custom/item.e2e.ts b/core/src/components/item/test/custom/item.e2e.ts new file mode 100644 index 00000000000..212c55acaf2 --- /dev/null +++ b/core/src/components/item/test/custom/item.e2e.ts @@ -0,0 +1,174 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => { + test.describe(title('item: custom'), () => { + test.describe('CSS shadow parts', () => { + test('should be able to customize native part', async ({ page }) => { + await page.setContent( + ` + + + + Item + + `, + config + ); + + const item = page.locator('ion-item'); + const backgroundColor = await item.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const native = shadowRoot?.querySelector('.item-native'); + return native ? window.getComputedStyle(native).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(255, 0, 0)'); + }); + + test('should be able to customize inner part', async ({ page }) => { + await page.setContent( + ` + + + + Item + + `, + config + ); + + const item = page.locator('ion-item'); + const backgroundColor = await item.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const inner = shadowRoot?.querySelector('.item-inner'); + return inner ? window.getComputedStyle(inner).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(0, 128, 0)'); + }); + + test('should be able to customize container part', async ({ page }) => { + await page.setContent( + ` + + + + Item + + `, + config + ); + + const item = page.locator('ion-item'); + const backgroundColor = await item.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const container = shadowRoot?.querySelector('.input-wrapper'); + return container ? window.getComputedStyle(container).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(0, 0, 255)'); + }); + + test('should be able to customize detail-icon part', async ({ page }) => { + await page.setContent( + ` + + + + Item + + `, + config + ); + + const item = page.locator('ion-item'); + const backgroundColor = await item.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const detailIcon = shadowRoot?.querySelector('.item-detail-icon'); + return detailIcon ? window.getComputedStyle(detailIcon).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(255, 0, 0)'); + }); + }); + + test.describe('CSS variables', () => { + test('should be able to customize background using css variables', async ({ page }) => { + await page.setContent( + ` + + + + Item + + `, + config + ); + + const item = page.locator('ion-item'); + const backgroundColor = await item.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const native = shadowRoot?.querySelector('.item-native'); + return native ? window.getComputedStyle(native).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(255, 0, 0)'); + }); + + test('should be able to customize padding using css variables', async ({ page }) => { + await page.setContent( + ` + + + + Item + + `, + config + ); + + const item = page.locator('ion-item'); + const paddingValues = await item.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const native = shadowRoot?.querySelector('.item-native'); + return { + paddingTop: native ? window.getComputedStyle(native).paddingTop : '', + paddingBottom: native ? window.getComputedStyle(native).paddingBottom : '', + paddingStart: native ? window.getComputedStyle(native).paddingLeft : '', + paddingEnd: native ? window.getComputedStyle(native).paddingRight : '', + }; + }); + expect(paddingValues.paddingTop).toBe('20px'); + expect(paddingValues.paddingBottom).toBe('20px'); + expect(paddingValues.paddingStart).toBe('10px'); + expect(paddingValues.paddingEnd).toBe('10px'); + }); + }); + }); +}); diff --git a/core/src/components/item/test/disabled/item.e2e.ts-snapshots/item-disabled-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item/test/disabled/item.e2e.ts-snapshots/item-disabled-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png index f28f1a2e822..6242db00c80 100644 Binary files a/core/src/components/item/test/disabled/item.e2e.ts-snapshots/item-disabled-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item/test/disabled/item.e2e.ts-snapshots/item-disabled-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-ios-ltr-Mobile-Chrome-linux.png index 12a6f33816f..7af8e7f6d16 100644 Binary files a/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-ios-rtl-Mobile-Chrome-linux.png index 203afb68626..1168247c91d 100644 Binary files a/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-md-ltr-Mobile-Chrome-linux.png index a69f926845f..6196feebf0d 100644 Binary files a/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-md-rtl-Mobile-Chrome-linux.png index 5c41b749cdd..7d0cca8bb2f 100644 Binary files a/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/dividers/item.e2e.ts-snapshots/item-dividers-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-ios-ltr-Mobile-Chrome-linux.png index 40b68bdffc6..ef89cb38056 100644 Binary files a/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-ios-rtl-Mobile-Chrome-linux.png index eee2db293a8..1e8434d19c7 100644 Binary files a/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-md-ltr-Mobile-Chrome-linux.png index 3aa4ba251d1..0ca2cb16e07 100644 Binary files a/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-md-rtl-Mobile-Chrome-linux.png index 3fc0a2bf770..0fed49bc527 100644 Binary files a/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/images/item.e2e.ts-snapshots/item-images-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Chrome-linux.png index 789c34c49ce..d4e11778941 100644 Binary files a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Firefox-linux.png index 819588d2217..9de82bc1f50 100644 Binary files a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Safari-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..cdcf57052b3 Binary files /dev/null and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-disabled-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Chrome-linux.png index 2eb2b1c08c6..1de205265ce 100644 Binary files a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Firefox-linux.png index 32996e6baec..a60b81edc1b 100644 Binary files a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Safari-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Safari-linux.png index 4b5c75cb54f..984f2cc43fe 100644 Binary files a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Chrome-linux.png index 471c06998ad..f72079cb094 100644 Binary files a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Firefox-linux.png index 349b310d9f3..41eb69aa763 100644 Binary files a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Safari-linux.png b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Safari-linux.png index 36e81018d6c..e6428dfae7c 100644 Binary files a/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Safari-linux.png and b/core/src/components/item/test/inputs/item.e2e.ts-snapshots/item-inputs-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-ios-ltr-Mobile-Chrome-linux.png index 77eca8d0bd2..25fe4ca9dae 100644 Binary files a/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-ios-rtl-Mobile-Chrome-linux.png index 49b05a9467f..28481905cf5 100644 Binary files a/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-md-ltr-Mobile-Chrome-linux.png index 41a6b8c8f94..c8cc8b98584 100644 Binary files a/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-md-rtl-Mobile-Chrome-linux.png index d85709708de..413abf13423 100644 Binary files a/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/lines/item.e2e.ts-snapshots/item-lines-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-ios-ltr-Mobile-Chrome-linux.png index e43d189a958..b2de0bb327c 100644 Binary files a/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-ios-rtl-Mobile-Chrome-linux.png index 183d77ced75..a5f82940113 100644 Binary files a/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-md-ltr-Mobile-Chrome-linux.png index 48e9e563277..d5ab7e32413 100644 Binary files a/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-md-rtl-Mobile-Chrome-linux.png index 3b4e3290728..b425136679b 100644 Binary files a/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/item/test/media/item.e2e.ts-snapshots/item-media-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png index 698d86126b5..ceb8da21633 100644 Binary files a/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-ios-ltr-Mobile-Chrome-linux.png index 07df58e12e7..0105bf075b2 100644 Binary files a/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-md-ltr-Mobile-Chrome-linux.png index 2915fd07bc7..7d3b49659fc 100644 Binary files a/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/item/test/states/item.e2e.ts-snapshots/item-states-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/label/label.tsx b/core/src/components/label/label.tsx index 022907039e1..000f3c0a797 100644 --- a/core/src/components/label/label.tsx +++ b/core/src/components/label/label.tsx @@ -20,6 +20,7 @@ import type { Color, StyleEventDetail } from '../../interface'; }) export class Label implements ComponentInterface { private inRange = false; + private loadTimeout: ReturnType | undefined; @Element() el!: HTMLElement; @@ -58,12 +59,18 @@ export class Label implements ComponentInterface { componentDidLoad() { if (this.noAnimate) { - setTimeout(() => { + this.loadTimeout = setTimeout(() => { this.noAnimate = false; }, 1000); } } + disconnectedCallback() { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + } + } + @Watch('color') colorChanged() { this.emitColor(); diff --git a/core/src/components/label/test/a11y/label.e2e.ts-snapshots/label-headings-scale-md-ltr-Mobile-Safari-linux.png b/core/src/components/label/test/a11y/label.e2e.ts-snapshots/label-headings-scale-md-ltr-Mobile-Safari-linux.png index b838f31b597..7346fe5c16f 100644 Binary files a/core/src/components/label/test/a11y/label.e2e.ts-snapshots/label-headings-scale-md-ltr-Mobile-Safari-linux.png and b/core/src/components/label/test/a11y/label.e2e.ts-snapshots/label-headings-scale-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/list-header/list-header.tsx b/core/src/components/list-header/list-header.tsx index 24deb535e23..7031aea3fea 100644 --- a/core/src/components/list-header/list-header.tsx +++ b/core/src/components/list-header/list-header.tsx @@ -8,6 +8,8 @@ import type { Color } from '../../interface'; /** * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component. * @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the visual appearance of the component. + * + * @part inner - The inner wrapper element that arranges the list header content. */ @Component({ tag: 'ion-list-header', @@ -42,7 +44,7 @@ export class ListHeader implements ComponentInterface { [`list-header-lines-${lines}`]: lines !== undefined, })} > -
+
diff --git a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ionic-md-ltr-light-Mobile-Chrome-linux.png index 44ac4a35422..a30b82b8b32 100644 Binary files a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ionic-md-rtl-light-Mobile-Chrome-linux.png index cbd8bf099ce..72df0acf409 100644 Binary files a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ios-ltr-Mobile-Chrome-linux.png index a2c47ce52eb..0699116e710 100644 Binary files a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ios-rtl-Mobile-Chrome-linux.png index ffd50838a05..3d5a64eb728 100644 Binary files a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-md-ltr-Mobile-Chrome-linux.png b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-md-ltr-Mobile-Chrome-linux.png index aab439c518f..24e93b5afca 100644 Binary files a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-md-rtl-Mobile-Chrome-linux.png b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-md-rtl-Mobile-Chrome-linux.png index 66bd2a998ad..90af01cc6a2 100644 Binary files a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-safe-area-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-safe-area-ionic-md-ltr-light-Mobile-Chrome-linux.png index 1f19013ca63..e91ff490a06 100644 Binary files a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-safe-area-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-safe-area-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-safe-area-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-safe-area-ionic-md-rtl-light-Mobile-Chrome-linux.png index f8b32a5cdd7..6779d2dfbe4 100644 Binary files a/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-safe-area-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/list-header/test/basic/list-header.e2e.ts-snapshots/list-header-safe-area-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list-header/test/custom/list-header.e2e.ts b/core/src/components/list-header/test/custom/list-header.e2e.ts new file mode 100644 index 00000000000..64a7ae105de --- /dev/null +++ b/core/src/components/list-header/test/custom/list-header.e2e.ts @@ -0,0 +1,34 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ directions: ['ltr'], modes: ['md'] }).forEach(({ title, config }) => { + test.describe(title('list-header: custom'), () => { + test.describe('CSS shadow parts', () => { + test('should be able to customize inner part', async ({ page }) => { + await page.setContent( + ` + + + Header + `, + config + ); + + const header = page.locator('ion-list-header'); + const backgroundColor = await header.evaluate((el) => { + const shadowRoot = el.shadowRoot; + const inner = shadowRoot?.querySelector('.list-header-inner'); + return inner ? window.getComputedStyle(inner).backgroundColor : ''; + }); + expect(backgroundColor).toBe('rgb(255, 0, 0)'); + }); + }); + }); +}); diff --git a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-rectangular-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-rectangular-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png index b8c97186fc2..7d6513aa270 100644 Binary files a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-rectangular-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-rectangular-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-round-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-round-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png index 7b698042499..127f1e10dc1 100644 Binary files a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-round-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-round-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-round-without-header-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-round-without-header-ionic-md-ltr-light-Mobile-Chrome-linux.png index e49e3db069d..dd96d2534bb 100644 Binary files a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-round-without-header-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-round-without-header-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-soft-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-soft-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png index e756c9d3c2f..09a618b8f0f 100644 Binary files a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-soft-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-soft-with-header-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-soft-without-header-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-soft-without-header-ionic-md-ltr-light-Mobile-Chrome-linux.png index cefd35a6eb7..5fa9e673b31 100644 Binary files a/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-soft-without-header-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/list/test/shape/list.e2e.ts-snapshots/list-shape-soft-without-header-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/menu-button/test/basic/menu-button.e2e.ts-snapshots/menu-button-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/menu-button/test/basic/menu-button.e2e.ts-snapshots/menu-button-diff-ios-ltr-Mobile-Chrome-linux.png index 133b1c1b1b7..465dd9656e0 100644 Binary files a/core/src/components/menu-button/test/basic/menu-button.e2e.ts-snapshots/menu-button-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/menu-button/test/basic/menu-button.e2e.ts-snapshots/menu-button-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/menu-button/test/basic/menu-button.e2e.ts-snapshots/menu-button-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/menu-button/test/basic/menu-button.e2e.ts-snapshots/menu-button-diff-md-ltr-Mobile-Chrome-linux.png index 59a43f700fd..683eeb2dfb3 100644 Binary files a/core/src/components/menu-button/test/basic/menu-button.e2e.ts-snapshots/menu-button-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/menu-button/test/basic/menu-button.e2e.ts-snapshots/menu-button-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/menu/test/custom/menu.e2e.ts-snapshots/menu-custom-split-pane-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/menu/test/custom/menu.e2e.ts-snapshots/menu-custom-split-pane-ios-ltr-Mobile-Chrome-linux.png index abdafea28a3..36032acc36c 100644 Binary files a/core/src/components/menu/test/custom/menu.e2e.ts-snapshots/menu-custom-split-pane-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/menu/test/custom/menu.e2e.ts-snapshots/menu-custom-split-pane-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/menu/test/custom/menu.e2e.ts-snapshots/menu-custom-split-pane-md-ltr-Mobile-Chrome-linux.png b/core/src/components/menu/test/custom/menu.e2e.ts-snapshots/menu-custom-split-pane-md-ltr-Mobile-Chrome-linux.png index 378fe57517f..c28cdc0083b 100644 Binary files a/core/src/components/menu/test/custom/menu.e2e.ts-snapshots/menu-custom-split-pane-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/menu/test/custom/menu.e2e.ts-snapshots/menu-custom-split-pane-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/animations/ionic.enter.ts b/core/src/components/modal/animations/ionic.enter.ts new file mode 100644 index 00000000000..0579189d13a --- /dev/null +++ b/core/src/components/modal/animations/ionic.enter.ts @@ -0,0 +1,53 @@ +import { createAnimation } from '@utils/animation/animation'; +import { getElementRoot } from '@utils/helpers'; + +import type { Animation } from '../../../interface'; +import type { ModalAnimationOptions } from '../modal-interface'; + +import { createSheetEnterAnimation } from './sheet'; + +const createEnterAnimation = () => { + const backdropAnimation = createAnimation() + .fromTo('opacity', 0.01, 'var(--backdrop-opacity)') + .beforeStyles({ + 'pointer-events': 'none', + }) + .afterClearStyles(['pointer-events']); + + const wrapperAnimation = createAnimation().keyframes([ + { offset: 0, opacity: 0.01, transform: 'translateY(40px)' }, + { offset: 1, opacity: 1, transform: `translateY(0px)` }, + ]); + + return { backdropAnimation, wrapperAnimation, contentAnimation: undefined }; +}; + +/** + * Ionic Modal Enter Animation + */ +export const ionicEnterAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => { + const { currentBreakpoint, expandToScroll } = opts; + const root = getElementRoot(baseEl); + const { wrapperAnimation, backdropAnimation, contentAnimation } = + currentBreakpoint !== undefined ? createSheetEnterAnimation(opts) : createEnterAnimation(); + + backdropAnimation.addElement(root.querySelector('ion-backdrop')!); + + wrapperAnimation.addElement(root.querySelector('.modal-wrapper')!); + + // The content animation is only added if scrolling is enabled for + // all the breakpoints. + !expandToScroll && contentAnimation?.addElement(baseEl.querySelector('.ion-page')!); + + backdropAnimation.duration(300).easing('ease-out'); + wrapperAnimation.duration(400).easing('cubic-bezier(0.32, 0.68, 0, 1)'); + contentAnimation?.duration(400).easing('cubic-bezier(0.32, 0.68, 0, 1)'); + + const baseAnimation = createAnimation().addElement(baseEl).addAnimation([backdropAnimation, wrapperAnimation]); + + if (contentAnimation) { + baseAnimation.addAnimation(contentAnimation); + } + + return baseAnimation; +}; diff --git a/core/src/components/modal/animations/ionic.leave.ts b/core/src/components/modal/animations/ionic.leave.ts new file mode 100644 index 00000000000..ce7bce765ac --- /dev/null +++ b/core/src/components/modal/animations/ionic.leave.ts @@ -0,0 +1,38 @@ +import { createAnimation } from '@utils/animation/animation'; +import { getElementRoot } from '@utils/helpers'; + +import type { Animation } from '../../../interface'; +import type { ModalAnimationOptions } from '../modal-interface'; + +import { createSheetLeaveAnimation } from './sheet'; + +const createLeaveAnimation = () => { + const backdropAnimation = createAnimation().fromTo('opacity', 'var(--backdrop-opacity)', 0); + + const wrapperAnimation = createAnimation().keyframes([ + { offset: 0, opacity: 0.99, transform: `translateY(0px)` }, + { offset: 1, opacity: 0, transform: 'translateY(40px)' }, + ]); + + return { backdropAnimation, wrapperAnimation }; +}; + +/** + * Md Modal Leave Animation + */ +export const ionicLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptions): Animation => { + const { currentBreakpoint } = opts; + const root = getElementRoot(baseEl); + const { wrapperAnimation, backdropAnimation } = + currentBreakpoint !== undefined ? createSheetLeaveAnimation(opts) : createLeaveAnimation(); + + backdropAnimation.addElement(root.querySelector('ion-backdrop')!); + wrapperAnimation.addElement(root.querySelector('.modal-wrapper')!); + + backdropAnimation.duration(250).easing('ease-in'); + wrapperAnimation.duration(400).easing('cubic-bezier(0.4, 0, 1, 1)'); + + const baseAnimation = createAnimation().addElement(baseEl).addAnimation([backdropAnimation, wrapperAnimation]); + + return baseAnimation; +}; diff --git a/core/src/components/modal/gestures/sheet.ts b/core/src/components/modal/gestures/sheet.ts index 8840e3123be..322bb82b84f 100644 --- a/core/src/components/modal/gestures/sheet.ts +++ b/core/src/components/modal/gestures/sheet.ts @@ -3,7 +3,7 @@ import { createGesture } from '@utils/gesture'; import { clamp, getElementRoot, raf } from '@utils/helpers'; import { FOCUS_TRAP_DISABLE_CLASS } from '@utils/overlays'; -import type { Animation } from '../../../interface'; +import type { Animation, ModalDragEventDetail } from '../../../interface'; import type { GestureDetail } from '../../../utils/gesture'; import { getBackdropValueForSheet } from '../utils'; @@ -53,18 +53,21 @@ export const createSheetGesture = ( getCurrentBreakpoint: () => number, onDismiss: () => void, onBreakpointChange: (breakpoint: number) => void, - staticBackdropOpacity: boolean + usePhysicsBasedGesture: boolean, + onDragStart: () => void, + onDragMove: (detail: ModalDragEventDetail) => void, + onDragEnd: (detail: ModalDragEventDetail) => void ) => { // Defaults for the sheet swipe animation const defaultBackdrop = [ { offset: 0, opacity: 'var(--backdrop-opacity)' }, - { offset: 1, opacity: staticBackdropOpacity ? 'var(--backdrop-opacity)' : 0.01 }, + { offset: 1, opacity: usePhysicsBasedGesture ? 'var(--backdrop-opacity)' : 0.01 }, ]; const customBackdrop = [ { offset: 0, opacity: 'var(--backdrop-opacity)' }, - { offset: 1 - backdropBreakpoint, opacity: staticBackdropOpacity ? 'var(--backdrop-opacity)' : 0 }, - { offset: 1, opacity: staticBackdropOpacity ? 'var(--backdrop-opacity)' : 0 }, + { offset: 1 - backdropBreakpoint, opacity: usePhysicsBasedGesture ? 'var(--backdrop-opacity)' : 0 }, + { offset: 1, opacity: usePhysicsBasedGesture ? 'var(--backdrop-opacity)' : 0 }, ]; const SheetDefaults = { @@ -348,6 +351,8 @@ export const createSheetGesture = ( }); animation.progressStart(true, 1 - currentBreakpoint); + + onDragStart(); }; const onMove = (detail: GestureDetail) => { @@ -424,9 +429,35 @@ export const createSheetGesture = ( offset = clamp(0.0001, processedStep, maxStep); animation.progressStep(offset); + + const snapBreakpoint = usePhysicsBasedGesture + ? calculateVelocitySnapBreakpoint(detail.deltaY, detail.velocityY, detail.currentY) + : calculatePositionSnapBreakpoint(detail.deltaY); + + const eventDetail: ModalDragEventDetail = { + currentY: detail.currentY, + deltaY: detail.deltaY, + velocityY: detail.velocityY, + progress: calculateProgress(detail.currentY), + snapBreakpoint: snapBreakpoint, + }; + + onDragMove(eventDetail); }; const onEnd = (detail: GestureDetail) => { + const snapBreakpoint = usePhysicsBasedGesture + ? calculateVelocitySnapBreakpoint(detail.deltaY, detail.velocityY, detail.currentY) + : calculatePositionSnapBreakpoint(detail.deltaY); + + const eventDetail: ModalDragEventDetail = { + currentY: detail.currentY, + deltaY: detail.deltaY, + velocityY: detail.velocityY, + progress: calculateProgress(detail.currentY), + snapBreakpoint, + }; + /** * If expandToScroll is disabled, we should not allow the moveSheetToBreakpoint * function to be called if the user is trying to swipe content upwards and the content @@ -441,23 +472,13 @@ export const createSheetGesture = ( * swap to moving on drag and if we don't swap back here then the footer will get stuck. */ swapFooterPosition('stationary'); + onDragEnd(eventDetail); + return; } - /** - * When the gesture releases, we need to determine - * the closest breakpoint to snap to. - */ - const velocity = detail.velocityY; - const threshold = (detail.deltaY + velocity * 350) / height; - - const diff = currentBreakpoint - threshold; - const closest = breakpoints.reduce((a, b) => { - return Math.abs(b - diff) < Math.abs(a - diff) ? b : a; - }); - moveSheetToBreakpoint({ - breakpoint: closest, + breakpoint: snapBreakpoint, breakpointOffset: offset, canDismiss: canDismissBlocksGesture, @@ -467,6 +488,8 @@ export const createSheetGesture = ( */ animated: true, }); + + onDragEnd(eventDetail); }; const moveSheetToBreakpoint = (options: MoveSheetToBreakpointOptions) => { @@ -480,6 +503,15 @@ export const createSheetGesture = ( const shouldPreventDismiss = canDismiss && breakpoint === 0; const snapToBreakpoint = shouldPreventDismiss ? currentBreakpoint : breakpoint; + /** + * Detect snap-back behavior: when the snap target is the same as the current breakpoint, + * the user released before crossing the threshold to a new breakpoint. + * Apply different timing and easing for snap-back vs. snap-to-new. + */ + const isSnapBack = snapToBreakpoint === currentBreakpoint; + const duration = usePhysicsBasedGesture ? (isSnapBack ? 300 : 400) : 500; + const easing = isSnapBack ? 'cubic-bezier(0.34, 1.4, 0.64, 1)' : 'cubic-bezier(0.32, 0.68, 0, 1)'; + const shouldRemainOpen = snapToBreakpoint !== 0; currentBreakpoint = 0; @@ -496,13 +528,13 @@ export const createSheetGesture = ( backdropAnimation.keyframes([ { offset: 0, - opacity: staticBackdropOpacity + opacity: usePhysicsBasedGesture ? 'var(--backdrop-opacity)' : `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(1 - breakpointOffset, backdropBreakpoint)})`, }, { offset: 1, - opacity: staticBackdropOpacity + opacity: usePhysicsBasedGesture ? 'var(--backdrop-opacity)' : `calc(var(--backdrop-opacity) * ${getBackdropValueForSheet(snapToBreakpoint, backdropBreakpoint)})`, }, @@ -525,6 +557,13 @@ export const createSheetGesture = ( animation.progressStep(0); } + /** + * Apply the appropriate easing curve for this snap behavior. + */ + if (usePhysicsBasedGesture) { + animation.easing(easing); + } + /** * Gesture should remain disabled until the * snapping animation completes. @@ -622,10 +661,167 @@ export const createSheetGesture = ( }, { oneTimeCallback: true } ) - .progressEnd(1, 0, animated ? 500 : 0); + .progressEnd(1, 0, animated ? duration : 0); }); }; + /** + * Calculates the breakpoint based on the current deltaY. + * This determines where the sheet should snap to when the user releases the + * gesture. + * + * @param deltaY The change in Y position since the gesture started. + * @returns The snap breakpoint value. + */ + const calculatePositionSnapBreakpoint = (deltaY: number): number => { + /** + * Calculates the real-time vertical position of the modal. + * We combine the wrapper's current bounding box position with the + * gesture's deltaY to account for the physical movement during the drag. + */ + const currentY = wrapperEl.getBoundingClientRect().top + deltaY; + /** + * Convert that pixel position back into a 0 to 1 progress value. + */ + const currentProgress = calculateProgress(currentY); + + /** + * Find and return the defined breakpoint that is closest to the + * current progress. + */ + const snapBreakpoint = breakpoints.reduce((a, b) => { + return Math.abs(b - currentProgress) < Math.abs(a - currentProgress) ? b : a; + }); + + return snapBreakpoint; + }; + + /** + * Calculates the snap breakpoint using velocity-based logic. + * This provides a more intuitive and responsive sheet behavior for the Ionic theme. + * + * Rules: + * 1. Fast downward flick (> 500 px/s) always dismisses, regardless of position + * 2. Fast upward flick (> 400 px/s) snaps to the next breakpoint above + * 3. If dragged 40% below current snap point without fast upward flick, dismisses + * 4. Otherwise, falls back to position-based snap (closest breakpoint) + * + * @param deltaY The change in Y position since gesture started + * @param velocityY The velocity in pixels per millisecond + * @param currentY The current Y position of the gesture + * @returns The snap breakpoint value + */ + const calculateVelocitySnapBreakpoint = (deltaY: number, velocityY: number, currentY: number): number => { + // Convert velocity from px/ms to px/s for easier threshold comparison + const velocityYPerSecond = velocityY * 1000; + + // Calculate current progress (0 = fully closed, 1 = fully expanded) + const currentProgress = calculateProgress(currentY); + + // Rule 1: Fast downward flick always dismisses + if (velocityYPerSecond > 500) { + return minBreakpoint; + } + + // Rule 2: Fast upward flick moves to next breakpoint above + if (velocityYPerSecond < -400) { + // Find next breakpoint above current position + const nextBreakpoint = breakpoints.find((bp) => bp > currentProgress); + // If no breakpoint above, stay at max breakpoint + return nextBreakpoint ?? maxBreakpoint; + } + + // Rule 3: 40% dismissal rule (only if not flicking up and 0 breakpoint exists) + if (minBreakpoint === 0 && currentBreakpoint > 0) { + // Calculate how far we've moved below the current snap point + const distanceBelowSnap = currentBreakpoint - currentProgress; + const percentageBelowSnap = distanceBelowSnap / currentBreakpoint; + + // If dragged more than 40% below and not flicking up, dismiss + if (percentageBelowSnap > 0.4 && velocityYPerSecond <= 400) { + return 0; + } + } + + // Rule 4: Fallback to position-based snap (existing logic) + return calculatePositionSnapBreakpoint(deltaY); + }; + + /** + * Calculates the progress of the swipe gesture. + * + * The progress is a value between 0 and 1 that represents how far + * the swipe has progressed towards closing the modal. + * + * A value closer to 1 means the modal is closer to being opened, + * while a value closer to 0 means the modal is closer to being closed. + * + * @param currentY The current Y position of the gesture + * @returns The progress of the sheet gesture + */ + const calculateProgress = (currentY: number): number => { + const minBreakpoint = breakpoints[0]; + const maxBreakpoint = breakpoints[breakpoints.length - 1]; + + /** + * The lowest point the sheet can be dragged to aka the point at which + * the sheet is fully closed. + */ + const maxY = convertBreakpointToY(minBreakpoint); + /** + * The highest point the sheet can be dragged to aka the point at which + * the sheet is fully open. + */ + const minY = convertBreakpointToY(maxBreakpoint); + // The total distance between the fully open and fully closed positions. + const totalDistance = maxY - minY; + // The distance from the current position to the fully closed position. + const distanceFromBottom = maxY - currentY; + /** + * The progress represents how far the sheet is from the bottom relative + * to the total distance. When the user starts swiping up, the progress + * should be close to 1, and when the user has swiped all the way down, + * the progress should be close to 0. + */ + const progress = distanceFromBottom / totalDistance; + // Round to the nearest thousandth to avoid returning very small decimal + const roundedProgress = Math.round(progress * 1000) / 1000; + + return Math.max(0, Math.min(1, roundedProgress)); + }; + + /** + * Converts a breakpoint value (0 to 1) into a pixel Y coordinate + * on the screen. + * + * @param breakpoint The breakpoint value (e.g., 0.5 for half-open) + * @returns The pixel Y coordinate on the screen + */ + const convertBreakpointToY = (breakpoint: number): number => { + const rect = baseEl.getBoundingClientRect(); + const modalHeight = rect.height; + // The bottom of the screen. + const viewportBottom = window.innerHeight; + /** + * The active height is how much of the modal is actually showing + * on the screen for this specific breakpoint. + */ + const activeHeight = modalHeight * breakpoint; + + /** + * To find the Y coordinate, start at the bottom of the screen + * and move up by the active height of the modal. + * + * A breakpoint of 1.0 means the active height is the full modal height + * (fully open). A breakpoint of 0.0 means the active height is 0 + * (fully closed). + * + * Since screen Y coordinates get smaller as you go up, we subtract the + * active height from the viewport bottom. + */ + return viewportBottom - activeHeight; + }; + const gesture = createGesture({ el: wrapperEl, gestureName: 'modalSheet', diff --git a/core/src/components/modal/gestures/swipe-to-close.ts b/core/src/components/modal/gestures/swipe-to-close.ts index 17ec454ff15..2b66e7eac24 100644 --- a/core/src/components/modal/gestures/swipe-to-close.ts +++ b/core/src/components/modal/gestures/swipe-to-close.ts @@ -4,7 +4,7 @@ import { createGesture } from '@utils/gesture'; import { clamp, getElementRoot } from '@utils/helpers'; import { OVERLAY_GESTURE_PRIORITY } from '@utils/overlays'; -import type { Animation } from '../../../interface'; +import type { Animation, ModalDragEventDetail } from '../../../interface'; import type { GestureDetail } from '../../../utils/gesture'; import type { Style as StatusBarStyle } from '../../../utils/native/status-bar'; import { setCardStatusBarDark, setCardStatusBarDefault } from '../utils'; @@ -20,7 +20,10 @@ export const createSwipeToCloseGesture = ( el: HTMLIonModalElement, animation: Animation, statusBarStyle: StatusBarStyle, - onDismiss: () => void + onDismiss: () => void, + onDragStart: () => void, + onDragMove: (detail: ModalDragEventDetail) => void, + onDragEnd: (detail: ModalDragEventDetail) => void ) => { /** * The step value at which a card modal @@ -142,6 +145,8 @@ export const createSwipeToCloseGesture = ( } animation.progressStart(true, isOpen ? 1 : 0); + + onDragStart(); }; const onMove = (detail: GestureDetail) => { @@ -220,6 +225,15 @@ export const createSwipeToCloseGesture = ( } lastStep = clampedStep; + + const eventDetail: ModalDragEventDetail = { + currentY: detail.currentY, + deltaY: detail.deltaY, + velocityY: detail.velocityY, + progress: calculateProgress(el, detail.deltaY), + }; + + onDragMove(eventDetail); }; const onEnd = (detail: GestureDetail) => { @@ -288,6 +302,15 @@ export const createSwipeToCloseGesture = ( } else if (shouldComplete) { onDismiss(); } + + const eventDetail: ModalDragEventDetail = { + currentY: detail.currentY, + deltaY: detail.deltaY, + velocityY: detail.velocityY, + progress: calculateProgress(el, detail.deltaY), + }; + + onDragEnd(eventDetail); }; const gesture = createGesture({ @@ -307,3 +330,43 @@ export const createSwipeToCloseGesture = ( const computeDuration = (remaining: number, velocity: number) => { return clamp(400, remaining / Math.abs(velocity * 1.1), 500); }; + +/** + * Calculates the progress of the swipe gesture. + * + * The progress is a value between 0 and 1 that represents how far + * the swipe has progressed towards closing the modal. + * + * A value closer to 1 means the modal is closer to being opened, + * while a value closer to 0 means the modal is closer to being closed. + * + * @param el The modal + * @param deltaY The change in Y position (positive when dragging down, negative when dragging up) + * @returns The progress of the swipe gesture + */ +const calculateProgress = (el: HTMLIonModalElement, deltaY: number): number => { + const windowHeight = window.innerHeight; + // Position when fully open + const modalTop = el.getBoundingClientRect().top; + /** + * The distance between the top of the modal and the bottom of the screen + * is the total distance the modal needs to travel to be fully closed. + */ + const totalDistance = windowHeight - modalTop; + /** + * The pull percentage is how far the user has swiped compared to the total + * distance needed to close the modal. + */ + const pullPercentage = deltaY / totalDistance; + /** + * The progress is the inverse of the pull percentage because + * when the user starts swiping up, the progress should be close to 1, + * and when the user has swiped all the way down, the progress should be + * close to 0. + */ + const progress = 1 - pullPercentage; + // Round to the nearest thousandth to avoid returning very small decimal + const roundedProgress = Math.round(progress * 1000) / 1000; + + return Math.max(0, Math.min(1, roundedProgress)); +}; diff --git a/core/src/components/modal/modal-interface.ts b/core/src/components/modal/modal-interface.ts index 29cc7111568..071a50fa869 100644 --- a/core/src/components/modal/modal-interface.ts +++ b/core/src/components/modal/modal-interface.ts @@ -44,3 +44,29 @@ export interface ModalCustomEvent extends CustomEvent { * The behavior setting for modals when the handle is pressed. */ export type ModalHandleBehavior = 'none' | 'cycle'; + +export interface ModalDragEventDetail { + /** + * The current Y coordinate of the drag event. + */ + currentY: number; + /** + * The change in Y coordinate since the last drag event. + */ + deltaY: number; + /** + * The velocity of the drag event in the Y direction. + */ + velocityY: number; + /** + * The progress of the drag event, represented as a value between 0 and 1. + * A value of 0 means the modal is at its lowest point (fully closed), + * while a value of 1 means the modal is at its highest point (fully open). + */ + progress: number; + /** + * The breakpoint that the sheet will snap to if the user releases + * the gesture. + */ + snapBreakpoint?: number; +} diff --git a/core/src/components/modal/modal.ionic.scss b/core/src/components/modal/modal.ionic.scss index 30306fce969..ec46628a9bc 100644 --- a/core/src/components/modal/modal.ionic.scss +++ b/core/src/components/modal/modal.ionic.scss @@ -8,7 +8,7 @@ --background: #{globals.$ion-bg-surface-default}; --box-shadow: #{globals.$ion-elevation-3}; // Backdrop opacity is 1 because the backdrop's background color has an alpha value - --backdrop-opacity: 1; + --backdrop-opacity: 0.7; color: globals.$ion-text-default; } diff --git a/core/src/components/modal/modal.native.scss b/core/src/components/modal/modal.native.scss index 8a8a06e6e5e..0cfab3f26fa 100644 --- a/core/src/components/modal/modal.native.scss +++ b/core/src/components/modal/modal.native.scss @@ -58,7 +58,12 @@ /** * Ensure that the sheet modal does not * completely cover the content. + * + * --ion-modal-offset-top is an internal property set by modal.tsx + * with the resolved root safe-area-top pixel value. This decouples + * the height calculation from --ion-safe-area-top (which is zeroed + * for sheet modals to prevent header double-padding). */ :host(.modal-sheet) { - --height: calc(100% - (var(--ion-safe-area-top) + 10px)); + --height: calc(100% - (var(--ion-modal-offset-top, 0px) + 10px)); } diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 1f8524de0d1..710d76acc94 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -35,6 +35,7 @@ import type { import { KEYBOARD_DID_OPEN } from '../../utils/keyboard/keyboard'; import type { OverlayEventDetail } from '../../utils/overlays-interface'; +import { ionicEnterAnimation } from './animations/ionic.enter'; import { iosEnterAnimation } from './animations/ios.enter'; import { iosLeaveAnimation } from './animations/ios.leave'; import { portraitToLandscapeTransition, landscapeToPortraitTransition } from './animations/ios.transition'; @@ -43,7 +44,16 @@ import { mdLeaveAnimation } from './animations/md.leave'; import type { MoveSheetToBreakpointOptions } from './gestures/sheet'; import { createSheetGesture } from './gestures/sheet'; import { createSwipeToCloseGesture, SwipeToCloseDefaults } from './gestures/swipe-to-close'; -import type { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from './modal-interface'; +import type { ModalBreakpointChangeEventDetail, ModalHandleBehavior, ModalDragEventDetail } from './modal-interface'; +import { + getInitialSafeAreaConfig, + getPositionBasedSafeAreaConfig, + applySafeAreaOverrides, + clearSafeAreaOverrides, + getRootSafeAreaTop, + hasCustomModalDimensions, + type ModalSafeAreaContext, +} from './safe-area-utils'; import { setCardStatusBarDark, setCardStatusBarDefault } from './utils'; // TODO(FW-2832): types @@ -74,6 +84,11 @@ export class Modal implements ComponentInterface, OverlayInterface { private coreDelegate: FrameworkDelegate = CoreDelegate(); private sheetTransition?: Promise; @State() private isSheetModal = false; + /** + * The breakpoint value that has been committed for a sheet modal. + * This represents the modal's resting state when it is not being dragged + * or animating toward a new position. + */ private currentBreakpoint?: number; private wrapperEl?: HTMLElement; private backdropEl?: HTMLIonBackdropElement; @@ -278,14 +293,33 @@ export class Modal implements ComponentInterface, OverlayInterface { @Listen('resize', { target: 'window' }) onWindowResize() { - // Only handle resize for iOS card modals when no custom animations are provided - if (getIonMode(this) !== 'ios' || !this.presentingElement || this.enterAnimation || this.leaveAnimation) { - return; - } + if (!this.presented) return; clearTimeout(this.resizeTimeout); this.resizeTimeout = setTimeout(() => { - this.handleViewTransition(); + const context = this.getSafeAreaContext(); + + // iOS card modals: handle portrait/landscape view transitions + if (context.isCardModal && !this.enterAnimation && !this.leaveAnimation) { + this.handleViewTransition(); + } + + // Sheet modals: re-compute the internal offset property since safe-area + // values may change on device rotation (e.g., portrait notch vs landscape). + if (context.isSheetModal) { + this.updateSheetOffsetTop(); + } + + // Regular (non-sheet, non-card) modals: update safe-area overrides + // since the viewport may have crossed the centered-dialog breakpoint. + if (!context.isSheetModal && !context.isCardModal) { + this.updateSafeAreaOverrides(); + + // Re-evaluate fullscreen safe-area padding: clear first, then re-apply. + const { contentEl, hasFooter } = this.findContentAndFooter(); + this.clearContentSafeAreaPadding(contentEl); + this.applyFullscreenSafeAreaTo(contentEl, hasFooter); + } }, 50); // Debounce to avoid excessive calls during active resizing } @@ -401,6 +435,21 @@ export class Modal implements ComponentInterface, OverlayInterface { */ @Event() ionMount!: EventEmitter; + /** + * Event that is emitted when the sheet modal or card modal gesture starts. + */ + @Event() ionDragStart!: EventEmitter; + + /** + * Event that is emitted when the sheet modal or card modal gesture moves. + */ + @Event() ionDragMove!: EventEmitter; + + /** + * Event that is emitted when the sheet modal or card modal gesture ends. + */ + @Event() ionDragEnd!: EventEmitter; + breakpointsChanged(breakpoints: number[] | undefined) { if (breakpoints !== undefined) { this.sortedBreakpoints = breakpoints.sort((a, b) => a - b); @@ -417,6 +466,9 @@ export class Modal implements ComponentInterface, OverlayInterface { this.triggerController.removeClickListener(); this.cleanupViewTransitionListener(); this.cleanupParentRemovalObserver(); + // Also called in dismiss() β€” intentional dual cleanup covers both + // dismiss-then-remove and direct DOM removal without dismiss. + this.cleanupSafeAreaOverrides(); } componentWillLoad() { @@ -605,6 +657,13 @@ export class Modal implements ComponentInterface, OverlayInterface { writeTask(() => this.el.classList.add('show-modal')); + // Recalculate isSheetModal before safe-area setup because framework + // bindings (e.g., Angular) may not have been applied when componentWillLoad ran. + this.isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined; + + // Set initial safe-area overrides before animation + this.setInitialSafeAreaOverrides(); + const hasCardModal = presentingElement !== undefined; /** @@ -618,7 +677,7 @@ export class Modal implements ComponentInterface, OverlayInterface { setCardStatusBarDark(); } - await present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, { + await present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation, ionicEnterAnimation, { presentingEl: presentingElement, currentBreakpoint: this.initialBreakpoint, backdropBreakpoint: this.backdropBreakpoint, @@ -626,6 +685,12 @@ export class Modal implements ComponentInterface, OverlayInterface { staticBackdropOpacity: getIonTheme(this) === 'ionic', }); + // Update safe-area based on actual position after animation + this.updateSafeAreaOverrides(); + + // Apply fullscreen safe-area padding if needed + this.applyFullscreenSafeArea(); + /* tslint:disable-next-line */ if (typeof window !== 'undefined') { /** @@ -658,14 +723,7 @@ export class Modal implements ComponentInterface, OverlayInterface { window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback); } - /** - * Recalculate isSheetModal because framework bindings (e.g., Angular) - * may not have been applied when componentWillLoad ran. - */ - const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined; - this.isSheetModal = isSheetModal; - - if (isSheetModal) { + if (this.isSheetModal) { this.initSheetGesture(); } else if (hasCardModal) { this.initSwipeToClose(); @@ -704,33 +762,15 @@ export class Modal implements ComponentInterface, OverlayInterface { const statusBarStyle = this.statusBarStyle ?? StatusBarStyle.Default; - this.gesture = createSwipeToCloseGesture(el, ani, statusBarStyle, () => { - /** - * While the gesture animation is finishing - * it is possible for a user to tap the backdrop. - * This would result in the dismiss animation - * being played again. Typically this is avoided - * by setting `presented = false` on the overlay - * component; however, we cannot do that here as - * that would prevent the element from being - * removed from the DOM. - */ - this.gestureAnimationDismissing = true; - - /** - * Reset the status bar style as the dismiss animation - * starts otherwise the status bar will be the wrong - * color for the duration of the dismiss animation. - * The dismiss method does this as well, but - * in this case it's only called once the animation - * has finished. - */ - setCardStatusBarDefault(this.statusBarStyle); - this.animation!.onFinish(async () => { - await this.dismiss(undefined, GESTURE); - this.gestureAnimationDismissing = false; - }); - }); + this.gesture = createSwipeToCloseGesture( + el, + ani, + statusBarStyle, + () => this.cardOnDismiss(), + () => this.onDragStart(), + (detail: ModalDragEventDetail) => this.onDragMove(detail), + (detail: ModalDragEventDetail) => this.onDragEnd(detail) + ); this.gesture.enable(true); } @@ -769,7 +809,10 @@ export class Modal implements ComponentInterface, OverlayInterface { this.ionBreakpointDidChange.emit({ breakpoint }); } }, - getIonTheme(this) === 'ionic' + getIonTheme(this) === 'ionic', + () => this.onDragStart(), + (detail: ModalDragEventDetail) => this.onDragMove(detail), + (detail: ModalDragEventDetail) => this.onDragEnd(detail) ); this.gesture = gesture; @@ -883,6 +926,34 @@ export class Modal implements ComponentInterface, OverlayInterface { }); } + private cardOnDismiss() { + /** + * While the gesture animation is finishing + * it is possible for a user to tap the backdrop. + * This would result in the dismiss animation + * being played again. Typically this is avoided + * by setting `presented = false` on the overlay + * component; however, we cannot do that here as + * that would prevent the element from being + * removed from the DOM. + */ + this.gestureAnimationDismissing = true; + + /** + * Reset the status bar style as the dismiss animation + * starts otherwise the status bar will be the wrong + * color for the duration of the dismiss animation. + * The dismiss method does this as well, but + * in this case it's only called once the animation + * has finished. + */ + setCardStatusBarDefault(this.statusBarStyle); + this.animation!.onFinish(async () => { + await this.dismiss(undefined, GESTURE); + this.gestureAnimationDismissing = false; + }); + } + /** * Dismiss the modal overlay after it has been presented. * This is a no-op if the overlay has not been presented yet. If you want @@ -899,6 +970,10 @@ export class Modal implements ComponentInterface, OverlayInterface { return false; } + // Cancel any pending resize timeout to prevent stale updates during dismiss + clearTimeout(this.resizeTimeout); + this.resizeTimeout = undefined; + /** * Because the canDismiss check below is async, * we need to claim a lock before the check happens, @@ -970,6 +1045,7 @@ export class Modal implements ComponentInterface, OverlayInterface { } this.cleanupViewTransitionListener(); this.cleanupParentRemovalObserver(); + this.cleanupSafeAreaOverrides(); this.cleanupChildRoutePassthrough(); } @@ -1146,6 +1222,11 @@ export class Modal implements ComponentInterface, OverlayInterface { } private handleViewTransition() { + // Only run view transitions when the modal is presented + if (!this.presented) { + return; + } + const isPortrait = window.innerWidth < 768; // Only transition if view state actually changed @@ -1191,6 +1272,10 @@ export class Modal implements ComponentInterface, OverlayInterface { transitionAnimation.play().then(() => { this.viewTransitionAnimation = undefined; + // Wait for a layout pass after the transition so getBoundingClientRect() + // in getPositionBasedSafeAreaConfig() reflects the new dimensions. + raf(() => this.updateSafeAreaOverrides()); + // After orientation transition, recreate the swipe-to-close gesture // with updated animation that reflects the new presenting element state this.reinitSwipeToClose(); @@ -1360,6 +1445,177 @@ export class Modal implements ComponentInterface, OverlayInterface { this.parentRemovalObserver = undefined; } + private onDragStart() { + this.ionDragStart.emit(); + } + + private onDragMove(detail: ModalDragEventDetail) { + this.ionDragMove.emit(detail); + } + + private onDragEnd(detail: ModalDragEventDetail) { + this.ionDragEnd.emit(detail); + } + + /** + * Creates the context object for safe-area utilities. + * + * `hasCustomDimensions` is only set by `setInitialSafeAreaOverrides()` + * because it is only read by `getInitialSafeAreaConfig()`. Other callers + * (resize handler, post-animation update, fullscreen-padding apply) would + * pay a `getComputedStyle()` cost for a value they never consult. + */ + private getSafeAreaContext(): ModalSafeAreaContext { + return { + isSheetModal: this.isSheetModal, + isCardModal: this.presentingElement !== undefined && getIonMode(this) === 'ios', + presentingElement: this.presentingElement, + breakpoints: this.breakpoints, + currentBreakpoint: this.currentBreakpoint, + }; + } + + /** + * Sets initial safe-area overrides before modal animation. + * Called in present() before animation starts. + * + * For sheet modals, the SCSS --height formula uses --ion-modal-offset-top + * (an internal property) instead of --ion-safe-area-top. We resolve the + * root safe-area-top to pixels and set --ion-modal-offset-top, decoupling + * the height calculation from --ion-safe-area-top (which is zeroed for + * sheets to prevent header content from getting double-offset padding). + */ + private setInitialSafeAreaOverrides(): void { + const context: ModalSafeAreaContext = { + ...this.getSafeAreaContext(), + hasCustomDimensions: hasCustomModalDimensions(this.el), + }; + const safeAreaConfig = getInitialSafeAreaConfig(context); + applySafeAreaOverrides(this.el, safeAreaConfig); + + // Set the internal offset property with the resolved root safe-area-top value + if (context.isSheetModal) { + this.updateSheetOffsetTop(); + } + } + + /** + * Resolves the current root --ion-safe-area-top value and sets the + * internal --ion-modal-offset-top property on the host element. + * Called on present and on resize (e.g., device rotation changes safe-area). + */ + private updateSheetOffsetTop(): void { + const safeAreaTop = getRootSafeAreaTop(); + this.el.style.setProperty('--ion-modal-offset-top', `${safeAreaTop}px`); + } + + /** + * Updates safe-area overrides during dynamic state changes. + * Called after animations, during gestures, and on orientation changes. + */ + private updateSafeAreaOverrides(): void { + const { wrapperEl, el } = this; + const context = this.getSafeAreaContext(); + + // Sheet modals: safe-area is fully determined at presentation time + // (top is always 0px, height is frozen). Nothing to update. + if (context.isSheetModal) return; + + // Card modals have fixed safe-area requirements set by initial prediction. + if (context.isCardModal) return; + + // wrapperEl is required for position-based detection below + if (!wrapperEl) return; + + // Regular modals: use position-based detection to correctly handle both + // fullscreen modals and centered dialogs with custom dimensions. + const safeAreaConfig = getPositionBasedSafeAreaConfig(wrapperEl); + applySafeAreaOverrides(el, safeAreaConfig); + } + + /** + * Applies safe-area-bottom scroll padding to ion-content inside + * fullscreen modals that have no ion-footer. This prevents content + * from being hidden behind the system navigation bar while keeping + * the modal background edge-to-edge (no visible gap). + */ + private applyFullscreenSafeArea(): void { + const context = this.getSafeAreaContext(); + if (context.isSheetModal || context.isCardModal) return; + + const { contentEl, hasFooter } = this.findContentAndFooter(); + this.applyFullscreenSafeAreaTo(contentEl, hasFooter); + } + + /** + * Sets --ion-content-safe-area-padding-bottom on the given ion-content + * when no footer is present, so ion-content's .inner-scroll includes + * safe-area-bottom in its scroll padding. This keeps the modal background + * edge-to-edge while ensuring content scrolls clear of the system nav bar. + * + * --ion-content-safe-area-padding-bottom is an internal CSS property used + * only by this code path. It is not part of ion-content's public API and + * should not be set by consumers. The default of 0px makes it a no-op + * when unset, which is the expected state for ion-content used outside of + * a fullscreen modal without a footer. + */ + private applyFullscreenSafeAreaTo(contentEl: HTMLElement | null, hasFooter: boolean): void { + // Only apply for standard Ionic layouts (has ion-content but no + // ion-footer). When a footer is present it handles its own safe-area + // padding. Custom modals with raw HTML are developer-controlled. + if (!contentEl || hasFooter) return; + + contentEl.style.setProperty('--ion-content-safe-area-padding-bottom', 'var(--ion-safe-area-bottom, 0px)'); + } + + /** + * Removes the internal --ion-content-safe-area-padding-bottom property + * from an already-located ion-content. Callers do their own + * findContentAndFooter() so they can also read hasFooter if needed. + */ + private clearContentSafeAreaPadding(contentEl: HTMLElement | null): void { + if (!contentEl) return; + contentEl.style.removeProperty('--ion-content-safe-area-padding-bottom'); + } + + /** + * Finds ion-content and ion-footer among direct children and one level of + * grandchildren (for wrapped components like ). + * + * Intentionally does NOT use findIonContent() or querySelector() because + * those search the full subtree and would match ion-content inside nested + * routes/pages. We only want direct slot children (+ one wrapper level). + * + * Uses a manual loop instead of querySelector(':scope > ...') because + * Stencil's mock-doc (used in spec tests) does not support :scope. + */ + private findContentAndFooter(): { contentEl: HTMLElement | null; hasFooter: boolean } { + let contentEl: HTMLElement | null = null; + let hasFooter = false; + for (const child of Array.from(this.el.children)) { + if (child.tagName === 'ION-CONTENT') contentEl = child as HTMLElement; + if (child.tagName === 'ION-FOOTER') hasFooter = true; + for (const grandchild of Array.from(child.children)) { + if (grandchild.tagName === 'ION-CONTENT' && !contentEl) contentEl = grandchild as HTMLElement; + if (grandchild.tagName === 'ION-FOOTER') hasFooter = true; + } + } + return { contentEl, hasFooter }; + } + + /** + * Clears all safe-area overrides and padding. + */ + private cleanupSafeAreaOverrides(): void { + clearSafeAreaOverrides(this.el); + + // Remove internal sheet offset property + this.el.style.removeProperty('--ion-modal-offset-top'); + + const { contentEl } = this.findContentAndFooter(); + this.clearContentSafeAreaPadding(contentEl); + } + render() { const { handle, diff --git a/core/src/components/modal/safe-area-utils.ts b/core/src/components/modal/safe-area-utils.ts new file mode 100644 index 00000000000..59ae3a4fdb5 --- /dev/null +++ b/core/src/components/modal/safe-area-utils.ts @@ -0,0 +1,233 @@ +import { win } from '@utils/browser'; +import { raf } from '@utils/helpers'; + +type SafeAreaValue = '0px' | 'inherit'; + +/** + * Configuration for safe-area CSS custom properties. + * Each direction can inherit the root safe-area value or be zeroed out. + */ +export interface SafeAreaConfig { + top: SafeAreaValue; + bottom: SafeAreaValue; + left: SafeAreaValue; + right: SafeAreaValue; +} + +/** + * Context information about the modal used to determine safe-area behavior. + */ +export interface ModalSafeAreaContext { + isSheetModal: boolean; + isCardModal: boolean; + presentingElement?: HTMLElement; + breakpoints?: number[]; + currentBreakpoint?: number; + /** + * Only consulted by `getInitialSafeAreaConfig()`. Callers that only use the + * context for non-initial paths can omit this. See `hasCustomModalDimensions()`. + */ + hasCustomDimensions?: boolean; +} + +/** + * These thresholds match the SCSS media query breakpoints in modal.vars.scss + * that trigger the centered dialog layout (non-fullscreen modal). + * + * SCSS defines two height breakpoints: $modal-inset-min-height-small (600px) + * and $modal-inset-min-height-large (768px). We use the smaller one because + * that's the threshold where the modal transitions from fullscreen to centered + * dialog β€” the larger breakpoint only increases the dialog's height. + */ +const MODAL_INSET_MIN_WIDTH = 768; +const MODAL_INSET_MIN_HEIGHT = 600; +const EDGE_THRESHOLD = 5; + +/** + * CSS values for `--width` / `--height` that are treated as fullscreen + * (modal touches the corresponding screen edges). Empty string means the + * property was not overridden. See `hasCustomModalDimensions()`. + */ +const FULLSCREEN_SIZE_VALUES = new Set(['', '100%', '100vw', '100vh', '100dvw', '100dvh', '100svw', '100svh']); + +/** + * Cache for resolved root safe-area-top value, invalidated once per frame. + */ +let cachedRootSafeAreaTop: number | null = null; +let cacheInvalidationScheduled = false; + +/** + * Determines if the current viewport meets the CSS media query conditions + * that cause regular modals to render as centered dialogs instead of fullscreen. + * Matches: @media (min-width: 768px) and (min-height: 600px) + */ +const isCenteredDialogViewport = (): boolean => { + if (!win) return false; + return win.matchMedia(`(min-width: ${MODAL_INSET_MIN_WIDTH}px) and (min-height: ${MODAL_INSET_MIN_HEIGHT}px)`) + .matches; +}; + +/** + * Resolves the current root --ion-safe-area-top value to pixels. + * Uses a temporary element because getComputedStyle on :root returns + * the declared value of custom properties (e.g. "env(safe-area-inset-top)") + * rather than a resolved number. + * + * Results are cached for the current frame to avoid repeated reflows. + */ +export const getRootSafeAreaTop = (): number => { + if (cachedRootSafeAreaTop !== null) { + return cachedRootSafeAreaTop; + } + + const doc = win?.document; + if (!doc?.body) { + return 0; + } + + const el = doc.createElement('div'); + el.style.cssText = + 'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' + 'padding-top:var(--ion-safe-area-top,0px);'; + doc.body.appendChild(el); + const value = parseFloat(getComputedStyle(el).paddingTop) || 0; + el.remove(); + + cachedRootSafeAreaTop = value; + if (!cacheInvalidationScheduled) { + cacheInvalidationScheduled = true; + raf(() => { + cachedRootSafeAreaTop = null; + cacheInvalidationScheduled = false; + }); + } + + return value; +}; + +/** + * True when the modal host declares BOTH a non-fullscreen `--width` AND a + * non-fullscreen `--height` (i.e. a centered-dialog-like modal that doesn't + * touch any screen edge). + * + * The conservative "both axes" check avoids mis-zeroing safe-area for + * partial-custom modals where the modal still touches top/bottom edges + * (e.g. only `--width` overridden). Partial cases fall through to the + * existing position-based post-animation correction. + */ +export const hasCustomModalDimensions = (hostEl: HTMLElement): boolean => { + const styles = getComputedStyle(hostEl); + const width = styles.getPropertyValue('--width').trim(); + const height = styles.getPropertyValue('--height').trim(); + return !FULLSCREEN_SIZE_VALUES.has(width) && !FULLSCREEN_SIZE_VALUES.has(height); +}; + +/** + * Returns the initial safe-area configuration based on modal type. + * This is called before animation starts and uses configuration-based prediction. + * + * @param context - Modal context information + * @returns SafeAreaConfig with initial safe-area values + */ +export const getInitialSafeAreaConfig = (context: ModalSafeAreaContext): SafeAreaConfig => { + const { isSheetModal, isCardModal } = context; + + // Sheet modals always zero top safe-area. The sheet height offset from the + // top edge is handled by --ion-modal-offset-top (set in modal.tsx) using + // the resolved root value, so --ion-safe-area-top is never needed for + // height calculation. Keeping it at 0px prevents header content from + // getting double-offset padding. + if (isSheetModal) { + return { + top: '0px', + bottom: 'inherit', + left: '0px', + right: '0px', + }; + } + + // Card modals need safe-area for height calculation. + // Note: isCardModal is already gated on mode === 'ios' by the caller. + if (isCardModal) { + return { + top: 'inherit', + bottom: 'inherit', + left: '0px', + right: '0px', + }; + } + + // On viewports that meet the centered dialog media query breakpoints, + // regular modals render as centered dialogs (not fullscreen), so they + // don't touch any screen edges and don't need safe-area insets. Also + // applies to phone viewports when the modal declares custom --width and + // --height; these don't touch screen edges either, so the initial + // prediction must be zero to avoid a post-animation correction flash. + if (isCenteredDialogViewport() || context.hasCustomDimensions) { + return { + top: '0px', + bottom: '0px', + left: '0px', + right: '0px', + }; + } + + // Fullscreen modals on phone - inherit all safe areas + return { + top: 'inherit', + bottom: 'inherit', + left: 'inherit', + right: 'inherit', + }; +}; + +/** + * Returns safe-area configuration based on actual modal position. + * Detects which edges the modal overlaps with and only applies safe-area to those edges. + * + * Note: On Android edge-to-edge (API 36+), getBoundingClientRect() may report + * inconsistent values. Sheet and card modals avoid this by using configuration-based + * prediction instead. Regular modals use coordinate detection which works reliably + * on web and iOS; Android edge-to-edge may need a configuration-based fallback + * once a reliable detection mechanism is available. + * + * @param wrapperEl - The modal wrapper element to measure + * @returns SafeAreaConfig based on position + */ +export const getPositionBasedSafeAreaConfig = (wrapperEl: HTMLElement): SafeAreaConfig => { + const rect = wrapperEl.getBoundingClientRect(); + const vh = win?.innerHeight ?? 0; + const vw = win?.innerWidth ?? 0; + + // Only apply safe-area to sides where modal overlaps with screen edge + return { + top: rect.top <= EDGE_THRESHOLD ? 'inherit' : '0px', + bottom: rect.bottom >= vh - EDGE_THRESHOLD ? 'inherit' : '0px', + left: rect.left <= EDGE_THRESHOLD ? 'inherit' : '0px', + right: rect.right >= vw - EDGE_THRESHOLD ? 'inherit' : '0px', + }; +}; + +/** + * Applies safe-area CSS custom property overrides to the modal host element. + * + * @param hostEl - The modal host element (ion-modal) + * @param config - Safe-area configuration to apply + */ +export const applySafeAreaOverrides = (hostEl: HTMLElement, config: SafeAreaConfig): void => { + hostEl.style.setProperty('--ion-safe-area-top', config.top); + hostEl.style.setProperty('--ion-safe-area-bottom', config.bottom); + hostEl.style.setProperty('--ion-safe-area-left', config.left); + hostEl.style.setProperty('--ion-safe-area-right', config.right); +}; + +/** + * Clears safe-area CSS custom property overrides from the modal host element. + * + * @param hostEl - The modal host element (ion-modal) + */ +export const clearSafeAreaOverrides = (hostEl: HTMLElement): void => { + hostEl.style.removeProperty('--ion-safe-area-top'); + hostEl.style.removeProperty('--ion-safe-area-bottom'); + hostEl.style.removeProperty('--ion-safe-area-left'); + hostEl.style.removeProperty('--ion-safe-area-right'); +}; diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Chrome-linux.png index 952c4a7d15a..ab9bc7cc88a 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Firefox-linux.png index c424abd7209..863db27b243 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Safari-linux.png index 48b443029fa..63413bc8890 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Chrome-linux.png index 21c0343f04f..6209284bdda 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Firefox-linux.png index b78c67efcd9..8b6cc73ad5a 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Safari-linux.png index a408c4c240f..a135451d5b2 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ionic-md-rtl-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Chrome-linux.png index 4fd10836a68..c9e68a42feb 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Chrome-linux.png index c7537dbaab6..316f8f6d0a4 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Chrome-linux.png index dc5cb4dd665..63481f2bb8f 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Chrome-linux.png index 067d3c17883..bc008a85d9c 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Chrome-linux.png index f18b5a944ee..5b0099508e6 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Firefox-linux.png index d1013377846..d25a5ace276 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Safari-linux.png index d3f7b22e276..a72a1b7d6b5 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Chrome-linux.png index 52b6cefadd5..48cee8d6079 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Firefox-linux.png index f72f28998d0..6e88b7e4c85 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Safari-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Safari-linux.png index 2e00430ae35..cb3e519b4e9 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ionic-md-rtl-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Chrome-linux.png index d3fa7d05fe8..1e09394c4c7 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Chrome-linux.png index 2fb05bf3bab..0e0848788b9 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Chrome-linux.png index 855b5746c77..ae4a3596b04 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Chrome-linux.png b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Chrome-linux.png index 5e245e311d5..07af936b3a9 100644 Binary files a/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/modal/test/basic/modal.e2e.ts-snapshots/modal-basic-present-tablet-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/card-viewport-resize/modal.e2e.ts b/core/src/components/modal/test/card-viewport-resize/modal.e2e.ts new file mode 100644 index 00000000000..3d7adc849c7 --- /dev/null +++ b/core/src/components/modal/test/card-viewport-resize/modal.e2e.ts @@ -0,0 +1,176 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('card modal: viewport resize'), () => { + test.beforeEach(async ({ page }) => { + // Start in portrait mode (mobile) + await page.setViewportSize({ width: 375, height: 667 }); + + await page.setContent( + ` + +
+ + + Card Viewport Resize Test + + + +

This page tests that viewport resize does not trigger card modal animation when modal is closed.

+ Open Card Modal + + + + Card Modal + + Close + + + + +

Modal content

+
+
+
+
+
+ + + `, + config + ); + }); + + test('should not animate presenting element when viewport resizes and modal is closed', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30679', + }); + + const mainPage = page.locator('#main-page'); + + // Verify the presenting element has no transform initially + const initialTransform = await mainPage.evaluate((el) => { + return window.getComputedStyle(el).transform; + }); + expect(initialTransform).toBe('none'); + + // Resize from portrait to landscape (crossing the 768px threshold) + await page.setViewportSize({ width: 900, height: 375 }); + + // Wait for the debounced resize handler (50ms) plus some buffer + await page.waitForTimeout(150); + + // The presenting element should still have no transform + // If the bug exists, it would have scale(0.93) or similar applied + const afterResizeTransform = await mainPage.evaluate((el) => { + return window.getComputedStyle(el).transform; + }); + expect(afterResizeTransform).toBe('none'); + }); + + test('should not animate presenting element when resizing multiple times with modal closed', async ({ page }) => { + const mainPage = page.locator('#main-page'); + + // Multiple resize cycles should not trigger the animation + for (let i = 0; i < 3; i++) { + // Portrait to landscape + await page.setViewportSize({ width: 900, height: 375 }); + await page.waitForTimeout(150); + + let transform = await mainPage.evaluate((el) => { + return window.getComputedStyle(el).transform; + }); + expect(transform).toBe('none'); + + // Landscape to portrait + await page.setViewportSize({ width: 375, height: 667 }); + await page.waitForTimeout(150); + + transform = await mainPage.evaluate((el) => { + return window.getComputedStyle(el).transform; + }); + expect(transform).toBe('none'); + } + }); + + test('should still animate presenting element correctly when modal is open and viewport resizes', async ({ + page, + }) => { + const mainPage = page.locator('#main-page'); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + // Open the modal + await page.click('#open-modal'); + await ionModalDidPresent.next(); + + // When modal is open in portrait, presenting element should be transformed + let transform = await mainPage.evaluate((el) => { + return window.getComputedStyle(el).transform; + }); + // The presenting element should have a scale transform when modal is open + expect(transform).not.toBe('none'); + + // Resize to landscape while modal is open + await page.setViewportSize({ width: 900, height: 375 }); + await page.waitForTimeout(150); + + // The modal transitions correctly - in landscape mode the presenting element + // should have different (or no) transform than portrait + transform = await mainPage.evaluate((el) => { + return window.getComputedStyle(el).transform; + }); + + // Note: The exact transform depends on the landscape handling + // The main point is that when modal IS open, the transition should work + // This test just ensures we don't break existing functionality + }); + + test('presenting element should return to normal after modal is dismissed', async ({ page }) => { + const mainPage = page.locator('#main-page'); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + // Open the modal + await page.click('#open-modal'); + await ionModalDidPresent.next(); + + // Close the modal + await page.click('#close-modal'); + await ionModalDidDismiss.next(); + + // Wait for animations to complete + await page.waitForTimeout(500); + + // The presenting element should be back to normal + const transform = await mainPage.evaluate((el) => { + return window.getComputedStyle(el).transform; + }); + expect(transform).toBe('none'); + + // Now resize the viewport - should not trigger animation + await page.setViewportSize({ width: 900, height: 375 }); + await page.waitForTimeout(150); + + const afterResizeTransform = await mainPage.evaluate((el) => { + return window.getComputedStyle(el).transform; + }); + expect(afterResizeTransform).toBe('none'); + }); + }); +}); diff --git a/core/src/components/modal/test/card/index.html b/core/src/components/modal/test/card/index.html index 363c779bb14..c47f5bda425 100644 --- a/core/src/components/modal/test/card/index.html +++ b/core/src/components/modal/test/card/index.html @@ -40,6 +40,7 @@ +

iOS only

@@ -50,6 +51,7 @@ > Card Modal Custom Radius +
@@ -162,6 +164,24 @@ const modal = await createModal(presentingEl, opts); await modal.present(); } + + async function dragEvents() { + const modal = await createModal(document.querySelectorAll('.ion-page')[1], { id: 'drag-events' }); + + modal.addEventListener('ionDragStart', (event) => { + console.log('Drag started'); + }); + + modal.addEventListener('ionDragMove', (event) => { + console.log('Drag moved', event.detail); + }); + + modal.addEventListener('ionDragEnd', (event) => { + console.log('Drag ended', event.detail); + }); + + await modal.present(); + } diff --git a/core/src/components/modal/test/card/modal-card.e2e.ts b/core/src/components/modal/test/card/modal-card.e2e.ts index 56cd7c5b4d8..8df6161ec70 100644 --- a/core/src/components/modal/test/card/modal-card.e2e.ts +++ b/core/src/components/modal/test/card/modal-card.e2e.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { configs, test } from '@utils/test/playwright'; +import { configs, dragElementBy, test } from '@utils/test/playwright'; import { CardModalPage } from '../fixtures'; @@ -95,4 +95,50 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c }); }); }); + + test.describe(title('card modal: drag events'), () => { + test('should emit ionDragStart, ionDragMove, and ionDragEnd events', async ({ page }) => { + await page.goto('/src/components/modal/test/card', config); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#drag-events'); + await ionModalDidPresent.next(); + + const ionDragStart = await page.spyOnEvent('ionDragStart'); + const ionDragMove = await page.spyOnEvent('ionDragMove'); + const ionDragEnd = await page.spyOnEvent('ionDragEnd'); + + const header = page.locator('.modal-card ion-header'); + + // Start the drag to verify it emits the events before the gesture ends + await dragElementBy(header, page, 0, 50, undefined, undefined, false); + + await ionDragStart.next(); + const dragMoveEvent = await ionDragMove.next(); + + expect(ionDragStart.length).toBe(1); + + expect(ionDragMove.length).toBeGreaterThan(0); + expect(Object.keys(dragMoveEvent.detail).length).toBe(4); + + expect(ionDragEnd.length).toBe(0); + + /** + * Drage the modal further to verify it does: + * - not emit the event again for `ionDragStart` + * - emit more `ionDragMove` events + * - emit the `ionDragEnd` event when the gesture ends + */ + await dragElementBy(header, page, 0, 100); + + const dragEndEvent = await ionDragEnd.next(); + + expect(ionDragStart.length).toBe(1); + expect(ionDragMove.length).toBeGreaterThan(0); + + expect(ionDragEnd.length).toBe(1); + expect(Object.keys(dragEndEvent.detail).length).toBe(4); + }); + }); }); diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Chrome-linux.png index 24f4553cc8f..1fdfadab07d 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Firefox-linux.png index 8bab2a20ad4..75cec98ab7b 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Safari-linux.png index 058817f2f78..d8da03effc7 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-present-tablet-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Chrome-linux.png index 1a22f8d7c08..e2feaaec6b2 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Firefox-linux.png index 37db9cae4f2..f279d8b3c1f 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Safari-linux.png index 3dc68f1d2ce..d3e8f78964b 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-custom-stacked-present-tablet-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Chrome-linux.png index 7f9f93513ca..f9e37268112 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Firefox-linux.png index 1f48543124c..5bc2a65be20 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Safari-linux.png index 1c0b930c431..1d2d9489111 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-present-tablet-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Chrome-linux.png index a61b3eb705e..aba4aaf714c 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Firefox-linux.png index 371b8960bf8..0f5d0d99b32 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Firefox-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Safari-linux.png b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Safari-linux.png index 8d7504b50fe..ab10214636e 100644 Binary files a/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/modal/test/card/modal-tablet.e2e.ts-snapshots/modal-card-stacked-present-tablet-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/dismiss-behavior/index.html b/core/src/components/modal/test/dismiss-behavior/index.html new file mode 100644 index 00000000000..448b84457d9 --- /dev/null +++ b/core/src/components/modal/test/dismiss-behavior/index.html @@ -0,0 +1,97 @@ + + + + + Modal - Dismiss Behavior + + + + + + + + + + +
+ + + Modal - Dismiss Behavior + + + + + + +
+
+ + + + diff --git a/core/src/components/modal/test/dismiss-behavior/modal.e2e.ts b/core/src/components/modal/test/dismiss-behavior/modal.e2e.ts new file mode 100644 index 00000000000..7969317c447 --- /dev/null +++ b/core/src/components/modal/test/dismiss-behavior/modal.e2e.ts @@ -0,0 +1,58 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('modal: dismiss behavior'), () => { + test.describe(title('modal: default dismiss'), () => { + test('should dismiss the last presented modal when the default dismiss button is clicked', async ({ page }) => { + await page.goto('/src/components/modal/test/dismiss-behavior', config); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + await page.click('#present-first-modal'); + await ionModalDidPresent.next(); + const firstModal = page.locator('ion-modal[data-testid="modal-1"]'); + await expect(firstModal).toBeVisible(); + + await page.click('#present-next-modal'); + await ionModalDidPresent.next(); + const secondModal = page.locator('ion-modal[data-testid="modal-2"]'); + await expect(secondModal).toBeVisible(); + + await page.click('ion-modal[data-testid="modal-2"] ion-button.dismiss-default'); + await ionModalDidDismiss.next(); + await secondModal.waitFor({ state: 'detached' }); + + await expect(firstModal).toBeVisible(); + await expect(secondModal).toBeHidden(); + }); + }); + + test.describe(title('modal: dismiss by id'), () => { + test('should dismiss the last presented modal when the dismiss by id button is clicked', async ({ page }) => { + await page.goto('/src/components/modal/test/dismiss-behavior', config); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss'); + + await page.click('#present-first-modal'); + await ionModalDidPresent.next(); + const firstModal = page.locator('ion-modal[data-testid="modal-1"]'); + await expect(firstModal).toBeVisible(); + + await page.click('#present-next-modal'); + await ionModalDidPresent.next(); + const secondModal = page.locator('ion-modal[data-testid="modal-2"]'); + await expect(secondModal).toBeVisible(); + + await page.click('ion-modal[data-testid="modal-2"] ion-button.dismiss-by-id'); + await ionModalDidDismiss.next(); + await secondModal.waitFor({ state: 'detached' }); + + await expect(firstModal).toBeVisible(); + await expect(secondModal).toBeHidden(); + }); + }); + }); +}); diff --git a/core/src/components/modal/test/modal-id.spec.tsx b/core/src/components/modal/test/modal-id.spec.tsx index 43f1a9eaa16..93b6d34fa91 100644 --- a/core/src/components/modal/test/modal-id.spec.tsx +++ b/core/src/components/modal/test/modal-id.spec.tsx @@ -1,7 +1,7 @@ +import { h } from '@stencil/core'; import { newSpecPage } from '@stencil/core/testing'; import { Modal } from '../modal'; -import { h } from '@stencil/core'; describe('modal: id', () => { it('modal should be assigned an incrementing id', async () => { @@ -52,4 +52,21 @@ describe('modal: id', () => { const alert = page.body.querySelector('ion-modal')!; expect(alert.id).toBe(id); }); + + it('should allow multiple modals with the same id', async () => { + const sharedId = 'shared-modal-id'; + + const page = await newSpecPage({ + components: [Modal], + template: () => [ + , + , + ], + }); + + const modals = page.body.querySelectorAll('ion-modal'); + expect(modals.length).toBe(2); + expect(modals[0].id).toBe(sharedId); + expect(modals[1].id).toBe(sharedId); + }); }); diff --git a/core/src/components/modal/test/safe-area/index.html b/core/src/components/modal/test/safe-area/index.html new file mode 100644 index 00000000000..14681f3820f --- /dev/null +++ b/core/src/components/modal/test/safe-area/index.html @@ -0,0 +1,262 @@ + + + + + Modal - Safe Area + + + + + + + + + + + +
+
+ + +
+ + + Modal - Safe Area + + + + +

Fullscreen Modals

+ + + +

Sheet Modals

+ + +

Card Modals (iOS)

+ + +

Centered Dialog (Tablet)

+ + +

Diagnostic Info

+
+

Window Width:

+

Window Height:

+

Is Tablet:

+
+
+
+
+ + + + diff --git a/core/src/components/modal/test/safe-area/modal.e2e.ts b/core/src/components/modal/test/safe-area/modal.e2e.ts new file mode 100644 index 00000000000..a65b6f3fee8 --- /dev/null +++ b/core/src/components/modal/test/safe-area/modal.e2e.ts @@ -0,0 +1,468 @@ +import { expect } from '@playwright/test'; +import { configs, test, Viewports } from '@utils/test/playwright'; + +/** + * These tests verify that safe-area CSS custom properties are correctly + * applied to modals based on their type and position. + * + * Safe-area handling is position-based and not affected by text direction. + * Testing only LTR to avoid redundant test runs. + */ + +/** + * The test page (index.html) sets these root safe-area values. + * Keep in sync with the :root block in test/safe-area/index.html. + */ +const TEST_SAFE_AREA_TOP = 47; +const TEST_SAFE_AREA_BOTTOM = 34; +/** Default value of --ion-padding (16px), applied via the .ion-padding class on ion-content in the test modal. */ +const TEST_ION_PADDING = 16; + +configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('modal: safe-area handling'), () => { + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/modal/test/safe-area', config); + }); + + test('fullscreen modal should inherit all safe-area values on phone', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#fullscreen-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // On phone viewport, fullscreen modal should inherit safe-area values + const safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + const safeAreaBottom = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-bottom'); + }); + + expect(safeAreaTop).toBe('inherit'); + expect(safeAreaBottom).toBe('inherit'); + }); + + test('regular modal should have safe-area zeroed on tablet (centered dialog)', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + await page.setViewportSize(Viewports.tablet.portrait); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#fullscreen-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // On tablet viewport, the CSS media query renders regular modals as + // centered dialogs (600x500). Since they don't touch screen edges, + // safe-area values should be zeroed out. + const safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + const safeAreaBottom = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-bottom'); + }); + + expect(safeAreaTop).toBe('0px'); + expect(safeAreaBottom).toBe('0px'); + }); + + test('sheet modal should only use bottom safe-area', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#sheet-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // Sheet modals should have top safe-area zeroed (doesn't touch top edge) + // but bottom safe-area inherited (touches bottom edge) + const safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + const safeAreaBottom = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-bottom'); + }); + + expect(safeAreaTop).toBe('0px'); + expect(safeAreaBottom).toBe('inherit'); + }); + + test('fullscreen modal without footer should set safe-area scroll padding on ion-content', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/31015', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#fullscreen-modal-no-footer'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // The wrapper should NOT have reduced height or padding-bottom. + // Safe-area compensation is handled by ion-content's scroll padding. + const wrapper = modal.locator('.modal-wrapper'); + const wrapperPaddingBottom = await wrapper.evaluate((el: HTMLElement) => { + return el.style.getPropertyValue('padding-bottom'); + }); + const wrapperHeight = await wrapper.evaluate((el: HTMLElement) => { + return el.style.getPropertyValue('height'); + }); + + expect(wrapperPaddingBottom).toBe(''); + expect(wrapperHeight).toBe(''); + + // ion-content should have --ion-content-safe-area-padding-bottom set so its + // .inner-scroll element includes safe-area in its bottom padding. + const content = modal.locator('ion-content'); + const safeAreaPadding = await content.evaluate((el: HTMLElement) => { + return el.style.getPropertyValue('--ion-content-safe-area-padding-bottom'); + }); + expect(safeAreaPadding).toBe('var(--ion-safe-area-bottom, 0px)'); + }); + + test('fullscreen modal with ion-content and no footer should not reduce wrapper content area', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/31015', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#fullscreen-modal-no-footer'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + const wrapper = modal.locator('.modal-wrapper'); + + // The wrapper's content area should equal the full viewport height. + // Safe-area compensation is handled by ion-content's scroll padding, + // not by reducing the wrapper. This prevents the visible white gap + // reported in #31015. + const { contentHeight, paddingBottom } = await wrapper.evaluate((el: HTMLElement) => { + const computed = getComputedStyle(el); + return { + contentHeight: parseFloat(computed.height), + paddingBottom: parseFloat(computed.paddingBottom), + }; + }); + const viewportHeight = await page.evaluate(() => window.innerHeight); + + expect(paddingBottom).toBeCloseTo(0, 0); + expect(contentHeight).toBeCloseTo(viewportHeight, 0); + }); + + test('fullscreen modal ion-content scroll padding should include safe-area-bottom', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/31015', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#fullscreen-modal-no-footer'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + const content = modal.locator('ion-content'); + + // The .inner-scroll element inside ion-content's shadow DOM should + // have padding-bottom that includes the safe-area-bottom value. + const innerScroll = content.locator('.inner-scroll'); + const scrollPaddingBottom = await innerScroll.evaluate((el: Element) => { + return parseFloat(getComputedStyle(el).paddingBottom); + }); + + expect(scrollPaddingBottom).toBe(TEST_ION_PADDING + TEST_SAFE_AREA_BOTTOM); + }); + + test('sheet modal at breakpoint 1 should keep top safe-area zeroed', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#sheet-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // Initially at breakpoint 0.5 β€” top safe-area should be zeroed + let safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + expect(safeAreaTop).toBe('0px'); + + // Move to breakpoint 1 (fully expanded) via the public method + const ionBreakpointDidChange = await page.spyOnEvent('ionBreakpointDidChange'); + await modal.evaluate((el: HTMLIonModalElement) => { + el.setCurrentBreakpoint(1); + }); + await ionBreakpointDidChange.next(); + + // At breakpoint 1, top safe-area should still be 0px because the + // sheet height is frozen with the resolved root value. This prevents + // header content from getting double-offset padding. + safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + expect(safeAreaTop).toBe('0px'); + }); + + test('sheet modal should have --ion-modal-offset-top set with resolved safe-area value', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#sheet-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // The internal --ion-modal-offset-top property should be set to the + // resolved root --ion-safe-area-top value. The SCSS --height formula + // uses this instead of --ion-safe-area-top directly. + const offsetTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-modal-offset-top'); + }); + expect(offsetTop).toBe(`${TEST_SAFE_AREA_TOP}px`); + }); + + test('fullscreen modal safe-area should update on resize from phone to tablet', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#fullscreen-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // On phone viewport, modal should inherit safe-area + const safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + expect(safeAreaTop).toBe('inherit'); + + // Resize to tablet viewport (centered dialog breakpoint) + await page.setViewportSize(Viewports.tablet.portrait); + + // Poll until the debounced resize handler updates safe-area overrides + await expect + .poll(async () => { + return modal.evaluate((el: HTMLIonModalElement) => el.style.getPropertyValue('--ion-safe-area-top')); + }) + .toBe('0px'); + + const safeAreaBottom = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-bottom'); + }); + + expect(safeAreaBottom).toBe('0px'); + }); + + test('centered dialog should have all safe-area values zeroed on tablet', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + await page.setViewportSize(Viewports.tablet.portrait); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#centered-dialog'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // Centered dialogs don't touch any edge, so all safe-areas should be zeroed + const safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + const safeAreaBottom = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-bottom'); + }); + + expect(safeAreaTop).toBe('0px'); + expect(safeAreaBottom).toBe('0px'); + }); + + test('centered dialog with custom dimensions on phone should zero safe-area from initial prediction', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/31015', + }); + + // Stay on phone viewport. This is the path where the centered-dialog + // media query does NOT match but the modal still doesn't touch screen + // edges because cssClass sets --width/--height. Without the initial + // prediction catching this, safe-area flashes inherited values and + // then snaps to 0px after animation. + const ionModalWillPresent = await page.spyOnEvent('ionModalWillPresent'); + await page.click('#centered-dialog'); + await ionModalWillPresent.next(); + + // Read inline style IMMEDIATELY after will-present fires, before the + // animation finishes. This captures the initial prediction value. + const modal = page.locator('ion-modal'); + const initial = await modal.evaluate((el: HTMLIonModalElement) => ({ + top: el.style.getPropertyValue('--ion-safe-area-top'), + bottom: el.style.getPropertyValue('--ion-safe-area-bottom'), + })); + + expect(initial.top).toBe('0px'); + expect(initial.bottom).toBe('0px'); + }); + + test('safe-area overrides should be cleared on dismiss', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + // Present modal programmatically so we control the lifecycle + // (the HTML page's presenters call modal.remove() after dismiss, + // which races with our post-dismiss evaluation) + await page.evaluate(async () => { + const modal = document.createElement('ion-modal'); + modal.component = document.createElement('div'); + document.body.appendChild(modal); + await modal.present(); + }); + + const modal = page.locator('ion-modal'); + + // Verify overrides are set + let safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + expect(safeAreaTop).not.toBe(''); + + // Dismiss the modal but don't remove the element + await modal.evaluate(async (el: HTMLIonModalElement) => { + await el.dismiss(); + }); + + // Verify overrides are cleared after dismiss + safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + expect(safeAreaTop).toBe(''); + + // Clean up + await modal.evaluate((el: HTMLIonModalElement) => el.remove()); + }); + }); +}); + +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('modal: card modal safe-area (iOS only)'), () => { + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/modal/test/safe-area', config); + }); + + test('card modal should inherit top and bottom safe-area', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#card-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // Card modals need top safe-area for height calculation + const safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + const safeAreaBottom = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-bottom'); + }); + const safeAreaLeft = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-left'); + }); + const safeAreaRight = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-right'); + }); + + expect(safeAreaTop).toBe('inherit'); + expect(safeAreaBottom).toBe('inherit'); + expect(safeAreaLeft).toBe('0px'); + expect(safeAreaRight).toBe('0px'); + }); + + test('card modal on tablet should still inherit safe-area values', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/30900', + }); + + await page.setViewportSize(Viewports.tablet.portrait); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#card-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + + // Card modals use safe-area values in CSS calculations regardless of viewport, + // so they should inherit even on tablet where regular modals become centered dialogs. + const safeAreaTop = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-top'); + }); + const safeAreaBottom = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-bottom'); + }); + const safeAreaLeft = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-left'); + }); + const safeAreaRight = await modal.evaluate((el: HTMLIonModalElement) => { + return el.style.getPropertyValue('--ion-safe-area-right'); + }); + + expect(safeAreaTop).toBe('inherit'); + expect(safeAreaBottom).toBe('inherit'); + expect(safeAreaLeft).toBe('0px'); + expect(safeAreaRight).toBe('0px'); + }); + }); +}); diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Chrome-linux.png index 66f50cf47d3..eb824a45504 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Firefox-linux.png index ba94d7bc50d..ba52e54004a 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Safari-linux.png index 37dffca6748..38d2de4a4fb 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-default-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png index 7ba73e882ef..128f258e508 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Chrome-linux.png index 66f50cf47d3..eb824a45504 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Firefox-linux.png index ba94d7bc50d..ba52e54004a 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Safari-linux.png index 37dffca6748..38d2de4a4fb 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-round-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png index 456bfb2f0ef..cea8ad8cfcc 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png index c48e1d7cac3..38066005b93 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Safari-linux.png index 91eb9c0f04d..0bde9487133 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-card-soft-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Chrome-linux.png index a99b7bd6daf..7de7a78aec2 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Firefox-linux.png index 700ad618ac1..2e045057173 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Safari-linux.png index c94b1aaf10e..40fde108dcc 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-default-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png index e30db5d4688..26ba7c6c017 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png index d01fcfb0d4a..404ec45441a 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png index 7459291ade5..4e2e55e3924 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Chrome-linux.png index a99b7bd6daf..7de7a78aec2 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Firefox-linux.png index 700ad618ac1..2e045057173 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Safari-linux.png index c94b1aaf10e..40fde108dcc 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-round-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png index 18e8290957c..5ad577fc781 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png index 0458a788ce9..d968f39935f 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Safari-linux.png index 2a444fea4cc..47ce8b30688 100644 Binary files a/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/shape/modal.e2e.ts-snapshots/modal-shape-sheet-soft-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/sheet/index.html b/core/src/components/modal/test/sheet/index.html index b2638351dec..ec9e15bc3af 100644 --- a/core/src/components/modal/test/sheet/index.html +++ b/core/src/components/modal/test/sheet/index.html @@ -169,6 +169,8 @@ Half Sheet w/ Footer + +
@@ -316,6 +318,27 @@ await modalElement.onDidDismiss(); modalElement.remove(); } + + function dragEvents() { + const modal = createModal({ + initialBreakpoint: 0.5, + breakpoints: [0, 0.25, 0.5, 0.75, 1], + }); + + modal.addEventListener('ionDragStart', (event) => { + console.log('Drag started'); + }); + + modal.addEventListener('ionDragMove', (event) => { + console.log('Drag moved', event.detail); + }); + + modal.addEventListener('ionDragEnd', (event) => { + console.log('Drag ended', event.detail); + }); + + modal.present(); + } diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts b/core/src/components/modal/test/sheet/modal.e2e.ts index ddd052c143d..ffa89001d9b 100644 --- a/core/src/components/modal/test/sheet/modal.e2e.ts +++ b/core/src/components/modal/test/sheet/modal.e2e.ts @@ -392,4 +392,50 @@ configs({ modes: ['ios', 'ionic-ios'], directions: ['ltr'] }).forEach(({ title, await expect(dragHandle).toBeFocused(); }); }); + + test.describe(title('sheet modal: drag events'), () => { + test('should emit ionDragStart, ionDragMove, and ionDragEnd events', async ({ page }) => { + await page.goto('/src/components/modal/test/sheet', config); + + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#drag-events'); + await ionModalDidPresent.next(); + + const ionDragStart = await page.spyOnEvent('ionDragStart'); + const ionDragMove = await page.spyOnEvent('ionDragMove'); + const ionDragEnd = await page.spyOnEvent('ionDragEnd'); + + const header = page.locator('.modal-sheet ion-header'); + + // Start the drag to verify it emits the events before the gesture ends + await dragElementBy(header, page, 0, 50, undefined, undefined, false); + + await ionDragStart.next(); + const dragMoveEvent = await ionDragMove.next(); + + expect(ionDragStart.length).toBe(1); + + expect(ionDragMove.length).toBeGreaterThan(0); + expect(Object.keys(dragMoveEvent.detail).length).toBe(5); + + expect(ionDragEnd.length).toBe(0); + + /** + * Drage the modal further to verify it does: + * - not emit the event again for `ionDragStart` + * - emit more `ionDragMove` events + * - emit the `ionDragEnd` event when the gesture ends + */ + await dragElementBy(header, page, 0, 100); + + const dragEndEvent = await ionDragEnd.next(); + + expect(ionDragStart.length).toBe(1); + expect(ionDragMove.length).toBeGreaterThan(0); + + expect(ionDragEnd.length).toBe(1); + expect(Object.keys(dragEndEvent.detail).length).toBe(5); + }); + }); }); diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Chrome-linux.png index 89bd3017ce1..8af3c41ebbd 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Firefox-linux.png index 53ab6bb4139..8f090e18fbc 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Safari-linux.png index 7f5fa3e5e8c..b122f894b71 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-backdrop-hidden-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Chrome-linux.png index 6d3e7da3f07..f8a56d65ca2 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Firefox-linux.png index b0062fafb73..e86bad9e199 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Safari-linux.png index f7f1bcd5bc7..be027a8f280 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-half-sheet-present-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Chrome-linux.png index f29b302d8d6..e6693de0c7a 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Firefox-linux.png index 9493f4f66cc..50e768ba9df 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Safari-linux.png index 8b0d981586f..17774509267 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-ios-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Chrome-linux.png index a60b0aff45c..e8eb1054ffb 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Firefox-linux.png index 3f5a38ba2ac..94d34f2b36b 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Safari-linux.png index 84e2362e3dd..13c2d845c89 100644 Binary files a/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/modal/test/sheet/modal.e2e.ts-snapshots/modal-sheet-present-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-active-picker-column-option-ios-ltr-Mobile-Safari-linux.png b/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-active-picker-column-option-ios-ltr-Mobile-Safari-linux.png index 30e2f7d4d1b..1caa116d857 100644 Binary files a/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-active-picker-column-option-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-active-picker-column-option-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-active-picker-column-option-md-ltr-Mobile-Safari-linux.png b/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-active-picker-column-option-md-ltr-Mobile-Safari-linux.png index d6f6de0778c..aa37a7bd340 100644 Binary files a/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-active-picker-column-option-md-ltr-Mobile-Safari-linux.png and b/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-active-picker-column-option-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-picker-column-option-ios-ltr-Mobile-Safari-linux.png b/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-picker-column-option-ios-ltr-Mobile-Safari-linux.png index 30e2f7d4d1b..1caa116d857 100644 Binary files a/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-picker-column-option-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-picker-column-option-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-picker-column-option-md-ltr-Mobile-Safari-linux.png b/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-picker-column-option-md-ltr-Mobile-Safari-linux.png index 3a2ad242b85..976a5f60665 100644 Binary files a/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-picker-column-option-md-ltr-Mobile-Safari-linux.png and b/core/src/components/picker-column-option/test/basic/picker-column-option.e2e.ts-snapshots/disabled-picker-column-option-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/picker-column/picker-column.tsx b/core/src/components/picker-column/picker-column.tsx index 6eb4241eac7..2411ef2d6cc 100644 --- a/core/src/components/picker-column/picker-column.tsx +++ b/core/src/components/picker-column/picker-column.tsx @@ -1,7 +1,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core'; import { doc } from '@utils/browser'; -import { getElementRoot, raf } from '@utils/helpers'; +import { raf } from '@utils/helpers'; import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from '@utils/native/haptic'; import { isPlatform } from '@utils/platform'; import { createColorClasses } from '@utils/theme'; @@ -123,9 +123,7 @@ export class PickerColumn implements ComponentInterface { * Because this initial call to scrollActiveItemIntoView has to fire before * the scroll listener is set up, we need to manage the active class manually. */ - const oldActive = getElementRoot(el).querySelector( - `.${PICKER_ITEM_ACTIVE_CLASS}` - ); + const oldActive = el.querySelector(`.${PICKER_ITEM_ACTIVE_CLASS}`); if (oldActive) { this.setPickerItemActiveState(oldActive, false); } diff --git a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-ltr-Mobile-Chrome-linux.png index 5417ee7f6da..ec6ef1cbb6e 100644 Binary files a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-rtl-Mobile-Chrome-linux.png index 553e95a3b76..9dc92e74714 100644 Binary files a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-ltr-Mobile-Chrome-linux.png b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-ltr-Mobile-Chrome-linux.png index 73bc3325d4b..207049157d6 100644 Binary files a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-rtl-Mobile-Chrome-linux.png b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-rtl-Mobile-Chrome-linux.png index 5d6f098c58b..8ed3f67838c 100644 Binary files a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-ltr-Mobile-Chrome-linux.png index 12e0eef4bd0..d5d72e9e0d6 100644 Binary files a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-rtl-Mobile-Chrome-linux.png index 9cd5d336442..dd88dd7f927 100644 Binary files a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-ltr-Mobile-Chrome-linux.png b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-ltr-Mobile-Chrome-linux.png index 5c2a7e0d963..b3d03236cb3 100644 Binary files a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-rtl-Mobile-Chrome-linux.png b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-rtl-Mobile-Chrome-linux.png index 5083c443bd1..9b160bf14e8 100644 Binary files a/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/animations/ios.enter.ts b/core/src/components/popover/animations/ios.enter.ts index aa4e0568143..02a078a2f71 100644 --- a/core/src/components/popover/animations/ios.enter.ts +++ b/core/src/components/popover/animations/ios.enter.ts @@ -7,11 +7,22 @@ import { getArrowDimensions, getPopoverDimensions, getPopoverPosition, + getSafeAreaInsets, shouldShowArrow, } from '../utils'; const POPOVER_IOS_BODY_PADDING = 5; +/** + * Minimum edge margin for iOS popovers ensures visual spacing from screen + * edges on devices without safe areas (e.g., older iPhones without notches). + * Previously this was a hardcoded `safeAreaMargin = 25` that served dual + * purpose: safe-area avoidance AND visual spacing. Now that actual safe-area + * insets are read dynamically, this floor preserves the visual spacing when + * safe-area values are 0. + */ +const POPOVER_IOS_MIN_EDGE_MARGIN = 25; + /** * iOS Popover Enter Animation */ @@ -53,7 +64,16 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => ); const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING; - const margin = size === 'cover' ? 0 : 25; + const rawSafeArea = getSafeAreaInsets(doc as Document); + const safeArea = + size === 'cover' + ? { top: 0, bottom: 0, left: 0, right: 0 } + : { + top: Math.max(rawSafeArea.top, POPOVER_IOS_MIN_EDGE_MARGIN), + bottom: Math.max(rawSafeArea.bottom, POPOVER_IOS_MIN_EDGE_MARGIN), + left: Math.max(rawSafeArea.left, POPOVER_IOS_MIN_EDGE_MARGIN), + right: Math.max(rawSafeArea.right, POPOVER_IOS_MIN_EDGE_MARGIN), + }; const { originX, @@ -63,9 +83,12 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => bottom, checkSafeAreaLeft, checkSafeAreaRight, + checkSafeAreaTop, + checkSafeAreaBottom, arrowTop, arrowLeft, addPopoverBottomClass, + hideArrow, } = calculateWindowAdjustment( side, results.top, @@ -75,7 +98,7 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => bodyHeight, contentWidth, contentHeight, - margin, + safeArea, results.originX, results.originY, results.referenceCoordinates, @@ -119,11 +142,15 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => } if (bottom !== undefined) { - contentEl.style.setProperty('bottom', `${bottom}px`); + let bottomValue = `${bottom}px`; + if (checkSafeAreaBottom) { + bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`; + } + contentEl.style.setProperty('bottom', `calc(${bottomValue})`); } - const safeAreaLeft = ' + var(--ion-safe-area-left, 0)'; - const safeAreaRight = ' - var(--ion-safe-area-right, 0)'; + const safeAreaLeft = ' + var(--ion-safe-area-left, 0px)'; + const safeAreaRight = ' - var(--ion-safe-area-right, 0px)'; let leftValue = `${left}px`; @@ -134,13 +161,18 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => leftValue = `${left}px${safeAreaRight}`; } - contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`); + let topValue = `${top}px`; + if (checkSafeAreaTop) { + topValue = `${top}px + var(--ion-safe-area-top, 0px)`; + } + + contentEl.style.setProperty('top', `calc(${topValue} + var(--offset-y, 0))`); contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`); contentEl.style.setProperty('transform-origin', `${originY} ${originX}`); if (arrowEl !== null) { const didAdjustBounds = results.top !== top || results.left !== left; - const showArrow = shouldShowArrow(side, didAdjustBounds, ev, trigger); + const showArrow = !hideArrow && shouldShowArrow(side, didAdjustBounds, ev, trigger); if (showArrow) { arrowEl.style.setProperty('top', `calc(${arrowTop}px + var(--offset-y, 0))`); diff --git a/core/src/components/popover/animations/md.enter.ts b/core/src/components/popover/animations/md.enter.ts index e25f745cec4..8de9976e86c 100644 --- a/core/src/components/popover/animations/md.enter.ts +++ b/core/src/components/popover/animations/md.enter.ts @@ -2,7 +2,7 @@ import { createAnimation } from '@utils/animation/animation'; import { getElementRoot } from '@utils/helpers'; import type { Animation } from '../../../interface'; -import { calculateWindowAdjustment, getPopoverDimensions, getPopoverPosition } from '../utils'; +import { calculateWindowAdjustment, getPopoverDimensions, getPopoverPosition, getSafeAreaInsets } from '../utils'; const POPOVER_MD_BODY_PADDING = 12; @@ -46,8 +46,22 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => ); const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING; - - const { originX, originY, top, left, bottom } = calculateWindowAdjustment( + // MD mode now applies safe-area insets (previously passed 0, ignoring all safe areas). + // This is needed for Android edge-to-edge (API 36+) where system bars overlap content. + const safeArea = size === 'cover' ? { top: 0, bottom: 0, left: 0, right: 0 } : getSafeAreaInsets(doc as Document); + + const { + originX, + originY, + top, + left, + bottom, + checkSafeAreaLeft, + checkSafeAreaRight, + checkSafeAreaTop, + checkSafeAreaBottom, + addPopoverBottomClass, + } = calculateWindowAdjustment( side, results.top, results.left, @@ -56,12 +70,28 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => bodyHeight, contentWidth, contentHeight, - 0, + safeArea, results.originX, results.originY, results.referenceCoordinates ); + const safeAreaLeftCalc = ' + var(--ion-safe-area-left, 0px)'; + const safeAreaRightCalc = ' - var(--ion-safe-area-right, 0px)'; + + let leftValue = `${left}px`; + if (checkSafeAreaLeft) { + leftValue = `${left}px${safeAreaLeftCalc}`; + } + if (checkSafeAreaRight) { + leftValue = `${left}px${safeAreaRightCalc}`; + } + + let topValue = `${top}px`; + if (checkSafeAreaTop) { + topValue = `${top}px + var(--ion-safe-area-top, 0px)`; + } + const baseAnimation = createAnimation(); const backdropAnimation = createAnimation(); const wrapperAnimation = createAnimation(); @@ -81,13 +111,17 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => contentAnimation .addElement(contentEl) .beforeStyles({ - top: `calc(${top}px + var(--offset-y, 0px))`, - left: `calc(${left}px + var(--offset-x, 0px))`, + top: `calc(${topValue} + var(--offset-y, 0px))`, + left: `calc(${leftValue} + var(--offset-x, 0px))`, 'transform-origin': `${originY} ${originX}`, }) .beforeAddWrite(() => { if (bottom !== undefined) { - contentEl.style.setProperty('bottom', `${bottom}px`); + let bottomValue = `${bottom}px`; + if (checkSafeAreaBottom) { + bottomValue = `${bottom}px + var(--ion-safe-area-bottom, 0px)`; + } + contentEl.style.setProperty('bottom', `calc(${bottomValue})`); } }) .fromTo('transform', 'scale(0.8)', 'scale(1)'); @@ -101,7 +135,7 @@ export const mdEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => if (size === 'cover') { baseEl.style.setProperty('--width', `${contentWidth}px`); } - if (originY === 'bottom') { + if (addPopoverBottomClass) { baseEl.classList.add('popover-bottom'); } }) diff --git a/core/src/components/popover/popover.scss b/core/src/components/popover/popover.common.scss similarity index 80% rename from core/src/components/popover/popover.scss rename to core/src/components/popover/popover.common.scss index ecd8acf2dc0..44c23801897 100644 --- a/core/src/components/popover/popover.scss +++ b/core/src/components/popover/popover.common.scss @@ -128,3 +128,21 @@ --offset-x: 5px; } } + +// Select Popover +// -------------------------------------------------- + +:host(.select-popover-rich-content) { + /** + * Rich content options (start/end slots, descriptions) need + * more horizontal space than the default fixed width provides, + * otherwise content gets cut off. + * + * A viewport-relative value is used instead of `auto` or + * `max-content` because the enter animation reads `--width` + * via getBoundingClientRect() before the first layout pass + * completes β€” intrinsic values return 0 at that point and + * cause the popover to be positioned off-screen on first open. + */ + --width: clamp(250px, calc(100vw - 40px), 400px); +} diff --git a/core/src/components/popover/popover.ionic.scss b/core/src/components/popover/popover.ionic.scss new file mode 100644 index 00000000000..533c697f768 --- /dev/null +++ b/core/src/components/popover/popover.ionic.scss @@ -0,0 +1,21 @@ +@use "../../themes/ionic/ionic.globals.scss" as globals; +@use "./popover.common"; + +// Ionic Popover +// -------------------------------------------------- + +:host { + --width: 250px; + --max-height: 90%; + --box-shadow: #{globals.$ion-elevation-4}; + --backdrop-opacity: 0.3; +} + +.popover-content { + @include globals.border-radius(globals.$ion-border-radius-400); + @include globals.transform-origin(start, top); +} + +.popover-viewport { + transition-delay: 100ms; +} diff --git a/core/src/components/popover/popover.ios.scss b/core/src/components/popover/popover.ios.scss index e3ccdcb217c..960d502832d 100644 --- a/core/src/components/popover/popover.ios.scss +++ b/core/src/components/popover/popover.ios.scss @@ -1,4 +1,4 @@ -@import "./popover"; +@import "./popover.common"; @import "./popover.ios.vars"; // iOS Popover diff --git a/core/src/components/popover/popover.md.scss b/core/src/components/popover/popover.md.scss index 7b4e5c2d19d..7b608c0da54 100644 --- a/core/src/components/popover/popover.md.scss +++ b/core/src/components/popover/popover.md.scss @@ -1,4 +1,4 @@ -@import "./popover"; +@import "./popover.common"; @import "./popover.md.vars"; // Material Design Popover diff --git a/core/src/components/popover/popover.tsx b/core/src/components/popover/popover.tsx index c330b471780..01f4fc05ff0 100644 --- a/core/src/components/popover/popover.tsx +++ b/core/src/components/popover/popover.tsx @@ -53,7 +53,7 @@ import { configureDismissInteraction, configureKeyboardInteraction, configureTri styleUrls: { ios: 'popover.ios.scss', md: 'popover.md.scss', - ionic: 'popover.md.scss', + ionic: 'popover.ionic.scss', }, shadow: true, }) @@ -528,7 +528,7 @@ export class Popover implements ComponentInterface, PopoverInterface { await waitForMount(); } - await present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, { + await present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, undefined, { event: event || this.event, size: this.size, trigger: this.triggerEl, diff --git a/core/src/components/popover/test/basic/index.html b/core/src/components/popover/test/basic/index.html index 8d10eceb5c9..dce0a242e0a 100644 --- a/core/src/components/popover/test/basic/index.html +++ b/core/src/components/popover/test/basic/index.html @@ -76,6 +76,20 @@ > Popover With Textarea + + + + + +
+
+
+
+ + + +

Popover safe area test. Triggers are positioned near screen edges.

+
+
+ + + + + + + + + diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts b/core/src/components/popover/test/safe-area/popover.e2e.ts new file mode 100644 index 00000000000..58cca8baa7c --- /dev/null +++ b/core/src/components/popover/test/safe-area/popover.e2e.ts @@ -0,0 +1,81 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * Safe-area behavior applies to both iOS and MD modes. + * Left/right safe areas are tested in landscape viewport. + */ +configs({ modes: ['ios', 'md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('popover: safe-area'), () => { + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/popover/test/safe-area', config); + }); + + test('should not overlap the bottom safe area when trigger is near the bottom edge', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28411', + }); + + /** + * Use a landscape-style viewport with bottom safe area. + * The bottom trigger is positioned at the very bottom of the screen. + */ + await page.setViewportSize({ + width: 414, + height: 400, + }); + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + + await page.locator('#bottom-trigger').click(); + await ionPopoverDidPresent.next(); + + await expect(page).toHaveScreenshot(screenshot('popover-safe-area-bottom')); + }); + + test('should not overlap the right safe area when trigger is near the right edge', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28411', + }); + + /** + * Use a landscape viewport with left/right safe areas + * to simulate a device rotated to landscape with notch on left. + */ + await page.setViewportSize({ + width: 600, + height: 400, + }); + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + + await page.locator('#right-trigger').click(); + await ionPopoverDidPresent.next(); + + await expect(page).toHaveScreenshot(screenshot('popover-safe-area-right')); + }); + + test('should handle a large popover near the bottom-right corner without overlapping safe areas', async ({ + page, + }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/issues/28411', + }); + + await page.setViewportSize({ + width: 600, + height: 400, + }); + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + + await page.locator('#large-popover-trigger').click(); + await ionPopoverDidPresent.next(); + + await expect(page).toHaveScreenshot(screenshot('popover-safe-area-large')); + }); + }); +}); diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..3b5b15725ee Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..fa56c8b3d61 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Safari-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..10125b226cd Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..c7a5574f5b5 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Firefox-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..7d936598896 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Safari-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..26d2d861dc4 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-bottom-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..706f94143b1 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..4692d2d3ea6 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Safari-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..f2721a2c6e8 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..f638c049ec1 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Firefox-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..900782be5d9 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Safari-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..9d89837628a Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-large-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..8d96e762184 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..4a9bcc9aa42 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Safari-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..14c84c9b58b Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..a5cbf4db869 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Firefox-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..abbd6c1406f Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Safari-linux.png b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..9a73a941665 Binary files /dev/null and b/core/src/components/popover/test/safe-area/popover.e2e.ts-snapshots/popover-safe-area-right-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/popover/test/size/popover.e2e.ts-snapshots/popover-size-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/size/popover.e2e.ts-snapshots/popover-size-ios-ltr-Mobile-Chrome-linux.png index b07f3a26f99..d33e323c899 100644 Binary files a/core/src/components/popover/test/size/popover.e2e.ts-snapshots/popover-size-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/popover/test/size/popover.e2e.ts-snapshots/popover-size-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/size/popover.e2e.ts-snapshots/popover-size-no-event-trigger-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/size/popover.e2e.ts-snapshots/popover-size-no-event-trigger-ios-ltr-Mobile-Chrome-linux.png index 8d669ddb9d5..b5046559505 100644 Binary files a/core/src/components/popover/test/size/popover.e2e.ts-snapshots/popover-size-no-event-trigger-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/popover/test/size/popover.e2e.ts-snapshots/popover-size-no-event-trigger-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-ios-ltr-Mobile-Chrome-linux.png index d3eb78a63c5..83f8c95a578 100644 Binary files a/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-ios-rtl-Mobile-Chrome-linux.png index 69bcaf3e376..934bc284781 100644 Binary files a/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-ios-rtl-Mobile-Chrome-linux.png and b/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-md-ltr-Mobile-Chrome-linux.png b/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-md-ltr-Mobile-Chrome-linux.png index bcb96aacb8a..55acedaca76 100644 Binary files a/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-md-rtl-Mobile-Chrome-linux.png b/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-md-rtl-Mobile-Chrome-linux.png index f1e55d2967e..6478b9d1c77 100644 Binary files a/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-md-rtl-Mobile-Chrome-linux.png and b/core/src/components/popover/test/standalone/popover.e2e.ts-snapshots/popover-standalone-basic-popover-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/popover/utils.ts b/core/src/components/popover/utils.ts index 794ebb20884..0d11a4dfeef 100644 --- a/core/src/components/popover/utils.ts +++ b/core/src/components/popover/utils.ts @@ -32,11 +32,79 @@ export interface PopoverStyles { originY: string; checkSafeAreaLeft: boolean; checkSafeAreaRight: boolean; + checkSafeAreaTop: boolean; + checkSafeAreaBottom: boolean; arrowTop: number; arrowLeft: number; addPopoverBottomClass: boolean; + hideArrow: boolean; } +export interface SafeAreaInsets { + top: number; + bottom: number; + left: number; + right: number; +} + +/** + * Shared per-frame cache for safe-area insets. Avoids creating a temporary + * DOM element and forcing a synchronous reflow on every call within the same + * frame (e.g., multiple popovers presenting simultaneously). Invalidated + * after the next animation frame so values stay fresh across orientation + * changes and viewport resizes. + */ +let cachedInsets: SafeAreaInsets | null = null; +let cacheInvalidationScheduled = false; + +/** + * Resolves the current --ion-safe-area-* CSS custom property values + * to actual pixel numbers. A temporary element is needed because + * getComputedStyle on :root returns the declared value of custom + * properties (e.g. "env(safe-area-inset-top)") rather than a + * resolved number. By assigning them to standard padding properties, + * the browser resolves the value. + * + * Results are cached for the current frame to avoid repeated reflows. + */ +export const getSafeAreaInsets = (doc: Document): SafeAreaInsets => { + if (cachedInsets !== null) { + return cachedInsets; + } + + if (doc.body === null) { + return { top: 0, bottom: 0, left: 0, right: 0 }; + } + + const el = doc.createElement('div'); + el.style.cssText = + 'position:fixed;visibility:hidden;pointer-events:none;top:0;left:0;' + + 'padding-top:var(--ion-safe-area-top,0px);' + + 'padding-bottom:var(--ion-safe-area-bottom,0px);' + + 'padding-left:var(--ion-safe-area-left,0px);' + + 'padding-right:var(--ion-safe-area-right,0px);'; + doc.body.appendChild(el); + const style = getComputedStyle(el); + const insets = { + top: parseFloat(style.paddingTop) || 0, + bottom: parseFloat(style.paddingBottom) || 0, + left: parseFloat(style.paddingLeft) || 0, + right: parseFloat(style.paddingRight) || 0, + }; + el.remove(); + + cachedInsets = insets; + if (!cacheInvalidationScheduled) { + cacheInvalidationScheduled = true; + raf(() => { + cachedInsets = null; + cacheInvalidationScheduled = false; + }); + } + + return insets; +}; + /** * Returns the dimensions of the popover * arrow on `ios` mode. If arrow is disabled @@ -804,6 +872,8 @@ const calculatePopoverCenterAlign = ( * Adjusts popover positioning coordinates * such that popover does not appear offscreen * or overlapping safe area bounds. + * + * @internal - This is not part of the public API. */ export const calculateWindowAdjustment = ( side: PositionSide, @@ -814,7 +884,7 @@ export const calculateWindowAdjustment = ( bodyHeight: number, contentWidth: number, contentHeight: number, - safeAreaMargin: number, + safeArea: SafeAreaInsets, contentOriginX: string, contentOriginY: string, triggerCoordinates?: ReferenceCoordinates, @@ -831,17 +901,20 @@ export const calculateWindowAdjustment = ( let originY = contentOriginY; let checkSafeAreaLeft = false; let checkSafeAreaRight = false; + let checkSafeAreaTop = false; + let checkSafeAreaBottom = false; const triggerTop = triggerCoordinates ? triggerCoordinates.top + triggerCoordinates.height : bodyHeight / 2 - contentHeight / 2; const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0; let addPopoverBottomClass = false; + const hideArrow = false; /** * Adjust popover so it does not * go off the left of the screen. */ - if (left < bodyPadding + safeAreaMargin) { + if (left < bodyPadding + safeArea.left) { left = bodyPadding; checkSafeAreaLeft = true; originX = 'left'; @@ -849,7 +922,7 @@ export const calculateWindowAdjustment = ( * Adjust popover so it does not * go off the right of the screen. */ - } else if (contentWidth + bodyPadding + left + safeAreaMargin > bodyWidth) { + } else if (contentWidth + bodyPadding + left + safeArea.right > bodyWidth) { checkSafeAreaRight = true; left = bodyWidth - contentWidth - bodyPadding; originX = 'right'; @@ -857,34 +930,54 @@ export const calculateWindowAdjustment = ( /** * Adjust popover so it does not - * go off the top of the screen. + * go off the bottom of the screen + * or overlap the bottom safe area. * If popover is on the left or the right of * the trigger, then we should not adjust top * margins. */ - if (triggerTop + triggerHeight + contentHeight > bodyHeight && (side === 'top' || side === 'bottom')) { - if (triggerTop - contentHeight > 0) { + if ( + triggerTop + triggerHeight + contentHeight > bodyHeight - safeArea.bottom && + (side === 'top' || side === 'bottom') + ) { + /** + * Calculate where the popover top would be if flipped + * above the trigger. Check whether that position clears + * the top safe area with room for bodyPadding. + */ + const idealFlipTop = triggerTop - contentHeight - triggerHeight - (arrowHeight - 1); + + if (idealFlipTop >= safeArea.top + bodyPadding) { /** - * While we strive to align the popover with the trigger - * on smaller screens this is not always possible. As a result, - * we adjust the popover up so that it does not hang - * off the bottom of the screen. However, we do not want to move - * the popover up so much that it goes off the top of the screen. - * - * We chose 12 here so that the popover position looks a bit nicer as - * it is not right up against the edge of the screen. + * Popover fits above the trigger without overlapping + * the top safe area. Use the ideal position directly β€” + * no safe-area CSS vars needed since it clears both edges. */ - top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1)); + top = idealFlipTop; arrowTop = top + contentHeight; originY = 'bottom'; addPopoverBottomClass = true; - + } else { /** - * If not enough room for popover to appear - * above trigger, then cut it off. + * Can't flip above the trigger. Constrain the bottom + * edge while keeping the top near the trigger. This + * creates a scrollable area anchored to the trigger. */ - } else { bottom = bodyPadding; + checkSafeAreaBottom = true; + + /** + * When the trigger is near the bottom of the screen, + * the calculated top may be at or past the bottom + * constraint, leaving zero visible height. In that + * case, pull top up to the top safe area so the + * popover fills the available space between safe areas. + */ + const bottomEdge = bodyHeight - safeArea.bottom - bodyPadding; + if (top >= bottomEdge) { + top = safeArea.top + bodyPadding; + checkSafeAreaTop = true; + } } } @@ -896,9 +989,12 @@ export const calculateWindowAdjustment = ( originY, checkSafeAreaLeft, checkSafeAreaRight, + checkSafeAreaTop, + checkSafeAreaBottom, arrowTop, arrowLeft, addPopoverBottomClass, + hideArrow, }; }; diff --git a/core/src/components/progress-bar/test/basic/progress-bar.e2e.ts-snapshots/progress-bar-basic-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/progress-bar/test/basic/progress-bar.e2e.ts-snapshots/progress-bar-basic-ionic-md-ltr-light-Mobile-Chrome-linux.png index f4993eeb508..d475e1c786a 100644 Binary files a/core/src/components/progress-bar/test/basic/progress-bar.e2e.ts-snapshots/progress-bar-basic-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/progress-bar/test/basic/progress-bar.e2e.ts-snapshots/progress-bar-basic-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/progress-bar/test/basic/progress-bar.e2e.ts-snapshots/progress-bar-basic-ionic-md-rtl-light-Mobile-Chrome-linux.png b/core/src/components/progress-bar/test/basic/progress-bar.e2e.ts-snapshots/progress-bar-basic-ionic-md-rtl-light-Mobile-Chrome-linux.png index 7ade1090088..446a875271f 100644 Binary files a/core/src/components/progress-bar/test/basic/progress-bar.e2e.ts-snapshots/progress-bar-basic-ionic-md-rtl-light-Mobile-Chrome-linux.png and b/core/src/components/progress-bar/test/basic/progress-bar.e2e.ts-snapshots/progress-bar-basic-ionic-md-rtl-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/radio-group/radio-group.scss b/core/src/components/radio-group/radio-group.scss new file mode 100644 index 00000000000..817db37c4ea --- /dev/null +++ b/core/src/components/radio-group/radio-group.scss @@ -0,0 +1,40 @@ +@import "../../themes/ionic.globals"; + +// Radio Group +// -------------------------------------------------- + +ion-radio-group { + // Prevents additional pixels from being rendered on top + vertical-align: top; +} + +// Radio Group: Top +// -------------------------------------------------- + +.radio-group-top { + line-height: 1.5; +} + +/** + * Error text should only be shown when .ion-invalid is present + * on the radio group. Otherwise the helper text should be shown. + */ +.radio-group-top .error-text { + display: none; + + color: ion-color(danger, base); +} + +.radio-group-top .helper-text { + display: block; + + color: $text-color-step-300; +} + +.ion-touched.ion-invalid .radio-group-top .error-text { + display: block; +} + +.ion-touched.ion-invalid .radio-group-top .helper-text { + display: none; +} diff --git a/core/src/components/radio-group/radio-group.tsx b/core/src/components/radio-group/radio-group.tsx index ab9ce1579cf..f1ff225faa0 100644 --- a/core/src/components/radio-group/radio-group.tsx +++ b/core/src/components/radio-group/radio-group.tsx @@ -1,7 +1,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; import { Build, Component, Element, Event, Host, Listen, Method, Prop, State, Watch, h } from '@stencil/core'; import { checkInvalidState } from '@utils/forms'; -import { renderHiddenInput } from '@utils/helpers'; +import { debounce, renderHiddenInput } from '@utils/helpers'; import { hostContext } from '@utils/theme'; import { getIonTheme } from '../../global/ionic-global'; @@ -110,6 +110,15 @@ export class RadioGroup implements ComponentInterface { this.valueChanged(this.value); } + /** @internal - Recompute which radio has tabindex 0. Call when radios are added/removed. */ + @Method() + async updateRadiosTabindex() { + this.scheduleTabindexUpdate(); + } + + /** Ensures that the tabindex update is debounced and only called once. */ + private scheduleTabindexUpdate = debounce(() => this.setRadioTabindex(this.value), 0); + private setRadioTabindex = (value: any | undefined) => { const radios = this.getRadios(); @@ -301,6 +310,19 @@ export class RadioGroup implements ComponentInterface { // to the bottom of the screen ev.preventDefault(); } + + // Inside a select interface, Enter commits the focused radio + // value (matching native + +
+ +
+ +

+ Click the button above to toggle the required prop on the 2nd textarea and then click the submit button to + attempt to submit the form. It should show a popup warning on the 2nd textarea to fill out the field when + the textarea is required and empty. When the textarea is not required, or the field is filled, the form + should submit by sending a console log. Click the reset button to reset the form and clear the textareas. +

+ +
+ + + +
+ + + + + + + diff --git a/core/src/components/textarea/test/form/textarea.e2e.ts b/core/src/components/textarea/test/form/textarea.e2e.ts new file mode 100644 index 00000000000..73de37c9473 --- /dev/null +++ b/core/src/components/textarea/test/form/textarea.e2e.ts @@ -0,0 +1,260 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across modes/directions + */ +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('textarea: form'), () => { + test('should be marked as invalid when required and empty', async ({ page }) => { + await page.setContent( + ` +
+ + +
+ + `, + config + ); + + let formSubmitted = false; + + const textarea = page.locator('ion-textarea'); + const submitButton = page.locator('button[type="submit"]'); + + // Check that the textarea's browser validation is working before submission + const validationInfo = await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + if (!nativeTextarea) { + return { isValid: false, willValidate: false, validationMessage: '', checkValidity: false }; + } + return { + isValid: nativeTextarea.validity.valid, + willValidate: nativeTextarea.willValidate, + validationMessage: nativeTextarea.validationMessage, + checkValidity: nativeTextarea.checkValidity(), + }; + }); + + expect(validationInfo.willValidate).toBe(true); + expect(validationInfo.isValid).toBe(false); + expect(validationInfo.checkValidity).toBe(false); + expect(validationInfo.validationMessage.length).toBeGreaterThan(0); + + // Click submit button - browser validation should prevent form submission + // and show the native validation popup + await submitButton.click(); + + // Wait for any async operations to complete + await page.waitForChanges(); + + // Check that form was not submitted (browser validation prevented it) + formSubmitted = await page.evaluate(() => (window as any).formSubmitted ?? false); + expect(formSubmitted).toBe(false); + + // Verify that the form's validation was triggered and it's invalid + const formValidity = await page.evaluate(() => { + const form = document.querySelector('form'); + return form ? form.checkValidity() : null; + }); + expect(formValidity).toBe(false); + + // Verify the textarea's validity is still false after submit attempt + const isValidAfterSubmit = await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + return nativeTextarea?.validity.valid ?? false; + }); + expect(isValidAfterSubmit).toBe(false); + }); + + test('should be marked as valid when required and filled', async ({ page }) => { + await page.setContent( + ` +
+ + +
+ + `, + config + ); + + const textarea = page.locator('ion-textarea'); + const submitButton = page.locator('button[type="submit"]'); + + // Type into the native textarea + await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + if (nativeTextarea) { + nativeTextarea.value = 'Test value'; + nativeTextarea.dispatchEvent(new Event('input', { bubbles: true })); + } + }); + + // Check that the textarea's browser validation is working before submission + const isValidBeforeSubmit = await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + return nativeTextarea?.validity.valid ?? false; + }); + expect(isValidBeforeSubmit).toBe(true); + + // Click submit button - form should submit since validation passes + await submitButton.click(); + + // Wait for any async operations to complete + await page.waitForChanges(); + + // Check that form was submitted (validation passed) + const formSubmitted = await page.evaluate(() => (window as any).formSubmitted ?? false); + expect(formSubmitted).toBe(true); + + // Verify that the form's validation passed + const formValidity = await page.evaluate(() => { + const form = document.querySelector('form'); + return form ? form.checkValidity() : null; + }); + expect(formValidity).toBe(true); + + // Verify the textarea's validity is still true after submit + const isValidAfterSubmit = await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + return nativeTextarea?.validity.valid ?? false; + }); + expect(isValidAfterSubmit).toBe(true); + }); + + test('should set formData when submit button is clicked', async ({ page }) => { + await page.setContent( + ` +
+ + +
+ + `, + config + ); + + const textarea = page.locator('ion-textarea'); + const submitButton = page.locator('button[type="submit"]'); + + // Type into the native textarea + await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + if (nativeTextarea) { + nativeTextarea.value = 'Test value'; + nativeTextarea.dispatchEvent(new Event('input', { bubbles: true })); + } + }); + + // Click submit button and wait for any async operations to complete + await submitButton.click(); + await page.waitForChanges(); + + // Verify that the textarea's value is set + const textareaValue = await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + return nativeTextarea?.value ?? ''; + }); + expect(textareaValue).toBe('Test value'); + + // Verify that the formData is set + const formData = await page.evaluate(() => { + const form = document.querySelector('form'); + if (!form) { + return null; + } + const formData = new FormData(form); + const entries: Record = {}; + for (const [key, value] of formData.entries()) { + entries[key] = value.toString(); + } + return entries; + }); + expect(formData).toBeDefined(); + expect(formData?.['textarea']).toBe('Test value'); + }); + + test('should reset formData when reset button is clicked', async ({ page }) => { + await page.setContent( + ` +
+ + + +
+ + `, + config + ); + + const textarea = page.locator('ion-textarea'); + const submitButton = page.locator('button[type="submit"]'); + const resetButton = page.locator('button[type="reset"]'); + + // Type into the native textarea + await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + if (nativeTextarea) { + nativeTextarea.value = 'Test value'; + nativeTextarea.dispatchEvent(new Event('input', { bubbles: true })); + } + }); + + // Click submit button and wait for any async operations to complete + await submitButton.click(); + await page.waitForChanges(); + + // Click reset button and wait for any async operations to complete + await resetButton.click(); + await page.waitForChanges(); + + // Verify that the textarea's value is cleared + const textareaValue = await textarea.evaluate((el: HTMLIonTextareaElement) => { + const nativeTextarea = el.shadowRoot?.querySelector('textarea') as HTMLTextAreaElement | null; + return nativeTextarea?.value ?? ''; + }); + expect(textareaValue).toBe(''); + + // Verify that the formData is cleared + const formData = await page.evaluate(() => { + const form = document.querySelector('form'); + if (!form) { + return null; + } + const formData = new FormData(form); + const entries: Record = {}; + for (const [key, value] of formData.entries()) { + entries[key] = value.toString(); + } + return entries; + }); + expect(formData?.['textarea']).toBe(''); + }); + }); +}); diff --git a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Chrome-linux.png index 6383767feb0..bd7e7bb1b20 100644 Binary files a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Firefox-linux.png index 590e68cb478..36b33f3dc2c 100644 Binary files a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Safari-linux.png index 49ef14c8359..e2c248546b4 100644 Binary files a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-invalid-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Chrome-linux.png index d08a2188bbb..33071c93e5d 100644 Binary files a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Firefox-linux.png index 74159afbcea..ec96f1dfd57 100644 Binary files a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Safari-linux.png index f04d7cf1e7a..73bdaccf600 100644 Binary files a/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/highlight/textarea.e2e.ts-snapshots/textarea-no-fill-valid-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/label-placement/textarea.e2e.ts b/core/src/components/textarea/test/label-placement/textarea.e2e.ts index 60015b7f05f..3d07e0497cc 100644 --- a/core/src/components/textarea/test/label-placement/textarea.e2e.ts +++ b/core/src/components/textarea/test/label-placement/textarea.e2e.ts @@ -307,7 +307,7 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co await page.setContent( ` diff --git a/core/src/components/textarea/test/rows/index.html b/core/src/components/textarea/test/rows/index.html new file mode 100644 index 00000000000..2c3ccf3549b --- /dev/null +++ b/core/src/components/textarea/test/rows/index.html @@ -0,0 +1,119 @@ + + + + + Textarea - Rows + + + + + + + + + + + + + + Textarea - Rows + + + + +
+ +
+

Rows: 1

+ +
+ +
+

Rows: 3

+ +
+ +
+

Rows: 8

+ +
+ + +
+

Rows: 3, Size Small

+ +
+ +
+

Rows: 3, Size Medium

+ +
+ +
+

Rows: 3, Size Large

+ +
+ + +
+

Rows: 3, Auto-grow

+ +
+
+
+
+ + diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts b/core/src/components/textarea/test/rows/textarea.e2e.ts new file mode 100644 index 00000000000..e7713dbedf6 --- /dev/null +++ b/core/src/components/textarea/test/rows/textarea.e2e.ts @@ -0,0 +1,88 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across directions + * This behavior only applies to the `ionic` theme. + */ +configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('textarea: rows'), () => { + test('should respect rows attribute', async ({ page }) => { + await page.setContent( + ` +
+ + + +
+ `, + config + ); + + const container = page.locator('#container'); + await expect(container).toHaveScreenshot(screenshot(`textarea-rows`)); + }); + + test('should respect rows attribute with different sizes', async ({ page }) => { + await page.setContent( + ` +
+ + + +
+ `, + config + ); + + const container = page.locator('#container'); + await expect(container).toHaveScreenshot(screenshot(`textarea-rows-sizes`)); + }); + + test('should respect rows when auto-grow is enabled', async ({ page }) => { + await page.setContent( + ` +
+ +
+ `, + config + ); + + const container = page.locator('#container'); + await expect(container).toHaveScreenshot(screenshot(`textarea-rows-autogrow`)); + }); + }); +}); diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..bcb6998c76a Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..edbd511467a Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 00000000000..e1e0bde964e Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-autogrow-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..b1694b69f31 Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..9ce22ad7195 Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 00000000000..f383c8d7f18 Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..7767a2a337e Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..dd27d0ba2da Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 00000000000..8e7e9e6d68b Binary files /dev/null and b/core/src/components/textarea/test/rows/textarea.e2e.ts-snapshots/textarea-rows-sizes-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Chrome-linux.png index 9be8ceb108d..4aa030254d4 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Firefox-linux.png index caa323f7209..f35d57b8926 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Safari-linux.png index 6617bdde44f..bcf9b794b42 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-default-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png index d693db26077..7c77fa8f235 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png index 468fe4c0f78..e6dc2b5ca3b 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png index 729951ea315..7dce87fc6af 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-rectangular-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Chrome-linux.png index 9be8ceb108d..4aa030254d4 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Firefox-linux.png index caa323f7209..f35d57b8926 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Safari-linux.png index 6617bdde44f..bcf9b794b42 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-round-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png index c15e3a8e42c..d01e10b457d 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png index 80b6de7e2cc..37c72836d01 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Safari-linux.png index 28104f1111a..644352063b9 100644 Binary files a/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/shape/textarea.e2e.ts-snapshots/textarea-soft-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Chrome-linux.png index 7107140d10e..9dbd98d09a7 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Firefox-linux.png index fcdfaf3c2ea..671cee24033 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Safari-linux.png index f391289ef1b..09b291d3829 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png index 7c8e6fccd51..1ffd8a71d1c 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png index 69a60bf60c3..2ed3be72597 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png index 81a409d852d..aec54176160 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png index 7c8e6fccd51..1ffd8a71d1c 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png index 69a60bf60c3..2ed3be72597 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Safari-linux.png index 81a409d852d..aec54176160 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-large-outline-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Chrome-linux.png index b7cfec2f118..f30a7fbe1dd 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Firefox-linux.png index 5b64bd43262..29d639de55f 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Safari-linux.png index 83fd0048d87..0e12dbecad6 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png index 9be8ceb108d..4aa030254d4 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png index caa323f7209..f35d57b8926 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png index 6617bdde44f..bcf9b794b42 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png index 9be8ceb108d..4aa030254d4 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png index caa323f7209..f35d57b8926 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Safari-linux.png index 6617bdde44f..bcf9b794b42 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-medium-outline-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Chrome-linux.png index a3ef0af8dd6..5453fe2f693 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Firefox-linux.png index 2bfb21d62da..544737212f6 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Safari-linux.png index 3e9914db8d2..b8599390f97 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png index ec81cfc7d5e..fca782da348 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png index bc4f4ef3ad4..1798ec420a8 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png index 38271e01b17..c23e93463bd 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-label-stacked-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png index ec81cfc7d5e..fca782da348 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png index bc4f4ef3ad4..1798ec420a8 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Safari-linux.png index 38271e01b17..c23e93463bd 100644 Binary files a/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/size/textarea.e2e.ts-snapshots/textarea-size-small-outline-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Chrome-linux.png index 8f1b29e6b5b..7b72dbed032 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Firefox-linux.png index dc865e89e1d..4b752928635 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Safari-linux.png index da668e67f5a..1fc12039b36 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-disabled-no-fill-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Chrome-linux.png index a8b6e3c3104..335e6ba65b0 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Firefox-linux.png index 9211b6c22f7..d0354bba73a 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Safari-linux.png index b04650787c9..efa61f3c9f8 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-focused-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Chrome-linux.png index 7b3ba4b7fbf..bb0b83fdad6 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Firefox-linux.png index da4eb5bfe40..fdb383547b8 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Safari-linux.png index 09b890d0dc7..911fdb8dc38 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-no-fill-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Chrome-linux.png index f829ca42ffd..18420957f07 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Chrome-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Firefox-linux.png index 8e307a39a1a..0464ad7ec67 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Firefox-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Safari-linux.png index eb0929c613a..bfc72642a42 100644 Binary files a/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Safari-linux.png and b/core/src/components/textarea/test/states/textarea.e2e.ts-snapshots/textarea-readonly-solid-invalid-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/textarea/test/textarea.spec.ts b/core/src/components/textarea/test/textarea.spec.ts index f1611a3e291..b0eeb949ba5 100644 --- a/core/src/components/textarea/test/textarea.spec.ts +++ b/core/src/components/textarea/test/textarea.spec.ts @@ -8,7 +8,8 @@ it('should inherit attributes', async () => { html: '', }); - const nativeEl = page.body.querySelector('ion-textarea textarea')!; + const textareaEl = page.body.querySelector('ion-textarea')!; + const nativeEl = textareaEl.shadowRoot!.querySelector('textarea')!; expect(nativeEl.getAttribute('title')).toBe('my title'); expect(nativeEl.getAttribute('tabindex')).toBe('-1'); expect(nativeEl.getAttribute('data-form-type')).toBe('password'); @@ -21,7 +22,7 @@ it('should inherit watched attributes', async () => { }); const textareaEl = page.body.querySelector('ion-textarea')!; - const nativeEl = textareaEl.querySelector('textarea')!; + const nativeEl = textareaEl.shadowRoot!.querySelector('textarea')!; expect(nativeEl.getAttribute('dir')).toBe('ltr'); @@ -52,7 +53,7 @@ describe('textarea: label rendering', () => { const textarea = page.body.querySelector('ion-textarea')!; - const labelText = textarea.querySelector('.label-text-wrapper')!; + const labelText = textarea.shadowRoot!.querySelector('.label-text-wrapper')!; expect(labelText.textContent).toBe('Label Prop Text'); }); @@ -66,9 +67,16 @@ describe('textarea: label rendering', () => { const textarea = page.body.querySelector('ion-textarea')!; - const labelText = textarea.querySelector('.label-text-wrapper')!; + // When using a slot, the content is in the light DOM, not directly + // accessible via textContent. Check that the slot element exists and + // the slotted content is in the light DOM. + const slotEl = textarea.shadowRoot!.querySelector('slot[name="label"]'); + const propEl = textarea.shadowRoot!.querySelector('.label-text'); + const slottedContent = textarea.querySelector('[slot="label"]'); - expect(labelText.textContent).toBe('Label Prop Slot'); + expect(slotEl).not.toBe(null); + expect(propEl).toBe(null); + expect(slottedContent?.textContent).toBe('Label Prop Slot'); }); it('should render label prop if both prop and slot provided', async () => { const page = await newSpecPage({ @@ -80,7 +88,7 @@ describe('textarea: label rendering', () => { const textarea = page.body.querySelector('ion-textarea')!; - const labelText = textarea.querySelector('.label-text-wrapper')!; + const labelText = textarea.shadowRoot!.querySelector('.label-text-wrapper')!; expect(labelText.textContent).toBe('Label Prop Text'); }); diff --git a/core/src/components/textarea/test/textarea.spec.tsx b/core/src/components/textarea/test/textarea.spec.tsx index e0ed6363f58..e7b0230756f 100644 --- a/core/src/components/textarea/test/textarea.spec.tsx +++ b/core/src/components/textarea/test/textarea.spec.tsx @@ -7,7 +7,8 @@ it('should render bottom content when helper text is defined', async () => { html: ``, }); - const bottomContent = page.body.querySelector('ion-textarea .textarea-bottom'); + const textarea = page.body.querySelector('ion-textarea')!; + const bottomContent = textarea.shadowRoot!.querySelector('.textarea-bottom'); expect(bottomContent).not.toBe(null); }); @@ -17,7 +18,8 @@ it('should render bottom content when helper text is undefined', async () => { html: ``, }); - const bottomContent = page.body.querySelector('ion-textarea .textarea-bottom'); + const textarea = page.body.querySelector('ion-textarea')!; + const bottomContent = textarea.shadowRoot!.querySelector('.textarea-bottom'); expect(bottomContent).toBe(null); }); @@ -27,6 +29,7 @@ it('should render bottom content when helper text is empty string', async () => html: ``, }); - const bottomContent = page.body.querySelector('ion-textarea .textarea-bottom'); + const textarea = page.body.querySelector('ion-textarea')!; + const bottomContent = textarea.shadowRoot!.querySelector('.textarea-bottom'); expect(bottomContent).toBe(null); }); diff --git a/core/src/components/textarea/textarea.common.scss b/core/src/components/textarea/textarea.common.scss index 0fbc42e1754..8e6ababcded 100644 --- a/core/src/components/textarea/textarea.common.scss +++ b/core/src/components/textarea/textarea.common.scss @@ -82,12 +82,12 @@ // Textarea Within An Item // -------------------------------------------------- -:host-context(ion-item) { +:host(.in-item) { align-self: baseline; } -:host-context(ion-item)[slot="start"], -:host-context(ion-item)[slot="end"] { +:host(.in-item[slot="start"]), +:host(.in-item[slot="end"]) { width: auto; } @@ -174,9 +174,13 @@ // otherwise the .input-cover will not be rendered at all // The input cover is not clickable when the input is disabled .cloned-input { - @include position(0, null, 0, 0); - position: absolute; + top: 0; + bottom: 0; + + // Reset height since absolute positioning with top/bottom handles sizing + height: auto; + max-height: none; pointer-events: none; } @@ -311,6 +315,8 @@ width: 100%; min-height: inherit; + + box-sizing: border-box; } // Textarea Highlight @@ -432,6 +438,10 @@ overflow: hidden; } +.textarea-outline { + box-sizing: border-box; +} + /** * If no label text is placed into the slot * then the element should be hidden otherwise @@ -500,17 +510,6 @@ z-index: 2; } -/** - * Ensures the textarea does not - * overlap the label. - */ -:host(.textarea-label-placement-stacked) textarea, -:host(.textarea-label-placement-floating) textarea, -:host(.textarea-label-placement-stacked[auto-grow]) .native-wrapper::after, -:host(.textarea-label-placement-floating[auto-grow]) .native-wrapper::after { - @include margin(8px, 0px, 0px, 0px); -} - /** * This makes the label sit over the textarea * when the textarea is blurred and has no value. diff --git a/core/src/components/textarea/textarea.ionic.outline.scss b/core/src/components/textarea/textarea.ionic.outline.scss index c18190b4363..801f34d6f12 100644 --- a/core/src/components/textarea/textarea.ionic.outline.scss +++ b/core/src/components/textarea/textarea.ionic.outline.scss @@ -53,13 +53,6 @@ border-top: none; } -// Textarea Fill: Outline, Native Textarea -// ---------------------------------------------------------------- - -:host(.textarea-fill-outline) textarea { - margin-top: globals.$ion-space-100; -} - // Focus // ---------------------------------------------------------------- diff --git a/core/src/components/textarea/textarea.ionic.scss b/core/src/components/textarea/textarea.ionic.scss index 53120a45df0..4ceb6379036 100644 --- a/core/src/components/textarea/textarea.ionic.scss +++ b/core/src/components/textarea/textarea.ionic.scss @@ -27,19 +27,11 @@ // Ionic Textarea Sizes // -------------------------------------------------- -// Setting height to 0 allows it to collapse in height -// instead of growing above the min height by default. -.textarea-wrapper-inner { - height: 0; -} - :host(.textarea-size-small) .textarea-wrapper-inner { --padding-top: #{globals.$ion-space-200}; --padding-end: #{globals.$ion-space-300}; --padding-bottom: #{globals.$ion-space-200}; --padding-start: #{globals.$ion-space-300}; - - min-height: globals.$ion-scale-2800; } :host(.textarea-size-medium) .textarea-wrapper-inner { @@ -47,8 +39,6 @@ --padding-end: #{globals.$ion-space-400}; --padding-bottom: #{globals.$ion-space-300}; --padding-start: #{globals.$ion-space-400}; - - min-height: globals.$ion-scale-3400; } :host(.textarea-size-large) .textarea-wrapper-inner { @@ -56,8 +46,13 @@ --padding-end: #{globals.$ion-space-500}; --padding-bottom: #{globals.$ion-space-400}; --padding-start: #{globals.$ion-space-500}; +} - min-height: globals.$ion-scale-3600; +// Native Textarea +// -------------------------------------------------- + +:host .native-wrapper::after { + @include globals.padding(0); } // Ionic Textarea Shapes @@ -75,17 +70,6 @@ --border-radius: #{globals.$ion-rectangular-xl}; } -// Textarea Wrapper -// ---------------------------------------------------------------- - -.textarea-wrapper { - gap: globals.$ion-space-100; -} - -.textarea-wrapper-inner { - @include globals.padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); -} - // Textarea Auto Grow // ---------------------------------------------------------------- @@ -101,36 +85,54 @@ max-height: inherit; } -// Textarea Label +// Textarea Wrapper // ---------------------------------------------------------------- -.label-text-wrapper { - @include globals.typography(globals.$ion-body-sm-medium); - - max-width: globals.$ion-scale-5000; +.textarea-wrapper { + gap: globals.$ion-space-100; +} - transition: color globals.$ion-transition-time-150 globals.$ion-transition-curve-expressive, - transform globals.$ion-transition-time-150 globals.$ion-transition-curve-expressive; +.textarea-wrapper-inner { + @include globals.padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); +} - color: globals.$ion-text-subtle; +:host([rows]:not([auto-grow])) .textarea-wrapper-inner { + @include globals.padding(0, var(--padding-end), 0, var(--padding-start)); } -:host(.label-floating) .label-text-wrapper { - @include globals.transform(none); +/** + * Top and bottom padding are applied to ensure that text remains visible + * as it scrolls past the container boundaries. This provides a "sneak peek" + * of the content transitioning off-screen, mirroring native textarea behavior + * where text isn't immediately clipped or "glued" to the border at the + * scroll limits. + */ +:host([rows]:not([auto-grow])) .textarea-wrapper-inner .native-textarea { + padding-top: var(--padding-top); + padding-bottom: var(--padding-bottom); } -// Textarea Slotted Content +// Textarea Highlight // ---------------------------------------------------------------- -ion-icon { - --color: globals.$ion-icon-subtlest; - - font-size: globals.$ion-scale-400; +:host(.has-focus.ion-valid), +:host(.ion-touched.ion-invalid) { + --border-width: #{globals.$ion-border-size-025}; } -.start-slot-wrapper, -.end-slot-wrapper { - margin-top: globals.$ion-space-050; +.textarea-highlight { + @include globals.position(null, null, -1px, 0); + + position: absolute; + + width: 100%; + height: globals.$ion-border-size-050; + + transform: scale(0); + + transition: transform globals.$ion-transition-time-200; + + background: var(--border-color); } // Textarea Bottom Content @@ -141,40 +143,96 @@ ion-icon { @include globals.typography(globals.$ion-body-sm-medium); } +.textarea-bottom .error-text { + color: globals.$ion-text-danger; +} + .textarea-bottom .helper-text, .textarea-bottom .counter { color: globals.$ion-text-subtlest; } -.textarea-bottom .error-text { - color: globals.$ion-text-danger; -} - :host(.has-focus.ion-valid) .helper-text { color: var(--highlight-color-valid); } -:host(.has-focus.ion-valid), -:host(.ion-touched.ion-invalid) { - --border-width: #{globals.$ion-border-size-025}; +// Textarea Label +// ---------------------------------------------------------------- + +.label-text-wrapper { + @include globals.typography(globals.$ion-body-sm-medium); + + max-width: globals.$ion-scale-5000; + + transition: color globals.$ion-transition-time-150 globals.$ion-transition-curve-expressive, + transform globals.$ion-transition-time-150 globals.$ion-transition-curve-expressive; + + color: globals.$ion-text-subtle; } -// Textarea Highlight +:host(.label-floating) .label-text-wrapper { + @include globals.transform(none); +} + +/** + * This makes the label sit above the textarea. + */ +:host(.label-floating) .label-text-wrapper { + $form-control-label-stacked-scale: 0.75; + + /** + * Label text should not extend + * beyond the bounds of the textarea. + */ + max-width: calc(100% / #{$form-control-label-stacked-scale}); +} + +.textarea-wrapper textarea { + /** + * When the floating label appears on top of the + * textarea, we need to fade the textarea out so that the + * label does not overlap with the placeholder. + */ + transition: opacity globals.$ion-transition-time-150 globals.$ion-transition-curve-expressive; +} + +// Textarea Label Placement - Fixed // ---------------------------------------------------------------- -.textarea-highlight { - @include globals.position(null, null, -1px, 0); +/** + * Label is on the left of the textarea in LTR and + * on the right in RTL. Label also has a fixed width. + */ +:host(.textarea-label-placement-fixed) .label-text { + $text-wrapper-width: calc(globals.$ion-scale-2400 + globals.$ion-space-100); - position: absolute; + flex: 0 0 $text-wrapper-width; - width: 100%; - height: globals.$ion-border-size-050; + width: $text-wrapper-width; + min-width: $text-wrapper-width; + max-width: globals.$ion-scale-5000; +} - transform: scale(0); +// Textarea Slotted Content +// ---------------------------------------------------------------- - transition: transform globals.$ion-transition-time-200; +ion-icon { + --color: globals.$ion-icon-subtlest; - background: var(--border-color); + font-size: globals.$ion-scale-400; +} + +// Start / End Slots +// ---------------------------------------------------------------- + +.start-slot-wrapper, +.end-slot-wrapper { + margin-top: globals.$ion-space-050; +} + +:host([rows]:not([auto-grow])) .start-slot-wrapper, +:host([rows]:not([auto-grow])) .end-slot-wrapper { + @include globals.padding(var(--padding-top), 0, var(--padding-bottom), 0); } // Textarea Focus @@ -237,42 +295,3 @@ ion-icon { :host(.textarea-readonly) { --background: #{globals.$ion-bg-input-read-only}; } - -/** - * This makes the label sit above the textarea. - */ -:host(.label-floating) .label-text-wrapper { - $form-control-label-stacked-scale: 0.75; - - /** - * Label text should not extend - * beyond the bounds of the textarea. - */ - max-width: calc(100% / #{$form-control-label-stacked-scale}); -} - -.textarea-wrapper textarea { - /** - * When the floating label appears on top of the - * textarea, we need to fade the textarea out so that the - * label does not overlap with the placeholder. - */ - transition: opacity globals.$ion-transition-time-150 globals.$ion-transition-curve-expressive; -} - -// Textarea Label Placement - Fixed -// ---------------------------------------------------------------- - -/** - * Label is on the left of the textarea in LTR and - * on the right in RTL. Label also has a fixed width. - */ -:host(.textarea-label-placement-fixed) .label-text { - $text-wrapper-width: calc(globals.$ion-scale-2400 + globals.$ion-space-100); - - flex: 0 0 $text-wrapper-width; - - width: $text-wrapper-width; - min-width: $text-wrapper-width; - max-width: globals.$ion-scale-5000; -} diff --git a/core/src/components/textarea/textarea.md.outline.scss b/core/src/components/textarea/textarea.md.outline.scss index 5cb2c66f348..8a81d6250c2 100644 --- a/core/src/components/textarea/textarea.md.outline.scss +++ b/core/src/components/textarea/textarea.md.outline.scss @@ -150,6 +150,8 @@ :host(.textarea-fill-outline) .textarea-outline-end { border-top: var(--border-width) var(--border-style) var(--border-color); border-bottom: var(--border-width) var(--border-style) var(--border-color); + + box-sizing: border-box; } /** diff --git a/core/src/components/textarea/textarea.native.scss b/core/src/components/textarea/textarea.native.scss index b0d3c36ec2e..d4ad2b0a697 100644 --- a/core/src/components/textarea/textarea.native.scss +++ b/core/src/components/textarea/textarea.native.scss @@ -120,6 +120,20 @@ @include globals.margin(0, globals.$form-control-label-margin, 0, 0); } +// Textarea Label Placement - Stacked & Floating +// ---------------------------------------------------------------- + +/** + * Ensures the textarea does not + * overlap the label. + */ +:host(.textarea-label-placement-stacked) textarea, +:host(.textarea-label-placement-floating) textarea, +:host(.textarea-label-placement-stacked[auto-grow]) .native-wrapper::after, +:host(.textarea-label-placement-floating[auto-grow]) .native-wrapper::after { + @include globals.margin(8px, 0px, 0px, 0px); +} + // Start / End Slots // ---------------------------------------------------------------- diff --git a/core/src/components/textarea/textarea.tsx b/core/src/components/textarea/textarea.tsx index 272b21f3077..4d08327a356 100644 --- a/core/src/components/textarea/textarea.tsx +++ b/core/src/components/textarea/textarea.tsx @@ -1,5 +1,6 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; import { + AttachInternals, Build, Component, Element, @@ -15,7 +16,7 @@ import { writeTask, } from '@stencil/core'; import type { NotchController } from '@utils/forms'; -import { createNotchController, checkInvalidState } from '@utils/forms'; +import { createNotchController, checkInvalidState, reportValidityToElementInternals } from '@utils/forms'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, debounceEvent, inheritAttributes, componentOnReady } from '@utils/helpers'; import { createSlotMutationController } from '@utils/slot-mutation-controller'; @@ -35,6 +36,16 @@ import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './text * @slot label - The label text to associate with the textarea. Use the `labelPlacement` property to control where the label is placed relative to the textarea. Use this if you need to render a label with custom HTML. (EXPERIMENTAL) * @slot start - Content to display at the leading edge of the textarea. (EXPERIMENTAL) * @slot end - Content to display at the trailing edge of the textarea. (EXPERIMENTAL) + * + * @part wrapper - The clickable label element that wraps the entire form field (label text, slots, and native textarea). + * @part container - The inner wrapper element that directly contains the native textarea element. + * @part label - The label text describing the textarea. + * @part native - The native textarea element. + * @part supporting-text - Supporting text displayed beneath the textarea label. + * @part helper-text - Supporting text displayed beneath the textarea label when the textarea is valid. + * @part error-text - Supporting text displayed beneath the textarea label when the textarea is invalid and touched. + * @part counter - The character counter displayed when the counter property is set. + * @part bottom - The container element for helper text, error text, and counter. */ @Component({ tag: 'ion-textarea', @@ -43,7 +54,10 @@ import type { TextareaChangeEventDetail, TextareaInputEventDetail } from './text md: 'textarea.md.scss', ionic: 'textarea.ionic.scss', }, - scoped: true, + shadow: { + delegatesFocus: true, + }, + formAssociated: true, }) export class Textarea implements ComponentInterface { private nativeInput?: HTMLTextAreaElement; @@ -73,6 +87,8 @@ export class Textarea implements ComponentInterface { @Element() el!: HTMLIonTextareaElement; + @AttachInternals() internals!: ElementInternals; + /** * The `hasFocus` state ensures the focus class is * added regardless of how the element is focused. @@ -134,7 +150,15 @@ export class Textarea implements ComponentInterface { /** * If `true`, the user cannot interact with the textarea. */ - @Prop() disabled = false; + @Prop({ reflect: true }) disabled = false; + + /** + * Update element internals when disabled prop changes + */ + @Watch('disabled') + protected disabledChanged() { + this.updateElementInternals(); + } /** * The fill for the item. If `"solid"` the item will have a background. If @@ -179,12 +203,12 @@ export class Textarea implements ComponentInterface { /** * If `true`, the user cannot modify the value. */ - @Prop() readonly = false; + @Prop({ reflect: true }) readonly = false; /** * If `true`, the user must fill in a value before submitting a form. */ - @Prop() required = false; + @Prop({ reflect: true }) required = false; /** * If `true`, the element will have its spelling and grammar checked. @@ -287,9 +311,25 @@ export class Textarea implements ComponentInterface { if (nativeInput && nativeInput.value !== value) { nativeInput.value = value; } + this.updateElementInternals(); this.runAutoGrow(); } + /** + * Update native input and element internals when required prop changes + */ + @Watch('required') + protected requiredChanged() { + // Explicitly update the native element's required attribute to ensure + // browser validation works correctly when required changes dynamically. + // While the template binding should handle this, we need to update it + // synchronously for the browser's validation to recognize the change. + if (this.nativeInput) { + this.nativeInput.required = this.required; + } + this.updateElementInternals(); + } + /** * dir is a globally enumerated attribute. * As a result, creating these as properties @@ -422,7 +462,15 @@ export class Textarea implements ComponentInterface { componentDidLoad() { this.originalIonInput = this.ionInput; + this.updateElementInternals(); this.runAutoGrow(); + + // Override focus() to delegate to the native textarea. + // This is needed for Safari and Firefox which don't properly + // delegate focus when calling focus() directly on the host. + this.el.focus = () => { + this.setFocus(); + }; } componentDidRender() { @@ -544,6 +592,40 @@ export class Textarea implements ComponentInterface { return this.value || ''; } + /** + * Called when the form state is restored. + * Restores the component's value. + */ + formStateRestoreCallback(value: string) { + this.value = value; + } + + /** + * Called when the form is reset. + * Resets the component's value. + */ + formResetCallback() { + this.value = ''; + } + + /** + * Updates the form value and reports validity state to the browser via + * ElementInternals. This should be called when the component loads, when + * the required prop changes, when the disabled prop changes, and when the value + * changes to ensure the form value stays in sync and validation state is updated. + */ + private updateElementInternals() { + // Disabled form controls should not be included in form data + // Pass null to setFormValue when disabled to exclude it from form submission + const value = this.disabled ? null : this.getValue(); + // ElementInternals may not be fully available in test environments + // so we need to check if the method exists before calling it + if (typeof this.internals.setFormValue === 'function') { + this.internals.setFormValue(value); + } + reportValidityToElementInternals(this.nativeInput, this.internals); + } + // `Event` type is used instead of `InputEvent` // since the types from Stencil are not derived // from the element (e.g. textarea and input @@ -595,6 +677,7 @@ export class Textarea implements ComponentInterface { 'label-text-wrapper': true, 'label-text-wrapper-hidden': !this.hasLabel, }} + part="label" > {label === undefined ? :
{label}
}
@@ -701,10 +784,10 @@ export class Textarea implements ComponentInterface { const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this; return [ -
+
{!isInvalid ? helperText : null}
, - - diff --git a/packages/vue/test/base/src/views/tabs-similar-prefixes/Home.vue b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home.vue new file mode 100644 index 00000000000..0954fac9d4f --- /dev/null +++ b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/vue/test/base/src/views/tabs-similar-prefixes/Home2.vue b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home2.vue new file mode 100644 index 00000000000..4e190d99e98 --- /dev/null +++ b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home2.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/vue/test/base/src/views/tabs-similar-prefixes/Home3.vue b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home3.vue new file mode 100644 index 00000000000..78099959b06 --- /dev/null +++ b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home3.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/vue/test/base/src/views/tabs-similar-prefixes/TabsSimilarPrefixes.vue b/packages/vue/test/base/src/views/tabs-similar-prefixes/TabsSimilarPrefixes.vue new file mode 100644 index 00000000000..8ec968edbd2 --- /dev/null +++ b/packages/vue/test/base/src/views/tabs-similar-prefixes/TabsSimilarPrefixes.vue @@ -0,0 +1,54 @@ + + + diff --git a/packages/vue/test/base/tests/e2e/specs/inputs.cy.js b/packages/vue/test/base/tests/e2e/specs/inputs.cy.js index 26c26f8ef95..42403e915bf 100644 --- a/packages/vue/test/base/tests/e2e/specs/inputs.cy.js +++ b/packages/vue/test/base/tests/e2e/specs/inputs.cy.js @@ -2,48 +2,70 @@ describe('Inputs', () => { beforeEach(() => { cy.visit('/inputs') }) - it('should have default value', () => { - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); - cy.get('ion-input').should('have.prop', 'value').and('eq', ''); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); - }); + + describe('basic functionality', () => { + it('should have default value', () => { + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); + cy.get('ion-input').should('have.prop', 'value').and('eq', ''); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + }); + + it('should set/reset values', () => { + cy.get('ion-button#set').click(); + + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', true); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', true); + cy.get('ion-input').should('have.prop', 'value').and('eq', 'Hello World'); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', '1234'); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 10, upper: 90 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', 'Lorem Ipsum'); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', 'Search Query'); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', '2019-01-31'); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'blue'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'cats'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'bananas'); + + cy.get('ion-button#reset').click(); + + cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); + cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); + cy.get('ion-input').should('have.prop', 'value').and('eq', ''); + cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); + cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); + cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); + cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); + cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); + cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); + cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); + cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + }); + + it('should reflect props when component has a default value', () => { + // Disable inputs + cy.get('ion-button#disable').click(); + + // Disabled prop + cy.get('ion-input').should('have.attr', 'disabled'); + cy.get('ion-input-otp').should('have.attr', 'disabled'); + cy.get('ion-textarea').should('have.attr', 'disabled'); - it('should set/reset values', () => { - cy.get('ion-button#set').click(); - - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', true); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', true); - cy.get('ion-input').should('have.prop', 'value').and('eq', 'Hello World'); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', '1234'); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 10, upper: 90 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', 'Lorem Ipsum'); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', 'Search Query'); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', '2019-01-31'); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'blue'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'cats'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'bananas'); - - cy.get('ion-button#reset').click(); - - cy.get('ion-checkbox').should('have.prop', 'checked').and('eq', false); - cy.get('ion-toggle').should('have.prop', 'checked').and('eq', false); - cy.get('ion-input').should('have.prop', 'value').and('eq', ''); - cy.get('ion-input-otp').should('have.prop', 'value').and('eq', ''); - cy.get('ion-range').should('have.prop', 'value').and('deep.eq', { lower: 30, upper: 70 }); - cy.get('ion-textarea').should('have.prop', 'value').and('eq', ''); - cy.get('ion-searchbar').should('have.prop', 'value').and('eq', ''); - cy.get('ion-datetime').should('have.prop', 'value').and('eq', ''); - cy.get('ion-radio-group').should('have.prop', 'value').and('eq', 'red'); - cy.get('ion-segment').should('have.prop', 'value').and('eq', 'dogs'); - cy.get('ion-select').should('have.prop', 'value').and('eq', 'apples'); + // Reset disabled state and set readonly state + cy.get('ion-button#disable').click(); + cy.get('ion-button#readonly').click(); + + // Readonly prop + cy.get('ion-input').should('have.attr', 'readonly'); + cy.get('ion-input-otp').should('have.attr', 'readonly'); + cy.get('ion-textarea').should('have.attr', 'readonly'); + }); }); describe('updating text input refs', () => { @@ -53,7 +75,11 @@ describe('Inputs', () => { cy.get('#input-ref').should('have.text', 'Hello Input'); }); it('typing into input-otp should update ref', () => { - cy.get('ion-input-otp input').eq(0).type('1234', { scrollBehavior: false }); + cy.get('ion-input-otp').shadow().find('input').eq(0).focus(); + cy.get('ion-input-otp').shadow().find('input').eq(0).type('1', { scrollBehavior: false }); + cy.get('ion-input-otp').shadow().find('input').eq(1).type('2', { scrollBehavior: false }); + cy.get('ion-input-otp').shadow().find('input').eq(2).type('3', { scrollBehavior: false }); + cy.get('ion-input-otp').shadow().find('input').eq(3).type('4', { scrollBehavior: false }); cy.get('#input-otp-ref').should('have.text', '1234'); }); @@ -63,9 +89,28 @@ describe('Inputs', () => { cy.get('#searchbar-ref').should('have.text', 'Hello Searchbar'); }); it('typing into textarea should update ref', () => { - cy.get('ion-textarea textarea').type('Hello Textarea', { scrollBehavior: false }); + cy.get('ion-textarea').shadow().find('textarea').type('Hello Textarea', { scrollBehavior: false }); cy.get('#textarea-ref').should('have.text', 'Hello Textarea'); }); }); + + describe('validation', () => { + it('should show invalid state for required inputs when empty and touched', () => { + cy.get('ion-input input').focus().blur(); + cy.get('ion-input').should('have.class', 'ion-invalid'); + + cy.get('ion-textarea').shadow().find('textarea').focus(); + cy.get('ion-textarea').blur(); + cy.get('ion-textarea').should('have.class', 'ion-invalid'); + }); + + it('should show valid state for required inputs when filled', () => { + cy.get('ion-input input').type('Test value', { scrollBehavior: false }); + cy.get('ion-input').should('have.class', 'ion-valid'); + + cy.get('ion-textarea').shadow().find('textarea').type('Test value', { scrollBehavior: false }); + cy.get('ion-textarea').should('have.class', 'ion-valid'); + }); + }); }) diff --git a/packages/vue/test/base/tests/e2e/specs/tabs.cy.js b/packages/vue/test/base/tests/e2e/specs/tabs.cy.js index 2b6cb15790f..1fdc83cf720 100644 --- a/packages/vue/test/base/tests/e2e/specs/tabs.cy.js +++ b/packages/vue/test/base/tests/e2e/specs/tabs.cy.js @@ -1,4 +1,45 @@ describe('Tabs', () => { + /** + * Verifies that tabs with similar route prefixes (e.g., /home, /home2, /home3) + * correctly select the matching tab instead of the first prefix match. + * + * @see https://github.com/ionic-team/ionic-framework/issues/30448 + */ + describe('Similar Route Prefixes', () => { + it('should select the correct tab when routes have similar prefixes', () => { + cy.visit('/tabs-similar-prefixes/home2'); + + cy.get('[data-testid="home2-content"]').should('be.visible'); + cy.get('[data-testid="home2-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + }); + + it('should select the correct tab when navigating via tab buttons', () => { + cy.visit('/tabs-similar-prefixes/home'); + + cy.get('[data-testid="home-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + + cy.get('[data-testid="home2-tab"]').click(); + cy.get('[data-testid="home2-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + + cy.get('[data-testid="home3-tab"]').click(); + cy.get('[data-testid="home3-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + }); + + it('should select the correct tab when directly navigating to home3', () => { + cy.visit('/tabs-similar-prefixes/home3'); + + cy.get('[data-testid="home3-content"]').should('be.visible'); + cy.get('[data-testid="home3-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + }); + }); + describe('With IonRouterOutlet', () => { it('should go back from child pages', () => { cy.visit('/tabs');