diff --git a/.github/workflows/actions/publish-npm/action.yml b/.github/actions/publish-npm/action.yml similarity index 54% rename from .github/workflows/actions/publish-npm/action.yml rename to .github/actions/publish-npm/action.yml index 5c5b49d56c6..3e58ba9bcc6 100644 --- a/.github/workflows/actions/publish-npm/action.yml +++ b/.github/actions/publish-npm/action.yml @@ -8,48 +8,53 @@ inputs: tag: description: 'The tag to publish to on NPM.' preid: - description: 'The prerelease identifier used when doing a prerelease.' + description: "Prerelease identifier such as 'alpha', 'beta', 'rc', or 'next'. Leave blank to skip prerelease tagging." working-directory: description: 'The directory of the package.' folder: default: './' description: 'A folder containing a package.json file.' - token: - description: 'The NPM authentication token required to publish.' + node-version: + description: 'Node.js version to use when publishing.' + required: false + default: '24.x' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - name: π’ Configure Node for Publish + uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: ${{ inputs.node-version }} + registry-url: 'https://registry.npmjs.org' + scope: '@ionic' # Provenance requires npm 9.5.0+ - - name: Install latest npm + - name: π¦ Install latest npm run: npm install -g npm@latest shell: bash # This ensures the local version of Lerna is installed # and that we do not use the global Lerna version - - name: Install root dependencies + - name: πΈοΈ Install root dependencies run: npm ci shell: bash - - name: Install Dependencies + - name: π¦ Install Dependencies run: npx lerna@5 bootstrap --include-dependencies --scope ${{ inputs.scope }} --ignore-scripts -- --legacy-peer-deps shell: bash working-directory: ${{ inputs.working-directory }} - - name: Update Version - run: npx lerna@5 version ${{ inputs.version }} --yes --exact --no-changelog --no-push --no-git-tag-version --preid=${{ inputs.preid }} + - name: π·οΈ Set Version + run: | + if [ -z "${{ inputs.preid }}" ]; then + npx lerna@5 version ${{ inputs.version }} --yes --exact --no-changelog --no-push --no-git-tag-version + else + npx lerna@5 version ${{ inputs.version }} --yes --exact --no-changelog --no-push --no-git-tag-version --preid=${{ inputs.preid }} + fi shell: bash working-directory: ${{ inputs.working-directory }} - - name: Run Build + - name: ποΈ Run Build run: npm run build shell: bash working-directory: ${{ inputs.working-directory }} - - name: Prepare NPM Token - run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > .npmrc - working-directory: ${{ inputs.working-directory }} - shell: bash - env: - NPM_TOKEN: ${{ inputs.token }} - - name: Publish to NPM + - name: π Publish to NPM run: npm publish ${{ inputs.folder }} --tag ${{ inputs.tag }} --provenance shell: bash working-directory: ${{ inputs.working-directory }} + diff --git a/.github/workflows/actions/build-angular-server/action.yml b/.github/workflows/actions/build-angular-server/action.yml index c48d1dcb3b6..0bf8392c2b5 100644 --- a/.github/workflows/actions/build-angular-server/action.yml +++ b/.github/workflows/actions/build-angular-server/action.yml @@ -3,23 +3,23 @@ description: 'Build Ionic Angular Server' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - name: Install Angular Server Dependencies + - name: πΈοΈ Install Angular Server Dependencies run: npm ci shell: bash working-directory: ./packages/angular-server - - name: Sync + - name: π Sync run: npm run sync shell: bash working-directory: ./packages/angular-server - - name: Build + - name: ποΈ Build run: npm run build.prod shell: bash working-directory: ./packages/angular-server diff --git a/.github/workflows/actions/build-angular/action.yml b/.github/workflows/actions/build-angular/action.yml index 349c6734e43..fc7496de421 100644 --- a/.github/workflows/actions/build-angular/action.yml +++ b/.github/workflows/actions/build-angular/action.yml @@ -3,31 +3,31 @@ description: 'Build Ionic Angular' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v6 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - name: Install Angular Dependencies + - name: πΈοΈ Install Angular Dependencies run: npm ci shell: bash working-directory: ./packages/angular - - name: Sync + - name: π Sync run: npm run sync shell: bash working-directory: ./packages/angular - - name: Lint + - name: ποΈ Lint run: npm run lint shell: bash working-directory: ./packages/angular - - name: Build + - name: ποΈ Build run: npm run build shell: bash working-directory: ./packages/angular - - name: Check Diff + - name: π Check Diff run: git diff --exit-code shell: bash working-directory: ./packages/angular diff --git a/.github/workflows/actions/build-core-stencil-prerelease/action.yml b/.github/workflows/actions/build-core-stencil-prerelease/action.yml index 070f84c4c3e..ef83ea5b4a6 100644 --- a/.github/workflows/actions/build-core-stencil-prerelease/action.yml +++ b/.github/workflows/actions/build-core-stencil-prerelease/action.yml @@ -8,20 +8,20 @@ inputs: runs: using: 'composite' steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - - name: Install Dependencies + - name: πΈοΈ Install Dependencies run: npm ci working-directory: ./core shell: bash - - name: Install Stencil ${{ inputs.stencil-version }} + - name: π¦ Install Stencil ${{ inputs.stencil-version }} working-directory: ./core run: npm i @stencil/core@${{ inputs.stencil-version }} shell: bash - - name: Build Core + - name: ποΈ Build Core run: npm run build -- --ci --debug --verbose working-directory: ./core shell: bash diff --git a/.github/workflows/actions/build-core/action.yml b/.github/workflows/actions/build-core/action.yml index b0ec39decf8..f0deb2bada0 100644 --- a/.github/workflows/actions/build-core/action.yml +++ b/.github/workflows/actions/build-core/action.yml @@ -8,22 +8,22 @@ inputs: runs: using: 'composite' steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x - - name: Install Dependencies + node-version: 24.x + - name: πΈοΈ Install Dependencies run: npm install working-directory: ./core shell: bash # If an Ionicons version was specified install that. # Otherwise just use the version defined in the package.json. - - name: Install Ionicons Version + - name: π¦ Install Ionicons Version if: inputs.ionicons-version != '' run: npm install ionicons@${{ inputs.ionicons-version }} working-directory: ./core shell: bash - - name: Build Core + - name: ποΈ Build Core run: npm run build -- --ci working-directory: ./core shell: bash diff --git a/.github/workflows/actions/build-react-router/action.yml b/.github/workflows/actions/build-react-router/action.yml index 61d5f6b2d45..fd997ea2386 100644 --- a/.github/workflows/actions/build-react-router/action.yml +++ b/.github/workflows/actions/build-react-router/action.yml @@ -3,9 +3,9 @@ description: 'Build Ionic React Router' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core @@ -16,19 +16,19 @@ runs: name: ionic-react path: ./packages/react filename: ReactBuild.zip - - name: Install Dependencies + - name: πΈοΈ Install Dependencies run: npm ci shell: bash working-directory: ./packages/react-router - - name: Sync + - name: π Sync run: npm run sync shell: bash working-directory: ./packages/react-router - - name: Lint + - name: ποΈ Lint run: npm run lint shell: bash working-directory: ./packages/react-router - - name: Build + - name: ποΈ Build run: npm run build shell: bash working-directory: ./packages/react-router diff --git a/.github/workflows/actions/build-react/action.yml b/.github/workflows/actions/build-react/action.yml index 6b8b9f74178..5de023a258f 100644 --- a/.github/workflows/actions/build-react/action.yml +++ b/.github/workflows/actions/build-react/action.yml @@ -3,35 +3,35 @@ description: 'Build Ionic React' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - name: Install React Dependencies + - name: πΈοΈ Install React Dependencies run: npm ci shell: bash working-directory: ./packages/react - - name: Sync + - name: π Sync run: npm run sync shell: bash working-directory: ./packages/react - - name: Lint + - name: ποΈ Lint run: npm run lint shell: bash working-directory: ./packages/react - - name: Build + - name: ποΈ Build run: npm run build shell: bash working-directory: ./packages/react - - name: Test Spec + - name: π§ͺ Test Spec run: npm run test.spec shell: bash working-directory: ./packages/react - - name: Check Diff + - name: π Check Diff run: git diff --exit-code shell: bash working-directory: ./packages/react diff --git a/.github/workflows/actions/build-vue-router/action.yml b/.github/workflows/actions/build-vue-router/action.yml index e1c7716f5ea..b2e375bb7ae 100644 --- a/.github/workflows/actions/build-vue-router/action.yml +++ b/.github/workflows/actions/build-vue-router/action.yml @@ -3,9 +3,9 @@ description: 'Builds Ionic Vue Router' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core @@ -16,23 +16,23 @@ runs: name: ionic-vue path: ./packages/vue filename: VueBuild.zip - - name: Install Vue Router Dependencies + - name: πΈοΈ Install Vue Router Dependencies run: npm ci shell: bash working-directory: ./packages/vue-router - - name: Sync + - name: π Sync run: npm run sync shell: bash working-directory: ./packages/vue-router - - name: Lint + - name: ποΈ Lint run: npm run lint shell: bash working-directory: ./packages/vue-router - - name: Build + - name: ποΈ Build run: npm run build shell: bash working-directory: ./packages/vue-router - - name: Test Spec + - name: π§ͺ Test Spec run: npm run test.spec shell: bash working-directory: ./packages/vue-router diff --git a/.github/workflows/actions/build-vue/action.yml b/.github/workflows/actions/build-vue/action.yml index bc8a47facc2..317f6f124ab 100644 --- a/.github/workflows/actions/build-vue/action.yml +++ b/.github/workflows/actions/build-vue/action.yml @@ -3,31 +3,31 @@ description: 'Build Ionic Vue' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - name: Install Vue Dependencies + - name: πΈοΈ Install Vue Dependencies run: npm ci shell: bash working-directory: ./packages/vue - - name: Sync + - name: π Sync run: npm run sync shell: bash working-directory: ./packages/vue - - name: Lint + - name: ποΈ Lint run: npm run lint shell: bash working-directory: ./packages/vue - - name: Build + - name: ποΈ Build run: npm run build shell: bash working-directory: ./packages/vue - - name: Check Diff + - name: π Check Diff run: git diff --exit-code shell: bash working-directory: ./packages/vue diff --git a/.github/workflows/actions/download-archive/action.yml b/.github/workflows/actions/download-archive/action.yml index 343e2451bce..70f201e3d67 100644 --- a/.github/workflows/actions/download-archive/action.yml +++ b/.github/workflows/actions/download-archive/action.yml @@ -10,10 +10,10 @@ inputs: runs: using: 'composite' steps: - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: ${{ inputs.name }} path: ${{ inputs.path }} - - name: Extract Archive + - name: π Extract Archive run: unzip -q -o ${{ inputs.path }}/${{ inputs.filename }} shell: bash diff --git a/.github/workflows/actions/test-angular-e2e/action.yml b/.github/workflows/actions/test-angular-e2e/action.yml index cd7ebfe0aec..0b99e99f1e2 100644 --- a/.github/workflows/actions/test-angular-e2e/action.yml +++ b/.github/workflows/actions/test-angular-e2e/action.yml @@ -6,9 +6,9 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core @@ -24,23 +24,23 @@ runs: name: ionic-angular-server path: ./packages/angular-server filename: AngularServerBuild.zip - - name: Create Test App + - name: π§ͺ Create Test App run: ./build.sh ${{ inputs.app }} shell: bash working-directory: ./packages/angular/test - - name: Install Dependencies + - name: πΈοΈ Install Dependencies run: npm install shell: bash working-directory: ./packages/angular/test/build/${{ inputs.app }} - - name: Install Playwright Browsers + - name: π¦ Install Playwright Browsers run: npx playwright install shell: bash working-directory: ./packages/angular/test/build/${{ inputs.app }} - - name: Sync Built Changes + - name: π Sync Built Changes run: npm run sync shell: bash working-directory: ./packages/angular/test/build/${{ inputs.app }} - - name: Run Tests + - name: π§ͺ Run Tests run: npm run test shell: bash working-directory: ./packages/angular/test/build/${{ inputs.app }} diff --git a/.github/workflows/actions/test-core-clean-build/action.yml b/.github/workflows/actions/test-core-clean-build/action.yml index ea6da763fd9..1f78cfaef74 100644 --- a/.github/workflows/actions/test-core-clean-build/action.yml +++ b/.github/workflows/actions/test-core-clean-build/action.yml @@ -3,16 +3,16 @@ description: 'Test Core Clean Build' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - name: Check Diff + - name: π Check Diff run: | git diff --exit-code || { echo -e "\033[1;31mβ οΈ Error: Differences Detected β οΈ\033[0m" diff --git a/.github/workflows/actions/test-core-lint/action.yml b/.github/workflows/actions/test-core-lint/action.yml index b0e45abdaea..35fc84da48a 100644 --- a/.github/workflows/actions/test-core-lint/action.yml +++ b/.github/workflows/actions/test-core-lint/action.yml @@ -3,21 +3,21 @@ description: 'Test Core Lint' runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x - - name: Install Dependencies + node-version: 24.x + - name: πΈοΈ Install Dependencies run: npm ci working-directory: ./core shell: bash - - name: Lint + - name: ποΈ Lint run: npm run lint shell: bash working-directory: ./core # Lint changes should be pushed # to the branch before the branch # is merge eligible. - - name: Check Lint Results + - name: π Check Lint Results run: git diff --exit-code shell: bash working-directory: ./core diff --git a/.github/workflows/actions/test-core-screenshot/action.yml b/.github/workflows/actions/test-core-screenshot/action.yml index f3d599f02ca..0c3965d632d 100644 --- a/.github/workflows/actions/test-core-screenshot/action.yml +++ b/.github/workflows/actions/test-core-screenshot/action.yml @@ -13,19 +13,19 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - name: Install Dependencies + - name: πΈοΈ Install Dependencies run: npm install shell: bash working-directory: ./core - - name: Test + - name: π§ͺ Test if: inputs.update != 'true' run: npm run test.e2e.docker.ci ${{ inputs.component }} -- --shard=${{ inputs.shard }}/${{ inputs.totalShards }} shell: bash @@ -60,13 +60,13 @@ runs: fi shell: bash working-directory: ./core - - name: Archive Updated Screenshots + - name: π¦ Archive Updated Screenshots if: inputs.update == 'true' && steps.test-and-update.outputs.hasUpdatedScreenshots == 'true' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: updated-screenshots-${{ inputs.shard }}-${{ inputs.totalShards }} path: UpdatedScreenshots-${{ inputs.shard }}-${{ inputs.totalShards }}.zip - - name: Archive Test Results + - name: π¦ Archive Test Results # The always() ensures that this step # runs even if the previous step fails. # We want the test results to be archived diff --git a/.github/workflows/actions/test-core-spec/action.yml b/.github/workflows/actions/test-core-spec/action.yml index cdec48fabff..af0684d243e 100644 --- a/.github/workflows/actions/test-core-spec/action.yml +++ b/.github/workflows/actions/test-core-spec/action.yml @@ -6,14 +6,14 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x - - name: Install Dependencies + node-version: 24.x + - name: πΈοΈ Install Dependencies run: npm ci working-directory: ./core shell: bash - - name: Install Stencil ${{ inputs.stencil-version }} + - name: π¦ Install Stencil ${{ inputs.stencil-version }} run: npm install @stencil/core@${{ inputs.stencil-version }} shell: bash working-directory: ./core @@ -23,7 +23,7 @@ runs: name: ionic-core path: ./core filename: CoreBuild.zip - - name: Test + - name: π§ͺ Test run: npm run test.spec -- --ci shell: bash working-directory: ./core diff --git a/.github/workflows/actions/test-react-e2e/action.yml b/.github/workflows/actions/test-react-e2e/action.yml index 3cf40c29b86..ad5148d9151 100644 --- a/.github/workflows/actions/test-react-e2e/action.yml +++ b/.github/workflows/actions/test-react-e2e/action.yml @@ -6,9 +6,9 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core @@ -24,23 +24,23 @@ runs: name: ionic-react-router path: ./packages/react-router filename: ReactRouterBuild.zip - - name: Create Test App + - name: π§ͺ Create Test App run: ./build.sh ${{ inputs.app }} shell: bash working-directory: ./packages/react/test - - name: Install Dependencies + - name: πΈοΈ Install Dependencies run: npm install shell: bash working-directory: ./packages/react/test/build/${{ inputs.app }} - - name: Sync Built Changes + - name: π Sync Built Changes run: npm run sync shell: bash working-directory: ./packages/react/test/build/${{ inputs.app }} - - name: Build + - name: ποΈ Build run: npm run build shell: bash working-directory: ./packages/react/test/build/${{ inputs.app }} - - name: Run Tests + - name: π§ͺ Run Tests run: npm run e2e shell: bash working-directory: ./packages/react/test/build/${{ inputs.app }} diff --git a/.github/workflows/actions/test-react-router-e2e/action.yml b/.github/workflows/actions/test-react-router-e2e/action.yml index f1f0150de11..784e354465a 100644 --- a/.github/workflows/actions/test-react-router-e2e/action.yml +++ b/.github/workflows/actions/test-react-router-e2e/action.yml @@ -6,9 +6,9 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core @@ -24,23 +24,23 @@ runs: name: ionic-react-router path: ./packages/react-router filename: ReactRouterBuild.zip - - name: Create Test App + - name: π§ͺ Create Test App run: ./build.sh ${{ inputs.app }} shell: bash working-directory: ./packages/react-router/test - - name: Install Dependencies + - name: πΈοΈ Install Dependencies run: npm install shell: bash working-directory: ./packages/react-router/test/build/${{ inputs.app }} - - name: Sync Built Changes + - name: π Sync Built Changes run: npm run sync shell: bash working-directory: ./packages/react-router/test/build/${{ inputs.app }} - - name: Build + - name: ποΈ Build run: npm run build shell: bash working-directory: ./packages/react-router/test/build/${{ inputs.app }} - - name: Run Tests + - name: π§ͺ Run Tests run: npm run e2e shell: bash working-directory: ./packages/react-router/test/build/${{ inputs.app }} diff --git a/.github/workflows/actions/test-vue-e2e/action.yml b/.github/workflows/actions/test-vue-e2e/action.yml index 905cb319a7f..228732067e9 100644 --- a/.github/workflows/actions/test-vue-e2e/action.yml +++ b/.github/workflows/actions/test-vue-e2e/action.yml @@ -6,9 +6,9 @@ inputs: runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x + node-version: 24.x - uses: ./.github/workflows/actions/download-archive with: name: ionic-core @@ -24,23 +24,23 @@ runs: name: ionic-vue-router path: ./packages/vue-router filename: VueRouterBuild.zip - - name: Create Test App + - name: π§ͺ Create Test App run: ./build.sh ${{ inputs.app }} shell: bash working-directory: ./packages/vue/test - - name: Install Dependencies + - name: π¦ Install Dependencies run: npm install shell: bash working-directory: ./packages/vue/test/build/${{ inputs.app }} - - name: Sync + - name: π Sync run: npm run sync shell: bash working-directory: ./packages/vue/test/build/${{ inputs.app }} - - name: Run Spec Tests + - name: π§ͺ Run Spec Tests run: npm run test:unit shell: bash working-directory: ./packages/vue/test/build/${{ inputs.app }} - - name: Run E2E Tests + - name: π§ͺ Run E2E Tests run: npm run test:e2e shell: bash working-directory: ./packages/vue/test/build/${{ inputs.app }} diff --git a/.github/workflows/actions/update-reference-screenshots/action.yml b/.github/workflows/actions/update-reference-screenshots/action.yml index 95d0c7b726b..e6f0aa817a3 100644 --- a/.github/workflows/actions/update-reference-screenshots/action.yml +++ b/.github/workflows/actions/update-reference-screenshots/action.yml @@ -7,13 +7,13 @@ on: runs: using: 'composite' steps: - - uses: actions/setup-node@v5 + - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: - node-version: 22.x - - uses: actions/download-artifact@v5 + node-version: 24.x + - uses: actions/download-artifact@v6 with: path: ./artifacts - - name: Extract Archives + - name: π Extract Archives # This finds all .zip files in the ./artifacts # directory, including nested directories. # It then unzips every .zip to the root directory @@ -21,7 +21,7 @@ runs: find . -type f -name 'UpdatedScreenshots-*.zip' -exec unzip -q -o -d ../ {} \; shell: bash working-directory: ./artifacts - - name: Push Screenshots + - name: πΈ Push Screenshots # Configure user as Ionitron # and push only the changed .png snapshots # to the remote branch. diff --git a/.github/workflows/actions/upload-archive/action.yml b/.github/workflows/actions/upload-archive/action.yml index 966b80e3a00..e836e84c82d 100644 --- a/.github/workflows/actions/upload-archive/action.yml +++ b/.github/workflows/actions/upload-archive/action.yml @@ -10,10 +10,10 @@ inputs: runs: using: 'composite' steps: - - name: Create Archive + - name: ποΈ Create Archive run: zip -q -r ${{ inputs.output }} ${{ inputs.paths }} shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: ${{ inputs.name }} path: ${{ inputs.output }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4800023a635..3e0ff766db6 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/test-core-clean-build test-core-lint: needs: [build-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/test-core-lint test-core-spec: needs: [build-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/build-vue build-vue-router: needs: [build-vue] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/build-angular build-angular-server: needs: [build-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/build-react build-react-router: needs: [build-react] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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 6ac78c8dc83..1e5f1cad4b4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -14,8 +14,8 @@ jobs: permissions: security-events: write steps: - - uses: actions/checkout@v5 - - uses: github/codeql-action/init@v3 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: github/codeql-action/init@v4 with: languages: javascript - - uses: github/codeql-action/analyze@v3 + - uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 20746438552..15861814e9d 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -1,7 +1,11 @@ name: 'Ionic Dev Build' on: - workflow_dispatch: + workflow_call: + +permissions: + contents: read + id-token: write jobs: create-dev-hash: @@ -9,7 +13,7 @@ jobs: outputs: dev-hash: ${{ steps.create-dev-hash.outputs.DEV_HASH }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 # 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 @@ -25,13 +29,12 @@ jobs: release-ionic: needs: [create-dev-hash] permissions: + contents: read id-token: write uses: ./.github/workflows/release-ionic.yml with: tag: dev version: ${{ needs.create-dev-hash.outputs.dev-hash }} - secrets: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} get-build: name: Get your dev build! diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a0f75e0db6a..e44ba2d7516 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,10 +1,11 @@ name: 'Ionic Nightly Build' on: - schedule: - # Run every Monday-Friday - # at 6:00 UTC (6:00 am UTC) - - cron: '00 06 * * 1-5' + workflow_call: + +permissions: + contents: read + id-token: write jobs: create-nightly-hash: @@ -12,7 +13,7 @@ jobs: outputs: nightly-hash: ${{ steps.create-nightly-hash.outputs.NIGHTLY_HASH }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 # 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 @@ -30,10 +31,10 @@ jobs: release-ionic: needs: [create-nightly-hash] permissions: + contents: read id-token: write uses: ./.github/workflows/release-ionic.yml + secrets: inherit with: tag: nightly version: ${{ needs.create-nightly-hash.outputs.nightly-hash }} - secrets: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-ionic.yml b/.github/workflows/release-ionic.yml index 16baa3384c0..b4470041ffb 100644 --- a/.github/workflows/release-ionic.yml +++ b/.github/workflows/release-ionic.yml @@ -14,23 +14,23 @@ on: preid: description: 'The prerelease identifier used when doing a prerelease.' type: string - secrets: - NPM_TOKEN: - required: true + +permissions: + contents: read + id-token: write jobs: release-core: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: ./.github/workflows/actions/publish-npm + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: ./.github/actions/publish-npm with: scope: '@ionic/core' tag: ${{ inputs.tag }} version: ${{ inputs.version }} preid: ${{ inputs.preid }} working-directory: 'core' - token: ${{ secrets.NPM_TOKEN }} - name: Cache Built @ionic/core uses: ./.github/workflows/actions/upload-archive with: @@ -48,34 +48,33 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore @ionic/docs built cache uses: ./.github/workflows/actions/download-archive with: name: ionic-docs path: ./packages/docs filename: DocsBuild.zip - - uses: ./.github/workflows/actions/publish-npm + - uses: ./.github/actions/publish-npm with: scope: '@ionic/docs' tag: ${{ inputs.tag }} version: ${{ inputs.version }} preid: ${{ inputs.preid }} working-directory: 'packages/docs' - token: ${{ secrets.NPM_TOKEN }} release-angular: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - uses: ./.github/workflows/actions/publish-npm + - uses: ./.github/actions/publish-npm with: scope: '@ionic/angular' tag: ${{ inputs.tag }} @@ -83,7 +82,6 @@ jobs: preid: ${{ inputs.preid }} working-directory: 'packages/angular' folder: './dist' - token: ${{ secrets.NPM_TOKEN }} - name: Cache Built @ionic/angular uses: ./.github/workflows/actions/upload-archive with: @@ -95,21 +93,20 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - uses: ./.github/workflows/actions/publish-npm + - uses: ./.github/actions/publish-npm with: scope: '@ionic/react' tag: ${{ inputs.tag }} version: ${{ inputs.version }} preid: ${{ inputs.preid }} working-directory: 'packages/react' - token: ${{ secrets.NPM_TOKEN }} - name: Cache Built @ionic/react uses: ./.github/workflows/actions/upload-archive with: @@ -121,21 +118,20 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - uses: ./.github/workflows/actions/publish-npm + - uses: ./.github/actions/publish-npm with: scope: '@ionic/vue' tag: ${{ inputs.tag }} version: ${{ inputs.version }} preid: ${{ inputs.preid }} working-directory: 'packages/vue' - token: ${{ secrets.NPM_TOKEN }} - name: Cache Built @ionic/vue uses: ./.github/workflows/actions/upload-archive with: @@ -147,14 +143,14 @@ jobs: needs: [release-core] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: name: ionic-core path: ./core filename: CoreBuild.zip - - uses: ./.github/workflows/actions/publish-npm + - uses: ./.github/actions/publish-npm with: scope: '@ionic/angular-server' tag: ${{ inputs.tag }} @@ -162,13 +158,12 @@ jobs: preid: ${{ inputs.preid }} working-directory: 'packages/angular-server' folder: './dist' - token: ${{ secrets.NPM_TOKEN }} release-react-router: needs: [release-react] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: @@ -181,20 +176,19 @@ jobs: name: ionic-react path: ./packages/react filename: ReactBuild.zip - - uses: ./.github/workflows/actions/publish-npm + - uses: ./.github/actions/publish-npm with: scope: '@ionic/react-router' tag: ${{ inputs.tag }} version: ${{ inputs.version }} preid: ${{ inputs.preid }} working-directory: 'packages/react-router' - token: ${{ secrets.NPM_TOKEN }} release-vue-router: needs: [release-vue] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Restore @ionic/core built cache uses: ./.github/workflows/actions/download-archive with: @@ -207,11 +201,10 @@ jobs: name: ionic-vue path: ./packages/vue filename: VueBuild.zip - - uses: ./.github/workflows/actions/publish-npm + - uses: ./.github/actions/publish-npm with: scope: '@ionic/vue-router' tag: ${{ inputs.tag }} version: ${{ inputs.version }} preid: ${{ inputs.preid }} working-directory: 'packages/vue-router' - token: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-orchestrator.yml b/.github/workflows/release-orchestrator.yml new file mode 100644 index 00000000000..2c41ed516f9 --- /dev/null +++ b/.github/workflows/release-orchestrator.yml @@ -0,0 +1,81 @@ +name: 'Release - Ionic Framework' + +on: + schedule: + # Run every Monday-Friday + # at 6:00 UTC (6:00 am UTC) + - cron: '00 06 * * 1-5' + workflow_dispatch: + inputs: + release-type: + description: 'Which Ionic release workflow should run?' + required: true + type: choice + default: dev + options: + - dev + - production + version: + description: 'Which version should be published? (Only for production releases)' + required: false + type: choice + options: + - patch + - minor + - major + - prepatch + - preminor + - premajor + - prerelease + tag: + description: 'Which npm tag should this be published to? (Only for production releases)' + required: false + type: choice + default: latest + options: + - latest + - next + preid: + description: 'Which prerelease identifier should be used? (Only for production releases)' + required: false + type: choice + default: '' + options: + - '' + - alpha + - beta + - rc + - next + +permissions: + contents: read + id-token: write + +jobs: + run-nightly: + if: ${{ github.event_name == 'schedule' }} + permissions: + contents: read + id-token: write + uses: ./.github/workflows/nightly.yml + secrets: inherit + + run-dev: + if: ${{ github.event_name == 'workflow_dispatch' && inputs.release-type == 'dev' }} + permissions: + contents: read + id-token: write + uses: ./.github/workflows/dev-build.yml + secrets: inherit + + run-production: + if: ${{ github.event_name == 'workflow_dispatch' && inputs.release-type == 'production' }} + permissions: + contents: read + id-token: write + uses: ./.github/workflows/release.yml + secrets: inherit + with: + version: ${{ inputs.version }} + tag: ${{ inputs.tag }} + preid: ${{ inputs.preid }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 084c8af3077..650486bb981 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,54 +1,61 @@ name: 'Ionic Production Release' on: - workflow_dispatch: + workflow_call: inputs: version: + description: 'Which version should be published?' required: true - type: choice - description: Which version should be published? - options: - - patch - - minor - - major - - prepatch - - preminor - - premajor - - prerelease + type: string tag: + description: 'Which npm tag should this be published to?' required: true - type: choice - description: Which npm tag should this be published to? - options: - - latest - - next + type: string preid: - type: choice - description: Which prerelease identifier should be used? This is only needed when version is "prepatch", "preminor", "premajor", or "prerelease". - options: - - '' - - alpha - - beta - - rc - - next + description: 'Which prerelease identifier should be used? This is only needed when version is "prepatch", "preminor", "premajor", or "prerelease".' + required: false + type: string + +permissions: + contents: read + id-token: write jobs: + validate_version: + name: β Validate Version Input + runs-on: ubuntu-latest + steps: + - name: π Ensure version is allowed + env: + VERSION: ${{ inputs.version }} + run: | + case "$VERSION" in + patch|minor|major|prepatch|preminor|premajor|prerelease) + exit 0 + ;; + *) + echo "::error::Invalid version input: '$VERSION'. Allowed values: patch, minor, major, prepatch, preminor, premajor, prerelease." + exit 1 + ;; + esac + shell: bash + release-ionic: + needs: [validate_version] permissions: + contents: read id-token: write uses: ./.github/workflows/release-ionic.yml with: tag: ${{ inputs.tag }} version: ${{ inputs.version }} preid: ${{ inputs.preid }} - secrets: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} finalize-release: needs: [release-ionic] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: token: ${{ secrets.IONITRON_TOKEN }} fetch-depth: 0 @@ -76,7 +83,7 @@ jobs: needs: [finalize-release] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 # 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 6b24a9d69bb..653d4060637 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/test-core-lint test-core-spec: needs: [build-core-with-stencil-nightly] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/build-vue build-vue-router: needs: [build-vue] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/build-angular build-angular-server: needs: [build-core-with-stencil-nightly] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/build-react build-react-router: needs: [build-react] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: ./.github/workflows/actions/test-react-e2e with: app: ${{ matrix.apps }} @@ -225,3 +225,35 @@ jobs: - name: Check build matrix status if: ${{ needs.test-react-e2e.result != 'success' }} run: exit 1 + + send-success-messages: + needs: [test-core-clean-build, test-core-lint, test-core-spec, verify-screenshots, verify-test-vue-e2e, verify-test-angular-e2e, verify-test-react-router-e2e, verify-test-react-e2e] + runs-on: ubuntu-latest + if: ${{ !cancelled() && !contains(needs.*.result, 'failure') }} + steps: + - name: Notify success on Discord + run: | + curl -H "Content-Type:application/json" \ + -d '{"embeds": [{"title": "β Workflow ${{github.workflow}} #${{github.run_number}} finished successfully", "color": 65280, "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}]}' \ + ${{secrets.DISCORD_NOTIFY_WEBHOOK}} + - name: Notify success on Slack + run: | + curl -H "Content-Type:application/json" \ + -d '{"title": "β Workflow ${{github.workflow}} #${{github.run_number}} finished successfully", "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}' \ + ${{secrets.SLACK_NOTIFY_SUCCESS_WEBHOOK}} + + send-failure-messages: + needs: [test-core-clean-build, test-core-lint, test-core-spec, verify-screenshots, verify-test-vue-e2e, verify-test-angular-e2e, verify-test-react-router-e2e, verify-test-react-e2e] + runs-on: ubuntu-latest + if: ${{ !cancelled() && contains(needs.*.result, 'failure') }} + steps: + - name: Notify failure on Discord + run: | + curl -H "Content-Type:application/json" \ + -d '{"content": "Alerting <@&1347593178580254761>!", "embeds": [{"title": "β Workflow ${{github.workflow}} #${{github.run_number}} failed", "color": 16711680, "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}]}' \ + ${{secrets.DISCORD_NOTIFY_WEBHOOK}} + - name: Notify failure on Slack + run: | + curl -H "Content-Type:application/json" \ + -d '{"title": "β Workflow ${{github.workflow}} #${{github.run_number}} failed", "url": "${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}"}' \ + ${{secrets.SLACK_NOTIFY_FAILURE_WEBHOOK}} diff --git a/.github/workflows/update-screenshots.yml b/.github/workflows/update-screenshots.yml index ef5dcf31347..967fd3965c4 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - 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@v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 # Normally, we could just push with the # default GITHUB_TOKEN, but that will # not cause the build workflow diff --git a/CHANGELOG.md b/CHANGELOG.md index 47210c4bf27..5aca45ac369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,63 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.10](https://github.com/ionic-team/ionic-framework/compare/v8.7.9...v8.7.10) (2025-11-19) + + +### Bug Fixes + +* **checkbox, toggle, radio-group:** improve screen reader announcement timing for validation errors ([#30714](https://github.com/ionic-team/ionic-framework/issues/30714)) ([92db364](https://github.com/ionic-team/ionic-framework/commit/92db36489cca944caf1593dbd518a1f025a171a2)) + + + + + +## [8.7.9](https://github.com/ionic-team/ionic-framework/compare/v8.7.8...v8.7.9) (2025-11-05) + + +### Bug Fixes + +* **accordion-group:** skip initial animation ([#30729](https://github.com/ionic-team/ionic-framework/issues/30729)) ([58d5638](https://github.com/ionic-team/ionic-framework/commit/58d563805fca1db88caeeb40a8f710ac30416d93)), closes [#30613](https://github.com/ionic-team/ionic-framework/issues/30613) + + + + + +## [8.7.8](https://github.com/ionic-team/ionic-framework/compare/v8.7.7...v8.7.8) (2025-10-29) + + +### Bug Fixes + +* **checkbox, toggle:** fire ionFocus and ionBlur ([#30733](https://github.com/ionic-team/ionic-framework/issues/30733)) ([54a1c86](https://github.com/ionic-team/ionic-framework/commit/54a1c86d6a5d533b0c8c2d18edc62454a7c17bab)) + + + + + +## [8.7.7](https://github.com/ionic-team/ionic-framework/compare/v8.7.6...v8.7.7) (2025-10-15) + + +### Bug Fixes + +* **header:** ensure one banner role in condensed header ([#30718](https://github.com/ionic-team/ionic-framework/issues/30718)) ([12084af](https://github.com/ionic-team/ionic-framework/commit/12084af163ed811b9c6bda3c7850fc0c53c60c7b)) +* **header:** prevent flickering during iOS page transitions ([#30705](https://github.com/ionic-team/ionic-framework/issues/30705)) ([820fa28](https://github.com/ionic-team/ionic-framework/commit/820fa2854331722d22efd0e38a1936117477967a)), closes [#25326](https://github.com/ionic-team/ionic-framework/issues/25326) +* **select:** improve screen reader announcement timing for validation errors ([#30723](https://github.com/ionic-team/ionic-framework/issues/30723)) ([03303d7](https://github.com/ionic-team/ionic-framework/commit/03303d73f0bfe2380ced7931525fc52fd8576367)) + + + + + +## [8.7.6](https://github.com/ionic-team/ionic-framework/compare/v8.7.5...v8.7.6) (2025-10-08) + + +### Bug Fixes + +* **tabs:** respect stencil lifecycle order for tab selection ([#30702](https://github.com/ionic-team/ionic-framework/issues/30702)) ([7bb9535](https://github.com/ionic-team/ionic-framework/commit/7bb9535f601d2469ce60687a9c03f8b1cfe4aba4)), closes [#30611](https://github.com/ionic-team/ionic-framework/issues/30611) + + + + + ## [8.7.5](https://github.com/ionic-team/ionic-framework/compare/v8.7.4...v8.7.5) (2025-09-24) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 5be7265795b..d19d8deea27 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,63 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.10](https://github.com/ionic-team/ionic-framework/compare/v8.7.9...v8.7.10) (2025-11-19) + + +### Bug Fixes + +* **checkbox, toggle, radio-group:** improve screen reader announcement timing for validation errors ([#30714](https://github.com/ionic-team/ionic-framework/issues/30714)) ([92db364](https://github.com/ionic-team/ionic-framework/commit/92db36489cca944caf1593dbd518a1f025a171a2)) + + + + + +## [8.7.9](https://github.com/ionic-team/ionic-framework/compare/v8.7.8...v8.7.9) (2025-11-05) + + +### Bug Fixes + +* **accordion-group:** skip initial animation ([#30729](https://github.com/ionic-team/ionic-framework/issues/30729)) ([58d5638](https://github.com/ionic-team/ionic-framework/commit/58d563805fca1db88caeeb40a8f710ac30416d93)), closes [#30613](https://github.com/ionic-team/ionic-framework/issues/30613) + + + + + +## [8.7.8](https://github.com/ionic-team/ionic-framework/compare/v8.7.7...v8.7.8) (2025-10-29) + + +### Bug Fixes + +* **checkbox, toggle:** fire ionFocus and ionBlur ([#30733](https://github.com/ionic-team/ionic-framework/issues/30733)) ([54a1c86](https://github.com/ionic-team/ionic-framework/commit/54a1c86d6a5d533b0c8c2d18edc62454a7c17bab)) + + + + + +## [8.7.7](https://github.com/ionic-team/ionic-framework/compare/v8.7.6...v8.7.7) (2025-10-15) + + +### Bug Fixes + +* **header:** ensure one banner role in condensed header ([#30718](https://github.com/ionic-team/ionic-framework/issues/30718)) ([12084af](https://github.com/ionic-team/ionic-framework/commit/12084af163ed811b9c6bda3c7850fc0c53c60c7b)) +* **header:** prevent flickering during iOS page transitions ([#30705](https://github.com/ionic-team/ionic-framework/issues/30705)) ([820fa28](https://github.com/ionic-team/ionic-framework/commit/820fa2854331722d22efd0e38a1936117477967a)), closes [#25326](https://github.com/ionic-team/ionic-framework/issues/25326) +* **select:** improve screen reader announcement timing for validation errors ([#30723](https://github.com/ionic-team/ionic-framework/issues/30723)) ([03303d7](https://github.com/ionic-team/ionic-framework/commit/03303d73f0bfe2380ced7931525fc52fd8576367)) + + + + + +## [8.7.6](https://github.com/ionic-team/ionic-framework/compare/v8.7.5...v8.7.6) (2025-10-08) + + +### Bug Fixes + +* **tabs:** respect stencil lifecycle order for tab selection ([#30702](https://github.com/ionic-team/ionic-framework/issues/30702)) ([7bb9535](https://github.com/ionic-team/ionic-framework/commit/7bb9535f601d2469ce60687a9c03f8b1cfe4aba4)), closes [#30611](https://github.com/ionic-team/ionic-framework/issues/30611) + + + + + ## [8.7.5](https://github.com/ionic-team/ionic-framework/compare/v8.7.4...v8.7.5) (2025-09-24) diff --git a/core/Dockerfile b/core/Dockerfile index 095cde63a4a..50a4687701c 100644 --- a/core/Dockerfile +++ b/core/Dockerfile @@ -1,5 +1,5 @@ # Get Playwright -FROM mcr.microsoft.com/playwright:v1.55.1 +FROM mcr.microsoft.com/playwright:v1.56.1 # Set the working directory WORKDIR /ionic diff --git a/core/package-lock.json b/core/package-lock.json index 92433ef97d7..17febb498f2 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/core", - "version": "8.7.5", + "version": "8.7.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "8.7.5", + "version": "8.7.10", "license": "MIT", "dependencies": { "@phosphor-icons/core": "^2.1.1", @@ -15,7 +15,7 @@ "tslib": "^2.1.0" }, "devDependencies": { - "@axe-core/playwright": "^4.10.2", + "@axe-core/playwright": "^4.11.0", "@capacitor/core": "^7.0.0", "@capacitor/haptics": "^7.0.0", "@capacitor/keyboard": "^7.0.0", @@ -23,7 +23,7 @@ "@clack/prompts": "^0.11.0", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^2.0.0", - "@playwright/test": "^1.55.1", + "@playwright/test": "^1.56.1", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", "@stencil/angular-output-target": "^0.10.0", @@ -59,12 +59,13 @@ "dev": true }, "node_modules/@axe-core/playwright": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.10.2.tgz", - "integrity": "sha512-6/b5BJjG6hDaRNtgzLIfKr5DfwyiLHO4+ByTLB0cJgWSM8Ll7KqtdblIS6bEkwSF642/Ex91vNqIl3GLXGlceg==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.11.0.tgz", + "integrity": "sha512-70vBT/Ylqpm65RQz2iCG2o0JJCEG/WCNyefTr2xcOcr1CoSee60gNQYUMZZ7YukoKkFLv26I/jjlsvwwp532oQ==", "dev": true, + "license": "MPL-2.0", "dependencies": { - "axe-core": "~4.10.3" + "axe-core": "~4.11.0" }, "peerDependencies": { "playwright-core": ">= 1.0.0" @@ -96,6 +97,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -746,6 +748,7 @@ "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.4.0.tgz", "integrity": "sha512-P6NnjoHyobZgTjynlZSn27d0SUj6j38inlNxFnKZr9qwU7/r6+0Sg2nWkGkIH/pMmXHsvGD8zVe6KUq1UncIjA==", "dev": true, + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -806,6 +809,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -822,6 +826,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -838,6 +843,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -854,6 +860,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -886,6 +893,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -902,6 +910,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -918,6 +927,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -934,6 +944,7 @@ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -950,6 +961,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -966,6 +978,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -982,6 +995,7 @@ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -998,6 +1012,7 @@ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1014,6 +1029,7 @@ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1030,6 +1046,7 @@ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1046,6 +1063,7 @@ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1062,6 +1080,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -1078,6 +1097,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -1094,6 +1114,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -1110,6 +1131,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -1126,6 +1148,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -1142,6 +1165,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openharmony" @@ -1158,6 +1182,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -1174,6 +1199,7 @@ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1190,6 +1216,7 @@ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1206,6 +1233,7 @@ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -1412,6 +1440,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz", "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "4.33.0", "@typescript-eslint/types": "4.33.0", @@ -2351,13 +2380,13 @@ "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==" }, "node_modules/@playwright/test": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz", - "integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.1.tgz", + "integrity": "sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.55.1" + "playwright": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -2555,6 +2584,7 @@ "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.0.tgz", "integrity": "sha512-oC3QFKO0X1yXVvETgc8OLY525MNKhn9vISBrbtKnGoPlokJ6rI8Vk1RK22TevnNrHLI4SExNLbcDnqilKR35JQ==", "license": "MIT", + "peer": true, "bin": { "stencil": "bin/stencil" }, @@ -3022,6 +3052,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.2.tgz", "integrity": "sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.7.2", "@typescript-eslint/types": "6.7.2", @@ -3258,7 +3289,6 @@ "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.25.3", "@vue/shared": "3.5.13", @@ -3273,7 +3303,6 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=0.12" }, @@ -3286,8 +3315,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/@vue/compiler-dom": { "version": "3.5.13", @@ -3295,7 +3323,6 @@ "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-core": "3.5.13", "@vue/shared": "3.5.13" @@ -3307,7 +3334,6 @@ "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.25.3", "@vue/compiler-core": "3.5.13", @@ -3325,8 +3351,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/@vue/compiler-sfc/node_modules/postcss": { "version": "8.5.3", @@ -3348,7 +3373,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -3364,7 +3388,6 @@ "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.13", "@vue/shared": "3.5.13" @@ -3376,7 +3399,6 @@ "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vue/shared": "3.5.13" } @@ -3387,7 +3409,6 @@ "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vue/reactivity": "3.5.13", "@vue/shared": "3.5.13" @@ -3399,7 +3420,6 @@ "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vue/reactivity": "3.5.13", "@vue/runtime-core": "3.5.13", @@ -3413,7 +3433,6 @@ "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-ssr": "3.5.13", "@vue/shared": "3.5.13" @@ -3427,8 +3446,7 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@zeit/schemas": { "version": "2.21.0", @@ -3465,6 +3483,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3713,10 +3732,11 @@ } }, "node_modules/axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", + "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", "dev": true, + "license": "MPL-2.0", "engines": { "node": ">=4" } @@ -4852,8 +4872,7 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "2.6.9", @@ -5287,6 +5306,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -8988,7 +9008,6 @@ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } @@ -9376,7 +9395,6 @@ } ], "license": "MIT", - "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -9903,13 +9921,13 @@ } }, "node_modules/playwright": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz", - "integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz", + "integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.55.1" + "playwright-core": "1.56.1" }, "bin": { "playwright": "cli.js" @@ -9922,11 +9940,12 @@ } }, "node_modules/playwright-core": { - "version": "1.55.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz", - "integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==", + "version": "1.56.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz", + "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -9948,6 +9967,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", "dev": true, + "peer": true, "dependencies": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -10063,6 +10083,7 @@ "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", "dev": true, + "peer": true, "peerDependencies": { "postcss": ">=5.0.0" } @@ -10116,6 +10137,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -10530,6 +10552,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.35.1.tgz", "integrity": "sha512-q5KxEyWpprAIcainhVy6HfRttD9kutQpHbeqDTWnqAFNJotiojetK6uqmcydNMymBEtC4I8bCYR+J3mTMqeaUA==", "dev": true, + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -10864,7 +10887,6 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -11667,7 +11689,8 @@ "node_modules/tslib": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", + "peer": true }, "node_modules/tsutils": { "version": "3.21.0", diff --git a/core/package.json b/core/package.json index 3956fcfaf94..0abffb958bc 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "8.7.5", + "version": "8.7.10", "description": "Base components for Ionic", "keywords": [ "ionic", @@ -37,7 +37,7 @@ "tslib": "^2.1.0" }, "devDependencies": { - "@axe-core/playwright": "^4.10.2", + "@axe-core/playwright": "^4.11.0", "@capacitor/core": "^7.0.0", "@capacitor/haptics": "^7.0.0", "@capacitor/keyboard": "^7.0.0", @@ -45,7 +45,7 @@ "@clack/prompts": "^0.11.0", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^2.0.0", - "@playwright/test": "^1.55.1", + "@playwright/test": "^1.56.1", "@rollup/plugin-node-resolve": "^8.4.0", "@rollup/plugin-virtual": "^2.0.3", "@stencil/angular-output-target": "^0.10.0", diff --git a/core/src/components/accordion/accordion.tsx b/core/src/components/accordion/accordion.tsx index 6321c581851..4f76c035e7f 100644 --- a/core/src/components/accordion/accordion.tsx +++ b/core/src/components/accordion/accordion.tsx @@ -42,7 +42,40 @@ const enum AccordionState { }) export class Accordion implements ComponentInterface { private accordionGroupEl?: HTMLIonAccordionGroupElement | null; - private updateListener = () => this.updateState(false); + private accordionGroupUpdateHandler = () => { + /** + * Determine if this update will cause an actual state change. + * We only want to mark as "interacted" if the state is changing. + */ + const accordionGroup = this.accordionGroupEl; + if (accordionGroup) { + const value = accordionGroup.value; + const accordionValue = this.value; + const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue; + const isExpanded = this.state === AccordionState.Expanded || this.state === AccordionState.Expanding; + const stateWillChange = shouldExpand !== isExpanded; + + /** + * Only mark as interacted if: + * 1. This is not the first update we've received with a defined value + * 2. The state is actually changing (prevents redundant updates from enabling animations) + */ + if (this.hasReceivedFirstUpdate && stateWillChange) { + this.hasInteracted = true; + } + + /** + * Only count this as the first update if the group value is defined. + * This prevents the initial undefined value from the group's componentDidLoad + * from being treated as the first real update. + */ + if (value !== undefined) { + this.hasReceivedFirstUpdate = true; + } + } + + this.updateState(); + }; private contentEl: HTMLDivElement | undefined; private contentElWrapper: HTMLDivElement | undefined; private headerEl: HTMLDivElement | undefined; @@ -54,6 +87,25 @@ export class Accordion implements ComponentInterface { @State() state: AccordionState = AccordionState.Collapsed; @State() isNext = false; @State() isPrevious = false; + /** + * Tracks whether a user-initiated interaction has occurred. + * Animations are disabled until the first interaction happens. + * This prevents the accordion from animating when it's programmatically + * set to an expanded or collapsed state on initial load. + */ + @State() hasInteracted = false; + + /** + * Tracks if this accordion has ever been expanded. + * Used to prevent the first expansion from animating. + */ + private hasEverBeenExpanded = false; + + /** + * Tracks if this accordion has received its first update from the group. + * Used to distinguish initial programmatic sets from user interactions. + */ + private hasReceivedFirstUpdate = false; /** * The value of the accordion. Defaults to an autogenerated @@ -92,15 +144,15 @@ export class Accordion implements ComponentInterface { connectedCallback() { const accordionGroupEl = (this.accordionGroupEl = this.el?.closest('ion-accordion-group')); if (accordionGroupEl) { - this.updateState(true); - addEventListener(accordionGroupEl, 'ionValueChange', this.updateListener); + this.updateState(); + addEventListener(accordionGroupEl, 'ionValueChange', this.accordionGroupUpdateHandler); } } disconnectedCallback() { const accordionGroupEl = this.accordionGroupEl; if (accordionGroupEl) { - removeEventListener(accordionGroupEl, 'ionValueChange', this.updateListener); + removeEventListener(accordionGroupEl, 'ionValueChange', this.accordionGroupUpdateHandler); } } @@ -237,10 +289,16 @@ export class Accordion implements ComponentInterface { ionItem.appendChild(iconEl); }; - private expandAccordion = (initialUpdate = false) => { + private expandAccordion = () => { const { contentEl, contentElWrapper } = this; - if (initialUpdate || contentEl === undefined || contentElWrapper === undefined) { + + /** + * If the content elements aren't available yet, just set the state. + * This happens on initial render before the DOM is ready. + */ + if (contentEl === undefined || contentElWrapper === undefined) { this.state = AccordionState.Expanded; + this.hasEverBeenExpanded = true; return; } @@ -252,6 +310,12 @@ export class Accordion implements ComponentInterface { cancelAnimationFrame(this.currentRaf); } + /** + * Mark that this accordion has been expanded at least once. + * This allows subsequent expansions to animate. + */ + this.hasEverBeenExpanded = true; + if (this.shouldAnimate()) { raf(() => { this.state = AccordionState.Expanding; @@ -272,9 +336,14 @@ export class Accordion implements ComponentInterface { } }; - private collapseAccordion = (initialUpdate = false) => { + private collapseAccordion = () => { const { contentEl } = this; - if (initialUpdate || contentEl === undefined) { + + /** + * If the content element isn't available yet, just set the state. + * This happens on initial render before the DOM is ready. + */ + if (contentEl === undefined) { this.state = AccordionState.Collapsed; return; } @@ -316,6 +385,19 @@ export class Accordion implements ComponentInterface { * of what is set in the config. */ private shouldAnimate = () => { + /** + * Don't animate until after the first user interaction. + * This prevents animations on initial load when accordions + * start in an expanded or collapsed state programmatically. + * + * Additionally, don't animate the very first expansion even if + * hasInteracted is true. This handles edge cases like React StrictMode + * where effects run twice and might incorrectly mark as interacted. + */ + if (!this.hasInteracted || !this.hasEverBeenExpanded) { + return false; + } + if (typeof (window as any) === 'undefined') { return false; } @@ -337,7 +419,7 @@ export class Accordion implements ComponentInterface { return true; }; - private updateState = async (initialUpdate = false) => { + private updateState = async () => { const accordionGroup = this.accordionGroupEl; const accordionValue = this.value; @@ -350,10 +432,10 @@ export class Accordion implements ComponentInterface { const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue; if (shouldExpand) { - this.expandAccordion(initialUpdate); + this.expandAccordion(); this.isNext = this.isPrevious = false; } else { - this.collapseAccordion(initialUpdate); + this.collapseAccordion(); /** * When using popout or inset, @@ -403,6 +485,12 @@ export class Accordion implements ComponentInterface { if (disabled || readonly) return; + /** + * Mark that the user has interacted with the accordion. + * This enables animations for all future state changes. + */ + this.hasInteracted = true; + if (accordionGroupEl) { /** * Because the accordion group may or may diff --git a/core/src/components/accordion/test/accordion.spec.ts b/core/src/components/accordion/test/accordion.spec.ts index 54883002dbf..e10fdc9d279 100644 --- a/core/src/components/accordion/test/accordion.spec.ts +++ b/core/src/components/accordion/test/accordion.spec.ts @@ -200,6 +200,87 @@ it('should set default values if not provided', async () => { expect(accordion.classList.contains('accordion-collapsed')).toEqual(false); }); +it('should not animate when initial value is set before load', async () => { + const page = await newSpecPage({ + components: [Item, Accordion, AccordionGroup], + }); + + const accordionGroup = page.doc.createElement('ion-accordion-group'); + accordionGroup.innerHTML = ` + + Label + Content + + + Label + Content + + `; + + accordionGroup.value = 'first'; + page.body.appendChild(accordionGroup); + + await page.waitForChanges(); + + const firstAccordion = accordionGroup.querySelector('ion-accordion[value="first"]')!; + + expect(firstAccordion.classList.contains('accordion-expanded')).toEqual(true); + expect(firstAccordion.classList.contains('accordion-expanding')).toEqual(false); +}); + +it('should not animate when initial value is set after load', async () => { + const page = await newSpecPage({ + components: [Item, Accordion, AccordionGroup], + }); + + const accordionGroup = page.doc.createElement('ion-accordion-group'); + accordionGroup.innerHTML = ` + + Label + Content + + + Label + Content + + `; + + page.body.appendChild(accordionGroup); + await page.waitForChanges(); + + accordionGroup.value = 'first'; + await page.waitForChanges(); + + const firstAccordion = accordionGroup.querySelector('ion-accordion[value="first"]')!; + + expect(firstAccordion.classList.contains('accordion-expanded')).toEqual(true); + expect(firstAccordion.classList.contains('accordion-expanding')).toEqual(false); +}); + +it('should not have animated class on first expansion', async () => { + const page = await newSpecPage({ + components: [Item, Accordion, AccordionGroup], + html: ` + + + Label + Content + + + `, + }); + + const accordionGroup = page.body.querySelector('ion-accordion-group')!; + const firstAccordion = page.body.querySelector('ion-accordion[value="first"]')!; + + // First expansion should not have the animated class + accordionGroup.value = 'first'; + await page.waitForChanges(); + + expect(firstAccordion.classList.contains('accordion-animated')).toEqual(false); + expect(firstAccordion.classList.contains('accordion-expanded')).toEqual(true); +}); + // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/27047 it('should not have animated class when animated="false"', async () => { const page = await newSpecPage({ diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx index ccf20ba012d..77ed273c432 100644 --- a/core/src/components/button/button.tsx +++ b/core/src/components/button/button.tsx @@ -399,11 +399,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf target, }; let fill = this.fill; - /** - * We check both undefined and null to - * work around https://github.com/ionic-team/stencil/issues/3586. - */ - if (fill == null) { + if (fill === undefined) { fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid'; } diff --git a/core/src/components/checkbox/checkbox.tsx b/core/src/components/checkbox/checkbox.tsx index 39793837454..e33eb885177 100644 --- a/core/src/components/checkbox/checkbox.tsx +++ b/core/src/components/checkbox/checkbox.tsx @@ -1,5 +1,6 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; -import { Component, Element, Event, Host, Method, Prop, h } from '@stencil/core'; +import { Build, Component, Element, Event, Host, Method, Prop, State, h } from '@stencil/core'; +import { checkInvalidState } from '@utils/forms'; import type { Attributes } from '@utils/helpers'; import { inheritAriaAttributes, renderHiddenInput } from '@utils/helpers'; import { createColorClasses, hostContext } from '@utils/theme'; @@ -36,8 +37,8 @@ export class Checkbox implements ComponentInterface { private inputLabelId = `${this.inputId}-lbl`; private helperTextId = `${this.inputId}-helper-text`; private errorTextId = `${this.inputId}-error-text`; - private focusEl?: HTMLElement; private inheritedAttributes: Attributes = {}; + private validationObserver?: MutationObserver; @Element() el!: HTMLIonCheckboxElement; @@ -133,6 +134,13 @@ export class Checkbox implements ComponentInterface { */ @Prop() size?: 'small'; + /** + * Track validation state for proper aria-live announcements. + */ + @State() isInvalid = false; + + @State() private hintTextId?: string; + /** * Emitted when the checked property has changed * as a result of a user action such as a click. @@ -151,18 +159,69 @@ export class Checkbox implements ComponentInterface { */ @Event() ionBlur!: EventEmitter; + connectedCallback() { + const { el } = this; + + // Watch for class changes to update validation state. + if (Build.isBrowser && typeof MutationObserver !== 'undefined') { + this.validationObserver = new MutationObserver(() => { + const newIsInvalid = checkInvalidState(el); + if (this.isInvalid !== newIsInvalid) { + this.isInvalid = newIsInvalid; + /** + * Screen readers tend to announce changes + * to `aria-describedby` when the attribute + * is changed during a blur event for a + * native form control. + * However, the announcement can be spotty + * when using a non-native form control + * and `forceUpdate()`. + * This is due to `forceUpdate()` internally + * rescheduling the DOM update to a lower + * priority queue regardless if it's called + * inside a Promise or not, thus causing + * the screen reader to potentially miss the + * change. + * By using a State variable inside a Promise, + * it guarantees a re-render immediately at + * a higher priority. + */ + Promise.resolve().then(() => { + this.hintTextId = this.getHintTextId(); + }); + } + }); + + this.validationObserver.observe(el, { + attributes: true, + attributeFilter: ['class'], + }); + } + + // Always set initial state + this.isInvalid = checkInvalidState(el); + } + componentWillLoad() { this.inheritedAttributes = { ...inheritAriaAttributes(this.el), }; + + this.hintTextId = this.getHintTextId(); + } + + disconnectedCallback() { + // Clean up validation observer to prevent memory leaks. + if (this.validationObserver) { + this.validationObserver.disconnect(); + this.validationObserver = undefined; + } } /** @internal */ @Method() async setFocus() { - if (this.focusEl) { - this.focusEl.focus(); - } + this.el.focus(); } /** @@ -182,7 +241,6 @@ export class Checkbox implements ComponentInterface { private toggleChecked = (ev: Event) => { ev.preventDefault(); - this.setFocus(); this.setChecked(!this.checked); this.indeterminate = false; }; @@ -220,10 +278,10 @@ export class Checkbox implements ComponentInterface { ev.stopPropagation(); }; - private getHintTextID(): string | undefined { - const { el, helperText, errorText, helperTextId, errorTextId } = this; + private getHintTextId(): string | undefined { + const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this; - if (el.classList.contains('ion-touched') && el.classList.contains('ion-invalid') && errorText) { + if (isInvalid && errorText) { return errorTextId; } @@ -239,7 +297,7 @@ export class Checkbox implements ComponentInterface { * This element should only be rendered if hint text is set. */ private renderHintText() { - const { helperText, errorText, helperTextId, errorTextId } = this; + const { helperText, errorText, helperTextId, errorTextId, isInvalid } = this; /** * undefined and empty string values should @@ -252,11 +310,11 @@ export class Checkbox implements ComponentInterface { return ( - - {helperText} + + {!isInvalid ? helperText : null} - - {errorText} + + {isInvalid ? errorText : null} ); @@ -293,13 +351,17 @@ export class Checkbox implements ComponentInterface { {/* @@ -327,9 +388,6 @@ export class Checkbox implements ComponentInterface { disabled={disabled} id={inputId} onChange={this.toggleChecked} - onFocus={() => this.onFocus()} - onBlur={() => this.onBlur()} - ref={(focusEl) => (this.focusEl = focusEl)} required={required} {...inheritedAttributes} /> diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts b/core/src/components/checkbox/test/basic/checkbox.e2e.ts index ac485dff2f9..26197359481 100644 --- a/core/src/components/checkbox/test/basic/checkbox.e2e.ts +++ b/core/src/components/checkbox/test/basic/checkbox.e2e.ts @@ -55,7 +55,10 @@ configs({ modes: ['ios', 'md', 'ionic-md'] }).forEach(({ title, screenshot, conf }); }); -configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { +/** + * This behavior does not vary across modes/directions + */ +configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { test.describe(title('checkbox: ionChange'), () => { test('should fire ionChange when interacting with checkbox', async ({ page }) => { await page.setContent( @@ -144,6 +147,197 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => expect(clickCount).toBe(1); }); }); + + test.describe(title('checkbox: ionFocus'), () => { + test('should not have visual regressions', async ({ page, pageUtils }) => { + await page.setContent( + ` + + + + Unchecked + + `, + config + ); + + await pageUtils.pressKeys('Tab'); + + const container = page.locator('#container'); + + await expect(container).toHaveScreenshot(screenshot(`checkbox-focus`)); + }); + + test('should not have visual regressions when interacting with checkbox in item', async ({ page, pageUtils }) => { + await page.setContent( + ` + + Unchecked + + `, + config + ); + + // Test focus with keyboard navigation + await pageUtils.pressKeys('Tab'); + + const item = page.locator('ion-item'); + + await expect(item).toHaveScreenshot(screenshot(`checkbox-in-item-focus`)); + }); + + test('should fire ionFocus when checkbox is focused', async ({ page, pageUtils }) => { + await page.setContent( + ` + + `, + config + ); + + const ionFocus = await page.spyOnEvent('ionFocus'); + + // Test focus with keyboard navigation + await pageUtils.pressKeys('Tab'); + + expect(ionFocus).toHaveReceivedEventTimes(1); + + // Reset focus + const checkbox = page.locator('ion-checkbox'); + const checkboxBoundingBox = (await checkbox.boundingBox())!; + await page.mouse.click(0, checkboxBoundingBox.height + 1); + + // Test focus with click + await checkbox.click(); + + expect(ionFocus).toHaveReceivedEventTimes(2); + }); + + test('should fire ionFocus when interacting with checkbox in item', async ({ page, pageUtils }) => { + await page.setContent( + ` + + + + `, + config + ); + + const ionFocus = await page.spyOnEvent('ionFocus'); + + // Test focus with keyboard navigation + await pageUtils.pressKeys('Tab'); + + expect(ionFocus).toHaveReceivedEventTimes(1); + + // Verify that the event target is the checkbox and not the item + const eventByKeyboard = ionFocus.events[0]; + expect((eventByKeyboard.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox'); + + // Reset focus + const checkbox = page.locator('ion-checkbox'); + const checkboxBoundingBox = (await checkbox.boundingBox())!; + await page.mouse.click(0, checkboxBoundingBox.height + 1); + + // Test focus with click + const item = page.locator('ion-item'); + await item.click(); + + expect(ionFocus).toHaveReceivedEventTimes(2); + + // Verify that the event target is the checkbox and not the item + const eventByClick = ionFocus.events[0]; + expect((eventByClick.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox'); + }); + + test('should not fire when programmatically setting a value', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + const ionFocus = await page.spyOnEvent('ionFocus'); + const checkbox = page.locator('ion-checkbox'); + + await checkbox.evaluate((el: HTMLIonCheckboxElement) => (el.checked = true)); + expect(ionFocus).not.toHaveReceivedEvent(); + }); + }); + + test.describe(title('checkbox: ionBlur'), () => { + test('should fire ionBlur when checkbox is blurred', async ({ page, pageUtils }) => { + await page.setContent( + ` + + `, + config + ); + + const ionBlur = await page.spyOnEvent('ionBlur'); + + // Test blur with keyboard navigation + // Focus the checkbox + await pageUtils.pressKeys('Tab'); + // Blur the checkbox + await pageUtils.pressKeys('Tab'); + + expect(ionBlur).toHaveReceivedEventTimes(1); + + // Test blur with click + const checkbox = page.locator('ion-checkbox'); + // Focus the checkbox + await checkbox.click(); + // Blur the checkbox by clicking outside of it + const checkboxBoundingBox = (await checkbox.boundingBox())!; + await page.mouse.click(0, checkboxBoundingBox.height + 1); + + expect(ionBlur).toHaveReceivedEventTimes(2); + }); + + test('should fire ionBlur after interacting with checkbox in item', async ({ page, pageUtils }) => { + await page.setContent( + ` + + + + `, + config + ); + + const ionBlur = await page.spyOnEvent('ionBlur'); + + // Test blur with keyboard navigation + // Focus the checkbox + await pageUtils.pressKeys('Tab'); + // Blur the checkbox + await pageUtils.pressKeys('Tab'); + + expect(ionBlur).toHaveReceivedEventTimes(1); + + // Verify that the event target is the checkbox and not the item + const event = ionBlur.events[0]; + expect((event.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox'); + + // Test blur with click + const item = page.locator('ion-item'); + await item.click(); + // Blur the checkbox by clicking outside of it + const itemBoundingBox = (await item.boundingBox())!; + await page.mouse.click(0, itemBoundingBox.height + 1); + + expect(ionBlur).toHaveReceivedEventTimes(2); + + // Verify that the event target is the checkbox and not the item + const eventByClick = ionBlur.events[0]; + expect((eventByClick.target as HTMLElement).tagName.toLowerCase()).toBe('ion-checkbox'); + }); + }); }); configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..45885364c12 Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..305f4d89e1b Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..23f542f7ec4 Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..2c0dcdc0baa Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..9a511961639 Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..8b59702aa81 Binary files /dev/null and b/core/src/components/checkbox/test/basic/checkbox.e2e.ts-snapshots/checkbox-in-item-focus-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/checkbox/test/basic/index.html b/core/src/components/checkbox/test/basic/index.html index bf4cf2e9baa..d78fa19d31f 100644 --- a/core/src/components/checkbox/test/basic/index.html +++ b/core/src/components/checkbox/test/basic/index.html @@ -60,6 +60,20 @@ Specified width Full-width + +