diff --git a/.bazelversion b/.bazelversion index e81e85b81044..5942a0d3a0e7 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -7.6.2 +7.7.1 diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml index 7fc107f4320e..26d7170c03d0 100644 --- a/.github/workflows/assistant-to-the-branch-manager.yml +++ b/.github/workflows/assistant-to-the-branch-manager.yml @@ -13,9 +13,9 @@ jobs: assistant_to_the_branch_manager: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false - - uses: angular/dev-infra/github-actions/branch-manager@c584c3565b71c7a8cda81d55bb14e3e66ef934da + - uses: angular/dev-infra/github-actions/branch-manager@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/ci.material-aio.yml b/.github/workflows/ci.material-aio.yml index dddf12c0ee2f..c2291523a2ee 100644 --- a/.github/workflows/ci.material-aio.yml +++ b/.github/workflows/ci.material-aio.yml @@ -21,11 +21,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Build @@ -35,11 +35,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Tests @@ -56,11 +56,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Lighthouse Audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5843558fd0b..0bfb3eca1583 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Checking package externals @@ -47,11 +47,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -63,11 +63,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -79,11 +79,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -96,11 +96,11 @@ jobs: runs-on: ubuntu-latest-16core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -112,11 +112,11 @@ jobs: runs-on: ubuntu-latest-16core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -128,11 +128,11 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Build and Verify Release Output @@ -152,15 +152,15 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae # See: https://github.com/puppeteer/puppeteer/pull/13196 and # https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md. - name: Disable AppArmor run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Build and Verify Release Output @@ -183,12 +183,12 @@ jobs: CI_RUNNER_NUMBER: ${{ github.run_id }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Browserstack Variables - uses: angular/dev-infra/github-actions/browserstack@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/browserstack@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Run tests on Browserstack run: ./scripts/circleci/run-browserstack-tests.sh diff --git a/.github/workflows/deploy-dev-app-main-push.yml b/.github/workflows/deploy-dev-app-main-push.yml index 6937d12b6fd5..de6de0f4dda8 100644 --- a/.github/workflows/deploy-dev-app-main-push.yml +++ b/.github/workflows/deploy-dev-app-main-push.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile diff --git a/.github/workflows/dev-infra.yml b/.github/workflows/dev-infra.yml index edae341d2c88..7b974341c41a 100644 --- a/.github/workflows/dev-infra.yml +++ b/.github/workflows/dev-infra.yml @@ -11,14 +11,14 @@ jobs: labels: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: angular/dev-infra/github-actions/pull-request-labeling@c584c3565b71c7a8cda81d55bb14e3e66ef934da + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: angular/dev-infra/github-actions/pull-request-labeling@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} post_approval_changes: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: angular/dev-infra/github-actions/post-approval-changes@c584c3565b71c7a8cda81d55bb14e3e66ef934da + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: angular/dev-infra/github-actions/post-approval-changes@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/docs-preview-build.yml b/.github/workflows/docs-preview-build.yml index 6b9694c48947..38b4cf20b37f 100644 --- a/.github/workflows/docs-preview-build.yml +++ b/.github/workflows/docs-preview-build.yml @@ -21,16 +21,16 @@ jobs: (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'docs: preview')) steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Build docs site run: pnpm bazel build //docs:build.production - - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c584c3565b71c7a8cda81d55bb14e3e66ef934da + - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: workflow-artifact-name: 'docs-preview' pull-number: '${{github.event.pull_request.number}}' diff --git a/.github/workflows/docs-preview-deploy.yml b/.github/workflows/docs-preview-deploy.yml index 7c44157d7999..548515e335b2 100644 --- a/.github/workflows/docs-preview-deploy.yml +++ b/.github/workflows/docs-preview-deploy.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 with: token: '${{secrets.GITHUB_TOKEN}}' @@ -40,7 +40,7 @@ jobs: npx -y firebase-tools@latest target:clear --config docs/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting mat-aio npx -y firebase-tools@latest target:apply --config docs/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting mat-aio ${{env.PREVIEW_SITE}} - - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c584c3565b71c7a8cda81d55bb14e3e66ef934da + - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: github-token: '${{secrets.GITHUB_TOKEN}}' workflow-artifact-name: 'docs-preview' diff --git a/.github/workflows/google-internal-tests.yml b/.github/workflows/google-internal-tests.yml index fdb85ad6234c..95c7e4013c96 100644 --- a/.github/workflows/google-internal-tests.yml +++ b/.github/workflows/google-internal-tests.yml @@ -12,8 +12,8 @@ jobs: trigger: runs-on: ubuntu-latest steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: angular/dev-infra/github-actions/google-internal-tests@c584c3565b71c7a8cda81d55bb14e3e66ef934da + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + - uses: angular/dev-infra/github-actions/google-internal-tests@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: run-tests-guide-url: http://go/angular-material-presubmit github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr.material-aio.yml b/.github/workflows/pr.material-aio.yml index 17f473e21370..124535111360 100644 --- a/.github/workflows/pr.material-aio.yml +++ b/.github/workflows/pr.material-aio.yml @@ -19,11 +19,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Build @@ -33,11 +33,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Tests @@ -54,11 +54,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Execute Lighthouse Audit diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 94be2853a713..6429f98ddec4 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Checking package externals @@ -43,7 +43,7 @@ jobs: - name: Check code format run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }} - name: Check Package Licenses - uses: angular/dev-infra/github-actions/linting/licenses@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/linting/licenses@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae # Commit message check is last intentionally, because the caretaker can fix it # during merge, while other lint failures have to be resolved by the PR author. - name: Check commit message @@ -53,11 +53,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Check API Goldens @@ -67,11 +67,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Run e2e tests @@ -81,11 +81,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Run integration tests @@ -95,11 +95,11 @@ jobs: runs-on: ubuntu-latest-16core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Run tests @@ -109,11 +109,11 @@ jobs: runs-on: ubuntu-latest-16core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Run tests @@ -123,11 +123,11 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Install node modules run: pnpm install --frozen-lockfile - name: Build and Verify Release Output @@ -150,15 +150,15 @@ jobs: CI_RUNNER_NUMBER: ${{ github.run_id }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: # Checking out the pull request commit is intended here as we need to run the changed code tests. ref: ${{ github.event.pull_request.head.sha }} - name: Install node modules run: pnpm install --frozen-lockfile - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Browserstack Variables - uses: angular/dev-infra/github-actions/browserstack@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/browserstack@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Run tests on Browserstack run: ./scripts/circleci/run-browserstack-tests.sh diff --git a/.github/workflows/preview-build-dev-app.yml b/.github/workflows/preview-build-dev-app.yml index fdb29ea489a7..5c1df85c6fe1 100644 --- a/.github/workflows/preview-build-dev-app.yml +++ b/.github/workflows/preview-build-dev-app.yml @@ -23,16 +23,16 @@ jobs: (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'dev-app preview')) steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae # Build the web package - run: bazel build //src/dev-app:web_package --symlink_prefix=dist/ - - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@c584c3565b71c7a8cda81d55bb14e3e66ef934da + - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: workflow-artifact-name: 'dev-app' pull-number: '${{github.event.pull_request.number}}' diff --git a/.github/workflows/preview-deploy-dev-app.yml b/.github/workflows/preview-deploy-dev-app.yml index ec7426d82272..577ddbcd50c7 100644 --- a/.github/workflows/preview-deploy-dev-app.yml +++ b/.github/workflows/preview-deploy-dev-app.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - name: Configure Firebase deploy target run: | @@ -33,7 +33,7 @@ jobs: npx -y firebase-tools@latest target:clear --project ${{env.PREVIEW_PROJECT}} hosting dev-app npx -y firebase-tools@latest target:apply --project ${{env.PREVIEW_PROJECT}} hosting dev-app ${{env.PREVIEW_SITE}} - - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@c584c3565b71c7a8cda81d55bb14e3e66ef934da + - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: github-token: '${{secrets.GITHUB_TOKEN}}' workflow-artifact-name: 'dev-app' diff --git a/.github/workflows/scheduled-ci.yml b/.github/workflows/scheduled-ci.yml index 39843b6c1888..34a7a4b74dc9 100644 --- a/.github/workflows/scheduled-ci.yml +++ b/.github/workflows/scheduled-ci.yml @@ -19,11 +19,11 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Setting up Angular snapshot builds @@ -39,11 +39,11 @@ jobs: runs-on: ubuntu-latest-4core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/setup@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/bazel/configure-remote@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Setting up Angular snapshot builds @@ -61,7 +61,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@c584c3565b71c7a8cda81d55bb14e3e66ef934da + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae # See: https://github.com/puppeteer/puppeteer/pull/13196 and # https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md. - name: Disable AppArmor diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index b5189ed3b724..29fffaf77ae2 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -25,7 +25,7 @@ jobs: steps: - name: 'Checkout code' - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false @@ -47,6 +47,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 + uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5 with: sarif_file: results.sarif diff --git a/.ng-dev/commit-message.mts b/.ng-dev/commit-message.mts index 6fe1a9f509eb..c868f0e0b1d4 100644 --- a/.ng-dev/commit-message.mts +++ b/.ng-dev/commit-message.mts @@ -14,11 +14,9 @@ export const commitMessage: CommitMessageConfig = { 'aria/grid', 'aria/listbox', 'aria/menu', - 'aria/radio-group', 'aria/tabs', 'aria/toolbar', 'aria/tree', - 'aria/ui-patterns', 'cdk-experimental/column-resize', 'cdk-experimental/popover-edit', 'cdk-experimental/scrolling', diff --git a/.ng-dev/google-sync-config.json b/.ng-dev/google-sync-config.json index c8fdea37cb24..a35504343075 100644 --- a/.ng-dev/google-sync-config.json +++ b/.ng-dev/google-sync-config.json @@ -28,6 +28,7 @@ "src/**/*spec.ts", "src/cdk/schematics/**/*", "src/cdk/testing/private/**/*", + "src/cdk/private/inner-html.ts", "src/material/schematics/**/*", "src/material/schematics/ng-generate/theme-color/**/*.bazel", "src/material/schematics/ng-generate/theme-color/index_bundled.d.ts", diff --git a/.nvmrc b/.nvmrc index aa50a62f2194..5767036af0e2 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.21.0 +22.21.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 09ad4fe5843b..7127456e5ea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,233 +1,34 @@ - -# 20.2.10 "aleutite-anchovy" (2025-10-22) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [b2cd596d3](https://github.com/angular/components/commit/b2cd596d315585c1f8b895c64230b49aecd3e6f8) | fix | **core:** differentiate container colors in m2 ([#32076](https://github.com/angular/components/pull/32076)) | - - - - -# 21.0.0-next.9 "plastic-fork" (2025-10-15) -## Breaking Changes -### cdk -- * `TestElement` implementations need to provide a `setContenteditableValue`. -### material -| Commit | Type | Description | -| -- | -- | -- | -| [71d590796c](https://github.com/angular/components/commit/71d590796c8c29a198fefa31864c06ddfd334738) | feat | **sort:** add content projection slot for custom icon ([#32016](https://github.com/angular/components/pull/32016)) | -| [85f596b3c3](https://github.com/angular/components/commit/85f596b3c32d22541c3825da3479b67832a2f4da) | feat | **table:** add harness for "no data" row ([#32075](https://github.com/angular/components/pull/32075)) | -| [ff9059d8d1](https://github.com/angular/components/commit/ff9059d8d14b82a465481e51f01582caf6d83b6c) | fix | **checkbox:** use GrayText for disabled colors in high contrast mode ([#32066](https://github.com/angular/components/pull/32066)) | -| [5f12b26ab3](https://github.com/angular/components/commit/5f12b26ab3abad9acb2a3d008100d06e0fb63ab5) | fix | **snack-bar:** add max height for snack bar ([#32000](https://github.com/angular/components/pull/32000)) | -| [7dfabca03d](https://github.com/angular/components/commit/7dfabca03d14729926b708e4c86d913bc5b8f735) | fix | **timepicker:** add interface for timepicker input ([#32050](https://github.com/angular/components/pull/32050)) | + +# 21.0.1 "sulfur-snack" (2025-11-26) ### cdk | Commit | Type | Description | | -- | -- | -- | -| [544438c865](https://github.com/angular/components/commit/544438c865532b30005ef7d03606fc4be4818a39) | fix | **testing:** make setContenteditableValue required ([#32058](https://github.com/angular/components/pull/32058)) | - - - - -# 20.2.9 "plastic-spoon" (2025-10-15) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [af3b961214](https://github.com/angular/components/commit/af3b9612145ed20a3290f773e4fbf51206804145) | fix | **checkbox:** use GrayText for disabled colors in high contrast mode ([#32066](https://github.com/angular/components/pull/32066)) | -| [a90abb0a77](https://github.com/angular/components/commit/a90abb0a77b77b0ec5188582a1ea4d587508da5e) | fix | **snack-bar:** add max height for snack bar ([#32000](https://github.com/angular/components/pull/32000)) | - - - - -# 21.0.0-next.8 "osmium-summit" (2025-10-08) - - - - -# 21.0.0-next.7 "selenium-summit" (2025-10-08) -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [82cd076bf](https://github.com/angular/components/commit/82cd076bfb3084e258f8e20af6cac570349822ce) | feat | **overlay:** Allow passing separate X and Y values for the viewportMargin ([#29563](https://github.com/angular/components/pull/29563)) | +| [ccc12c387](https://github.com/angular/components/commit/ccc12c3879b3b58d6821e80ba1ef79db25a49572) | fix | **testing:** errors in harnesses when using Vitest ([#32399](https://github.com/angular/components/pull/32399)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [527fc3718](https://github.com/angular/components/commit/527fc3718326fe0f0fd32a4e7c6130627d0e9cce) | feat | **bottom-sheet:** add injector to MatBottomSheetConfig ([#31965](https://github.com/angular/components/pull/31965)) | -| [3665b433e](https://github.com/angular/components/commit/3665b433ea3c30c0ad612ee717cde67d5b73e103) | fix | **datepicker:** error due to synchronous change detection | -| [2d5942cb0](https://github.com/angular/components/commit/2d5942cb0fe86f9c3d26b1925b1d4390cfc6613b) | fix | **datepicker:** make date filter nullable ([#31980](https://github.com/angular/components/pull/31980)) | -| [05d71e53a](https://github.com/angular/components/commit/05d71e53a16f7cdb19b96e208601a64e1389fa8a) | fix | **menu:** fix divider color property ([#31815](https://github.com/angular/components/pull/31815)) | -| [c848d24db](https://github.com/angular/components/commit/c848d24dbb9bc468b47b107ae6a82ce32ded8502) | fix | **timepicker:** assign form control value before emitting events ([#31981](https://github.com/angular/components/pull/31981)) | -### material-date-fns-adapter -| Commit | Type | Description | -| -- | -- | -- | -| [b61d8841a](https://github.com/angular/components/commit/b61d8841ad70cced4ca6098bd0187d68ac00e486) | fix | parse time string containing only hours ([#31978](https://github.com/angular/components/pull/31978)) | - - - - -# 20.2.8 "strontium-summit" (2025-10-08) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [6d61babe7](https://github.com/angular/components/commit/6d61babe77a6a6f0c464b498ee773561b795663f) | fix | **datepicker:** error due to synchronous change detection | -| [e43dcabd0](https://github.com/angular/components/commit/e43dcabd005239a380ffef50cfea374b24a67735) | fix | **datepicker:** make date filter nullable ([#31980](https://github.com/angular/components/pull/31980)) | -| [f30c1c6c3](https://github.com/angular/components/commit/f30c1c6c3f6490ddf21ba668d4065c4d5f89f26f) | fix | **radio:** Hovering over label of a radio will show the pointer cursor ([#32015](https://github.com/angular/components/pull/32015)) | -| [de3f9e566](https://github.com/angular/components/commit/de3f9e5662edae3c19f44a89ee1ee34555eba147) | fix | **timepicker:** assign form control value before emitting events ([#31981](https://github.com/angular/components/pull/31981)) | -### material-date-fns-adapter -| Commit | Type | Description | -| -- | -- | -- | -| [2d1f8d068](https://github.com/angular/components/commit/2d1f8d068e18fa1e0044e0dd4c968c3fa6761e10) | fix | parse time string containing only hours ([#31978](https://github.com/angular/components/pull/31978)) | - - - - -# 21.0.0-next.6 "carbon-flamingo" (2025-10-01) -## Breaking Changes -### material -- * `AnimationCurves` has been removed. - * `AnimationDurations` has been removed. - * `NativeDateAdapter.useUtcForDisplay` has been removed. -### cdk-experimental -| Commit | Type | Description | -| -- | -- | -- | -| [f9e7eff127](https://github.com/angular/components/commit/f9e7eff127447a685126f3c942571653aa245728) | feat | **combobox:** introduce new signals-based combobox ([#31872](https://github.com/angular/components/pull/31872)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [ef70029820](https://github.com/angular/components/commit/ef70029820423ff7ba6a5a1a372c24a0583a03f2) | feat | **chips:** allow for modifiers to be specified on separator keys ([#31914](https://github.com/angular/components/pull/31914)) | -| [1b06a8ea80](https://github.com/angular/components/commit/1b06a8ea8033a9cadffb52102aa74cc4f768adb1) | fix | **core:** remove deprecated APIs for v21 ([#31924](https://github.com/angular/components/pull/31924)) | -| [81eeff4bed](https://github.com/angular/components/commit/81eeff4beddbadd6518aca123182ae271be80819) | fix | **menu:** prevent child menu reopening while parent is animating away ([#31958](https://github.com/angular/components/pull/31958)) | -| [813f66b839](https://github.com/angular/components/commit/813f66b839d1684c3d5342e55ba61e948c979fab) | fix | **menu:** switch internal state to signals ([#31934](https://github.com/angular/components/pull/31934)) | -| [131c7ff804](https://github.com/angular/components/commit/131c7ff80420a9501618be1cb1c4b168cd286c4a) | fix | **paginator:** trim extraneous announcements ([#31943](https://github.com/angular/components/pull/31943)) | -| [6fba2049c3](https://github.com/angular/components/commit/6fba2049c36b86659a1713baf965da8651b6aae4) | fix | **select:** rotate arrow while open ([#31936](https://github.com/angular/components/pull/31936)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [667a007d0d](https://github.com/angular/components/commit/667a007d0d4017d14eadb2db4b5c0665efa1c2b9) | fix | **scrolling:** Fix undefined error when documentElement.style is undefined ([#31904](https://github.com/angular/components/pull/31904)) | -| [2918e2804a](https://github.com/angular/components/commit/2918e2804a4563c64a20822605424d66de7adb91) | fix | **scrolling:** prevent subpixel gaps in virtual scroll viewport ([#31940](https://github.com/angular/components/pull/31940)) | - - - - -# 20.2.7 "ceramic-nebula" (2025-10-01) -### material +| [043d9cacc](https://github.com/angular/components/commit/043d9cacc7d76828d02f921447b48688ab3d4129) | fix | **select:** render panel next to trigger ([#32363](https://github.com/angular/components/pull/32363)) | +### multiple | Commit | Type | Description | | -- | -- | -- | -| [85ed6550c2](https://github.com/angular/components/commit/85ed6550c2a3f8199350dcb1fb7d6f775dc2ba61) | fix | **menu:** prevent child menu reopening while parent is animating away ([#31958](https://github.com/angular/components/pull/31958)) | +| [02965bb0e](https://github.com/angular/components/commit/02965bb0eee969549518a49a796497ae1fe89569) | fix | resolve forward ref errors ([#32413](https://github.com/angular/components/pull/32413)) | - -# 21.0.0-next.5 "hungry-kiwi" (2025-09-24) + +# 21.0.0 "damask-dachshund" (2025-11-19) ## Breaking Changes ### cdk +- * `LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY` has been removed. + * `TREE_KEY_MANAGER_FACTORY` has been removed. + * `TREE_KEY_MANAGER_FACTORY_PROVIDER` has been removed. - * `$z-index-overlay-container` has been removed. Use `$overlay-container-z-index` instead. * `$z-index-overlay` has been removed. Use `$overlay-z-index` instead. * `$dark-backdrop-background` has been removed. Use `$overlay-backdrop-color` instead. * `$z-index-overlay-backdrop` has been removed. Use `$overlay-backdrop-z-index` instead. -### cdk-experimental -| Commit | Type | Description | -| -- | -- | -- | -| [f0047282a](https://github.com/angular/components/commit/f0047282a10551bb9044c74e4b441e795bec1de9) | fix | **ui-patterns:** preserveContent should not render until first visible ([#31660](https://github.com/angular/components/pull/31660)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [737c69fce](https://github.com/angular/components/commit/737c69fce7c362a4e49fb7f7869ed7237f4f8a35) | feat | **radio:** Hovering over label of a radio will show the pointer cursor ([#31894](https://github.com/angular/components/pull/31894)) | -| [3619903fa](https://github.com/angular/components/commit/3619903fa26910a784f01bee627e30d922b9b525) | feat | **testing:** Add icon name filtering to MatButtonHarness ([#31852](https://github.com/angular/components/pull/31852)) | -| [3b95117e5](https://github.com/angular/components/commit/3b95117e5901b4f53b685ca031f12f108a96f86f) | fix | **chips:** remove visible overflow for labels ([#31679](https://github.com/angular/components/pull/31679)) | -| [7a17fe950](https://github.com/angular/components/commit/7a17fe950dd69505d0cea705445aff14312a0212) | fix | **core:** move internal tokens ([#31907](https://github.com/angular/components/pull/31907)) | -| [977f46fe6](https://github.com/angular/components/commit/977f46fe61c9e77d0b3706a94016c14cd0cdbe90) | fix | **form-field:** restore error message animation ([#31774](https://github.com/angular/components/pull/31774)) | -| [ff10f0448](https://github.com/angular/components/commit/ff10f044826323811eee0f918e9c1515a5514c5e) | fix | **stepper:** Adjust aria tab-related roles to fix violations ([#31844](https://github.com/angular/components/pull/31844)) | -| [b6d81939c](https://github.com/angular/components/commit/b6d81939c822684a4084b3081a2a42d885e1907d) | fix | **table:** style no data row properly ([#31895](https://github.com/angular/components/pull/31895)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [84fc0d963](https://github.com/angular/components/commit/84fc0d9638948ee2a94659d5af78894809f2a30c) | fix | **overlay:** remove deprecated variables ([#31898](https://github.com/angular/components/pull/31898)) | - - - - -# 20.2.5 "sparkling-penguin" (2025-09-24) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [36be42637](https://github.com/angular/components/commit/36be42637df5021ae9afa4d097e84d83d78c197e) | fix | **core:** move internal tokens ([#31907](https://github.com/angular/components/pull/31907)) | -| [3ac762be5](https://github.com/angular/components/commit/3ac762be5dc180594c0681e2842e35123195feb6) | fix | **form-field:** restore error message animation ([#31774](https://github.com/angular/components/pull/31774)) | -| [65f23c003](https://github.com/angular/components/commit/65f23c003e94d59bbb08ad81eb66d725b1b5e79a) | fix | **table:** style no data row properly ([#31895](https://github.com/angular/components/pull/31895)) | - - - - -# 21.0.0-next.4 "v21.0.0-next4 release" (2025-09-17) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [962a60c11](https://github.com/angular/components/commit/962a60c113ffdcb8920c1b4fee60d850007fda5f) | fix | **button:** do not show hover state on devices that don't support hover ([#31866](https://github.com/angular/components/pull/31866)) | -| [31562a4b8](https://github.com/angular/components/commit/31562a4b8f2700d43c53f2c170f14d4b28a7ac53) | fix | **core:** separate text/bg colors in utility classes ([#31879](https://github.com/angular/components/pull/31879)) | -| [a2906ddf3](https://github.com/angular/components/commit/a2906ddf36de4897b839df46941e59f3c0e0d727) | fix | **tabs:** attach content inside the zone ([#31868](https://github.com/angular/components/pull/31868)) | -### cdk-experimental -| Commit | Type | Description | -| -- | -- | -- | -| [c21dfa348](https://github.com/angular/components/commit/c21dfa348d49c2312e0f5c523f5d13ea51a7118f) | fix | **ui-patterns:** enter/space/click in single selection mode should not deselect tree item ([#31843](https://github.com/angular/components/pull/31843)) | - - - - -# 20.2.4 "v20.2.4 release" (2025-09-17) -### material -| Commit | Type | Description | -| -- | -- | -- | -| [e7a0c19d0](https://github.com/angular/components/commit/e7a0c19d09bbe38852168c1a5ffcfeed66c1da6f) | fix | **tabs:** attach content inside the zone ([#31868](https://github.com/angular/components/pull/31868)) | - - - - -# 21.0.0-next.3 "red-envelope" (2025-09-11) -## Breaking Changes -### material -- * `MatCommonModule` has been removed. - * `GranularSanityChecks` has been removed. - * `MATERIAL_SANITY_CHECKS` has been removed. - * `SanityChecks` has been removed. -### cdk-experimental -| Commit | Type | Description | -| -- | -- | -- | -| [f137183858](https://github.com/angular/components/commit/f137183858bf6bb9d47f7d184ea55241f2ee4a61) | fix | **accordion:** removes inert attribute from accordion trigger ([#31817](https://github.com/angular/components/pull/31817)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [931ac3c1c7](https://github.com/angular/components/commit/931ac3c1c7f8468acaae1e9b266bdd5f52cd35fd) | fix | **chips:** Adjust trailing icon opacity based on chip state ([#31828](https://github.com/angular/components/pull/31828)) | -| [c832533062](https://github.com/angular/components/commit/c832533062738e91142d0222f99b1e6859e89cce) | fix | **core:** remove MatCommonModule ([#31813](https://github.com/angular/components/pull/31813)) | -| [878700d10a](https://github.com/angular/components/commit/878700d10ab042b6a62c6f86f4fcc24d5a0ae685) | fix | **progress-bar:** avoid CSP issues due to buffer dots ([#31818](https://github.com/angular/components/pull/31818)) | -| [5a1a0ba4e6](https://github.com/angular/components/commit/5a1a0ba4e68c40886505c8c096f6c1257b9edb7b) | fix | **select:** ensure proper highlighting on selection ([#31789](https://github.com/angular/components/pull/31789)) | -| [e0a35c52d5](https://github.com/angular/components/commit/e0a35c52d582d9f08aa9e76490481984dae53b75) | fix | **slider:** incorrect indicator transform origin in M3 ([#31834](https://github.com/angular/components/pull/31834)) | -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [54f641e330](https://github.com/angular/components/commit/54f641e33073172e426b444500fdea552c94d2e3) | fix | **drag-drop:** allow axis lock to be reset ([#31829](https://github.com/angular/components/pull/31829)) | - - - - -# 20.2.3 "tango-heels" (2025-09-11) -### cdk -| Commit | Type | Description | -| -- | -- | -- | -| [442d4ca6b7](https://github.com/angular/components/commit/442d4ca6b752eabcf60003df0f1ad6905dddad5f) | fix | **drag-drop:** allow axis lock to be reset ([#31829](https://github.com/angular/components/pull/31829)) | -### material -| Commit | Type | Description | -| -- | -- | -- | -| [ec33bf8eba](https://github.com/angular/components/commit/ec33bf8eba46b1281e6b2a814a1ce3e15475ec20) | fix | **progress-bar:** avoid CSP issues due to buffer dots ([#31818](https://github.com/angular/components/pull/31818)) | -| [9a68265302](https://github.com/angular/components/commit/9a682653023fd3d181d4b5b318ffc13974a9bf2b) | fix | **slider:** incorrect indicator transform origin in M3 ([#31834](https://github.com/angular/components/pull/31834)) | - - - - -# 21.0.0-next.2 "plastic-screw" (2025-09-03) -## Breaking Changes -### cdk -- * `LIVE_ANNOUNCER_ELEMENT_TOKEN_FACTORY` has been removed. - * `TREE_KEY_MANAGER_FACTORY` has been removed. - * `TREE_KEY_MANAGER_FACTORY_PROVIDER` has been removed. +- * `TemplatePortalDirective` has been removed. Use `CdkPortal` instead. + * `PortalHostDirective` has been removed. Use `CdkPortalOutlet` instead. +- * `TestElement` implementations need to provide a `setContenteditableValue`. ### material - * `MAT_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY` has been removed. * `MAT_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY` has been removed. @@ -236,7 +37,14 @@ - * `MAT_BUTTON_TOGGLE_GROUP_DEFAULT_OPTIONS_FACTORY` has been removed. - * `MAT_FAB_DEFAULT_OPTIONS_FACTORY` has been removed. - * `MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY` has been removed. +- * `AnimationCurves` has been removed. + * `AnimationDurations` has been removed. + * `NativeDateAdapter.useUtcForDisplay` has been removed. - * `MAT_DATE_LOCAL_FACTORY` has been removed. +- * `MatCommonModule` has been removed. + * `GranularSanityChecks` has been removed. + * `MATERIAL_SANITY_CHECKS` has been removed. + * `SanityChecks` has been removed. - * `matDatepickerAnimations` symbol has been removed. - * `MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY` has been removed. * `MAT_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER` has been removed. @@ -287,111 +95,243 @@ ### material-luxon-adapter | Commit | Type | Description | | -- | -- | -- | -| [c1486fb7f8](https://github.com/angular/components/commit/c1486fb7f86c94d4a5986a60752094abe1b79912) | fix | remove deprecated factory functions | +| [c1486fb7f](https://github.com/angular/components/commit/c1486fb7f86c94d4a5986a60752094abe1b79912) | fix | remove deprecated factory functions | ### material-moment-adapter | Commit | Type | Description | | -- | -- | -- | -| [6cee2b9e2d](https://github.com/angular/components/commit/6cee2b9e2d26027804233ade82fede9cfbbe06cb) | fix | remove deprecated factory functions | +| [6cee2b9e2](https://github.com/angular/components/commit/6cee2b9e2d26027804233ade82fede9cfbbe06cb) | fix | remove deprecated factory functions | +### cdk-experimental +| Commit | Type | Description | +| -- | -- | -- | +| [f9e7eff12](https://github.com/angular/components/commit/f9e7eff127447a685126f3c942571653aa245728) | feat | **combobox:** introduce new signals-based combobox ([#31872](https://github.com/angular/components/pull/31872)) | +| [f13718385](https://github.com/angular/components/commit/f137183858bf6bb9d47f7d184ea55241f2ee4a61) | fix | **accordion:** removes inert attribute from accordion trigger ([#31817](https://github.com/angular/components/pull/31817)) | +| [668a2b4d5](https://github.com/angular/components/commit/668a2b4d5aa89750c4c4cb65d2659d9a41042ba5) | fix | **ui-patterns:** deselectAll unavailable items ([#31734](https://github.com/angular/components/pull/31734)) | +| [c21dfa348](https://github.com/angular/components/commit/c21dfa348d49c2312e0f5c523f5d13ea51a7118f) | fix | **ui-patterns:** enter/space/click in single selection mode should not deselect tree item ([#31843](https://github.com/angular/components/pull/31843)) | +| [d2c3bb971](https://github.com/angular/components/commit/d2c3bb971db4515d04b8842bcf96d4a8f8a65dac) | fix | **ui-patterns:** focus list when using active desce… ([#31756](https://github.com/angular/components/pull/31756)) | +| [f0047282a](https://github.com/angular/components/commit/f0047282a10551bb9044c74e4b441e795bec1de9) | fix | **ui-patterns:** preserveContent should not render until first visible ([#31660](https://github.com/angular/components/pull/31660)) | +| [df0d753f1](https://github.com/angular/components/commit/df0d753f166562ef15252b0923a583a93b76a4b2) | fix | **ui-patterns:** Tree expand/collapse key should work in follow focus mode ([#31747](https://github.com/angular/components/pull/31747)) | ### google-maps | Commit | Type | Description | | -- | -- | -- | -| [17cc7606a1](https://github.com/angular/components/commit/17cc7606a1efad644254505d116092f0cee20bfe) | fix | some events not firing on advanced marker | +| [17cc7606a](https://github.com/angular/components/commit/17cc7606a1efad644254505d116092f0cee20bfe) | fix | some events not firing on advanced marker | ### material | Commit | Type | Description | | -- | -- | -- | -| [255df78cb1](https://github.com/angular/components/commit/255df78cb154b613d5f7db2b64b66fbcca27da45) | fix | **autocomplete:** remove deprecated factory functions | -| [e8d005cbf8](https://github.com/angular/components/commit/e8d005cbf8823ad70e3e17d52df3924d6cd015df) | fix | **bottom-sheet:** remove deprecated animation definitions | -| [7dc05280f7](https://github.com/angular/components/commit/7dc05280f7876260636a9800442e9d972099eb28) | fix | **button-toggle:** remove deprecated factory functions | -| [69316b8713](https://github.com/angular/components/commit/69316b8713b0ae54de39cc893bc7a10ff356bb74) | fix | **button:** remove deprecated factory functions | -| [b2c4df2d1e](https://github.com/angular/components/commit/b2c4df2d1e31a831f2082534e4228d1f3623bb56) | fix | **checkbox:** remove deprecated factory functions | -| [24932b6e23](https://github.com/angular/components/commit/24932b6e23a3f1ebb6759e228bdcdda9ef77de73) | fix | **core:** remove deprecated factory functions | -| [870433501c](https://github.com/angular/components/commit/870433501c9435128f14b40d478803c477fd5a50) | fix | **core:** rename some utility classes ([#31795](https://github.com/angular/components/pull/31795)) | -| [3d89dfc3e9](https://github.com/angular/components/commit/3d89dfc3e9db1bc3acf168e517611ec2ac4ef121) | fix | **datepicker:** remove deprecated animation definitions | -| [89ff55f411](https://github.com/angular/components/commit/89ff55f411943492f54e8b8862770127b2dafb0d) | fix | **datepicker:** remove deprecated factory functions | -| [65c9b5ec76](https://github.com/angular/components/commit/65c9b5ec76982cdb16cd92c17a9d7c747549760c) | fix | **dialog:** remove deprecated animation definitions | -| [aee4e92397](https://github.com/angular/components/commit/aee4e923973d4f78ece4323eb712b760aef9e84c) | fix | **expansion:** remove deprecated animation definitions | -| [232b9fc595](https://github.com/angular/components/commit/232b9fc59535c5f04aaf16e07589e593395df4c1) | fix | **form-field:** remove deprecated animation definitions | -| [dd5abe37e9](https://github.com/angular/components/commit/dd5abe37e94adf6a93c174f01d77eef19ef5919c) | fix | **icon:** remove deprecated factory functions | -| [814ff1a25a](https://github.com/angular/components/commit/814ff1a25a5cb0bfeead32903ea8a23c70bf50da) | fix | **menu:** remove deprecated animation definitions | -| [4b06a1a64b](https://github.com/angular/components/commit/4b06a1a64b0ab6d73e33b546079bde9ace6050ae) | fix | **menu:** remove deprecated factory functions | -| [6d26c0fc1a](https://github.com/angular/components/commit/6d26c0fc1a382c6f4cd5f24761b93944ec3a0966) | fix | **paginator:** remove deprecated factory functions | -| [8fc72e9319](https://github.com/angular/components/commit/8fc72e93194edc3c6f49c5ef9ac9caf227d7ef3e) | fix | **progress-bar:** remove deprecated factory functions | -| [e1b68922bd](https://github.com/angular/components/commit/e1b68922bd6b5559ac9bfb6267c1b80df2f5e483) | fix | **progress-spinner:** remove deprecated factory functions | -| [29c296f2fb](https://github.com/angular/components/commit/29c296f2fb6d83b99cb4886856154c3c2b5c8c28) | fix | **radio:** remove deprecated factory functions | -| [7d25138f58](https://github.com/angular/components/commit/7d25138f580193f3cf0e09ef2d35659466750a75) | fix | **select:** remove deprecated animation definitions | -| [479b4e343c](https://github.com/angular/components/commit/479b4e343c55dc9227566d85ee7525b2e6c67e02) | fix | **select:** remove deprecated factory functions | -| [e87b95dc95](https://github.com/angular/components/commit/e87b95dc955942db118f6ed2144dbda82d399415) | fix | **sidenav:** remove deprecated animation definitions | -| [4ec00baf54](https://github.com/angular/components/commit/4ec00baf54e739ccf7e8af686b0fa0ef7c2ad750) | fix | **sidenav:** remove deprecated factory functions | -| [2259c7b4c7](https://github.com/angular/components/commit/2259c7b4c7810eacbaaeafff116faa6f0fa47ea3) | fix | **snack-bar:** remove deprecated animation definitions | -| [54aad6efc4](https://github.com/angular/components/commit/54aad6efc4af6312971be4ee22d6cc85d587acb2) | fix | **snack-bar:** remove deprecated factory functions | -| [6bdd8ae097](https://github.com/angular/components/commit/6bdd8ae09748821e2050daeab1318b08d7f3c92a) | fix | **sort:** remove deprecated animation definitions | -| [f32bf20891](https://github.com/angular/components/commit/f32bf20891903b7182e87276c4968d880762ed52) | fix | **sort:** remove deprecated factory functions | -| [bb7dbce929](https://github.com/angular/components/commit/bb7dbce92995385be39ee77517606d4006d4e07e) | fix | **stepper:** remove deprecated animation definitions | -| [ac840a8c6c](https://github.com/angular/components/commit/ac840a8c6c4731e9429425b52c9006285127c3d9) | fix | **stepper:** remove deprecated factory functions | -| [a7fce5e2c3](https://github.com/angular/components/commit/a7fce5e2c39110dca64ed5f9d68f145308d4d990) | fix | **tabs:** remove deprecated animation definitions | -| [a5a7c12f03](https://github.com/angular/components/commit/a5a7c12f0315e95984234d3d31f2e8d513bbddbf) | fix | **tabs:** remove deprecated factory functions | -| [ca1cd86dad](https://github.com/angular/components/commit/ca1cd86dad3628f80614aab104672d63b2288b26) | fix | **tooltip:** remove deprecated animation definitions | -| [bc10a87c4a](https://github.com/angular/components/commit/bc10a87c4af99d416b8e27553e1fe553bc24cf91) | fix | **tooltip:** remove deprecated factory functions | +| [527fc3718](https://github.com/angular/components/commit/527fc3718326fe0f0fd32a4e7c6130627d0e9cce) | feat | **bottom-sheet:** add injector to MatBottomSheetConfig ([#31965](https://github.com/angular/components/pull/31965)) | +| [ef7002982](https://github.com/angular/components/commit/ef70029820423ff7ba6a5a1a372c24a0583a03f2) | feat | **chips:** allow for modifiers to be specified on separator keys ([#31914](https://github.com/angular/components/pull/31914)) | +| [29f0bb238](https://github.com/angular/components/commit/29f0bb238a2a327ac6ed69071f6aa555693ab2d4) | feat | **chips:** make ChipInput optional for MatChipGrid ([#31693](https://github.com/angular/components/pull/31693)) | +| [dea603b88](https://github.com/angular/components/commit/dea603b88cf35d310674964b8b25514d5fe26772) | feat | **core:** add experimental utility classes ([#31702](https://github.com/angular/components/pull/31702)) | +| [737c69fce](https://github.com/angular/components/commit/737c69fce7c362a4e49fb7f7869ed7237f4f8a35) | feat | **radio:** Hovering over label of a radio will show the pointer cursor ([#31894](https://github.com/angular/components/pull/31894)) | +| [71d590796](https://github.com/angular/components/commit/71d590796c8c29a198fefa31864c06ddfd334738) | feat | **sort:** add content projection slot for custom icon ([#32016](https://github.com/angular/components/pull/32016)) | +| [85f596b3c](https://github.com/angular/components/commit/85f596b3c32d22541c3825da3479b67832a2f4da) | feat | **table:** add harness for "no data" row ([#32075](https://github.com/angular/components/pull/32075)) | +| [3619903fa](https://github.com/angular/components/commit/3619903fa26910a784f01bee627e30d922b9b525) | feat | **testing:** Add icon name filtering to MatButtonHarness ([#31852](https://github.com/angular/components/pull/31852)) | +| [9aa4cc881](https://github.com/angular/components/commit/9aa4cc88188d4dd5e933ca9767a0de06234f1474) | fix | **autocomplete:** empty autocomplete obscuring content ([#32348](https://github.com/angular/components/pull/32348)) | +| [255df78cb](https://github.com/angular/components/commit/255df78cb154b613d5f7db2b64b66fbcca27da45) | fix | **autocomplete:** remove deprecated factory functions | +| [cf04a3898](https://github.com/angular/components/commit/cf04a3898839affe430cdcfd3dbc5f58cf8c82f1) | fix | **autocomplete:** render overlay next to trigger ([#32282](https://github.com/angular/components/pull/32282)) | +| [e8d005cbf](https://github.com/angular/components/commit/e8d005cbf8823ad70e3e17d52df3924d6cd015df) | fix | **bottom-sheet:** remove deprecated animation definitions | +| [7dc05280f](https://github.com/angular/components/commit/7dc05280f7876260636a9800442e9d972099eb28) | fix | **button-toggle:** remove deprecated factory functions | +| [962a60c11](https://github.com/angular/components/commit/962a60c113ffdcb8920c1b4fee60d850007fda5f) | fix | **button:** do not show hover state on devices that don't support hover ([#31866](https://github.com/angular/components/pull/31866)) | +| [69316b871](https://github.com/angular/components/commit/69316b8713b0ae54de39cc893bc7a10ff356bb74) | fix | **button:** remove deprecated factory functions | +| [b2c4df2d1](https://github.com/angular/components/commit/b2c4df2d1e31a831f2082534e4228d1f3623bb56) | fix | **checkbox:** remove deprecated factory functions | +| [931ac3c1c](https://github.com/angular/components/commit/931ac3c1c7f8468acaae1e9b266bdd5f52cd35fd) | fix | **chips:** Adjust trailing icon opacity based on chip state ([#31828](https://github.com/angular/components/pull/31828)) | +| [13a9c48a0](https://github.com/angular/components/commit/13a9c48a093b1bfeab8f0328030dae60aca7519f) | fix | **chips:** refactor non-interactive actions to prevent adding click handlers ([#31664](https://github.com/angular/components/pull/31664)) | +| [3b95117e5](https://github.com/angular/components/commit/3b95117e5901b4f53b685ca031f12f108a96f86f) | fix | **chips:** remove visible overflow for labels ([#31679](https://github.com/angular/components/pull/31679)) | +| [347963303](https://github.com/angular/components/commit/3479633036abf467354e936c1ddb4fd2cefcd6ea) | fix | **chips:** strengthen edit/remove icons focus/hover styling ([#31759](https://github.com/angular/components/pull/31759)) | +| [1b06a8ea8](https://github.com/angular/components/commit/1b06a8ea8033a9cadffb52102aa74cc4f768adb1) | fix | **core:** remove deprecated APIs for v21 ([#31924](https://github.com/angular/components/pull/31924)) | +| [24932b6e2](https://github.com/angular/components/commit/24932b6e23a3f1ebb6759e228bdcdda9ef77de73) | fix | **core:** remove deprecated factory functions | +| [c83253306](https://github.com/angular/components/commit/c832533062738e91142d0222f99b1e6859e89cce) | fix | **core:** remove MatCommonModule ([#31813](https://github.com/angular/components/pull/31813)) | +| [18cedc737](https://github.com/angular/components/commit/18cedc7375bf0ab0590c9a984e5d380d52610ccc) | fix | **core:** rename corner extra-small to xs ([#32101](https://github.com/angular/components/pull/32101)) | +| [870433501](https://github.com/angular/components/commit/870433501c9435128f14b40d478803c477fd5a50) | fix | **core:** rename some utility classes ([#31795](https://github.com/angular/components/pull/31795)) | +| [605e2c9f6](https://github.com/angular/components/commit/605e2c9f6557b41f03ad3f9268d6c536433e1555) | fix | **core:** rename utility-classes to system-classes ([#31745](https://github.com/angular/components/pull/31745)) | +| [31562a4b8](https://github.com/angular/components/commit/31562a4b8f2700d43c53f2c170f14d4b28a7ac53) | fix | **core:** separate text/bg colors in utility classes ([#31879](https://github.com/angular/components/pull/31879)) | +| [3d89dfc3e](https://github.com/angular/components/commit/3d89dfc3e9db1bc3acf168e517611ec2ac4ef121) | fix | **datepicker:** remove deprecated animation definitions | +| [89ff55f41](https://github.com/angular/components/commit/89ff55f411943492f54e8b8862770127b2dafb0d) | fix | **datepicker:** remove deprecated factory functions | +| [47bdfb200](https://github.com/angular/components/commit/47bdfb200769847292a1f07c22f52f96a0892874) | fix | **dialog:** afterOpened emitting too early when animations are disabled ([#32211](https://github.com/angular/components/pull/32211)) | +| [65c9b5ec7](https://github.com/angular/components/commit/65c9b5ec76982cdb16cd92c17a9d7c747549760c) | fix | **dialog:** remove deprecated animation definitions | +| [aee4e9239](https://github.com/angular/components/commit/aee4e923973d4f78ece4323eb712b760aef9e84c) | fix | **expansion:** remove deprecated animation definitions | +| [232b9fc59](https://github.com/angular/components/commit/232b9fc59535c5f04aaf16e07589e593395df4c1) | fix | **form-field:** remove deprecated animation definitions | +| [dd5abe37e](https://github.com/angular/components/commit/dd5abe37e94adf6a93c174f01d77eef19ef5919c) | fix | **icon:** remove deprecated factory functions | +| [05d71e53a](https://github.com/angular/components/commit/05d71e53a16f7cdb19b96e208601a64e1389fa8a) | fix | **menu:** fix divider color property ([#31815](https://github.com/angular/components/pull/31815)) | +| [814ff1a25](https://github.com/angular/components/commit/814ff1a25a5cb0bfeead32903ea8a23c70bf50da) | fix | **menu:** remove deprecated animation definitions | +| [4b06a1a64](https://github.com/angular/components/commit/4b06a1a64b0ab6d73e33b546079bde9ace6050ae) | fix | **menu:** remove deprecated factory functions | +| [6d26c0fc1](https://github.com/angular/components/commit/6d26c0fc1a382c6f4cd5f24761b93944ec3a0966) | fix | **paginator:** remove deprecated factory functions | +| [131c7ff80](https://github.com/angular/components/commit/131c7ff80420a9501618be1cb1c4b168cd286c4a) | fix | **paginator:** trim extraneous announcements ([#31943](https://github.com/angular/components/pull/31943)) | +| [8fc72e931](https://github.com/angular/components/commit/8fc72e93194edc3c6f49c5ef9ac9caf227d7ef3e) | fix | **progress-bar:** remove deprecated factory functions | +| [e1b68922b](https://github.com/angular/components/commit/e1b68922bd6b5559ac9bfb6267c1b80df2f5e483) | fix | **progress-spinner:** remove deprecated factory functions | +| [29c296f2f](https://github.com/angular/components/commit/29c296f2fb6d83b99cb4886856154c3c2b5c8c28) | fix | **radio:** remove deprecated factory functions | +| [5a1a0ba4e](https://github.com/angular/components/commit/5a1a0ba4e68c40886505c8c096f6c1257b9edb7b) | fix | **select:** ensure proper highlighting on selection ([#31789](https://github.com/angular/components/pull/31789)) | +| [7d25138f5](https://github.com/angular/components/commit/7d25138f580193f3cf0e09ef2d35659466750a75) | fix | **select:** remove deprecated animation definitions | +| [479b4e343](https://github.com/angular/components/commit/479b4e343c55dc9227566d85ee7525b2e6c67e02) | fix | **select:** remove deprecated factory functions | +| [e87b95dc9](https://github.com/angular/components/commit/e87b95dc955942db118f6ed2144dbda82d399415) | fix | **sidenav:** remove deprecated animation definitions | +| [4ec00baf5](https://github.com/angular/components/commit/4ec00baf54e739ccf7e8af686b0fa0ef7c2ad750) | fix | **sidenav:** remove deprecated factory functions | +| [2259c7b4c](https://github.com/angular/components/commit/2259c7b4c7810eacbaaeafff116faa6f0fa47ea3) | fix | **snack-bar:** remove deprecated animation definitions | +| [54aad6efc](https://github.com/angular/components/commit/54aad6efc4af6312971be4ee22d6cc85d587acb2) | fix | **snack-bar:** remove deprecated factory functions | +| [6bdd8ae09](https://github.com/angular/components/commit/6bdd8ae09748821e2050daeab1318b08d7f3c92a) | fix | **sort:** remove deprecated animation definitions | +| [f32bf2089](https://github.com/angular/components/commit/f32bf20891903b7182e87276c4968d880762ed52) | fix | **sort:** remove deprecated factory functions | +| [ff10f0448](https://github.com/angular/components/commit/ff10f044826323811eee0f918e9c1515a5514c5e) | fix | **stepper:** Adjust aria tab-related roles to fix violations ([#31844](https://github.com/angular/components/pull/31844)) | +| [bb7dbce92](https://github.com/angular/components/commit/bb7dbce92995385be39ee77517606d4006d4e07e) | fix | **stepper:** remove deprecated animation definitions | +| [ac840a8c6](https://github.com/angular/components/commit/ac840a8c6c4731e9429425b52c9006285127c3d9) | fix | **stepper:** remove deprecated factory functions | +| [a7fce5e2c](https://github.com/angular/components/commit/a7fce5e2c39110dca64ed5f9d68f145308d4d990) | fix | **tabs:** remove deprecated animation definitions | +| [a5a7c12f0](https://github.com/angular/components/commit/a5a7c12f0315e95984234d3d31f2e8d513bbddbf) | fix | **tabs:** remove deprecated factory functions | +| [7dfabca03](https://github.com/angular/components/commit/7dfabca03d14729926b708e4c86d913bc5b8f735) | fix | **timepicker:** add interface for timepicker input ([#32050](https://github.com/angular/components/pull/32050)) | +| [cd14409a3](https://github.com/angular/components/commit/cd14409a390baf18b49280da69b5f710e9ec72ce) | fix | **timepicker:** render overlay next to trigger ([#32288](https://github.com/angular/components/pull/32288)) | +| [ca1cd86da](https://github.com/angular/components/commit/ca1cd86dad3628f80614aab104672d63b2288b26) | fix | **tooltip:** remove deprecated animation definitions | +| [bc10a87c4](https://github.com/angular/components/commit/bc10a87c4af99d416b8e27553e1fe553bc24cf91) | fix | **tooltip:** remove deprecated factory functions | ### cdk | Commit | Type | Description | | -- | -- | -- | -| [30f6c3c457](https://github.com/angular/components/commit/30f6c3c4575ca4e4d3a8cb8c152d3ffba67b9e0f) | fix | **a11y:** remove deprecated factory functions | +| [82cd076bf](https://github.com/angular/components/commit/82cd076bfb3084e258f8e20af6cac570349822ce) | feat | **overlay:** Allow passing separate X and Y values for the viewportMargin ([#29563](https://github.com/angular/components/pull/29563)) | +| [30f6c3c45](https://github.com/angular/components/commit/30f6c3c4575ca4e4d3a8cb8c152d3ffba67b9e0f) | fix | **a11y:** remove deprecated factory functions | +| [b112568d1](https://github.com/angular/components/commit/b112568d194963566b971c13c9c88b78990c221f) | fix | **overlay:** add DI token to opt out of popovers ([#32306](https://github.com/angular/components/pull/32306)) | +| [0e4bf076b](https://github.com/angular/components/commit/0e4bf076b163da2600a159ab084ad200d47296ac) | fix | **overlay:** hide native backdrop | +| [84f8f10ba](https://github.com/angular/components/commit/84f8f10ba871463e4f71a529a6bb0212093dc19b) | fix | **overlay:** make it easier to set default for overlay directive | +| [84fc0d963](https://github.com/angular/components/commit/84fc0d9638948ee2a94659d5af78894809f2a30c) | fix | **overlay:** remove deprecated variables ([#31898](https://github.com/angular/components/pull/31898)) | +| [a4dc30ce8](https://github.com/angular/components/commit/a4dc30ce8e2b397fcec5d585b6f0e3faa3835f5f) | fix | **overlay:** simplify matching the overlay to the trigger width | +| [8d00344f2](https://github.com/angular/components/commit/8d00344f200452971209075913e96804afa6f619) | fix | **overlay:** simplify public API of overlay directive | +| [6234d82e2](https://github.com/angular/components/commit/6234d82e2ca8101fdd1aac97257e92cb02466ef7) | fix | **overlay:** update golden file ([#32367](https://github.com/angular/components/pull/32367)) | +| [03c5d34db](https://github.com/angular/components/commit/03c5d34dbd684a37a608cf9d33e20c293163919c) | fix | **portal:** remove deprecated directives ([#32117](https://github.com/angular/components/pull/32117)) | +| [5b45df30c](https://github.com/angular/components/commit/5b45df30ca60b75b27313d3b1b52c42ac037eb4f) | fix | **table:** ensure CdkTable updates view with OnPush and trackBy ([#31451](https://github.com/angular/components/pull/31451)) | +| [544438c86](https://github.com/angular/components/commit/544438c865532b30005ef7d03606fc4be4818a39) | fix | **testing:** make setContenteditableValue required ([#32058](https://github.com/angular/components/pull/32058)) | +### aria +| Commit | Type | Description | +| -- | -- | -- | +| [a821a3ef0](https://github.com/angular/components/commit/a821a3ef07d99d5ce8631469cd818929b9c0559b) | feat | **grid:** create the aria grid ([#32092](https://github.com/angular/components/pull/32092)) | +| [f9d3cde14](https://github.com/angular/components/commit/f9d3cde14984c6da006e09e8e79809247e017a65) | feat | **menu:** create the aria menu ([#32080](https://github.com/angular/components/pull/32080)) | +| [a0b580027](https://github.com/angular/components/commit/a0b5800277c206127999e21eda77ef003c066aee) | feat | **toolbar:** adds skip disabled toolbar example to dev-app ([#32127](https://github.com/angular/components/pull/32127)) | +| [5396c4347](https://github.com/angular/components/commit/5396c43474fe272ad5e21366c027cd1ae0c6d593) | feat | **toolbar:** adds toolbar basic vertical example to dev-app ([#32126](https://github.com/angular/components/pull/32126)) | +| [ba9f79be5](https://github.com/angular/components/commit/ba9f79be528c6228e50a73809ec62efcbfc7afc6) | feat | **toolbar:** adds toolbar-basic-horizontal-example to dev-app ([#32106](https://github.com/angular/components/pull/32106)) | +| [ec6045b27](https://github.com/angular/components/commit/ec6045b270211131b6987e316fb3252745c89e11) | fix | **accordion:** rename value to panelId for trigger and panel ([#32295](https://github.com/angular/components/pull/32295)) | +| [0b03c6e96](https://github.com/angular/components/commit/0b03c6e96485f83447ed3451070be138bf21d4e6) | fix | **combobox:** add missing apis ([#32124](https://github.com/angular/components/pull/32124)) | +| [25223a2f8](https://github.com/angular/components/commit/25223a2f8be7b13699b94b99a1335e4ac7185772) | fix | **combobox:** dialog popup support ([#32279](https://github.com/angular/components/pull/32279)) | +| [a47ebeb96](https://github.com/angular/components/commit/a47ebeb964e794e3868659d68e46b3b782d1550b) | fix | **combobox:** disabled state ([#32308](https://github.com/angular/components/pull/32308)) | +| [127d3dba5](https://github.com/angular/components/commit/127d3dba5754a4ceaa60f56ced7b4795c29372cc) | fix | **combobox:** escape key behavior ([#32364](https://github.com/angular/components/pull/32364)) | +| [c3279ca4b](https://github.com/angular/components/commit/c3279ca4b47f91cd61e0ba0be24bdd10869fa059) | fix | **combobox:** highlighting edge cases ([#32136](https://github.com/angular/components/pull/32136)) | +| [1232805db](https://github.com/angular/components/commit/1232805db6f0a0a07154b3601eaa8b9cc0d94f31) | fix | **combobox:** readonly behavior ([#32169](https://github.com/angular/components/pull/32169)) | +| [6c46f950d](https://github.com/angular/components/commit/6c46f950d451862692c2c74f41a704a3ef0fd472) | fix | **combobox:** several small fixes ([#32202](https://github.com/angular/components/pull/32202)) | +| [8beb22f8c](https://github.com/angular/components/commit/8beb22f8c8d0d9f415225826188c38964c65ceee) | fix | **combobox:** use click instead of pointerup ([#32324](https://github.com/angular/components/pull/32324)) | +| [92d933421](https://github.com/angular/components/commit/92d933421953a2f5123d0f823dc7880bb4e69098) | fix | **grid:** fix navigation bugs and add grid behavior unit tests ([#32140](https://github.com/angular/components/pull/32140)) | +| [e3babf3fc](https://github.com/angular/components/commit/e3babf3fc3229879e38751eff2c5941ed2152ae7) | fix | **grid:** rtl navigation ([#32170](https://github.com/angular/components/pull/32170)) | +| [fe79e982c](https://github.com/angular/components/commit/fe79e982cb7b3f0b42c2be056face255ccfe5783) | fix | **menu:** add expansion delay ([#32293](https://github.com/angular/components/pull/32293)) | +| [3d1cafbdd](https://github.com/angular/components/commit/3d1cafbdd3839c12d5298f216abaef59f717f04c) | fix | **menu:** add selectable to inputs ([#32131](https://github.com/angular/components/pull/32131)) | +| [56631cb85](https://github.com/angular/components/commit/56631cb858fda514cfd81e23064e7859aa338843) | fix | **menu:** deferred content import | +| [95e648ad8](https://github.com/angular/components/commit/95e648ad8a4fb3591fd20229db3a0649ac95ebb8) | fix | **menu:** disabled state ([#32301](https://github.com/angular/components/pull/32301)) | +| [ce20dbe1a](https://github.com/angular/components/commit/ce20dbe1a3e4be0e1002533f7f1910b1f9efd0c3) | fix | **menu:** lazy render trigger ([#32203](https://github.com/angular/components/pull/32203)) | +| [19095030a](https://github.com/angular/components/commit/19095030a0a6f102aa1ba7276afbc12fd79a27c7) | fix | **menu:** public api cleanup ([#32189](https://github.com/angular/components/pull/32189)) | +| [4b5db1328](https://github.com/angular/components/commit/4b5db13286b263cb28918938dce515376b4a5f5b) | fix | **menu:** rtl text direction ([#32254](https://github.com/angular/components/pull/32254)) | +| [bac171d8b](https://github.com/angular/components/commit/bac171d8bc82fd7bfd716cc5214c49f1bfbc76bb) | fix | **toolbar:** allow developers to wrap widgets ([#32341](https://github.com/angular/components/pull/32341)) | +| [d8acd69f5](https://github.com/angular/components/commit/d8acd69f5d26c85ffc08224e67be5f594c85e38a) | fix | **tree:** adds rtl keyboard functionality for tree ([#32305](https://github.com/angular/components/pull/32305)) | +| [86558d2aa](https://github.com/angular/components/commit/86558d2aa6437556bac805a6acdf8c31f5b2cfb8) | fix | **tree:** internal conformance check ([#32337](https://github.com/angular/components/pull/32337)) | +| [09e31e716](https://github.com/angular/components/commit/09e31e71673a73d80033e7aaf8ad8f6ae9db8f89) | fix | **tree:** only reset selected values if used in combobox ([#32329](https://github.com/angular/components/pull/32329)) | +| [05f936ae1](https://github.com/angular/components/commit/05f936ae14d8943ced0ad981cde8f3c2ac95afd1) | fix | **tree:** tree item visibility issue ([#32156](https://github.com/angular/components/pull/32156)) | +| [afe4d063d](https://github.com/angular/components/commit/afe4d063dd8f3dc5932519761693e7a79de77dc1) | fix | **ui-patterns:** internal conformance fixes ([#32102](https://github.com/angular/components/pull/32102)) | +### multiple +| Commit | Type | Description | +| -- | -- | -- | +| [8a76ccfa0](https://github.com/angular/components/commit/8a76ccfa0f4db446aa679c00858f44d6b594dc04) | fix | allow ids to be inputs ([#32320](https://github.com/angular/components/pull/32320)) | +| [e7f9ef3e8](https://github.com/angular/components/commit/e7f9ef3e826a543b422505671c74e73b2a2f495e) | fix | change delays to use ms ([#32321](https://github.com/angular/components/pull/32321)) | +| [a8bbd2816](https://github.com/angular/components/commit/a8bbd281629aaa8978aa8c23a394d63a24058c45) | fix | change value inputs to 'values' for array-based types ([#32300](https://github.com/angular/components/pull/32300)) | +| [b50ecb9b6](https://github.com/angular/components/commit/b50ecb9b6ebb9f9769bd8694a4a5671071eeccef) | fix | enable overwriting `preventDefault` to allow triggering hyperlinks using enter key ([#32123](https://github.com/angular/components/pull/32123)) | +| [f38020952](https://github.com/angular/components/commit/f38020952b78fe3d87c8a3f6bda63684c018dd32) | fix | expose active from public api ([#32330](https://github.com/angular/components/pull/32330)) | +| [9ac3ab5f9](https://github.com/angular/components/commit/9ac3ab5f9cad2952c1a8fa139794de9518e6190a) | fix | expose element ([#32328](https://github.com/angular/components/pull/32328)) | +| [caa2b3b65](https://github.com/angular/components/commit/caa2b3b6595d8a69c5deaac827be69d2aac1111a) | fix | prevent focus on disabled components ([#32263](https://github.com/angular/components/pull/32263)) | +| [551ce3df3](https://github.com/angular/components/commit/551ce3df36785a7552f465b81ecede5796716f73) | fix | transform boolean attr ([#32319](https://github.com/angular/components/pull/32319)) | - -# 20.2.2 "plastic-moose" (2025-09-03) + +# 20.2.10 "aleutite-anchovy" (2025-10-22) +### material +| Commit | Type | Description | +| -- | -- | -- | +| [b2cd596d3](https://github.com/angular/components/commit/b2cd596d315585c1f8b895c64230b49aecd3e6f8) | fix | **core:** differentiate container colors in m2 ([#32076](https://github.com/angular/components/pull/32076)) | - -# 21.0.0-next.1 "althupite-avocado" (2025-08-27) -### cdk + +# 20.2.9 "plastic-spoon" (2025-10-15) +### material | Commit | Type | Description | | -- | -- | -- | -| [540637270](https://github.com/angular/components/commit/540637270a5b72173f299e2ef1731499f43f19d3) | fix | **tree:** resolve memory leak ([#31754](https://github.com/angular/components/pull/31754)) | +| [af3b961214](https://github.com/angular/components/commit/af3b9612145ed20a3290f773e4fbf51206804145) | fix | **checkbox:** use GrayText for disabled colors in high contrast mode ([#32066](https://github.com/angular/components/pull/32066)) | +| [a90abb0a77](https://github.com/angular/components/commit/a90abb0a77b77b0ec5188582a1ea4d587508da5e) | fix | **snack-bar:** add max height for snack bar ([#32000](https://github.com/angular/components/pull/32000)) | + + + + +# 20.2.8 "strontium-summit" (2025-10-08) ### material | Commit | Type | Description | | -- | -- | -- | -| [29f0bb238](https://github.com/angular/components/commit/29f0bb238a2a327ac6ed69071f6aa555693ab2d4) | feat | **chips:** make ChipInput optional for MatChipGrid ([#31693](https://github.com/angular/components/pull/31693)) | -| [13a9c48a0](https://github.com/angular/components/commit/13a9c48a093b1bfeab8f0328030dae60aca7519f) | fix | **chips:** refactor non-interactive actions to prevent adding click handlers ([#31664](https://github.com/angular/components/pull/31664)) | -| [347963303](https://github.com/angular/components/commit/3479633036abf467354e936c1ddb4fd2cefcd6ea) | fix | **chips:** strengthen edit/remove icons focus/hover styling ([#31759](https://github.com/angular/components/pull/31759)) | -| [605e2c9f6](https://github.com/angular/components/commit/605e2c9f6557b41f03ad3f9268d6c536433e1555) | fix | **core:** rename utility-classes to system-classes ([#31745](https://github.com/angular/components/pull/31745)) | -| [8010c7cde](https://github.com/angular/components/commit/8010c7cde8e05661a978023dfd3c8c01a2861f97) | fix | **datepicker:** add visible labels to calendar buttons ([#31777](https://github.com/angular/components/pull/31777)) | -| [46e189569](https://github.com/angular/components/commit/46e189569ed30b63ae8817ff4390bb1274a5a8b9) | fix | **sort:** error if signal is bound to disabled input ([#31776](https://github.com/angular/components/pull/31776)) | -### cdk-experimental +| [6d61babe7](https://github.com/angular/components/commit/6d61babe77a6a6f0c464b498ee773561b795663f) | fix | **datepicker:** error due to synchronous change detection | +| [e43dcabd0](https://github.com/angular/components/commit/e43dcabd005239a380ffef50cfea374b24a67735) | fix | **datepicker:** make date filter nullable ([#31980](https://github.com/angular/components/pull/31980)) | +| [f30c1c6c3](https://github.com/angular/components/commit/f30c1c6c3f6490ddf21ba668d4065c4d5f89f26f) | fix | **radio:** Hovering over label of a radio will show the pointer cursor ([#32015](https://github.com/angular/components/pull/32015)) | +| [de3f9e566](https://github.com/angular/components/commit/de3f9e5662edae3c19f44a89ee1ee34555eba147) | fix | **timepicker:** assign form control value before emitting events ([#31981](https://github.com/angular/components/pull/31981)) | +### material-date-fns-adapter | Commit | Type | Description | | -- | -- | -- | -| [668a2b4d5](https://github.com/angular/components/commit/668a2b4d5aa89750c4c4cb65d2659d9a41042ba5) | fix | **ui-patterns:** deselectAll unavailable items ([#31734](https://github.com/angular/components/pull/31734)) | -| [d2c3bb971](https://github.com/angular/components/commit/d2c3bb971db4515d04b8842bcf96d4a8f8a65dac) | fix | **ui-patterns:** focus list when using active desce… ([#31756](https://github.com/angular/components/pull/31756)) | -| [df0d753f1](https://github.com/angular/components/commit/df0d753f166562ef15252b0923a583a93b76a4b2) | fix | **ui-patterns:** Tree expand/collapse key should work in follow focus mode ([#31747](https://github.com/angular/components/pull/31747)) | +| [2d1f8d068](https://github.com/angular/components/commit/2d1f8d068e18fa1e0044e0dd4c968c3fa6761e10) | fix | parse time string containing only hours ([#31978](https://github.com/angular/components/pull/31978)) | - -# 20.2.1 "armalcolite-alligator" (2025-08-27) + +# 20.2.7 "ceramic-nebula" (2025-10-01) +### material +| Commit | Type | Description | +| -- | -- | -- | +| [85ed6550c2](https://github.com/angular/components/commit/85ed6550c2a3f8199350dcb1fb7d6f775dc2ba61) | fix | **menu:** prevent child menu reopening while parent is animating away ([#31958](https://github.com/angular/components/pull/31958)) | + + + + +# 20.2.5 "sparkling-penguin" (2025-09-24) +### material +| Commit | Type | Description | +| -- | -- | -- | +| [36be42637](https://github.com/angular/components/commit/36be42637df5021ae9afa4d097e84d83d78c197e) | fix | **core:** move internal tokens ([#31907](https://github.com/angular/components/pull/31907)) | +| [3ac762be5](https://github.com/angular/components/commit/3ac762be5dc180594c0681e2842e35123195feb6) | fix | **form-field:** restore error message animation ([#31774](https://github.com/angular/components/pull/31774)) | +| [65f23c003](https://github.com/angular/components/commit/65f23c003e94d59bbb08ad81eb66d725b1b5e79a) | fix | **table:** style no data row properly ([#31895](https://github.com/angular/components/pull/31895)) | + + + + +# 20.2.4 "v20.2.4 release" (2025-09-17) +### material +| Commit | Type | Description | +| -- | -- | -- | +| [e7a0c19d0](https://github.com/angular/components/commit/e7a0c19d09bbe38852168c1a5ffcfeed66c1da6f) | fix | **tabs:** attach content inside the zone ([#31868](https://github.com/angular/components/pull/31868)) | + + + + +# 20.2.3 "tango-heels" (2025-09-11) ### cdk | Commit | Type | Description | | -- | -- | -- | -| [ee808f8f3](https://github.com/angular/components/commit/ee808f8f32fc27d59c56b63044355e4c7e0e416c) | fix | **tree:** resolve memory leak ([#31754](https://github.com/angular/components/pull/31754)) | +| [442d4ca6b7](https://github.com/angular/components/commit/442d4ca6b752eabcf60003df0f1ad6905dddad5f) | fix | **drag-drop:** allow axis lock to be reset ([#31829](https://github.com/angular/components/pull/31829)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [04c598ad0](https://github.com/angular/components/commit/04c598ad0a1a16a0149b324dc442255c2fdc0c17) | fix | **datepicker:** add visible labels to calendar buttons ([#31777](https://github.com/angular/components/pull/31777)) | -| [839f3c1c1](https://github.com/angular/components/commit/839f3c1c1354438fdfa554662f5278703937678f) | fix | **sort:** error if signal is bound to disabled input ([#31776](https://github.com/angular/components/pull/31776)) | +| [ec33bf8eba](https://github.com/angular/components/commit/ec33bf8eba46b1281e6b2a814a1ce3e15475ec20) | fix | **progress-bar:** avoid CSP issues due to buffer dots ([#31818](https://github.com/angular/components/pull/31818)) | +| [9a68265302](https://github.com/angular/components/commit/9a682653023fd3d181d4b5b318ffc13974a9bf2b) | fix | **slider:** incorrect indicator transform origin in M3 ([#31834](https://github.com/angular/components/pull/31834)) | + + + + +# 20.2.2 "plastic-moose" (2025-09-03) - -# 21.0.0-next.0 "neodymium-fountain" (2025-08-20) + +# 20.2.1 "armalcolite-alligator" (2025-08-27) ### cdk | Commit | Type | Description | | -- | -- | -- | -| [5b45df30c](https://github.com/angular/components/commit/5b45df30ca60b75b27313d3b1b52c42ac037eb4f) | fix | **table:** ensure CdkTable updates view with OnPush and trackBy ([#31451](https://github.com/angular/components/pull/31451)) | +| [ee808f8f3](https://github.com/angular/components/commit/ee808f8f32fc27d59c56b63044355e4c7e0e416c) | fix | **tree:** resolve memory leak ([#31754](https://github.com/angular/components/pull/31754)) | ### material | Commit | Type | Description | | -- | -- | -- | -| [dea603b88](https://github.com/angular/components/commit/dea603b88cf35d310674964b8b25514d5fe26772) | feat | **core:** add experimental utility classes ([#31702](https://github.com/angular/components/pull/31702)) | +| [04c598ad0](https://github.com/angular/components/commit/04c598ad0a1a16a0149b324dc442255c2fdc0c17) | fix | **datepicker:** add visible labels to calendar buttons ([#31777](https://github.com/angular/components/pull/31777)) | +| [839f3c1c1](https://github.com/angular/components/commit/839f3c1c1354438fdfa554662f5278703937678f) | fix | **sort:** error if signal is bound to disabled input ([#31776](https://github.com/angular/components/pull/31776)) | diff --git a/MODULE.bazel b/MODULE.bazel index 103c8047efc7..588532b156cf 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -4,21 +4,21 @@ module( name = "components", ) -bazel_dep(name = "yq.bzl", version = "0.3.1") -bazel_dep(name = "rules_nodejs", version = "6.5.2") -bazel_dep(name = "aspect_rules_js", version = "2.6.2") +bazel_dep(name = "yq.bzl", version = "0.3.2") +bazel_dep(name = "rules_nodejs", version = "6.6.2") +bazel_dep(name = "aspect_rules_js", version = "2.8.2") bazel_dep(name = "rules_pkg", version = "1.1.0") -bazel_dep(name = "tar.bzl", version = "0.6.0") +bazel_dep(name = "tar.bzl", version = "0.7.0") bazel_dep(name = "aspect_bazel_lib", version = "2.21.2") -bazel_dep(name = "aspect_rules_esbuild", version = "0.23.0") +bazel_dep(name = "aspect_rules_esbuild", version = "0.24.0") bazel_dep(name = "aspect_rules_jasmine", version = "2.0.0") bazel_dep(name = "platforms", version = "1.0.0") -bazel_dep(name = "aspect_rules_ts", version = "3.7.0") +bazel_dep(name = "aspect_rules_ts", version = "3.7.1") bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "rules_browsers") git_override( module_name = "rules_browsers", - commit = "6a699bf3e896690e2923cf3ade29fbd4e492e366", + commit = "f066f614451374721fac787fb1f0dbbd818d50d4", remote = "https://github.com/devversion/rules_browsers.git", ) @@ -32,14 +32,14 @@ git_override( bazel_dep(name = "rules_angular") git_override( module_name = "rules_angular", - commit = "c3721b6ece2050a59a97562e3b95527a3092b03b", + commit = "9b751f826628cab386d1e8ae9c1fa766dbc6a495", remote = "https://github.com/devversion/rules_angular.git", ) bazel_dep(name = "devinfra") git_override( module_name = "devinfra", - commit = "c584c3565b71c7a8cda81d55bb14e3e66ef934da", + commit = "95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae", remote = "https://github.com/angular/dev-infra.git", ) @@ -97,30 +97,52 @@ npm.npm_translate_lock( "@angular/aria": [ "//integration:__subpackages__", "//docs:__subpackages__", + "//src/components-examples:__subpackages__", + "//src/dev-app:__subpackages__", ], "@angular/cdk": [ - "//integration:__subpackages__", "//docs:__subpackages__", + "//integration:__subpackages__", + "//src/aria:__subpackages__", + "//src/cdk-experimental:__subpackages__", + "//src/components-examples:__subpackages__", + "//src/dev-app:__subpackages__", + "//src/material-experimental:__subpackages__", + "//src/material:__subpackages__", + "//src/e2e-app:__subpackages__", ], "@angular/cdk-experimental": [ - "//integration:__subpackages__", "//docs:__subpackages__", + "//integration:__subpackages__", + "//src/components-examples:__subpackages__", + "//src/dev-app:__subpackages__", + "//src/material-experimental:__subpackages__", + "//src/e2e-app:__subpackages__", ], "@angular/material": [ - "//integration:__subpackages__", "//docs:__subpackages__", + "//integration:__subpackages__", + "//src/components-examples:__subpackages__", + "//src/dev-app:__subpackages__", + "//src/material-experimental:__subpackages__", + "//src/material-moment-adapter:__subpackages__", + "//src/e2e-app:__subpackages__", ], "@angular/material-experimental": [ "//integration:__subpackages__", "//docs:__subpackages__", + "//src/components-examples:__subpackages__", + "//src/dev-app:__subpackages__", ], "@angular/google-maps": [ "//integration:__subpackages__", "//docs:__subpackages__", + "//src/dev-app:__subpackages__", ], "@angular/youtube-player": [ "//integration:__subpackages__", "//docs:__subpackages__", + "//src/dev-app:__subpackages__", ], "@angular/material-moment-adapter": [ "//integration:__subpackages__", @@ -133,6 +155,7 @@ npm.npm_translate_lock( "@angular/material-luxon-adapter": [ "//integration:__subpackages__", "//docs:__subpackages__", + "//src/components-examples:__subpackages__", ], }, pnpm_lock = "//:pnpm-lock.yaml", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 1db2dbbcba98..cee88376eace 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -14,23 +14,26 @@ "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.0.0/MODULE.bazel": "e118477db5c49419a88d78ebc7a2c2cea9d49600fe0f490c1903324a2c16ecd9", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f", + "https://bcr.bazel.build/modules/aspect_bazel_lib/2.17.1/MODULE.bazel": "9b027af55f619c7c444cead71061578fab6587e5e1303fa4ed61d49d2b1a7262", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.2/MODULE.bazel": "276347663a25b0d5bd6cad869252bea3e160c4d980e764b15f3bae7f80b30624", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.2/source.json": "f42051fa42629f0e59b7ac2adf0a55749144b11f1efcd8c697f0ee247181e526", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.7.7/MODULE.bazel": "491f8681205e31bb57892d67442ce448cda4f472a8e6b3dc062865e29a64f89c", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.9.3/MODULE.bazel": "66baf724dbae7aff4787bf2245cc188d50cb08e07789769730151c0943587c14", - "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.23.0/MODULE.bazel": "9b437a9ec25a619304940434fa03b8d41248213eb7009da2c898f3d6a4075ef3", - "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.23.0/source.json": "7b4cac4e61bae4262e7f67f6bec0b200fcb9060044f12e84a3bc37e0be245de7", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.24.0/MODULE.bazel": "15d19e46ec74e9e49ddf3c335e7a91b0657571654b0960bdcd10b771eeb4f7cb", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.24.0/source.json": "6cc8c0ba6c623527e383acfe4ee73f290eeead2431093668db3b7a579a948deb", "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.0/MODULE.bazel": "071d1952527721bf8b180e1299def24edaece9d7466e31a311981640da82c6be", "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.0/source.json": "45fa9603cdfe100575a12d8b65fa425fe8713dd8c9f0cdf802168b670bc0e299", "https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5", "https://bcr.bazel.build/modules/aspect_rules_js/2.4.2/MODULE.bazel": "0d01db38b96d25df7ed952a5e96eac4b3802723d146961974bf020f6dd07591d", "https://bcr.bazel.build/modules/aspect_rules_js/2.6.2/MODULE.bazel": "ed2a871f4ab8fbde0cab67c425745069d84ea64b64313fa1a2954017326511f5", - "https://bcr.bazel.build/modules/aspect_rules_js/2.6.2/source.json": "59933fb8ddabd9740a3c12ff5552f06f2b8d68c3633883c681c757bf227c3763", + "https://bcr.bazel.build/modules/aspect_rules_js/2.8.2/MODULE.bazel": "76526405d6a65dae45db16b8b4619b502063ac573d2a2ae0a90fddc7d3247288", + "https://bcr.bazel.build/modules/aspect_rules_js/2.8.2/source.json": "a03164e3f59a05d9a6d205a477ea49ba8ee141ab764a9f38b43e6302eb4fa2b9", "https://bcr.bazel.build/modules/aspect_rules_ts/3.6.3/MODULE.bazel": "d09db394970f076176ce7bab5b5fa7f0d560fd4f30b8432ea5e2c2570505b130", "https://bcr.bazel.build/modules/aspect_rules_ts/3.7.0/MODULE.bazel": "5aace216caf88638950ef061245d23c36f57c8359e56e97f02a36f70bb09c50f", - "https://bcr.bazel.build/modules/aspect_rules_ts/3.7.0/source.json": "4a8115ea69dd796353232ff27a7e93e6d7d1ad43bea1eb33c6bd3acfa656bf2e", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.7.1/MODULE.bazel": "cbed416847e2c46c4c0fe275e3a3c8e302d236d0fb04a094e9af82d14e7c5040", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.7.1/source.json": "7914a860fdf6ac399a3142fee2579184f0810fe0b7dee2eb9216ab72e6d8864e", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.3/MODULE.bazel": "20f53b145f40957a51077ae90b37b7ce83582a1daf9350349f0f86179e19dd0d", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.6/MODULE.bazel": "cafb8781ad591bc57cc765dca5fefab08cf9f65af363d162b79d49205c7f8af7", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.8/MODULE.bazel": "aa975a83e72bcaac62ee61ab12b788ea324a1d05c4aab28aadb202f647881679", @@ -48,7 +51,8 @@ "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "407729e232f611c3270005b016b437005daa7b1505826798ea584169a476e878", - "https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/source.json": "72bfbe19a3936675719157798de64631e9ac54c2b41f13b544b821d094f4840a", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", + "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", @@ -152,7 +156,8 @@ "https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d", "https://bcr.bazel.build/modules/rules_nodejs/6.5.0/MODULE.bazel": "546d0cf79f36f9f6e080816045f97234b071c205f4542e3351bd4424282a8810", "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d", - "https://bcr.bazel.build/modules/rules_nodejs/6.5.2/source.json": "6a6ca0940914d55c550d1417cad13a56c9900e23f651a762d8ccc5a64adcf661", + "https://bcr.bazel.build/modules/rules_nodejs/6.6.2/MODULE.bazel": "9fdb5e1d50246a25761f150fcc820dc47e4052330a8408451e628804f9ca64a6", + "https://bcr.bazel.build/modules/rules_nodejs/6.6.2/source.json": "6e8c1ecc64ff8da147c1620f862ad77d7b19c5d1b52b3aa5e847d5b3d0de4cc3", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453", @@ -186,13 +191,13 @@ "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468", "https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351", - "https://bcr.bazel.build/modules/tar.bzl/0.6.0/MODULE.bazel": "a3584b4edcfafcabd9b0ef9819808f05b372957bbdff41601429d5fd0aac2e7c", - "https://bcr.bazel.build/modules/tar.bzl/0.6.0/source.json": "4a620381df075a16cb3a7ed57bd1d05f7480222394c64a20fa51bdb636fda658", + "https://bcr.bazel.build/modules/tar.bzl/0.7.0/MODULE.bazel": "cc1acd85da33c80e430b65219a620d54d114628df24a618c3a5fa0b65e988da9", + "https://bcr.bazel.build/modules/tar.bzl/0.7.0/source.json": "9becb80306f42d4810bfa16379fb48aad0b01ce5342bc12fe47dcd6af3ac4d7a", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", "https://bcr.bazel.build/modules/yq.bzl/0.2.0/MODULE.bazel": "6f3a675677db8885be4d607fde14cc51829715e3a879fb016eb9bf336786ce6d", - "https://bcr.bazel.build/modules/yq.bzl/0.3.1/MODULE.bazel": "9bcb7151b3cd4681b89d350530eaf7b45e32a44dda94843b8932b0cb1cd4594a", - "https://bcr.bazel.build/modules/yq.bzl/0.3.1/source.json": "f0b0f204a2a6b0e34b4c9541efe8c04f2ef1af65948daa784eccea738b21dbd2", + "https://bcr.bazel.build/modules/yq.bzl/0.3.2/MODULE.bazel": "0384efa70e8033d842ea73aa4b7199fa099709e236a7264345c03937166670b6", + "https://bcr.bazel.build/modules/yq.bzl/0.3.2/source.json": "c4ec3e192477e154f08769e29d69e8fd36e8a4f0f623997f3e1f6f7d328f7d7d", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", @@ -203,8 +208,8 @@ "moduleExtensions": { "@@aspect_rules_esbuild~//esbuild:extensions.bzl%esbuild": { "general": { - "bzlTransitiveDigest": "W+cy7GU3S29h8PPWylmMlPB5Z16vuZzJX4k0jlXGFoc=", - "usagesDigest": "H070ZIHhSlR+Han009l+GdDSuT9AJssdyVHQ7xjstSo=", + "bzlTransitiveDigest": "Kp1ElwnSsU9URW4hnpM9wzhITxGUNAp4tu7dOwA82Qo=", + "usagesDigest": "w3kRc6iou9hC6M+vwECvdfk0P8nsVNjHSl4Ftru44zU=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -214,6 +219,7 @@ "ruleClassName": "esbuild_repositories", "attributes": { "esbuild_version": "0.19.9", + "integrity": "", "platform": "darwin-x64" } }, @@ -222,6 +228,7 @@ "ruleClassName": "esbuild_repositories", "attributes": { "esbuild_version": "0.19.9", + "integrity": "", "platform": "darwin-arm64" } }, @@ -230,6 +237,7 @@ "ruleClassName": "esbuild_repositories", "attributes": { "esbuild_version": "0.19.9", + "integrity": "", "platform": "linux-x64" } }, @@ -238,6 +246,7 @@ "ruleClassName": "esbuild_repositories", "attributes": { "esbuild_version": "0.19.9", + "integrity": "", "platform": "linux-arm64" } }, @@ -246,6 +255,7 @@ "ruleClassName": "esbuild_repositories", "attributes": { "esbuild_version": "0.19.9", + "integrity": "", "platform": "win32-x64" } }, @@ -282,8 +292,7 @@ "extra_build_content": "", "generate_bzl_library_targets": false, "extract_full_archive": false, - "exclude_package_contents": [], - "system_tar": "auto" + "exclude_package_contents": [] } }, "npm__esbuild_0.19.9__links": { @@ -358,6 +367,11 @@ "aspect_tools_telemetry_report", "aspect_tools_telemetry~~telemetry~aspect_tools_telemetry_report" ], + [ + "aspect_rules_js~", + "bazel_lib", + "bazel_lib~" + ], [ "aspect_rules_js~", "bazel_skylib", @@ -368,10 +382,20 @@ "bazel_tools", "bazel_tools" ], + [ + "bazel_lib~", + "bazel_skylib", + "bazel_skylib~" + ], + [ + "bazel_lib~", + "bazel_tools", + "bazel_tools" + ], [ "tar.bzl~", - "aspect_bazel_lib", - "aspect_bazel_lib~" + "bazel_lib", + "bazel_lib~" ], [ "tar.bzl~", @@ -388,8 +412,8 @@ }, "@@aspect_rules_js~//npm:extensions.bzl%pnpm": { "general": { - "bzlTransitiveDigest": "kFo9dd9KDRPKtK0RkVyoouzNI0l+bqG8cEaw+4+ZnVw=", - "usagesDigest": "YbFxfk4LS8ZRSHiDWDKkBXNqTdHX58mv4QOfh7MzUAM=", + "bzlTransitiveDigest": "b2732vQWZpf2g1U6Rf2M0kXVkyN5QVnEn69W2+zHZPQ=", + "usagesDigest": "mGFa8hiEDwjp+vco80yrqwO3lIlHo8Jtigydy53mzxU=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -399,11 +423,11 @@ "ruleClassName": "npm_import_rule", "attributes": { "package": "pnpm", - "version": "8.6.7", + "version": "8.15.9", "root_package": "", "link_workspace": "", "link_packages": {}, - "integrity": "sha512-vRIWpD/L4phf9Bk2o/O2TDR8fFoJnpYrp2TKqTIZF/qZ2/rgL3qKXzHofHgbXsinwMoSEigz28sqk3pQ+yMEQQ==", + "integrity": "sha512-SZQ0ydj90aJ5Tr9FUrOyXApjOrzuW7Fee13pDzL0e1E6ypjNXP0AHDHw20VLw4BO3M1XhQHkyik6aBYWa72fgQ==", "url": "", "commit": "", "patch_args": [ @@ -419,8 +443,7 @@ "extra_build_content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\njs_binary(name = \"pnpm\", data = glob([\"package/**\"]), entry_point = \"package/dist/pnpm.cjs\", visibility = [\"//visibility:public\"])", "generate_bzl_library_targets": false, "extract_full_archive": true, - "exclude_package_contents": [], - "system_tar": "auto" + "exclude_package_contents": [] } }, "pnpm__links": { @@ -428,7 +451,7 @@ "ruleClassName": "npm_import_links", "attributes": { "package": "pnpm", - "version": "8.6.7", + "version": "8.15.9", "dev": false, "root_package": "", "link_packages": {}, @@ -485,6 +508,11 @@ "bazel_features", "bazel_features~" ], + [ + "aspect_rules_js~", + "bazel_lib", + "bazel_lib~" + ], [ "aspect_rules_js~", "bazel_skylib", @@ -505,10 +533,20 @@ "bazel_features_version", "bazel_features~~version_extension~bazel_features_version" ], + [ + "bazel_lib~", + "bazel_skylib", + "bazel_skylib~" + ], + [ + "bazel_lib~", + "bazel_tools", + "bazel_tools" + ], [ "tar.bzl~", - "aspect_bazel_lib", - "aspect_bazel_lib~" + "bazel_lib", + "bazel_lib~" ], [ "tar.bzl~", @@ -525,7 +563,7 @@ }, "@@aspect_rules_ts~//ts:extensions.bzl%ext": { "general": { - "bzlTransitiveDigest": "9IJp6IlB/FMHFBJe4MX/DQM4zi3oArC8yqYE/+NyPwk=", + "bzlTransitiveDigest": "pEu5+6q07zdUqscbVkhWTNLGT9OGRpnFCF4OGHv1n1k=", "usagesDigest": "V/cnAeJuoOSXti/YIjg6+EA/7x3Pcs9RlVXNWKtaDsA=", "recordedFileInputs": { "@@rules_browsers~//package.json": "84dc1ba9b1c667a25894e97218bd8f247d54f24bb694efb397a881be3c06a4c5" @@ -585,11 +623,6 @@ "aspect_rules_ts", "aspect_rules_ts~" ], - [ - "aspect_rules_ts~", - "aspect_tools_telemetry_report", - "aspect_tools_telemetry~~telemetry~aspect_tools_telemetry_report" - ], [ "aspect_rules_ts~", "bazel_tools", @@ -601,7 +634,7 @@ "@@aspect_tools_telemetry~//:extension.bzl%telemetry": { "general": { "bzlTransitiveDigest": "gA7tPEdJXhskzPIEUxjX9IdDrM6+WjfbgXJ8Ez47umk=", - "usagesDigest": "iQKFO513wdLp8ePYECqwesGU0Ase6Q+uoj+I7R3Bcco=", + "usagesDigest": "yKXvAODYd53Ah33no0MGsMxLdQFblPRRu56XxJsj/jo=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -611,9 +644,9 @@ "ruleClassName": "tel_repository", "attributes": { "deps": { - "aspect_rules_js": "2.6.2", - "aspect_rules_esbuild": "0.23.0", - "aspect_rules_ts": "3.7.0", + "aspect_rules_js": "2.8.2", + "aspect_rules_esbuild": "0.24.0", + "aspect_rules_ts": "3.7.1", "aspect_tools_telemetry": "0.2.8" } } @@ -635,7 +668,7 @@ }, "@@pybind11_bazel~//:python_configure.bzl%extension": { "general": { - "bzlTransitiveDigest": "whINYge95GgPtysKDbNHQ0ZlWYdtKybHs5y2tLF+x7Q=", + "bzlTransitiveDigest": "dFd3A3f+jPCss+EDKMp/jxjcUhfMku130eT1KGxSCwA=", "usagesDigest": "gNvOHVcAlwgDsNXD0amkv2CC96mnaCThPQoE44y8K+w=", "recordedFileInputs": { "@@pybind11_bazel~//MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e" @@ -707,7 +740,7 @@ }, "@@rules_browsers~//browsers:extensions.bzl%browsers": { "general": { - "bzlTransitiveDigest": "6QMCx97Hwh2hyQPqZEA9AKAxbpygF41+K8xJfeqJYm8=", + "bzlTransitiveDigest": "ljZlVgWkQJnI6EvlHVfYit2EttUE52gDTbvmota5YO8=", "usagesDigest": "1PlExi+b77pSr2tAxFCVbpCtFoA7oixHabaL3dmas4Y=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -717,9 +750,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "4bc6d611d55dc96b213c8605cb8ac27d3c21973bf8b663df4cbf756c989e6745", + "sha256": "1419fa328bd7ea2697f26412ec693867516e4ef23c32eb13143a0b0b179b604b", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/143.0.7482.0/linux64/chrome-headless-shell-linux64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/linux64/chrome-headless-shell-linux64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-linux64/chrome-headless-shell" @@ -736,9 +769,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "830cc2aafedbe7c9fe671c9898046f8900c06da89d12653ddc3ef26084d2f516", + "sha256": "792cbf9b77219b4476e41c49647bcd15e55f0988002fa1e4e6a720eb430c7eda", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/143.0.7482.0/mac-x64/chrome-headless-shell-mac-x64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-x64/chrome-headless-shell-mac-x64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-x64/chrome-headless-shell" @@ -755,9 +788,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "5b5792f5c2d05c3f1f782346910869b61a37b9003f212315b19f4e46710cf8b9", + "sha256": "f0c1917769775e826dfa69936381d0d95b06fe67cf631ecd842380d5de0e4c7f", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/143.0.7482.0/mac-arm64/chrome-headless-shell-mac-arm64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-arm64/chrome-headless-shell-mac-arm64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-arm64/chrome-headless-shell" @@ -774,9 +807,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "19bdbf6e1579b6c056b74709520ac9df573f4e80e4f026cc2360a29443cf6c0c", + "sha256": "6ce0f20dd743a804890f45f5349370e1aa7cd3ac3482c04686fcff5fafd01bb3", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/143.0.7482.0/win64/chrome-headless-shell-win64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/win64/chrome-headless-shell-win64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-win64/chrome-headless-shell.exe" @@ -793,9 +826,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "ea41e7a217d878c00e9d66a0724ff54be7d02d08adb7f6458b7d8487b6fbcd84", + "sha256": "baf4bf9d22881265487732f17d35a49e9aadd0837aa5c1c1eea520c8aa24a97f", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/143.0.7482.0/linux64/chromedriver-linux64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/linux64/chromedriver-linux64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-linux64/chromedriver" @@ -810,9 +843,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "aede9b67301b930ff9c673df28429aa82ce05c105a4ccbef7e0cd30a97ae429d", + "sha256": "87560768d5aa203b37c0a1b8459a35b05e4ece54afee2df530f3bc33de4f63c5", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/143.0.7482.0/mac-x64/chromedriver-mac-x64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-x64/chromedriver-mac-x64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-mac-x64/chromedriver" @@ -827,9 +860,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "5adf89a3e8edc6755920f4cfe2fe0515d40684878ef5201da5e02a9d491c4003", + "sha256": "99821795fa7c87eb92fb15248e23b237c83f397486d22ad9a10771622c36a5a0", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/143.0.7482.0/mac-arm64/chromedriver-mac-arm64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-arm64/chromedriver-mac-arm64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-mac-arm64/chromedriver" @@ -844,9 +877,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "a3dfe62b3e9e7a42bd324c07dcbbcc3a733a736b2a59f0e93b9250b88103ab73", + "sha256": "6e180e234a710c3cbf69566f64a662ed85473db6ae82275fd359f80ab288df99", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/143.0.7482.0/win64/chromedriver-win64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/win64/chromedriver-win64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-win64/chromedriver.exe" @@ -861,9 +894,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "c66a48222ff67d51560240d321895c6926c9b3af345cbf688ced8517781d88d1", + "sha256": "00fb922cda6bab971e02bcbfb77923b0a234388ed7d77c23506ca0a1a61d4a86", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/144.0/linux-x86_64/en-US/firefox-144.0.tar.xz" + "https://archive.mozilla.org/pub/firefox/releases/145.0/linux-x86_64/en-US/firefox-145.0.tar.xz" ], "named_files": { "FIREFOX": "firefox/firefox" @@ -878,9 +911,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "1e444b80921bc999d56c05a7decc1eaf88c0297cac5b90416299af2c77f5ecc9", + "sha256": "1c4556480deac8424049f3081a6de1e2c6de619bab3e8ce53e5a497b8d6d919e", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/144.0/mac/en-US/Firefox%20144.0.dmg" + "https://archive.mozilla.org/pub/firefox/releases/145.0/mac/en-US/Firefox%20145.0.dmg" ], "named_files": { "FIREFOX": "Firefox.app/Contents/MacOS/firefox" @@ -895,9 +928,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "1e444b80921bc999d56c05a7decc1eaf88c0297cac5b90416299af2c77f5ecc9", + "sha256": "1c4556480deac8424049f3081a6de1e2c6de619bab3e8ce53e5a497b8d6d919e", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/144.0/mac/en-US/Firefox%20144.0.dmg" + "https://archive.mozilla.org/pub/firefox/releases/145.0/mac/en-US/Firefox%20145.0.dmg" ], "named_files": { "FIREFOX": "Firefox.app/Contents/MacOS/firefox" @@ -912,9 +945,9 @@ "bzlFile": "@@rules_browsers~//browsers/private:browser_repo.bzl", "ruleClassName": "browser_repo", "attributes": { - "sha256": "d1e8a7c061e25a41c8dfa85e3aee8e86e9263c69104d80906c978c8d0556563a", + "sha256": "4b0345c113242653d923b369fcbd48e3089c57658f8c1542f887c8a375d50306", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/144.0/win64/en-US/Firefox%20Setup%20144.0.exe" + "https://archive.mozilla.org/pub/firefox/releases/145.0/win64/en-US/Firefox%20Setup%20145.0.exe" ], "named_files": { "FIREFOX": "core/firefox.exe" @@ -931,7 +964,7 @@ }, "@@rules_fuzzing~//fuzzing/private:extensions.bzl%non_module_dependencies": { "general": { - "bzlTransitiveDigest": "hVgJRQ3Er45/UUAgNn1Yp2Khcp/Y8WyafA2kXIYmQ5M=", + "bzlTransitiveDigest": "VMhyxXtdJvrNlLts7afAymA+pOatXuh5kLdxzVAZ/04=", "usagesDigest": "YnIrdgwnf3iCLfChsltBdZ7yOJh706lpa2vww/i2pDI=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1022,7 +1055,7 @@ }, "@@rules_java~//java:rules_java_deps.bzl%compatibility_proxy": { "general": { - "bzlTransitiveDigest": "KIX40nDfygEWbU+rq3nYpt3tVgTK/iO8PKh5VMBlN7M=", + "bzlTransitiveDigest": "C4xqrMy1wN4iuTN6Z2eCm94S5XingHhD6uwrIXvCxVI=", "usagesDigest": "pwHZ+26iLgQdwvdZeA5wnAjKnNI3y6XO2VbhOTeo5h8=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1045,7 +1078,7 @@ }, "@@rules_kotlin~//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "fus14IFJ/1LGWWGKPH/U18VnJCoMjfDt1ckahqCnM0A=", + "bzlTransitiveDigest": "eecmTsmdIQveoA97hPtH3/Ej/kugbdCI24bhXIXaly8=", "usagesDigest": "aJF6fLy82rR95Ff5CZPAqxNoFgOMLMN5ImfBS0nhnkg=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1114,8 +1147,8 @@ }, "@@rules_nodejs~//nodejs:extensions.bzl%node": { "general": { - "bzlTransitiveDigest": "FmfMiNXAxRoLWw3NloQbssosE1egrSvzirbQnso7j7E=", - "usagesDigest": "dMNT+qiygolcxk3T1nFST159tXO2wdWnMeeer3WBWnY=", + "bzlTransitiveDigest": "NwcLXHrbh2hoorA/Ybmcpjxsn/6avQmewDglodkDrgo=", + "usagesDigest": "791PxjWKNhqx8QIqF31Tk2pVapSPSG4/sWazBhT0nJE=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -1259,7 +1292,7 @@ }, "@@rules_python~//python/extensions:pip.bzl%pip": { "general": { - "bzlTransitiveDigest": "sQcIxpGBMBOMzUZK9ARAkAR7oUiEiGgKZhHLEf9Prfk=", + "bzlTransitiveDigest": "VKf3JZIRvp7gyc5Q9pSqri7bmB3079s5o6Yg7IaUHZI=", "usagesDigest": "MKs5B778/fEkKhBaxuBt3oCCW+wPRuh2AxtITF8AMSU=", "recordedFileInputs": { "@@rules_python~//tools/publish/requirements_linux.txt": "8175b4c8df50ae2f22d1706961884beeb54e7da27bd2447018314a175981997d", @@ -4178,7 +4211,7 @@ "@@yq.bzl~//yq:extensions.bzl%yq": { "general": { "bzlTransitiveDigest": "61Uz+o5PnlY0jJfPZEUNqsKxnM/UCLeWsn5VVCc8u5Y=", - "usagesDigest": "3Yefzt7ha1NB6ak4d2Xsxna4bkR35URwnEmIjuJOJbA=", + "usagesDigest": "8WxU6nCX5v/OeKDUimcSUbdP6GCmRwYDt/sj3AEzZ50=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/docs/.nvmrc b/docs/.nvmrc index aa50a62f2194..5767036af0e2 100644 --- a/docs/.nvmrc +++ b/docs/.nvmrc @@ -1 +1 @@ -22.21.0 +22.21.1 diff --git a/docs/defs.bzl b/docs/defs.bzl index c02e5e57e0d5..aa9d3720a682 100644 --- a/docs/defs.bzl +++ b/docs/defs.bzl @@ -19,6 +19,7 @@ COMMON_CONFIG = [ # Project dependencies common across libs/tests DEPS = [ + "//docs:node_modules/zone.js", "//docs:node_modules/@angular/aria", "//docs:node_modules/@angular/cdk", "//docs:node_modules/@angular/cdk-experimental", @@ -36,7 +37,15 @@ APPLICATION_CONFIG = COMMON_CONFIG + [ TEST_DEPS = [ "@rules_browsers//browsers/chromium", "@rules_browsers//browsers/firefox", + "//docs:node_modules/@types/jasmine", + "//docs:node_modules/@types/node", + "//docs:node_modules/jasmine-core", + "//docs:node_modules/karma", + "//docs:node_modules/karma-chrome-launcher", + "//docs:node_modules/karma-coverage", "//docs:node_modules/karma-firefox-launcher", + "//docs:node_modules/karma-jasmine", + "//docs:node_modules/karma-jasmine-html-reporter", ] # Common dependencies of Angular CLI test suites diff --git a/docs/src/app/pages/component-viewer/component-overview.html b/docs/src/app/pages/component-viewer/component-overview.html index 1404d543d260..715e4e126a72 100644 --- a/docs/src/app/pages/component-viewer/component-overview.html +++ b/docs/src/app/pages/component-viewer/component-overview.html @@ -2,9 +2,10 @@

Overview for {{docItem.id}}

- + @if (showToc | async) { diff --git a/docs/src/app/pages/system-variables/index.ts b/docs/src/app/pages/system-variables/index.ts deleted file mode 100644 index cd7ebb8992f0..000000000000 --- a/docs/src/app/pages/system-variables/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export * from './system-variables'; diff --git a/docs/src/app/pages/system-variables/system-variables.html b/docs/src/app/pages/system-variables/system-variables.html deleted file mode 100644 index 613e900b60d2..000000000000 --- a/docs/src/app/pages/system-variables/system-variables.html +++ /dev/null @@ -1,242 +0,0 @@ -

- Angular Material components depend on system variables defined as CSS variables through the - material.theme - Sass mixin. This page provides guidance and documentation for using these variables to - customize components. -

- -

Colors

- -

- Material Design uses color to create accessible, personal color schemes - that communicate your product's hierarchy, state, and brand. See Material - Design's Color System - page to learn more about its use and purpose. -

-

- The following colors are the most often used in Angular Material components. Use these - colors and follow their uses to add theme colors to your application's custom components. -

- -
-
-
-
Primary
-
--mat-sys-primary
-
-
-

- The most common color used by Angular Material components to - participate in the application theme. -

-

- Examples include the background color - of filled buttons, the icon color of selected radio buttons, and the - outline color of form fields. -

-

- Use the color --mat-sys-on-primary for - icons, text, and other visual elements placed on a primary background. This - color is calculated to be optimal for accessibility and legibility. -

-
-
- -
-
-
Surface
-
--mat-sys-surface
-
-
-

- A low-emphasis background color that provides a clear contrast for - both light and dark themes and their varied theme colors. -

-

- Examples include the background color of the application and most - components such as the dialog, card, table, and more. -

-

- Use the color --mat-sys-on-surface for - icons, text, and other visual elements placed on a surface background. This - color is calculated to be optimal for accessibility and legibility. -

-
-
- -
-
-
Error
-
--mat-sys-error
-
-
-

- High-contrast color meant to alert the user to attract immediate attention. -

-

- Examples include the background color of the badge and the text color of invalid - form fields inputs. -

-

- Use the color --mat-sys-on-error for - icons, text, and other visual elements placed on an error background. This - color is calculated to be optimal for accessibility and legibility. -

-
-
- -
-
-
Outline
-
--mat-sys-outline
-
-
-

- Used for borders and dividers to help provide visual separation between - and around elements. -

-

- Examples include the color of the divider and border color of an outlined - form field. -

-

- Use the color --mat-sys-outline-variant for a less - prominent outline. -

-
-
-
- - - Other available colors - -

- These colors are less commonly used in Angular Material components but - are available for adding color variety and creating additional emphasis - to components. -

-

- Colors may be paired with a --mat-sys-on- variable - that should be used for text and icons placed within a filled container. -

- -

Alternative Theme Colors

- - - -

Surface Colors

- -

- The following colors should be used for backgrounds and large, - low-emphasis areas of the screen. -

- -

- Containers filled with a surface color should apply the - --mat-sys-on-surface color to text - and icons placed within. -

- - - -

Fixed Colors

- -

- These colors are the same for both light and dark themes. They are unused - by any Angular Material components. -

- - - -
- -

Typography

- -

- There are five categories of font types defined by Material Design: body, display, headline, - label, and title. Each category has three sizes: small, medium, and large. -

-

- Learn more about how these categories and their sizes should be used in your application by - visiting Material Design's - Typography documentation. -

- - -@for (category of ['body', 'display', 'headline', 'label', 'title']; track $index) { -
-
{{category}}
- @for (size of ['small', 'medium', 'large']; track $index) { -
-
-
--mat-sys-{{category}}-{{size}}
-
-
Lorem ipsum dolor
-
- } -
-} - -

- Each system variable can be applied to the "font" CSS style. Additionally, the parts of the variable definition - can be accessed individually by appending the keywords "font", "line-height", "size", "tracking", and "weight". -

-

- For example, the values for medium body text may be defined as follows: -

-
---mat-sys-body-medium: 400 0.875rem / 1.25rem Roboto, sans-serif;
---mat-sys-body-medium-font: Roboto, sans-serif;
---mat-sys-body-medium-line-height: 1.25rem;
---mat-sys-body-medium-size: 0.875rem;
---mat-sys-body-medium-tracking: 0.016rem;
---mat-sys-body-medium-weight: 400;
-
- -

Elevation

- -

- Material Design provides six levels of elevation that can be used to provide - a sense of depth and organization to an application's UI. Learn more at Material Design's - Elevation guide. -

- -

- These levels are defined as CSS box-shadow values that can be styled to an element. -

- -@for (level of [0, 1, 2, 3, 4, 5]; track $index) { -
- box-shadow: var(--mat-sys-level{{level}}) -
-} - -

Overrides

- -

- The mat.theme-overrides mixin - can be included to emit different definitions for the system variables and - override the definitions emitted from mat.theme. -

- -
- This example container has several system variables overridden by including the - following Sass code: - -
-  @include mat.theme-overrides((
-    primary: #ebdcff,
-    on-primary: #230f46,
-    body-medium: 500 1.15rem/1.3rem Arial,
-    corner-large: 32px,
-    level3: 0 4px 6px 1px var(--mat-sys-surface-dim),
-  ));
-
diff --git a/docs/src/app/pages/system-variables/system-variables.scss b/docs/src/app/pages/system-variables/system-variables.scss deleted file mode 100644 index 87193de7efe4..000000000000 --- a/docs/src/app/pages/system-variables/system-variables.scss +++ /dev/null @@ -1,196 +0,0 @@ -@use '@angular/material' as mat; - -:host { - display: block; - max-width: 1000px; -} - -h1 { - font: var(--mat-sys-title-large); - font-size: 28px; - padding-top: 32px; -} - -h2 { - font: var(--mat-sys-title-large); -} - -a { - color: var(--mat-sys-primary); -} - -.demo-warn { - background: var(--mat-sys-error-container); - color: var(--mat-sys-on-error-container); - border: 1px solid var(--mat-sys-outline-variant); - border-radius: var(--mat-sys-corner-extra-small); - padding: 8px; -} - -.demo-group { - display: grid; - grid-template-columns: 1fr 1fr; - grid-gap: 24px; - margin-top: 24px; -} - -@media (max-width: 1000px) { - .demo-group { grid-template-columns: auto;} -} - -.demo-color-container { - border-radius: var(--mat-sys-corner-small); - display: inline-block; - font: var(--mat-sys-body-medium); - vertical-align: top; -} - -.demo-heading { - color: var(--mat-sys-on-primary); - background: var(--mat-sys-primary); - border: 1px solid var(--mat-sys-outline); - border-top-right-radius: var(--mat-sys-corner-small); - border-top-left-radius: var(--mat-sys-corner-small); - border-bottom: none; - padding: 16px; - display: flex; - align-items: center; - justify-content: space-between; -} - -.demo-name { - font: var(--mat-sys-title-medium); -} - -.demo-variable { - font: var(--mat-sys-title-small); - font-family: monospace; - text-align: right; -} - -.demo-description { - border: 1px solid var(--mat-sys-outline); - border-bottom-right-radius: var(--mat-sys-corner-small); - border-bottom-left-radius: var(--mat-sys-corner-small); - padding: 0 16px; -} - -.demo-code { - font-family: monospace; -} - -.demo-surface-variable { - display: inline-block; - font-family: monospace; - background: var(--mat-sys-primary-container); - color: var(--mat-sys-on-primary-container); - padding: 2px 6px; - margin: 0 2px; - border-radius: 4px; -} - -mat-expansion-panel { - margin-top: 24px; - overflow: visible; - @include mat.expansion-overrides(( - 'container-text-font': var(--mat-sys-body-medium-font), - 'container-text-size': var(--mat-sys-body-medium-size), - 'container-text-weight': var(--mat-sys-body-medium-weight), - 'container-text-line-height': var(--mat-sys-body-medium-line-height), - 'container-text-tracking': var(--mat-sys-body-medium-tracking), - )); -} - -.demo-compact-color-container { - border-radius: var(--mat-sys-corner-small); - border: 1px solid var(--mat-sys-outline); - overflow: hidden; // Hide child heading background color - margin-top: 24px; - - .demo-heading { - border: none; - border-radius: 0; - - &:not(:nth-child(1)) { - border-top: 1px solid var(--mat-sys-outline); - } - } - - .demo-variables { - text-align: end; - } -} - -.demo-typography-group { - border: 1px solid var(--mat-sys-outline); - border-radius: var(--mat-sys-corner-small); - margin-top: 40px; - overflow: hidden; -} - -.demo-typography-title { - text-transform: capitalize; - font: var(--mat-sys-title-medium); - padding: 16px; - border-bottom: 1px solid var(--mat-sys-outline); - background: var(--mat-sys-primary-container); - color: var(--mat-sys-on-primary-container); -} - -.demo-typography-variable { - min-width: 240px; -} - -.demo-typography-example { - padding: 16px; - display: flex; - align-items: baseline; - border-top: 1px solid var(--mat-sys-outline-variant); - - &:nth-child(1) { - border: none; - } - .demo-surface-variable { - margin-right: 16px; - } -} - -.demo-typography-text { - display: inline-block; -} - -.demo-elevation { - height: 40px; - width: 300px; - margin: 32px 0; - display: flex; - align-items: center; - justify-content: center; - background: var(--mat-sys-surface-container); - color: var(--mat-sys-on-surface); - border-radius: var(--mat-sys-corner-extra-small); -} - -.demo-code-block { - background: var(--mat-sys-surface-container-low); - padding: 16px; - border-radius: var(--mat-sys-corner-small); - border: 1px solid var(--mat-sys-outline); -} - -.demo-overrides { - background-color: var(--mat-sys-primary); - color: var(--mat-sys-on-primary); - font: var(--mat-sys-body-medium); - border-radius: var(--mat-sys-corner-small); - box-shadow: var(--mat-sys-level3); - padding: 16px; - - @include mat.theme-overrides(( - primary: #ebdcff, - on-primary: #230f46, - body-medium: 500 1.15rem/1.3rem Arial, - corner-large: 32px, - level3: 0 4px 6px 1px var(--mat-sys-surface-dim), - )); -} diff --git a/docs/src/app/pages/system-variables/system-variables.ts b/docs/src/app/pages/system-variables/system-variables.ts deleted file mode 100644 index e5b17ffb9cf9..000000000000 --- a/docs/src/app/pages/system-variables/system-variables.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ChangeDetectionStrategy, Component, input} from '@angular/core'; -import {MatExpansionPanel, MatExpansionPanelHeader} from '@angular/material/expansion'; - -interface Color { - name: string; - background: string; - text: string; - hideText?: boolean; -} - -@Component({ - selector: 'theme-demo-colors', - template: ` -
- @for (color of colors(); track $index) { -
-
{{color.name}}
-
-
{{color.background}}
- @if (!color.hideText) { -
{{color.text}}
- } -
-
- } -
- `, - styleUrl: 'system-variables.scss', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class ThemeDemoColors { - colors = input(); -} - -@Component({ - selector: 'app-system-variables', - templateUrl: './system-variables.html', - styleUrls: ['./system-variables.scss'], - imports: [MatExpansionPanel, MatExpansionPanelHeader, ThemeDemoColors], -}) -export class SystemVariables { - alternativeThemeColors: Color[] = [ - { - name: 'Primary Container', - background: '--mat-sys-primary-container', - text: '--mat-sys-on-primary-container', - }, - { - name: 'Secondary', - background: '--mat-sys-secondary', - text: '--mat-sys-on-secondary', - }, - { - name: 'Secondary Container', - background: '--mat-sys-secondary-container', - text: '--mat-sys-on-secondary-container', - }, - { - name: 'Tertiary', - background: '--mat-sys-tertiary', - text: '--mat-sys-on-tertiary', - }, - { - name: 'Tertiary Container', - background: '--mat-sys-tertiary-container', - text: '--mat-sys-on-tertiary-container', - }, - { - name: 'Error Container', - background: '--mat-sys-error-container', - text: '--mat-sys-on-error-container', - }, - ]; - - surfaceColors: Color[] = [ - { - name: 'Surface Dim', - background: '--mat-sys-surface-dim', - text: '--mat-sys-on-surface', - hideText: true, - }, - { - name: 'Surface Bright', - background: '--mat-sys-surface-bright', - text: '--mat-sys-on-surface', - hideText: true, - }, - { - name: 'Surface Container Lowest', - background: '--mat-sys-surface-container-lowest', - text: '--mat-sys-on-surface', - hideText: true, - }, - { - name: 'Surface Container Low', - background: '--mat-sys-surface-container-low', - text: '--mat-sys-on-surface', - hideText: true, - }, - { - name: 'Surface Container', - background: '--mat-sys-surface-container', - text: '--mat-sys-on-surface', - hideText: true, - }, - { - name: 'Surface Container High', - background: '--mat-sys-surface-container-high', - text: '--mat-sys-on-surface', - hideText: true, - }, - { - name: 'Surface Container Highest', - background: '--mat-sys-surface-container-highest', - text: '--mat-sys-on-surface', - hideText: true, - }, - ]; - - fixedColors: Color[] = [ - { - name: 'Primary Fixed', - background: '--mat-sys-primary-fixed', - text: '--mat-sys-on-primary-fixed', - }, - { - name: 'Primary Fixed Dim', - background: '--mat-sys-primary-fixed-dim', - text: '--mat-sys-on-primary-fixed', - }, - { - name: 'Secondary Fixed', - background: '--mat-sys-secondary-fixed', - text: '--mat-sys-on-secondary-fixed', - }, - { - name: 'Secondary Fixed Dim', - background: '--mat-sys-secondary-fixed-dim', - text: '--mat-sys-on-secondary-fixed', - }, - { - name: 'Tertiary Fixed', - background: '--mat-sys-tertiary-fixed', - text: '--mat-sys-on-tertiary-fixed', - }, - { - name: 'Tertiary Fixed Dim', - background: '--mat-sys-tertiary-fixed-dim', - text: '--mat-sys-on-tertiary-fixed', - }, - ]; -} diff --git a/docs/src/app/routes.ts b/docs/src/app/routes.ts index af7844550f20..fb28a39e315d 100644 --- a/docs/src/app/routes.ts +++ b/docs/src/app/routes.ts @@ -57,10 +57,10 @@ export const MATERIAL_DOCS_ROUTES: Routes = [ path: 'cdk/drag-drop/:tab', redirectTo: externalRedirect('https://angular.dev/guide/drag-drop'), }, + {path: 'guide/system-variables', redirectTo: '/guide/theming-your-components'}, // In v19, the theming system became based on system variables and the mat.theme mixin. // The following guides were consolidated into the main theming guide, which redirects // users to v18 docs if they are looking for this content. - {path: 'guide/theming-your-components', redirectTo: '/guide/theming'}, {path: 'guide/typography', redirectTo: '/guide/theming'}, {path: 'guide/customizing-component-styles', redirectTo: '/guide/theming'}, {path: 'guide/elevation', redirectTo: '/guide/theming'}, diff --git a/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.css b/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.css new file mode 100644 index 000000000000..024ce71a26d8 --- /dev/null +++ b/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.css @@ -0,0 +1,43 @@ +.docs-angular-aria-banner { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; + margin-bottom: 24px; + background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); + border-left: 4px solid var(--mat-sys-primary); + border-radius: 4px; + font-size: 14px; + line-height: 1.6; +} + +.docs-angular-aria-banner-icon { + font-size: 20px; + flex-shrink: 0; + fill: var(--mat-sys-primary); +} + +.docs-angular-aria-banner-content { + flex: 1; +} + +.docs-angular-aria-banner-content strong { + color: var(--mat-sys-primary); +} + +.docs-angular-aria-banner-content a { + color: var(--mat-sys-primary); + text-decoration: underline; + font-weight: 500; +} + +@media (max-width: 600px) { + .docs-angular-aria-banner { + padding: 12px; + gap: 8px; + } + + .docs-angular-aria-banner-icon { + font-size: 18px; + } +} diff --git a/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.html b/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.html new file mode 100644 index 000000000000..888cf5700406 --- /dev/null +++ b/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.html @@ -0,0 +1,16 @@ +
+ + + + +
+ Now Available in Angular Aria! +
+ The Angular team has introduced a new low-level component library called Angular Aria. + Consider using the + Angular Aria {{ componentName }} + component as an alternative to this CDK component. +
+
diff --git a/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.ts b/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.ts new file mode 100644 index 000000000000..cb4741730a83 --- /dev/null +++ b/docs/src/app/shared/doc-viewer/angular-aria-banner/angular-aria-banner.ts @@ -0,0 +1,36 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Component, Input} from '@angular/core'; + +/** + * Mapping of CDK component names to their Angular Aria documentation URLs. + */ +const ANGULAR_ARIA_LINKS: Record = { + 'listbox': 'https://angular.dev/guide/aria/listbox', + 'tree': 'https://angular.dev/guide/aria/tree', + 'accordion': 'https://angular.dev/guide/aria/accordion', + 'menu': 'https://angular.dev/guide/aria/menu', +}; + +/** + * Banner component that guides users to use the new Angular Aria components for CDK components + * that have equivalent Angular Aria components. + */ +@Component({ + selector: 'angular-aria-banner', + templateUrl: 'angular-aria-banner.html', + styleUrl: 'angular-aria-banner.css', +}) +export class AngularAriaBanner { + @Input() componentName: string = ''; + + get ariaLink(): string { + return ANGULAR_ARIA_LINKS[this.componentName.toLowerCase()] || ''; + } +} diff --git a/docs/src/app/shared/doc-viewer/doc-viewer.ts b/docs/src/app/shared/doc-viewer/doc-viewer.ts index 93eb369c1227..aba8ca165325 100644 --- a/docs/src/app/shared/doc-viewer/doc-viewer.ts +++ b/docs/src/app/shared/doc-viewer/doc-viewer.ts @@ -38,6 +38,7 @@ import {ExampleViewer} from '../example-viewer/example-viewer'; import {HeaderLink} from './header-link'; import {DeprecatedFieldComponent} from './deprecated-tooltip'; import {ModuleImportCopyButton} from './module-import-copy-button'; +import {AngularAriaBanner} from './angular-aria-banner/angular-aria-banner'; @Injectable({providedIn: 'root'}) class DocFetcher { @@ -154,6 +155,9 @@ export class DocViewer implements OnDestroy { this._loadComponents('material-docs-example', ExampleViewer); this._loadComponents('header-link', HeaderLink); + // Inject Angular Aria banner for specific CDK components + this._injectAngularAriaBanner(); + // Create tooltips for the deprecated fields this._createTooltipsForDeprecated(); @@ -267,4 +271,37 @@ export class DocViewer implements OnDestroy { this._portalHosts.push(elementPortalOutlet); }); } + + /** + * Injects the Angular Aria migration banner for specific CDK components. + */ + private _injectAngularAriaBanner() { + const componentName = this.name(); + const componentsWithAriaBanner = ['listbox', 'tree', 'accordion', 'menu']; + + if (!componentName || !componentsWithAriaBanner.includes(componentName.toLowerCase())) { + return; + } + + // Create a container div for the banner at the beginning of the document + const bannerContainer = document.createElement('div'); + bannerContainer.setAttribute('angular-aria-banner', ''); + bannerContainer.setAttribute('componentName', componentName); + + // Insert the banner at the beginning of the document content + const firstChild = this._elementRef.nativeElement.firstChild; + if (firstChild) { + this._elementRef.nativeElement.insertBefore(bannerContainer, firstChild); + } else { + this._elementRef.nativeElement.appendChild(bannerContainer); + } + + // Create and attach the banner component + const portalHost = new DomPortalOutlet(bannerContainer, this._appRef, this._injector); + const bannerPortal = new ComponentPortal(AngularAriaBanner, this._viewContainerRef); + const bannerComponent = portalHost.attach(bannerPortal); + bannerComponent.instance.componentName = componentName; + + this._portalHosts.push(portalHost); + } } diff --git a/docs/src/app/shared/documentation-items/documentation-items.spec.ts b/docs/src/app/shared/documentation-items/documentation-items.spec.ts index 5ea9d7ae11f5..2ef9aa419ed0 100644 --- a/docs/src/app/shared/documentation-items/documentation-items.spec.ts +++ b/docs/src/app/shared/documentation-items/documentation-items.spec.ts @@ -8,7 +8,6 @@ describe('DocViewer', () => { let docsItems: DocumentationItems; beforeEach(() => { - TestBed.configureTestingModule({}); docsItems = TestBed.inject(DocumentationItems); }); diff --git a/docs/src/app/shared/guide-items/guide-items.spec.ts b/docs/src/app/shared/guide-items/guide-items.spec.ts index 703c79165d52..cd899651364c 100644 --- a/docs/src/app/shared/guide-items/guide-items.spec.ts +++ b/docs/src/app/shared/guide-items/guide-items.spec.ts @@ -5,7 +5,6 @@ describe('GuideItems', () => { let guideItems: GuideItems; beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({}); guideItems = TestBed.inject(GuideItems); })); diff --git a/docs/src/app/shared/guide-items/guide-items.ts b/docs/src/app/shared/guide-items/guide-items.ts index feb5aac17361..0ef4cd047d5b 100644 --- a/docs/src/app/shared/guide-items/guide-items.ts +++ b/docs/src/app/shared/guide-items/guide-items.ts @@ -7,7 +7,6 @@ */ import {Injectable} from '@angular/core'; -import {SystemVariables} from '../../pages/system-variables'; import {ComponentType} from '@angular/cdk/portal'; export interface GuideItem { @@ -24,12 +23,6 @@ const GUIDES: GuideItem[] = [ document: '/docs-content/guides/getting-started.md.html', overview: 'Add Angular Material to your project!', }, - { - id: 'schematics', - name: 'Schematics', - document: '/docs-content/guides/schematics.md.html', - overview: 'Use schematics to quickly generate views with Material Design components.', - }, { id: 'theming', name: 'Theming Angular Material', @@ -37,10 +30,16 @@ const GUIDES: GuideItem[] = [ overview: "Customize your application with Angular Material's theming system.", }, { - id: 'system-variables', - name: 'System Variables', - document: SystemVariables, - overview: 'Understand the system variables available to use in your application.', + id: 'theming-your-components', + name: 'Theming your components', + document: '/docs-content/guides/theming-your-components.md.html', + overview: 'Applying Angular Material theming to your own components', + }, + { + id: 'schematics', + name: 'Schematics', + document: '/docs-content/guides/schematics.md.html', + overview: 'Use schematics to quickly generate views with Material Design components.', }, { id: 'creating-a-custom-form-field-control', diff --git a/docs/src/app/shared/stackblitz/stackblitz-writer.spec.ts b/docs/src/app/shared/stackblitz/stackblitz-writer.spec.ts index 772427a49b4f..51651b0a43dd 100644 --- a/docs/src/app/shared/stackblitz/stackblitz-writer.spec.ts +++ b/docs/src/app/shared/stackblitz/stackblitz-writer.spec.ts @@ -102,7 +102,6 @@ describe('StackBlitzWriter', () => { const expectedFiles = jasmine.objectContaining({ 'angular.json': 'fake', 'src/main.ts': `import {ExampleComponent} from './test';`, - 'src/test.ts': 'fake', 'src/index.html': ``, 'src/example/test.ts': `ExampleComponent diff --git a/docs/src/app/shared/stackblitz/stackblitz-writer.ts b/docs/src/app/shared/stackblitz/stackblitz-writer.ts index c62142999dd1..02e80c662cb4 100644 --- a/docs/src/app/shared/stackblitz/stackblitz-writer.ts +++ b/docs/src/app/shared/stackblitz/stackblitz-writer.ts @@ -40,7 +40,6 @@ const TEMPLATE_PATH = '/assets/stackblitz/'; */ export const TEMPLATE_FILES = [ 'angular.json', - 'karma.conf.js', 'package.json', 'package-lock.json', 'tsconfig.app.json', @@ -49,7 +48,6 @@ export const TEMPLATE_FILES = [ 'src/index.html', 'src/main.ts', 'src/styles.css', - 'src/test.ts', ]; /** @@ -215,8 +213,8 @@ export class StackBlitzWriter { // Replace `bootstrapApplication(MaterialDocsExample,` // will be replaced as `bootstrapApplication(ButtonDemo,` fileContent = fileContent.replace( - /bootstrapApplication\(MaterialDocsExample,/g, - `bootstrapApplication(${mainComponentName},`, + /bootstrapApplication\(MaterialDocsExample/g, + `bootstrapApplication(${mainComponentName}`, ); const dotIndex = data.indexFilename.lastIndexOf('.'); diff --git a/docs/src/assets/stackblitz/angular.json b/docs/src/assets/stackblitz/angular.json index 50b3897f59a6..22f238bc4efd 100644 --- a/docs/src/assets/stackblitz/angular.json +++ b/docs/src/assets/stackblitz/angular.json @@ -21,7 +21,7 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { "outputPath": "dist/example-app", "index": "src/index.html", @@ -65,7 +65,7 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "example-app:build:production" @@ -76,19 +76,11 @@ }, "defaultConfiguration": "development" }, - "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n", - "options": { - "buildTarget": "example-app:build" - } - }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular/build:karma", "options": { - "main": "src/test.ts", "polyfills": ["zone.js", "zone.js/testing"], "tsConfig": "tsconfig.spec.json", - "karmaConfig": "karma.conf.js", "inlineStyleLanguage": "css", "assets": ["src/assets"], "styles": ["src/styles.css", "@angular/material/prebuilt-themes/azure-blue.css"], diff --git a/docs/src/assets/stackblitz/karma.conf.js b/docs/src/assets/stackblitz/karma.conf.js deleted file mode 100644 index c82069f17d49..000000000000 --- a/docs/src/assets/stackblitz/karma.conf.js +++ /dev/null @@ -1,41 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function (config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage'), - require('@angular-devkit/build-angular/plugins/karma'), - ], - client: { - jasmine: { - // you can add configuration options for Jasmine here - // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html - // for example, you can disable the random execution with `random: false` - // or set a specific seed with `seed: 4321` - }, - clearContext: false, // leave Jasmine Spec Runner output visible in browser - }, - jasmineHtmlReporter: { - suppressAll: true, // removes the duplicated traces - }, - coverageReporter: { - dir: require('path').join(__dirname, './coverage/example-app'), - subdir: '.', - reporters: [{type: 'html'}, {type: 'text-summary'}], - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true, - }); -}; diff --git a/docs/src/assets/stackblitz/package-lock.json b/docs/src/assets/stackblitz/package-lock.json index aad077fc2f57..aafdfb45f15b 100644 --- a/docs/src/assets/stackblitz/package-lock.json +++ b/docs/src/assets/stackblitz/package-lock.json @@ -8,26 +8,25 @@ "name": "example-app", "version": "0.0.0", "dependencies": { - "@angular/cdk": "^20.2.0", - "@angular/common": "^20.2.0", - "@angular/compiler": "^20.2.0", - "@angular/core": "^20.2.0", - "@angular/forms": "^20.2.0", - "@angular/localize": "^20.2.0", - "@angular/material": "^20.2.0", - "@angular/material-luxon-adapter": "^20.2.0", - "@angular/platform-browser": "^20.2.0", - "@angular/platform-browser-dynamic": "^20.2.0", - "@angular/router": "^20.2.0", + "@angular/cdk": "^21.0.0", + "@angular/common": "^21.0.0", + "@angular/compiler": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/forms": "^21.0.0", + "@angular/localize": "^21.0.0", + "@angular/material": "^21.0.0", + "@angular/material-luxon-adapter": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/router": "^21.0.0", "luxon": "^3.7.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^20.2.0", - "@angular/cli": "^20.2.0", - "@angular/compiler-cli": "^20.2.0", + "@angular/build": "^21.0.0", + "@angular/cli": "^21.0.0", + "@angular/compiler-cli": "^21.0.0", "@types/jasmine": "~5.1.0", "@types/luxon": "^3.0.0", "@types/node": "^12.11.1", @@ -41,57 +40,57 @@ } }, "node_modules/@algolia/abtesting": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.1.0.tgz", - "integrity": "sha512-sEyWjw28a/9iluA37KLGu8vjxEIlb60uxznfTUmXImy7H5NvbpSO6yYgmgH5KiD7j+zTUUihiST0jEP12IoXow==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.6.1.tgz", + "integrity": "sha512-wV/gNRkzb7sI9vs1OneG129hwe3Q5zPj7zigz3Ps7M5Lpo2hSorrOnXNodHEOV+yXE/ks4Pd+G3CDFIjFTWhMQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-abtesting": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.35.0.tgz", - "integrity": "sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.40.1.tgz", + "integrity": "sha512-cxKNATPY5t+Mv8XAVTI57altkaPH+DZi4uMrnexPxPHODMljhGYY+GDZyHwv9a+8CbZHcY372OkxXrDMZA4Lnw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.35.0.tgz", - "integrity": "sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.40.1.tgz", + "integrity": "sha512-XP008aMffJCRGAY8/70t+hyEyvqqV7YKm502VPu0+Ji30oefrTn2al7LXkITz7CK6I4eYXWRhN6NaIUi65F1OA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.35.0.tgz", - "integrity": "sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.40.1.tgz", + "integrity": "sha512-gWfQuQUBtzUboJv/apVGZMoxSaB0M4Imwl1c9Ap+HpCW7V0KhjBddqF2QQt5tJZCOFsfNIgBbZDGsEPaeKUosw==", "dev": true, "license": "MIT", "engines": { @@ -99,151 +98,151 @@ } }, "node_modules/@algolia/client-insights": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.35.0.tgz", - "integrity": "sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.40.1.tgz", + "integrity": "sha512-RTLjST/t+lsLMouQ4zeLJq2Ss+UNkLGyNVu+yWHanx6kQ3LT5jv8UvPwyht9s7R6jCPnlSI77WnL80J32ZuyJg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.35.0.tgz", - "integrity": "sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.40.1.tgz", + "integrity": "sha512-2FEK6bUomBzEYkTKzD0iRs7Ljtjb45rKK/VSkyHqeJnG+77qx557IeSO0qVFE3SfzapNcoytTofnZum0BQ6r3Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.35.0.tgz", - "integrity": "sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.40.1.tgz", + "integrity": "sha512-Nju4NtxAvXjrV2hHZNLKVJLXjOlW6jAXHef/CwNzk1b2qIrCWDO589ELi5ZHH1uiWYoYyBXDQTtHmhaOVVoyXg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.35.0.tgz", - "integrity": "sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.40.1.tgz", + "integrity": "sha512-Mw6pAUF121MfngQtcUb5quZVqMC68pSYYjCRZkSITC085S3zdk+h/g7i6FxnVdbSU6OztxikSDMh1r7Z+4iPlA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/ingestion": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.35.0.tgz", - "integrity": "sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.40.1.tgz", + "integrity": "sha512-z+BPlhs45VURKJIxsR99NNBWpUEEqIgwt10v/fATlNxc4UlXvALdOsWzaFfe89/lbP5Bu4+mbO59nqBC87ZM/g==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.35.0.tgz", - "integrity": "sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.40.1.tgz", + "integrity": "sha512-VJMUMbO0wD8Rd2VVV/nlFtLJsOAQvjnVNGkMkspFiFhpBA7s/xJOb+fJvvqwKFUjbKTUA7DjiSi1ljSMYBasXg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.35.0.tgz", - "integrity": "sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.40.1.tgz", + "integrity": "sha512-ehvJLadKVwTp9Scg9NfzVSlBKH34KoWOQNTaN8i1Ac64AnO6iH2apJVSP6GOxssaghZ/s8mFQsDH3QIZoluFHA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.35.0.tgz", - "integrity": "sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.40.1.tgz", + "integrity": "sha512-PbidVsPurUSQIr6X9/7s34mgOMdJnn0i6p+N6Ab+lsNhY5eiu+S33kZEpZwkITYBCIbhzDLOvb7xZD3gDi+USA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0" + "@algolia/client-common": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.35.0.tgz", - "integrity": "sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.40.1.tgz", + "integrity": "sha512-ThZ5j6uOZCF11fMw9IBkhigjOYdXGXQpj6h4k+T9UkZrF2RlKcPynFzDeRgaLdpYk8Yn3/MnFbwUmib7yxj5Lw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0" + "@algolia/client-common": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.35.0.tgz", - "integrity": "sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.40.1.tgz", + "integrity": "sha512-H1gYPojO6krWHnUXu/T44DrEun/Wl95PJzMXRcM/szstNQczSbwq6wIFJPI9nyE95tarZfUNU3rgorT+wZ6iCQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0" + "@algolia/client-common": "5.40.1" }, "engines": { "node": ">= 14.0.0" @@ -253,6 +252,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -263,13 +263,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2003.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.4.tgz", - "integrity": "sha512-OKT6QuoG+rZZmRQ9mSZoh1pCGB5jBrkb45CzI8VdaLFhrZ7Uqg1wri1tj5KpSQYLAd22uUFNIE0uzMmGwXHbYA==", + "version": "0.2100.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2100.0.tgz", + "integrity": "sha512-BNt6Rw53WauCw31ku/r/ksVIY+Pi8XZptsSUIHiDUeqB2iZOWu4L3c5kuDGmoGkGByY588H48hfR2MgIpBhgAg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.4", + "@angular-devkit/core": "21.0.0", "rxjs": "7.8.2" }, "engines": { @@ -278,171 +278,86 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/build-angular": { - "version": "20.3.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.4.tgz", - "integrity": "sha512-Lc/Hi4A0xBDbPb0tg7JHofIkqTigwKFOTU4Y/y/sN/9BQ7z3M/cHylaFceCtTjwnqybOzWyR58bStLO1wIN8Gw==", + "node_modules/@angular-devkit/core": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.0.0.tgz", + "integrity": "sha512-d3n5GvrwqN1AUkWE3Wd8rrdY2u6/5bzorlZVT5W4CcH7ekAIoMu4SBTbSJ7bfRe/l2z/A1WZ6hFlnQzLclOjJA==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.4", - "@angular-devkit/build-webpack": "0.2003.4", - "@angular-devkit/core": "20.3.4", - "@angular/build": "20.3.4", - "@babel/core": "7.28.3", - "@babel/generator": "7.28.3", - "@babel/helper-annotate-as-pure": "7.27.3", - "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-transform-async-generator-functions": "7.28.0", - "@babel/plugin-transform-async-to-generator": "7.27.1", - "@babel/plugin-transform-runtime": "7.28.3", - "@babel/preset-env": "7.28.3", - "@babel/runtime": "7.28.3", - "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "20.3.4", - "ansi-colors": "4.1.3", - "autoprefixer": "10.4.21", - "babel-loader": "10.0.0", - "browserslist": "^4.21.5", - "copy-webpack-plugin": "13.0.1", - "css-loader": "7.1.2", - "esbuild-wasm": "0.25.9", - "fast-glob": "3.3.3", - "http-proxy-middleware": "3.0.5", - "istanbul-lib-instrument": "6.0.3", + "ajv": "8.17.1", + "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", - "karma-source-map-support": "1.4.0", - "less": "4.4.0", - "less-loader": "12.3.0", - "license-webpack-plugin": "4.0.2", - "loader-utils": "3.3.1", - "mini-css-extract-plugin": "2.9.4", - "open": "10.2.0", - "ora": "8.2.0", "picomatch": "4.0.3", - "piscina": "5.1.3", - "postcss": "8.5.6", - "postcss-loader": "8.1.1", - "resolve-url-loader": "5.0.0", "rxjs": "7.8.2", - "sass": "1.90.0", - "sass-loader": "16.0.5", - "semver": "7.7.2", - "source-map-loader": "5.0.0", - "source-map-support": "0.5.21", - "terser": "5.43.1", - "tree-kill": "1.2.2", - "tslib": "2.8.1", - "webpack": "5.101.2", - "webpack-dev-middleware": "7.4.2", - "webpack-dev-server": "5.2.2", - "webpack-merge": "6.0.1", - "webpack-subresource-integrity": "5.1.0" + "source-map": "0.7.6" }, "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, - "optionalDependencies": { - "esbuild": "0.25.9" - }, "peerDependencies": { - "@angular/compiler-cli": "^20.0.0", - "@angular/core": "^20.0.0", - "@angular/localize": "^20.0.0", - "@angular/platform-browser": "^20.0.0", - "@angular/platform-server": "^20.0.0", - "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.4", - "@web/test-runner": "^0.20.0", - "browser-sync": "^3.0.2", - "jest": "^29.5.0", - "jest-environment-jsdom": "^29.5.0", - "karma": "^6.3.0", - "ng-packagr": "^20.0.0", - "protractor": "^7.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "typescript": ">=5.8 <6.0" + "chokidar": "^4.0.0" }, "peerDependenciesMeta": { - "@angular/core": { - "optional": true - }, - "@angular/localize": { - "optional": true - }, - "@angular/platform-browser": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "@angular/ssr": { - "optional": true - }, - "@web/test-runner": { - "optional": true - }, - "browser-sync": { - "optional": true - }, - "jest": { - "optional": true - }, - "jest-environment-jsdom": { - "optional": true - }, - "karma": { - "optional": true - }, - "ng-packagr": { - "optional": true - }, - "protractor": { - "optional": true - }, - "tailwindcss": { + "chokidar": { "optional": true } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular/build": { - "version": "20.3.4", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.4.tgz", - "integrity": "sha512-2ZPyEQxg9FCM7gv5NZHZVCec6ubb7HM/fkzr7wcYQArN8E8EknlyfXn6G5AoqkEsQZGqtJOD8sfti0m4kYiwkQ==", + "node_modules/@angular-devkit/schematics": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.0.0.tgz", + "integrity": "sha512-8zwXp8OTzJO3IY3Ge3lLqXokNAtQy6kM1FeTyPT20M+0AQHTX9WJlGaYEWdLYI9WwNPWy1/Iq6AaZNcR5phPpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "21.0.0", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.19", + "ora": "9.0.0", + "rxjs": "7.8.2" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/build": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.0.0.tgz", + "integrity": "sha512-TobXT9fXZVee1yULlcOVowOurCUoJlku8st5vzkRZekP520qRjBSEbIk8V2emkFbzgzOeJUtXv1pvrBY7yAYhQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.4", - "@babel/core": "7.28.3", + "@angular-devkit/architect": "0.2100.0", + "@babel/core": "7.28.4", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", - "@inquirer/confirm": "5.1.14", + "@inquirer/confirm": "5.1.19", "@vitejs/plugin-basic-ssl": "2.1.0", "beasties": "0.3.5", - "browserslist": "^4.23.0", - "esbuild": "0.25.9", + "browserslist": "^4.26.0", + "esbuild": "0.26.0", "https-proxy-agent": "7.0.6", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", - "listr2": "9.0.1", - "magic-string": "0.30.17", + "listr2": "9.0.5", + "magic-string": "0.30.19", "mrmime": "2.0.1", "parse5-html-rewriting-stream": "8.0.0", "picomatch": "4.0.3", "piscina": "5.1.3", - "rollup": "4.52.3", - "sass": "1.90.0", - "semver": "7.7.2", + "rolldown": "1.0.0-beta.47", + "sass": "1.93.2", + "semver": "7.7.3", "source-map-support": "0.5.21", - "tinyglobby": "0.2.14", - "vite": "7.1.5", + "tinyglobby": "0.2.15", + "undici": "7.16.0", + "vite": "7.2.2", "watchpack": "2.4.4" }, "engines": { @@ -451,25 +366,25 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "lmdb": "3.4.2" + "lmdb": "3.4.3" }, "peerDependencies": { - "@angular/compiler": "^20.0.0", - "@angular/compiler-cli": "^20.0.0", - "@angular/core": "^20.0.0", - "@angular/localize": "^20.0.0", - "@angular/platform-browser": "^20.0.0", - "@angular/platform-server": "^20.0.0", - "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.4", + "@angular/compiler": "^21.0.0", + "@angular/compiler-cli": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/localize": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/platform-server": "^21.0.0", + "@angular/service-worker": "^21.0.0", + "@angular/ssr": "^21.0.0", "karma": "^6.4.0", "less": "^4.2.0", - "ng-packagr": "^20.0.0", + "ng-packagr": "^21.0.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "tslib": "^2.3.0", - "typescript": ">=5.8 <6.0", - "vitest": "^3.1.1" + "typescript": ">=5.9 <6.0", + "vitest": "^4.0.8" }, "peerDependenciesMeta": { "@angular/core": { @@ -510,638 +425,457 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", - "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", + "node_modules/@angular/build/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular/build/node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "node_modules/@angular/build/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular/build/node_modules/vite": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.5.tgz", - "integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", + "node_modules/@angular/build/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@angular/build/node_modules/vite/node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "node_modules/@angular/build/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@inquirer/confirm": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", - "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "node_modules/@angular/build/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@inquirer/core": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.2.2.tgz", - "integrity": "sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==", + "node_modules/@angular/build/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.0", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@inquirer/type": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", - "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "node_modules/@angular/build/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular-devkit/build-angular/node_modules/@types/node": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", + "node_modules/@angular/build/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~7.14.0" + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@angular/build/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@angular/build/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/@angular/build/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/@angular/build/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/@angular/build/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-angular/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@angular/build/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.2003.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.4.tgz", - "integrity": "sha512-p5nBSTu9ijz+v3ku+eWc8m+nYjn8q36dkupoKUCgaG1UkPTeHvgI341xPqvY1uxyYzCaxuUms44XIkdAJbmzww==", + "node_modules/@angular/build/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.2003.4", - "rxjs": "7.8.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^5.0.2" + "node": ">=18" } }, - "node_modules/@angular-devkit/core": { - "version": "20.3.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.4.tgz", - "integrity": "sha512-r83jn9yVdPh618oGgoKPggMsQGOkQqJbxEutd4CE9mnotPCE2uRTIyaFMh8sohNUeoQNRmj9rbr2pWGVlgERpg==", + "node_modules/@angular/build/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "ajv": "8.17.1", - "ajv-formats": "3.0.1", - "jsonc-parser": "3.3.1", - "picomatch": "4.0.3", - "rxjs": "7.8.2", - "source-map": "0.7.6" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^4.0.0" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@angular-devkit/schematics": { - "version": "20.3.4", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.4.tgz", - "integrity": "sha512-JYlcmVBKNT9+cQ6T2tmu+yVQ2bJk8tG0mXvPHWXrl/M4c6NObhSSThK50tJHy0Xo3gl8WgogOxUeJNnBq67cIQ==", + "node_modules/@angular/build/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@angular-devkit/core": "20.3.4", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.17", - "ora": "8.2.0", - "rxjs": "7.8.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=18" } }, - "node_modules/@angular/cdk": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.7.tgz", - "integrity": "sha512-QTqxPJSMXyjaswtpUrziwdoKRhqT2P9/Ascwzjg8T/SofV1850pc3YmonoOFrurYrmd4plZzWdr7raGcBWIh/Q==", + "node_modules/@angular/build/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "parse5": "^8.0.0", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": "^20.0.0 || ^21.0.0", - "@angular/core": "^20.0.0 || ^21.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@angular/cli": { - "version": "20.3.4", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.4.tgz", - "integrity": "sha512-aNPk9ljY2PTc+ZoA7M67rxNvhbUJvzkuYcAdJuHbAIDoWcr2HhyFM2aGrLH3DVemY39xN/haJHj3FYJ8rCdXNw==", + "node_modules/@angular/build/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.2003.4", - "@angular-devkit/core": "20.3.4", - "@angular-devkit/schematics": "20.3.4", - "@inquirer/prompts": "7.8.2", - "@listr2/prompt-adapter-inquirer": "3.0.1", - "@modelcontextprotocol/sdk": "1.17.3", - "@schematics/angular": "20.3.4", - "@yarnpkg/lockfile": "1.1.0", - "algoliasearch": "5.35.0", - "ini": "5.0.0", - "jsonc-parser": "3.3.1", - "listr2": "9.0.1", - "npm-package-arg": "13.0.0", - "pacote": "21.0.0", - "resolve": "1.22.10", - "semver": "7.7.2", - "yargs": "18.0.0", - "zod": "3.25.76" - }, - "bin": { - "ng": "bin/ng.js" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" + "node": ">=18" } }, - "node_modules/@angular/cli/node_modules/@inquirer/checkbox": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.2.4.tgz", - "integrity": "sha512-2n9Vgf4HSciFq8ttKXk+qy+GsyTXPV1An6QAwe/8bkbbqvG4VW1I/ZY1pNu2rf+h9bdzMLPbRSfcNxkHBy/Ydw==", + "node_modules/@angular/build/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.0", - "@inquirer/core": "^10.2.2", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "yoctocolors-cjs": "^2.1.2" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular/cli/node_modules/@inquirer/confirm": { - "version": "5.1.18", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.18.tgz", - "integrity": "sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==", + "node_modules/@angular/build/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/type": "^3.0.8" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular/cli/node_modules/@inquirer/core": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.2.2.tgz", - "integrity": "sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==", + "node_modules/@angular/build/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.0", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular/cli/node_modules/@inquirer/editor": { - "version": "4.2.20", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.20.tgz", - "integrity": "sha512-7omh5y5bK672Q+Brk4HBbnHNowOZwrb/78IFXdrEB9PfdxL3GudQyDk8O9vQ188wj3xrEebS2M9n18BjJoI83g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/external-editor": "^1.0.2", - "@inquirer/type": "^3.0.8" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/@inquirer/expand": { - "version": "4.0.20", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.20.tgz", - "integrity": "sha512-Dt9S+6qUg94fEvgn54F2Syf0Z3U8xmnBI9ATq2f5h9xt09fs2IJXSCIXyyVHwvggKWFXEY/7jATRo2K6Dkn6Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/type": "^3.0.8", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/@inquirer/external-editor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz", - "integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==", + "node_modules/@angular/build/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "chardet": "^2.1.0", - "iconv-lite": "^0.7.0" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular/cli/node_modules/@inquirer/input": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.2.4.tgz", - "integrity": "sha512-cwSGpLBMwpwcZZsc6s1gThm0J+it/KIJ+1qFL2euLmSKUMGumJ5TcbMgxEjMjNHRGadouIYbiIgruKoDZk7klw==", + "node_modules/@angular/build/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/type": "^3.0.8" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular/cli/node_modules/@inquirer/number": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.20.tgz", - "integrity": "sha512-bbooay64VD1Z6uMfNehED2A2YOPHSJnQLs9/4WNiV/EK+vXczf/R988itL2XLDGTgmhMF2KkiWZo+iEZmc4jqg==", + "node_modules/@angular/build/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/type": "^3.0.8" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular/cli/node_modules/@inquirer/password": { - "version": "4.0.20", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.20.tgz", - "integrity": "sha512-nxSaPV2cPvvoOmRygQR+h0B+Av73B01cqYLcr7NXcGXhbmsYfUb8fDdw2Us1bI2YsX+VvY7I7upgFYsyf8+Nug==", + "node_modules/@angular/build/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.0", - "@inquirer/core": "^10.2.2", - "@inquirer/type": "^3.0.8" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } } }, - "node_modules/@angular/cli/node_modules/@inquirer/prompts": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.2.tgz", - "integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==", + "node_modules/@angular/build/node_modules/@inquirer/confirm": { + "version": "5.1.19", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.19.tgz", + "integrity": "sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.2.1", - "@inquirer/confirm": "^5.1.14", - "@inquirer/editor": "^4.2.17", - "@inquirer/expand": "^4.0.17", - "@inquirer/input": "^4.2.1", - "@inquirer/number": "^3.0.17", - "@inquirer/password": "^4.0.17", - "@inquirer/rawlist": "^4.1.5", - "@inquirer/search": "^3.1.0", - "@inquirer/select": "^4.3.1" + "@inquirer/core": "^10.3.0", + "@inquirer/type": "^3.0.9" }, "engines": { "node": ">=18" @@ -1155,16 +889,21 @@ } } }, - "node_modules/@angular/cli/node_modules/@inquirer/rawlist": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.8.tgz", - "integrity": "sha512-CQ2VkIASbgI2PxdzlkeeieLRmniaUU1Aoi5ggEdm6BIyqopE9GuDXdDOj9XiwOqK5qm72oI2i6J+Gnjaa26ejg==", + "node_modules/@angular/build/node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/type": "^3.0.8", - "yoctocolors-cjs": "^2.1.2" + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" }, "engines": { "node": ">=18" @@ -1178,18 +917,12 @@ } } }, - "node_modules/@angular/cli/node_modules/@inquirer/search": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.1.3.tgz", - "integrity": "sha512-D5T6ioybJJH0IiSUK/JXcoRrrm8sXwzrVMjibuPs+AgxmogKslaafy1oxFiorNI4s3ElSkeQZbhYQgLqiL8h6Q==", + "node_modules/@angular/build/node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.2.2", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "yoctocolors-cjs": "^2.1.2" - }, "engines": { "node": ">=18" }, @@ -1202,79 +935,32 @@ } } }, - "node_modules/@angular/cli/node_modules/@inquirer/select": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.3.4.tgz", - "integrity": "sha512-Qp20nySRmfbuJBBsgPU7E/cL62Hf250vMZRzYDcBHty2zdD1kKCnoDFWRr0WO2ZzaXp3R7a4esaVGJUx0E6zvA==", + "node_modules/@angular/build/node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@inquirer/ansi": "^1.0.0", - "@inquirer/core": "^10.2.2", - "@inquirer/figures": "^1.0.13", - "@inquirer/type": "^3.0.8", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@angular/cli/node_modules/@inquirer/type": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", - "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } + "undici-types": "~7.16.0" } }, - "node_modules/@angular/cli/node_modules/@listr2/prompt-adapter-inquirer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz", - "integrity": "sha512-3XFmGwm3u6ioREG+ynAQB7FoxfajgQnMhIu8wC5eo/Lsih4aKDg0VuIMGaOsYn7hJSJagSeaD4K8yfpkEoDEmA==", + "node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.1.0.tgz", + "integrity": "sha512-dOxxrhgyDIEUADhb/8OlV9JIqYLgos03YorAueTIeOUskLJSEsfwCByjbu98ctXitUN3znXKp0bYD/WHSudCeA==", "dev": true, "license": "MIT", - "dependencies": { - "@inquirer/type": "^3.0.7" - }, "engines": { - "node": ">=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "peerDependencies": { - "@inquirer/prompts": ">= 3 < 8", - "listr2": "9.0.1" - } - }, - "node_modules/@angular/cli/node_modules/@types/node": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~7.14.0" + "vite": "^6.0.0 || ^7.0.0" } }, - "node_modules/@angular/cli/node_modules/ansi-regex": { + "node_modules/@angular/build/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", @@ -1284,7 +970,7 @@ "node": ">=8" } }, - "node_modules/@angular/cli/node_modules/ansi-styles": { + "node_modules/@angular/build/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -1300,31 +986,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@angular/cli/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/cli/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@angular/cli/node_modules/is-fullwidth-code-point": { + "node_modules/@angular/build/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", @@ -1334,7 +996,7 @@ "node": ">=8" } }, - "node_modules/@angular/cli/node_modules/string-width": { + "node_modules/@angular/build/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -1349,7 +1011,7 @@ "node": ">=8" } }, - "node_modules/@angular/cli/node_modules/strip-ansi": { + "node_modules/@angular/build/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -1362,1884 +1024,1775 @@ "node": ">=8" } }, - "node_modules/@angular/cli/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/@angular/build/node_modules/vite": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", + "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@angular/common": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.3.tgz", - "integrity": "sha512-iArFCXvgYJCpxLZv8o6rV7Cxuqv1hbndoeUmQgL7ekXwVS6BA49VErXbTPM+pfhAJ+v1fc/DG3rzBwXk3eW2lw==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, - "peerDependencies": { - "@angular/core": "20.3.3", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, - "node_modules/@angular/compiler": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.3.tgz", - "integrity": "sha512-7AUtF7PO8xo+jOgrhLRPXmt65M/KFuYIsVZGVLB1FTCUAPByFJEUYOSnUuHyvFQQqHesK4aYSP27slDpHH/PSA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@angular/compiler-cli": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.3.tgz", - "integrity": "sha512-kSIE6hkTiZGiJLyisp5Q6NXOHiDNOItp7N2HVNPrK1bqzM8foN6H6BE1a+LYO3Lwy3PkwQFzx03BnzxkM4sWng==", - "license": "MIT", - "dependencies": { - "@babel/core": "7.28.3", - "@jridgewell/sourcemap-codec": "^1.4.14", - "chokidar": "^4.0.0", - "convert-source-map": "^1.5.1", - "reflect-metadata": "^0.2.0", - "semver": "^7.0.0", - "tslib": "^2.3.0", - "yargs": "^18.0.0" + "node": "^20.19.0 || >=22.12.0" }, - "bin": { - "ng-xi18n": "bundles/src/bin/ng_xi18n.js", - "ngc": "bundles/src/bin/ngc.js" + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "@angular/compiler": "20.3.3", - "typescript": ">=5.8 <6.0" + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { - "typescript": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } }, - "node_modules/@angular/core": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.3.tgz", - "integrity": "sha512-AWBCixxw4N9VgKT1uwrRPr1dH3CpT/ffcCsXJQ8TjzsKYjVBkXVht5OjtxJOWOQ2KaHwsGFEmDMv9fc1BHDFhQ==", + "node_modules/@angular/build/node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "tslib": "^2.3.0" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" }, - "peerDependencies": { - "@angular/compiler": "20.3.3", - "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.15.0" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/@angular/build/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, - "peerDependenciesMeta": { - "@angular/compiler": { - "optional": true - }, - "zone.js": { - "optional": true - } + "engines": { + "node": ">=8" } }, - "node_modules/@angular/forms": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.3.tgz", - "integrity": "sha512-Rv3sO1vOAbw03IRK30CB45eucxZ1rI0Jyaa6QVmDlOzQ4bktkanbGxQtaxBdc9bKPBO1SVx27eTbStR7i3BNRg==", + "node_modules/@angular/cdk": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.0.0.tgz", + "integrity": "sha512-wCr5D3mEC+p69IMDC7vf8bWx18mfUNNRdsiK3XD0m1PqfeNfnCJb+Bnkks37MC/SU01uCNrAokRaTbWL6pk1Wg==", "license": "MIT", "dependencies": { + "parse5": "^8.0.0", "tslib": "^2.3.0" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, "peerDependencies": { - "@angular/common": "20.3.3", - "@angular/core": "20.3.3", - "@angular/platform-browser": "20.3.3", + "@angular/common": "^21.0.0 || ^22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@angular/localize": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-20.3.3.tgz", - "integrity": "sha512-myToeQiFPzOxXu5CBoQPnMPCpM4HtQh9m+tiL5RsMtZs5NrD3DX9QxzVJl2Y4nozpphfQejoI1t2fbS7yM5qAQ==", + "node_modules/@angular/cli": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.0.0.tgz", + "integrity": "sha512-713DfTD/ThIy/BOmZ+8zhXo/OhPE9jYaAS0UhXVhtp2ptqzRqSzLvW9fWgtqP4ITAqulOoitiWPLXxOEQ2Cixw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "7.28.3", - "@types/babel__core": "7.20.5", - "tinyglobby": "^0.2.12", - "yargs": "^18.0.0" + "@angular-devkit/architect": "0.2100.0", + "@angular-devkit/core": "21.0.0", + "@angular-devkit/schematics": "21.0.0", + "@inquirer/prompts": "7.9.0", + "@listr2/prompt-adapter-inquirer": "3.0.5", + "@modelcontextprotocol/sdk": "1.20.1", + "@schematics/angular": "21.0.0", + "@yarnpkg/lockfile": "1.1.0", + "algoliasearch": "5.40.1", + "ini": "5.0.0", + "jsonc-parser": "3.3.1", + "listr2": "9.0.5", + "npm-package-arg": "13.0.1", + "pacote": "21.0.3", + "parse5-html-rewriting-stream": "8.0.0", + "resolve": "1.22.11", + "semver": "7.7.3", + "yargs": "18.0.0", + "zod": "3.25.76" }, "bin": { - "localize-extract": "tools/bundles/src/extract/cli.js", - "localize-migrate": "tools/bundles/src/migrate/cli.js", - "localize-translate": "tools/bundles/src/translate/cli.js" + "ng": "bin/ng.js" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@angular/compiler": "20.3.3", - "@angular/compiler-cli": "20.3.3" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@angular/material": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-20.2.7.tgz", - "integrity": "sha512-VXsP5qkQQ3sCGkSHsgDku/OVlunGsqssOM057foOKJuajECsI3ZpGuLJ13nvLm9Z147UZOZfP463ixZIjd4XuQ==", + "node_modules/@angular/cli/node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "@angular/cdk": "20.2.7", - "@angular/common": "^20.0.0 || ^21.0.0", - "@angular/core": "^20.0.0 || ^21.0.0", - "@angular/forms": "^20.0.0 || ^21.0.0", - "@angular/platform-browser": "^20.0.0 || ^21.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@angular/material-luxon-adapter": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/@angular/material-luxon-adapter/-/material-luxon-adapter-20.2.7.tgz", - "integrity": "sha512-6L6NU1q7OWzxQ2uLQtNAGHOp4M1p/lAB2UKc5Z/F3myWx6njJMgAtxuuq7xvaCY2TyDUP7n5bFAPmGB9fmiyZQ==", + "node_modules/@angular/cli/node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" }, "peerDependencies": { - "@angular/core": "^20.0.0 || ^21.0.0", - "@angular/material": "20.2.7", - "luxon": "^3.0.0" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@angular/platform-browser": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.3.tgz", - "integrity": "sha512-RUWpg49GnXdINjomRFrE/SRioxEehYqUzDVskDWddNeNhV9Z21zeC6Ao2i5q8UKq0y/oq2ShX7XFLprxqLoLnQ==", + "node_modules/@angular/cli/node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" }, "peerDependencies": { - "@angular/animations": "20.3.3", - "@angular/common": "20.3.3", - "@angular/core": "20.3.3" + "@types/node": ">=18" }, "peerDependenciesMeta": { - "@angular/animations": { + "@types/node": { "optional": true } } }, - "node_modules/@angular/platform-browser-dynamic": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.3.tgz", - "integrity": "sha512-QzJUeHgzUWpNrPF4tgqwO3jzmDGlM8aLwbA7BJBp2BOJZKg34BF4CzTngK5Z6LWcZ4gofTSzDOw9TIrk8U8g6Q==", + "node_modules/@angular/cli/node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" }, "peerDependencies": { - "@angular/common": "20.3.3", - "@angular/compiler": "20.3.3", - "@angular/core": "20.3.3", - "@angular/platform-browser": "20.3.3" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@angular/router": { - "version": "20.3.3", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.3.tgz", - "integrity": "sha512-IrO5GY/vmaWwNdfR51xswNnBSxeEuvQAUqK3H0UNxhZlIE9gUS6pbbSidGGrQOZK+i0nd/rDz7j+RV7h2NK9aA==", + "node_modules/@angular/cli/node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.3.0" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">=18" }, "peerDependencies": { - "@angular/common": "20.3.3", - "@angular/core": "20.3.3", - "@angular/platform-browser": "20.3.3", - "rxjs": "^6.5.3 || ^7.4.0" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "node_modules/@angular/cli/node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "node_modules/@angular/cli/node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, "engines": { - "node": ">=6.9.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", + "node_modules/@angular/cli/node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "node_modules/@angular/cli/node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "node_modules/@angular/cli/node_modules/@inquirer/prompts": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.9.0.tgz", + "integrity": "sha512-X7/+dG9SLpSzRkwgG5/xiIzW0oMrV3C0HOa7YHG1WnrLK+vCQHfte4k/T80059YBdei29RBC3s+pSMvPJDU9/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@inquirer/checkbox": "^4.3.0", + "@inquirer/confirm": "^5.1.19", + "@inquirer/editor": "^4.2.21", + "@inquirer/expand": "^4.0.21", + "@inquirer/input": "^4.2.5", + "@inquirer/number": "^3.0.21", + "@inquirer/password": "^4.0.21", + "@inquirer/rawlist": "^4.1.9", + "@inquirer/search": "^3.2.0", + "@inquirer/select": "^4.4.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "node_modules/@angular/cli/node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "node_modules/@angular/cli/node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", - "semver": "^6.3.1" + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" }, "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "node_modules/@angular/cli/node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" }, "engines": { - "node": ">=6.9.0" + "node": ">=18" }, "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "node_modules/@angular/cli/node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" + "engines": { + "node": ">=18" }, "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "node_modules/@angular/cli/node_modules/@listr2/prompt-adapter-inquirer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz", + "integrity": "sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==", + "dev": true, "license": "MIT", + "dependencies": { + "@inquirer/type": "^3.0.8" + }, "engines": { - "node": ">=6.9.0" + "node": ">=20.0.0" + }, + "peerDependencies": { + "@inquirer/prompts": ">= 3 < 8", + "listr2": "9.0.5" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "node_modules/@angular/cli/node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "undici-types": "~7.16.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "node_modules/@angular/cli/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "node_modules/@angular/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=8" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "node_modules/@angular/cli/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "node_modules/@angular/cli/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "node_modules/@angular/cli/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=8" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "node_modules/@angular/cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=8" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "node_modules/@angular/cli/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, + "node_modules/@angular/common": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.0.0.tgz", + "integrity": "sha512-uFvQDYU5X5nEnI9C4Bkdxcu4aIzNesGLJzmFlnwChVxB4BxIRF0uHL0oRhdkInGTIzPDJPH4nF6B/22c5gDVqA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.24.7" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "21.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@angular/compiler": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.0.0.tgz", + "integrity": "sha512-6jCH3UYga5iokj5F40SR4dlwo9ZRMkT8YzHCTijwZuDX9zvugp9jPof092RvIeNsTvCMVfGWuM9yZ1DRUsU/yg==", "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@angular/compiler-cli": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.0.0.tgz", + "integrity": "sha512-KTXp+e2UPGyfFew6Wq95ULpHWQ20dhqkAMZ6x6MCYfOe2ccdnGYsAbLLmnWGmSg5BaOI4B0x/1XCFZf/n6WDgA==", "license": "MIT", + "dependencies": { + "@babel/core": "7.28.4", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^4.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^18.0.0" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js" + }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "21.0.0", + "typescript": ">=5.9 <6.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", - "dev": true, + "node_modules/@angular/core": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.0.0.tgz", + "integrity": "sha512-bqi8fT4csyITeX8vdN5FJDBWx5wuWzdCg4mKSjHd+onVzZLyZ8bcnuAKz4mklgvjvwuXoRYukmclUurLwfq3Rg==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "21.0.0", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } } }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "node_modules/@angular/forms": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.0.0.tgz", + "integrity": "sha512-kcudwbZs/ddKqaELz4eEW9kOGCsX61qsf9jkQsGTARBEOUcU2K+rM6mX5sTf9azHvQ9wlX4N36h0eYzBA4Y4Qg==", "license": "MIT", "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "21.0.0", + "@angular/core": "21.0.0", + "@angular/platform-browser": "21.0.0", + "@standard-schema/spec": "^1.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "node_modules/@angular/localize": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-21.0.0.tgz", + "integrity": "sha512-SHK/D6nYkbn3VrM7sZtipiayICc8S6IZyjd4/5ARLeZJ/giYAxqv++bV0EV1MEayAZi4g6t0qsUY4KolDClphQ==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/core": "7.28.4", + "@types/babel__core": "7.20.5", + "tinyglobby": "^0.2.12", + "yargs": "^18.0.0" }, "bin": { - "parser": "bin/babel-parser.js" + "localize-extract": "tools/bundles/src/extract/cli.js", + "localize-migrate": "tools/bundles/src/migrate/cli.js", + "localize-translate": "tools/bundles/src/translate/cli.js" }, "engines": { - "node": ">=6.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "21.0.0", + "@angular/compiler-cli": "21.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", - "dev": true, + "node_modules/@angular/material": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-21.0.0.tgz", + "integrity": "sha512-s3+fhN7F5T1TAltZXYXOgY1wuVbICCrBJpV2TN8nJXDT0wroTYAljgBmsr6ZjDwYJewwP0OPvcj2NlOGDpa6oA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.3.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@angular/cdk": "21.0.0", + "@angular/common": "^21.0.0 || ^22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", + "@angular/forms": "^21.0.0 || ^22.0.0", + "@angular/platform-browser": "^21.0.0 || ^22.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "dev": true, + "node_modules/@angular/material-luxon-adapter": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/material-luxon-adapter/-/material-luxon-adapter-21.0.0.tgz", + "integrity": "sha512-83oof7/D2XFsgscrTvVhDNnUKqECTNDVpPFXcbLXwNTQZ+ZUXvpPcVpon99UJMj3+nZlwWHyOQsycxBMO2WGTw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "tslib": "^2.3.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@angular/core": "^21.0.0 || ^22.0.0", + "@angular/material": "21.0.0", + "luxon": "^3.7.2" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "dev": true, + "node_modules/@angular/platform-browser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.0.0.tgz", + "integrity": "sha512-KQrANla4RBLhcGkwlndqsKzBwVFOWQr1640CfBVjj2oz4M3dW5hyMtXivBACvuwyUhYU/qJbqlDMBXl/OUSudQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@angular/animations": "21.0.0", + "@angular/common": "21.0.0", + "@angular/core": "21.0.0" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "dev": true, + "node_modules/@angular/router": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.0.0.tgz", + "integrity": "sha512-ARx1R2CmTgAezlMkUpV40V4T/IbXhL7dm4SuMVKbuEOsCKZC0TLOSSTsGYY7HKem45JHlJaByv819cJnabFgBg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" + "tslib": "^2.3.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@angular/common": "21.0.0", + "@angular/core": "21.0.0", + "@angular/platform-browser": "21.0.0", + "rxjs": "^6.5.3 || ^7.4.0" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", - "dev": true, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "license": "MIT", "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", - "dev": true, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "dev": true, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { + "node_modules/@babel/helper-module-imports": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "dev": true, + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "dev": true, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", - "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-properties": { + "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", - "dev": true, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", - "dev": true, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" - }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", - "dev": true, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", - "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", - "dev": true, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/types": "^7.28.5" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "parser": "bin/babel-parser.js" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "dev": true, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "dev": true, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "dev": true, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "dev": true, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.1.90" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "node_modules/@emnapi/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.26.0.tgz", + "integrity": "sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "node_modules/@esbuild/android-arm": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.26.0.tgz", + "integrity": "sha512-C0hkDsYNHZkBtPxxDx177JN90/1MiCpvBNjz1f5yWJo1+5+c5zr8apjastpEG+wtPjo9FFtGG7owSsAxyKiHxA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "node_modules/@esbuild/android-arm64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.26.0.tgz", + "integrity": "sha512-DDnoJ5eoa13L8zPh87PUlRd/IyFaIKOlRbxiwcSbeumcJ7UZKdtuMCHa1Q27LWQggug6W4m28i4/O2qiQQ5NZQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "node_modules/@esbuild/android-x64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.26.0.tgz", + "integrity": "sha512-bKDkGXGZnj0T70cRpgmv549x38Vr2O3UWLbjT2qmIkdIWcmlg8yebcFWoT9Dku7b5OV3UqPEuNKRzlNhjwUJ9A==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.26.0.tgz", + "integrity": "sha512-6Z3naJgOuAIB0RLlJkYc81An3rTlQ/IeRdrU3dOea8h/PvZSgitZV+thNuIccw0MuK1GmIAnAmd5TrMZad8FTQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.26.0.tgz", + "integrity": "sha512-OPnYj0zpYW0tHusMefyaMvNYQX5pNQuSsHFTHUBNp3vVXupwqpxofcjVsUx11CQhGVkGeXjC3WLjh91hgBG2xw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.26.0.tgz", + "integrity": "sha512-jix2fa6GQeZhO1sCKNaNMjfj5hbOvoL2F5t+w6gEPxALumkpOV/wq7oUBMHBn2hY2dOm+mEV/K+xfZy3mrsxNQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.26.0.tgz", + "integrity": "sha512-tccJaH5xHJD/239LjbVvJwf6T4kSzbk6wPFerF0uwWlkw/u7HL+wnAzAH5GB2irGhYemDgiNTp8wJzhAHQ64oA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "node_modules/@esbuild/linux-arm": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.26.0.tgz", + "integrity": "sha512-JY8NyU31SyRmRpuc5W8PQarAx4TvuYbyxbPIpHAZdr/0g4iBr8KwQBS4kiiamGl2f42BBecHusYCsyxi7Kn8UQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.26.0.tgz", + "integrity": "sha512-IMJYN7FSkLttYyTbsbme0Ra14cBO5z47kpamo16IwggzzATFY2lcZAwkbcNkWiAduKrTgFJP7fW5cBI7FzcuNQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.26.0.tgz", + "integrity": "sha512-XITaGqGVLgk8WOHw8We9Z1L0lbLFip8LyQzKYFKO4zFo1PFaaSKsbNjvkb7O8kEXytmSGRkYpE8LLVpPJpsSlw==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.26.0.tgz", + "integrity": "sha512-MkggfbDIczStUJwq9wU7gQ7kO33d8j9lWuOCDifN9t47+PeI+9m2QVh51EI/zZQ1spZtFMC1nzBJ+qNGCjJnsg==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.26.0.tgz", + "integrity": "sha512-fUYup12HZWAeccNLhQ5HwNBPr4zXCPgUWzEq2Rfw7UwqwfQrFZ0SR/JljaURR8xIh9t+o1lNUFTECUTmaP7yKA==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.26.0.tgz", + "integrity": "sha512-MzRKhM0Ip+//VYwC8tialCiwUQ4G65WfALtJEFyU0GKJzfTYoPBw5XNWf0SLbCUYQbxTKamlVwPmcw4DgZzFxg==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.26.0.tgz", + "integrity": "sha512-QhCc32CwI1I4Jrg1enCv292sm3YJprW8WHHlyxJhae/dVs+KRWkbvz2Nynl5HmZDW/m9ZxrXayHzjzVNvQMGQA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.26.0.tgz", + "integrity": "sha512-1D6vi6lfI18aNT1aTf2HV+RIlm6fxtlAp8eOJ4mmnbYmZ4boz8zYDar86sIYNh0wmiLJEbW/EocaKAX6Yso2fw==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "node_modules/@esbuild/linux-x64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.26.0.tgz", + "integrity": "sha512-rnDcepj7LjrKFvZkx+WrBv6wECeYACcFjdNPvVPojCPJD8nHpb3pv3AuR9CXgdnjH1O23btICj0rsp0L9wAnHA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.26.0.tgz", + "integrity": "sha512-FSWmgGp0mDNjEXXFcsf12BmVrb+sZBBBlyh3LwB/B9ac3Kkc8x5D2WimYW9N7SUkolui8JzVnVlWh7ZmjCpnxw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.26.0.tgz", + "integrity": "sha512-0QfciUDFryD39QoSPUDshj4uNEjQhp73+3pbSAaxjV2qGOEDsM67P7KbJq7LzHoVl46oqhIhJ1S+skKGR7lMXA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.26.0.tgz", + "integrity": "sha512-vmAK+nHhIZWImwJ3RNw9hX3fU4UGN/OqbSE0imqljNbUQC3GvVJ1jpwYoTfD6mmXmQaxdJY6Hn4jQbLGJKg5Yw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.26.0.tgz", + "integrity": "sha512-GPXF7RMkJ7o9bTyUsnyNtrFMqgM3X+uM/LWw4CeHIjqc32fm0Ir6jKDnWHpj8xHFstgWDUYseSABK9KCkHGnpg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.26.0.tgz", + "integrity": "sha512-nUHZ5jEYqbBthbiBksbmHTlbb5eElyVfs/s1iHQ8rLBq1eWsd5maOnDpCocw1OM8kFK747d1Xms8dXJHtduxSw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.26.0.tgz", + "integrity": "sha512-TMg3KCTCYYaVO+R6P5mSORhcNDDlemUVnUbb8QkboUtOhb5JWKAzd5uMIMECJQOxHZ/R+N8HHtDF5ylzLfMiLw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.26.0.tgz", + "integrity": "sha512-apqYgoAUd6ZCb9Phcs8zN32q6l0ZQzQBdVXOofa6WvHDlSOhwCWgSfVQabGViThS40Y1NA4SCvQickgZMFZRlA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", - "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.26.0.tgz", + "integrity": "sha512-FGJAcImbJNZzLWu7U6WB0iKHl4RuY4TsXEwxJPl9UZLS47agIZuILZEX3Pagfw7I4J3ddflomt9f0apfaJSbaw==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@esbuild/win32-x64": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.26.0.tgz", + "integrity": "sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "20 || >=22" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@isaacs/balanced-match": "^4.0.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "20 || >=22" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "minipass": "^7.0.4" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.0.0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=8" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "dev": true, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "dev": true, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@babel/preset-env": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", - "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", - "dev": true, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.0", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.3", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.3", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, - "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.3.tgz", + "integrity": "sha512-zR6Y45VNtW5s+A+4AyhrJk0VJKhXdkLhrySCpCu7PSdnakebsOzNxf58p5Xoq66vOSuueGAxlqDAF49HwdrSTQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=14.17.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.3.tgz", + "integrity": "sha512-nfGm5pQksBGfaj9uMbjC0YyQreny/Pl7mIDtHtw6g7WQuCgeLullr9FNRsYyKplaEJBPrCVpEjpAznxTBIrXBw==", "cpu": [ - "ppc64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } + "darwin" + ] }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.3.tgz", + "integrity": "sha512-Kjqomp7i0rgSbYSUmv9JnXpS55zYT/YcW3Bdf9oqOTjcH0/8tFAP8MLhu/i9V2pMKIURDZk63Ww49DTK0T3c/Q==", "cpu": [ "arm" ], @@ -3247,16 +2800,13 @@ "license": "MIT", "optional": true, "os": [ - "android" - ], - "engines": { - "node": ">=18" - } + "linux" + ] }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.3.tgz", + "integrity": "sha512-uX9eaPqWb740wg5D3TCvU/js23lSRSKT7lJrrQ8IuEG/VLgpPlxO3lHDywU44yFYdGS7pElBn6ioKFKhvALZlw==", "cpu": [ "arm64" ], @@ -3264,16 +2814,13 @@ "license": "MIT", "optional": true, "os": [ - "android" - ], - "engines": { - "node": ">=18" - } + "linux" + ] }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.3.tgz", + "integrity": "sha512-7/8l20D55CfwdMupkc3fNxNJdn4bHsti2X0cp6PwiXlLeSFvAfWs5kCCx+2Cyje4l4GtN//LtKWjTru/9hDJQg==", "cpu": [ "x64" ], @@ -3281,16 +2828,13 @@ "license": "MIT", "optional": true, "os": [ - "android" - ], - "engines": { - "node": ">=18" - } + "linux" + ] }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.3.tgz", + "integrity": "sha512-yWVR0e5Gl35EGJBsAuqPOdjtUYuN8CcTLKrqpQFoM+KsMadViVCulhKNhkcjSGJB88Am5bRPjMro4MBB9FS23Q==", "cpu": [ "arm64" ], @@ -3298,33 +2842,75 @@ "license": "MIT", "optional": true, "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } + "win32" + ] }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", - "cpu": [ + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.3.tgz", + "integrity": "sha512-1JdBkcO0Vrua4LUgr4jAe4FUyluwCeq/pDkBrlaVjX3/BBWP1TzVjCL+TibWNQtPAL1BITXPAhlK5Ru4FBd/hg==", + "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ], + "win32" + ] + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.20.1.tgz", + "integrity": "sha512-j/P+yuxXfgxb+mW7OEoRCM3G47zCTDqUPivJo/VzpjbG8I9csTXtOprCf5FfOfHK4whOJny0aHuBEON+kS7CCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, "engines": { "node": ">=18" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", "cpu": [ "arm64" ], @@ -3332,16 +2918,13 @@ "license": "MIT", "optional": true, "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } + "darwin" + ] }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", "cpu": [ "x64" ], @@ -3349,16 +2932,13 @@ "license": "MIT", "optional": true, "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } + "darwin" + ] }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", "cpu": [ "arm" ], @@ -3367,15 +2947,12 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", "cpu": [ "arm64" ], @@ -3384,117 +2961,142 @@ "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": ">=18" - } + ] }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", "cpu": [ - "loong64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ], + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "dev": true, + "license": "MIT", + "optional": true, "engines": { - "node": ">=18" + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", "cpu": [ - "mips64el" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", "cpu": [ - "riscv64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", "cpu": [ - "s390x" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", "cpu": [ "x64" ], @@ -3502,50 +3104,50 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "freebsd" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", "cpu": [ "arm64" ], @@ -3553,473 +3155,627 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", "cpu": [ - "x64" + "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", "cpu": [ - "arm64" + "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openharmony" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", "cpu": [ - "x64" + "s390x" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "sunos" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "openharmony" ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@inquirer/ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.0.tgz", - "integrity": "sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==", + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@inquirer/figures": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", - "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" + "node": ">= 10" } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "20 || >=22" + "node": ">= 10" } }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "dev": true, "license": "ISC", "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": ">=12" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "semver": "^7.3.5" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "node_modules/@npmcli/git": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.1.tgz", + "integrity": "sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^6.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "node_modules/@npmcli/git/node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", "dev": true, "license": "ISC", "dependencies": { - "minipass": "^7.0.4" + "which": "^6.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@npmcli/git/node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=6.0.0" + "node": ">=16" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" + "license": "ISC", + "engines": { + "node": "20 || >=22" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "node_modules/@npmcli/git/node_modules/proc-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", + "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "node_modules/@npmcli/git/node_modules/which": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "bin": { + "node-which": "bin/which.js" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/buffers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz", - "integrity": "sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q==", + "node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", + "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "bin": { + "installed-package-contents": "bin/index.js" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@jsonjoy.com/codegen": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", - "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@jsonjoy.com/json-pack": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.14.0.tgz", - "integrity": "sha512-LpWbYgVnKzphN5S6uss4M25jJ/9+m6q6UJoeN6zTkK4xAGhKsiBRPVeF7OYMWonn5repMQbE5vieRXcMUrKDKw==", + "node_modules/@npmcli/package-json": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.4.tgz", + "integrity": "sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", + "dependencies": { + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/glob": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "@jsonjoy.com/base64": "^1.1.2", - "@jsonjoy.com/buffers": "^1.0.0", - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/json-pointer": "^1.0.1", - "@jsonjoy.com/util": "^1.9.0", - "hyperdyperid": "^1.2.0", - "thingies": "^2.5.0" + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" }, "engines": { - "node": ">=10.0" + "node": "20 || >=22" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@jsonjoy.com/json-pointer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", - "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "Apache-2.0", + "license": "BlueOak-1.0.0", "dependencies": { - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/util": "^1.9.0" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=10.0" + "node": "20 || >=22" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/package-json/node_modules/proc-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", + "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", + "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@jsonjoy.com/util": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", - "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "Apache-2.0", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", "dependencies": { - "@jsonjoy.com/buffers": "^1.0.0", - "@jsonjoy.com/codegen": "^1.0.0" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">=10.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.3.tgz", + "integrity": "sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=16" + } }, - "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.2.tgz", - "integrity": "sha512-NK80WwDoODyPaSazKbzd3NEJ3ygePrkERilZshxBViBARNz21rmediktGHExoj9n5t9+ChlgLlxecdFKLCuCKg==", - "cpu": [ - "arm64" - ], + "node_modules/@npmcli/run-script/node_modules/proc-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", + "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.96.0.tgz", + "integrity": "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "dev": true, + "hasInstallScript": true, "license": "MIT", "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } }, - "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.2.tgz", - "integrity": "sha512-zevaowQNmrp3U7Fz1s9pls5aIgpKRsKb3dZWDINtLiozh3jZI9fBrI19lYYBxqdyiIyNdlyiidPnwPShj4aK+w==", + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" - ] + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.2.tgz", - "integrity": "sha512-OmHCULY17rkx/RoCoXlzU7LyR8xqrksgdYWwtYa14l/sseezZ8seKWXcogHcjulBddER5NnEFV4L/Jtr2nyxeg==", + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", "cpu": [ - "arm" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.2.tgz", - "integrity": "sha512-ZBEfbNZdkneebvZs98Lq30jMY8V9IJzckVeigGivV7nTHJc+89Ctomp1kAIWKlwIG0ovCDrFI448GzFPORANYg==", + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" - ] + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.2.tgz", - "integrity": "sha512-vL9nM17C77lohPYE4YaAQvfZCSVJSryE4fXdi8M7uWPBnU+9DJabgKVAeyDb84ZM2vcFseoBE4/AagVtJeRE7g==", + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", "cpu": [ "x64" ], @@ -4027,89 +3783,62 @@ "license": "MIT", "optional": true, "os": [ - "linux" - ] + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-win32-arm64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.2.tgz", - "integrity": "sha512-SXWjdBfNDze4ZPeLtYIzsIeDJDJ/SdsA0pEXcUBayUIMO0FQBHfVZZyHXQjjHr4cvOAzANBgIiqaXRwfMhzmLw==", + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.2.tgz", - "integrity": "sha512-IY+r3bxKW6Q6sIPiMC0L533DEfRJSXibjSI3Ft/w9Q8KQBNqEIvUFXt+09wV8S5BRk0a8uSF19YWxuRwEfI90g==", + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", "cpu": [ - "x64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ] - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", - "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.6", - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.5", - "eventsource": "^3.0.2", - "eventsource-parser": "^3.0.0", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, + "linux" + ], "engines": { - "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "node": ">= 10.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", "cpu": [ "arm64" ], @@ -4117,55 +3846,62 @@ "license": "MIT", "optional": true, "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" + "linux" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", "cpu": [ - "arm" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", "cpu": [ "x64" ], @@ -4174,77 +3910,82 @@ "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" - ] + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@napi-rs/nice": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", - "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 10" + "node": ">= 10.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/nice-android-arm-eabi": "1.1.1", - "@napi-rs/nice-android-arm64": "1.1.1", - "@napi-rs/nice-darwin-arm64": "1.1.1", - "@napi-rs/nice-darwin-x64": "1.1.1", - "@napi-rs/nice-freebsd-x64": "1.1.1", - "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", - "@napi-rs/nice-linux-arm64-gnu": "1.1.1", - "@napi-rs/nice-linux-arm64-musl": "1.1.1", - "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", - "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", - "@napi-rs/nice-linux-s390x-gnu": "1.1.1", - "@napi-rs/nice-linux-x64-gnu": "1.1.1", - "@napi-rs/nice-linux-x64-musl": "1.1.1", - "@napi-rs/nice-openharmony-arm64": "1.1.1", - "@napi-rs/nice-win32-arm64-msvc": "1.1.1", - "@napi-rs/nice-win32-ia32-msvc": "1.1.1", - "@napi-rs/nice-win32-x64-msvc": "1.1.1" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@napi-rs/nice-android-arm-eabi": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", - "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", "cpu": [ - "arm" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "android" + "win32" ], "engines": { - "node": ">= 10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@napi-rs/nice-android-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", - "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.47.tgz", + "integrity": "sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw==", "cpu": [ "arm64" ], @@ -4255,13 +3996,13 @@ "android" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-darwin-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", - "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.47.tgz", + "integrity": "sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw==", "cpu": [ "arm64" ], @@ -4272,13 +4013,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-darwin-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", - "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.47.tgz", + "integrity": "sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ==", "cpu": [ "x64" ], @@ -4289,13 +4030,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-freebsd-x64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", - "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.47.tgz", + "integrity": "sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA==", "cpu": [ "x64" ], @@ -4306,13 +4047,13 @@ "freebsd" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", - "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.47.tgz", + "integrity": "sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA==", "cpu": [ "arm" ], @@ -4323,13 +4064,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-linux-arm64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", - "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.47.tgz", + "integrity": "sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA==", "cpu": [ "arm64" ], @@ -4340,13 +4081,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-linux-arm64-musl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", - "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.47.tgz", + "integrity": "sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ==", "cpu": [ "arm64" ], @@ -4357,15 +4098,15 @@ "linux" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-linux-ppc64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", - "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.47.tgz", + "integrity": "sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ==", "cpu": [ - "ppc64" + "x64" ], "dev": true, "license": "MIT", @@ -4374,15 +4115,15 @@ "linux" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-linux-riscv64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", - "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.47.tgz", + "integrity": "sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ==", "cpu": [ - "riscv64" + "x64" ], "dev": true, "license": "MIT", @@ -4391,83 +4132,83 @@ "linux" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-linux-s390x-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", - "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.47.tgz", + "integrity": "sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "openharmony" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-linux-x64-gnu": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", - "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.47.tgz", + "integrity": "sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ==", "cpu": [ - "x64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.7" + }, "engines": { - "node": ">= 10" + "node": ">=14.0.0" } }, - "node_modules/@napi-rs/nice-linux-x64-musl": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", - "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.47.tgz", + "integrity": "sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-openharmony-arm64": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", - "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.47.tgz", + "integrity": "sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw==", "cpu": [ - "arm64" + "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openharmony" + "win32" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-win32-arm64-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", - "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.47.tgz", + "integrity": "sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", @@ -4476,286 +4217,470 @@ "win32" ], "engines": { - "node": ">= 10" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@napi-rs/nice-win32-ia32-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", - "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", "cpu": [ - "ia32" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } + "android" + ] }, - "node_modules/@napi-rs/nice-win32-x64-msvc": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", - "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } + "android" + ] }, - "node_modules/@ngtools/webpack": { - "version": "20.3.4", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.4.tgz", - "integrity": "sha512-5G2Q9VguR73RaSbqVpsW1LumITsPxceLL0+L94IOHoV4eYlFciJTXLWgcRUoChh73bt0wLMgdwUVWKjaVtjOLQ==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^20.0.0", - "typescript": ">=5.8 <6.0", - "webpack": "^5.54.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@npmcli/agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@npmcli/git": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", - "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@npmcli/git/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "engines": { - "node": ">=16" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@npmcli/git/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.0.0.tgz", + "integrity": "sha512-50eEsBaT++Gwr+5FAhaKIzTUjpE1DJAwmE5QwtogbTnr2viZc8CsbFOfuMrokQbgdcXRvbkBDPXgO15STMcDRQ==", + "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" + "@angular-devkit/core": "21.0.0", + "@angular-devkit/schematics": "21.0.0", + "jsonc-parser": "3.3.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" } }, - "node_modules/@npmcli/installed-package-contents": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-3.0.0.tgz", - "integrity": "sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==", + "node_modules/@sigstore/bundle": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "npm-bundled": "^4.0.0", - "npm-normalize-package-bin": "^4.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/node-gyp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", - "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", + "node_modules/@sigstore/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.0.0.tgz", + "integrity": "sha512-NgbJ+aW9gQl/25+GIEGYcCyi8M+ng2/5X04BMuIgoDfgvp18vDcoNHOQjQsG9418HGNYRxG3vfEXaR1ayD37gg==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", + "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/@npmcli/package-json": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", - "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", + "node_modules/@sigstore/sign": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.0.1.tgz", + "integrity": "sha512-KFNGy01gx9Y3IBPG/CergxR9RZpN43N+lt3EozEfeoyqm8vEiLxwRl3ZO5sPx3Obv1ix/p7FWOlPc2Jgwfp9PA==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.2", "proc-log": "^5.0.0", - "semver": "^7.5.3", - "validate-npm-package-license": "^3.0.4" + "promise-retry": "^2.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/package-json/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/@sigstore/tuf": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.0.tgz", + "integrity": "sha512-0QFuWDHOQmz7t66gfpfNO6aEjoFrdhkJaej/AOqb4kqWZVbPWFZifXZzkxyQBB1OwTbkhdT3LNpMFxwkTvf+2w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "node_modules/@sigstore/verify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.0.0.tgz", + "integrity": "sha512-moXtHH33AobOhTZF8xcX1MpOFqdvfCk7v6+teJL8zymBiDXwEsQH6XG9HGx2VIxnJZNm4cNSzflTLDnQLmIdmw==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true, - "license": "ISC", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT", + "peer": true + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.0.0.tgz", + "integrity": "sha512-h5x5ga/hh82COe+GoD4+gKUeV4T3iaYOxqLt41GRKApinPI7DMidhCmNVTjKfhCWFJIGXaFJee07XczdT4jdZQ==", + "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^10.0.1" + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/package-json/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "node_modules/@npmcli/package-json/node_modules/minimatch": { + "node_modules/@tufjs/models/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", @@ -4771,5916 +4696,1317 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/promise-spawn": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-8.0.3.tgz", - "integrity": "sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, "dependencies": { - "which": "^5.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "tslib": "^2.4.0" } }, - "node_modules/@npmcli/promise-spawn/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jasmine": { + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.13.tgz", + "integrity": "sha512-MYCcDkruFc92LeYZux5BC0dmqo2jk+M5UIZ4/oFnAPCXN9mCcQhLyj7F3/Za7rocVyt5YRr1MmqJqFlvQ9LVcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", "dev": true, "license": "ISC", "engines": { - "node": ">=16" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "isexe": "^3.1.1" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, "bin": { - "node-which": "bin/which.js" + "acorn": "bin/acorn" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=0.4.0" } }, - "node_modules/@npmcli/redact": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", - "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 14" } }, - "node_modules/@npmcli/run-script": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", - "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=16" + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "node_modules/algoliasearch": { + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.40.1.tgz", + "integrity": "sha512-iUNxcXUNg9085TJx0HJLjqtDE0r1RZ0GOGrt8KNQqQT5ugu8lZsHuMUYW/e0lHhq6xBvmktU9Bw4CXP9VQeKrg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" + "@algolia/abtesting": "1.6.1", + "@algolia/client-abtesting": "5.40.1", + "@algolia/client-analytics": "5.40.1", + "@algolia/client-common": "5.40.1", + "@algolia/client-insights": "5.40.1", + "@algolia/client-personalization": "5.40.1", + "@algolia/client-query-suggestions": "5.40.1", + "@algolia/client-search": "5.40.1", + "@algolia/ingestion": "1.40.1", + "@algolia/monitoring": "1.40.1", + "@algolia/recommend": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 14.0.0" } }, - "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" + "environment": "^1.0.0" }, "engines": { - "node": ">= 10.0.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">= 10.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">= 10.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", - "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", - "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", - "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", - "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", - "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", - "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", - "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", - "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", - "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", - "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", - "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", - "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", - "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", - "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", - "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", - "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", - "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", - "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", - "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", - "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", - "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", - "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@schematics/angular": { - "version": "20.3.4", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.4.tgz", - "integrity": "sha512-aIgeRAfSJdxVfsES521vNb6Nuy+Uei7xE85hD+mQHX+62bN7vJhM/tmMFIYS7UF8d0rRnMPiGEr2Rfx6Om5iPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "20.3.4", - "@angular-devkit/schematics": "20.3.4", - "jsonc-parser": "3.3.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@sigstore/bundle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", - "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@sigstore/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", - "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@sigstore/protobuf-specs": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.3.tgz", - "integrity": "sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@sigstore/sign": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", - "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@sigstore/tuf": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", - "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/protobuf-specs": "^0.4.1", - "tuf-js": "^3.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@sigstore/verify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", - "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tufjs/canonical-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", - "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/@tufjs/models": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", - "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.5" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/jasmine": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.9.tgz", - "integrity": "sha512-8t4HtkW4wxiPVedMpeZ63n3vlWxEIquo/zc1Tm8ElU+SqVV7+D3Na2PWaJUp179AzTragMWVwkMv7mvty0NfyQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/luxon": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz", - "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "12.20.55", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", - "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node-forge": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", - "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", - "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", - "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/algoliasearch": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.35.0.tgz", - "integrity": "sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.1.0", - "@algolia/client-abtesting": "5.35.0", - "@algolia/client-analytics": "5.35.0", - "@algolia/client-common": "5.35.0", - "@algolia/client-insights": "5.35.0", - "@algolia/client-personalization": "5.35.0", - "@algolia/client-query-suggestions": "5.35.0", - "@algolia/client-search": "5.35.0", - "@algolia/ingestion": "1.35.0", - "@algolia/monitoring": "1.35.0", - "@algolia/recommend": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", - "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/babel-loader": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", - "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": "^18.20.0 || ^20.10.0 || >=22.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5.61.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.12.tgz", - "integrity": "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/beasties": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", - "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "css-select": "^6.0.0", - "css-what": "^7.0.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "htmlparser2": "^10.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.49", - "postcss-media-query-parser": "^0.2.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^4.0.0", - "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/tar": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", - "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001748", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz", - "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chardet": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", - "integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", - "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", - "license": "ISC", - "dependencies": { - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/connect/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/connect/node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/connect/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/connect/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/copy-anything": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", - "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-what": "^3.14.1" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/copy-webpack-plugin": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", - "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-parent": "^6.0.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", - "tinyglobby": "^0.2.12" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/core-js-compat": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", - "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.25.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", - "dev": true, - "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.27.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/css-select": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", - "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^7.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "nth-check": "^2.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", - "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true, - "license": "MIT" - }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "bin": { - "detect-libc": "bin/detect-libc.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "license": "MIT" - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true, - "license": "MIT" - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.230", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.230.tgz", - "integrity": "sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz", - "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==", - "license": "MIT" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/engine.io": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", - "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.17.1" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", - "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "punycode": "^1.4.1", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true, - "license": "MIT" - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" - } - }, - "node_modules/esbuild-wasm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.9.tgz", - "integrity": "sha512-Jpv5tCSwQg18aCqCRD3oHIX/prBhXMDapIoG//A+6+dV0e7KQMGFg85ihJ5T1EeMjbZjON3TqFy0VrGAnIHLDA==", - "dev": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/exponential-backoff": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", - "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", - "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", - "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regex.js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", - "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hosted-git-info": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.0.tgz", - "integrity": "sha512-gEf705MZLrDPkbbhi8PnoO4ZwYgKoNL+ISZ3AjZMht2r3N5tuTwncyDi6Fv2/qDnMmZxgs0yI8WDOyR8q3G+SQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^11.1.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/htmlparser2": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", - "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.1", - "entities": "^6.0.0" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-errors/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-middleware": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", - "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.15", - "debug": "^4.3.6", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.3", - "is-plain-object": "^5.0.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.18" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ignore-walk": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", - "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", - "dev": true, - "license": "ISC", - "dependencies": { - "minimatch": "^10.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", - "dev": true, - "license": "MIT" - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", - "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-what": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", - "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jasmine-core": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.8.0.tgz", - "integrity": "sha512-Q9dqmpUAfptwyueW3+HqBOkSuYd9I/clZSSfN97wXE/Nr2ROFNCwIBEC1F6kb3QXS9Fcz0LjFYSDQT+BiwjuhA==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": "^4.5.0 || >= 5.9" } }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", + "node_modules/baseline-browser-mapping": { + "version": "2.8.30", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", + "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "license": "Apache-2.0", "bin": { - "jiti": "bin/jiti.js" + "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/beasties": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", + "integrity": "sha512-NaWu+f4YrJxEttJSm16AzMIFtVldCvaJ68b1L098KpqXmxt9xOLtKoLkKxb8ekhOrLqEJAbvT6n6SEvB/sac7A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "css-select": "^6.0.0", + "css-what": "^7.0.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "htmlparser2": "^10.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-media-query-parser": "^0.2.3" }, "engines": { - "node": ">=6" + "node": ">=14.0.0" } }, - "node_modules/json-parse-even-better-errors": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", - "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "node": ">=8" }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ], - "license": "MIT" - }, - "node_modules/karma": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", - "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dev": true, "license": "MIT", "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.7.2", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" }, "engines": { - "node": ">= 10" + "node": ">=18" } }, - "node_modules/karma-chrome-launcher": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", - "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "which": "^1.2.1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "fill-range": "^7.1.1" }, - "bin": { - "which": "bin/which" + "engines": { + "node": ">=8" } }, - "node_modules/karma-coverage": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", - "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", - "dev": true, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.2.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.1", - "istanbul-reports": "^3.0.5", - "minimatch": "^3.0.4" + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=10.0.0" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/karma-coverage/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/cacache": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", "dev": true, "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/karma-jasmine": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.0.1.tgz", - "integrity": "sha512-FkL1Kk+JAKmim8VWU8RXKZBpl0lLI7J8LijM0/q7oP7emfB6QMZV1Az+JgqGKSLpF0tYaav+KUVFQroZUxQTHA==", + "node_modules/cacache/node_modules/glob": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "jasmine-core": "^4.1.0" + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" }, "engines": { - "node": ">=12" + "node": "20 || >=22" }, - "peerDependencies": { - "karma": "^6.0.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/karma-jasmine-html-reporter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", - "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", + "node_modules/cacache/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "license": "MIT", - "peerDependencies": { - "jasmine-core": "^4.0.0 || ^5.0.0", - "karma": "^6.0.0", - "karma-jasmine": "^5.0.0" + "license": "ISC", + "engines": { + "node": "20 || >=22" } }, - "node_modules/karma-jasmine/node_modules/jasmine-core": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", - "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "node_modules/cacache/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "source-map-support": "^0.5.5" - } - }, - "node_modules/karma/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", + "@isaacs/brace-expansion": "^5.0.0" + }, "engines": { - "node": ">=8" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/karma/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/cacache/node_modules/ssri": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "minipass": "^7.0.3" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/karma/node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">= 0.4" } }, - "node_modules/karma/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 0.4" }, "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/karma/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } + "node_modules/caniuse-lite": { + "version": "1.0.30001756", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", + "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" }, - "node_modules/karma/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/karma/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "dev": true, "license": "MIT" }, - "node_modules/karma/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 6" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/karma/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/karma/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/cli-spinners": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.3.0.tgz", + "integrity": "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/karma/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/karma/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">= 0.6" + "node": ">= 12" } }, - "node_modules/karma/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "license": "ISC", "dependencies": { - "mime-db": "1.52.0" + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=20" } }, - "node_modules/karma/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, - "node_modules/karma/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=8.6" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/karma/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "side-channel": "^1.0.6" + "color-name": "~1.1.4" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=7.0.0" } }, - "node_modules/karma/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10.0" } }, - "node_modules/karma/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" + "ms": "2.0.0" } }, - "node_modules/karma/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/connect/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/karma/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/karma/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ee-first": "1.1.1" }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/karma/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "node_modules/connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, "engines": { "node": ">= 0.6" } }, - "node_modules/karma/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/karma/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, - "node_modules/karma/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/launch-editor": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", - "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" - } - }, - "node_modules/less": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/less/-/less-4.4.0.tgz", - "integrity": "sha512-kdTwsyRuncDfjEs0DlRILWNvxhDG/Zij4YLO4TMJgDLW+8OzpfkdPnRgrsRuY1o+oaxJGWsps5f/RVBgGmmN0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" - }, - "bin": { - "lessc": "bin/lessc" - }, "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "needle": "^3.1.0", - "source-map": "~0.6.0" + "node": ">=6.6.0" } }, - "node_modules/less-loader": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.3.0.tgz", - "integrity": "sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==", + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 18.12.0" + "optional": true, + "peer": true, + "dependencies": { + "is-what": "^3.14.1" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "less": "^3.5.0 || ^4.0.0", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/less/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">=6" + "node": ">= 0.10" } }, - "node_modules/less/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">= 8" } }, - "node_modules/less/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/css-select": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", + "integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==", "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver" + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^7.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "nth-check": "^2.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/less/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/css-what": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz", + "integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==", "dev": true, - "license": "BSD-3-Clause", - "optional": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/license-webpack-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", - "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", - "dev": true, - "license": "ISC", - "dependencies": { - "webpack-sources": "^3.0.0" + "node": ">= 6" }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-sources": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", "dev": true, "license": "MIT" }, - "node_modules/listr2": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz", - "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true, "license": "MIT", - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, "engines": { - "node": ">=20.0.0" + "node": ">=4.0" } }, - "node_modules/listr2/node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lmdb": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.2.tgz", - "integrity": "sha512-nwVGUfTBUwJKXd6lRV8pFNfnrCC1+l49ESJRM19t/tFb/97QfJEixe5DYRvug5JO7DSFKoKaVy7oGMt5rVqZvg==", - "dev": true, - "hasInstallScript": true, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", - "optional": true, "dependencies": { - "msgpackr": "^1.11.2", - "node-addon-api": "^6.1.0", - "node-gyp-build-optional-packages": "5.2.2", - "ordered-binary": "^1.5.3", - "weak-lru-cache": "^1.2.2" + "ms": "^2.1.3" }, - "bin": { - "download-lmdb-prebuilds": "bin/download-prebuilds.js" + "engines": { + "node": ">=6.0" }, - "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.4.2", - "@lmdb/lmdb-darwin-x64": "3.4.2", - "@lmdb/lmdb-linux-arm": "3.4.2", - "@lmdb/lmdb-linux-arm64": "3.4.2", - "@lmdb/lmdb-linux-x64": "3.4.2", - "@lmdb/lmdb-win32-arm64": "3.4.2", - "@lmdb/lmdb-win32-x64": "3.4.2" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/lmdb/node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.11.5" + "node": ">= 0.8" } }, - "node_modules/loader-utils": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", - "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 12.13.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "dev": true, "license": "MIT" }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=18" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "get-east-asian-width": "^1.3.1" + "domelementtype": "^2.3.0" }, "engines": { - "node": ">=18" + "node": ">= 4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/log4js": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", - "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "flatted": "^3.2.7", - "rfdc": "^1.3.0", - "streamroller": "^3.1.5" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=8.0" + "node": ">= 0.4" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" }, - "node_modules/luxon": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", - "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", - "license": "MIT", - "engines": { - "node": ">=12" - } + "node_modules/electron-to-chromium": { + "version": "1.5.259", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", + "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "license": "ISC" }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } + "license": "MIT" }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "ssri": "^12.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "iconv-lite": "^0.6.2" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", "dev": true, "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, "engines": { - "node": ">= 0.4" + "node": ">=10.2.0" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=10.0.0" } }, - "node_modules/memfs": { - "version": "4.48.1", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.48.1.tgz", - "integrity": "sha512-vWO+1ROkhOALF1UnT9aNOOflq5oFDlqwTXaPg6duo07fBLxSH0+bcF0TY1lbA1zTNKyGgDxgaDdKx5MaewLX5A==", + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@jsonjoy.com/json-pack": "^1.11.0", - "@jsonjoy.com/util": "^1.9.0", - "glob-to-regex.js": "^1.0.1", - "thingies": "^2.5.0", - "tree-dump": "^1.0.3", - "tslib": "^2.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">= 8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "mime-db": "1.52.0" }, "engines": { - "node": ">=8.6" + "node": ">= 0.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": ">= 0.6" } }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "node_modules/ent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", + "integrity": "sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==", "dev": true, "license": "MIT", - "bin": { - "mime": "cli.js" + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "punycode": "^1.4.1", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=4.0.0" + "node": ">= 0.4" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.6" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, "engines": { - "node": ">= 0.6" + "node": ">=6" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, "license": "MIT", "engines": { @@ -10690,784 +6016,880 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", - "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "prr": "~1.0.1" }, - "peerDependencies": { - "webpack": "^5.0.0" + "bin": { + "errno": "cli.js" } }, - "node_modules/minimalistic-assert": { + "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 0.4" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 0.4" } }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">= 0.4" } }, - "node_modules/minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "node_modules/esbuild": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.26.0.tgz", + "integrity": "sha512-3Hq7jri+tRrVWha+ZeIVhl4qJRha/XjRNSopvTsOaCvfPHrflTYTcUFcEjMKdxofsXXsdc4zjg5NOTnL4Gl57Q==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^1.0.3", - "minizlib": "^3.0.1" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" }, "optionalDependencies": { - "encoding": "^0.1.13" + "@esbuild/aix-ppc64": "0.26.0", + "@esbuild/android-arm": "0.26.0", + "@esbuild/android-arm64": "0.26.0", + "@esbuild/android-x64": "0.26.0", + "@esbuild/darwin-arm64": "0.26.0", + "@esbuild/darwin-x64": "0.26.0", + "@esbuild/freebsd-arm64": "0.26.0", + "@esbuild/freebsd-x64": "0.26.0", + "@esbuild/linux-arm": "0.26.0", + "@esbuild/linux-arm64": "0.26.0", + "@esbuild/linux-ia32": "0.26.0", + "@esbuild/linux-loong64": "0.26.0", + "@esbuild/linux-mips64el": "0.26.0", + "@esbuild/linux-ppc64": "0.26.0", + "@esbuild/linux-riscv64": "0.26.0", + "@esbuild/linux-s390x": "0.26.0", + "@esbuild/linux-x64": "0.26.0", + "@esbuild/netbsd-arm64": "0.26.0", + "@esbuild/netbsd-x64": "0.26.0", + "@esbuild/openbsd-arm64": "0.26.0", + "@esbuild/openbsd-x64": "0.26.0", + "@esbuild/openharmony-arm64": "0.26.0", + "@esbuild/sunos-x64": "0.26.0", + "@esbuild/win32-arm64": "0.26.0", + "@esbuild/win32-ia32": "0.26.0", + "@esbuild/win32-x64": "0.26.0" } }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { - "node": ">= 8" + "node": ">=6" } }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "eventsource-parser": "^3.0.1" }, "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18.0.0" } }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true, - "license": "ISC" + "license": "Apache-2.0" }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">=8" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" } }, - "node_modules/minipass-sized/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true, - "license": "ISC" + "license": "MIT" }, - "node_modules/minizlib": { + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, "engines": { - "node": ">= 18" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.6" + "to-regex-range": "^5.0.1" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">=8" } }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dev": true, "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, "engines": { - "node": ">=10" + "node": ">= 0.8" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/msgpackr": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", - "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, - "license": "MIT", - "optional": true, - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } + "license": "ISC" }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, - "hasInstallScript": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + "engines": { + "node": ">=4.0" }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" + "engines": { + "node": ">= 0.6" } }, - "node_modules/mute-stream": { + "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 0.8" } }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=6 <7 || >=8" } }, - "node_modules/needle": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", - "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, - "license": "MIT", - "optional": true, + "license": "ISC", "dependencies": { - "iconv-lite": "^0.6.3", - "sax": "^1.2.4" - }, - "bin": { - "needle": "bin/needle" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 4.4.x" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/negotiator": { + "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.6" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "license": "MIT" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "license": "MIT", - "optional": true + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, "engines": { - "node": ">= 6.13.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp": { - "version": "11.4.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.4.2.tgz", - "integrity": "sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "tar": "^7.4.3", - "tinyglobby": "^0.2.12", - "which": "^5.0.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, - "bin": { - "node-gyp": "bin/node-gyp.js" + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" + "engines": { + "node": ">= 0.4" }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "Apache-2.0", - "optional": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "engines": { - "node": ">=18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { - "node": ">=16" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-gyp/node_modules/tar": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", - "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=18" + "node": ">= 0.4" } }, - "node_modules/node-gyp/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "node_modules/hosted-git-info": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", + "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" + "lru-cache": "^11.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "engines": { - "node": ">=18" + "node": "20 || >=22" } }, - "node_modules/node-releases": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, "license": "MIT" }, - "node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", "dev": true, - "license": "ISC", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", "dependencies": { - "abbrev": "^3.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } + "license": "BSD-2-Clause" }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/npm-bundled": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", - "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "npm-normalize-package-bin": "^4.0.0" + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8.0.0" } }, - "node_modules/npm-install-checks": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", - "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "semver": "^7.1.1" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 14" } }, - "node_modules/npm-normalize-package-bin": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", - "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 14" } }, - "node_modules/npm-package-arg": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.0.tgz", - "integrity": "sha512-+t2etZAGcB7TbbLHfDwooV9ppB2LhhcT6A+L9cahsf9mEUAoQ6CktLEVvEnpD0N5CkX7zJqnPGaFtoQDy9EkHQ==", + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "hosted-git-info": "^9.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=0.10.0" } }, - "node_modules/npm-packlist": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.2.tgz", - "integrity": "sha512-DrIWNiWT0FTdDRjGOYfEEZUNe1IzaSZ+up7qBTKnrQDySpdmuOQvytrqQlpK5QrCA4IThMvL4wTumqaa1ZvVIQ==", + "node_modules/ignore-walk": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", + "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "dev": true, "license": "ISC", "dependencies": { - "ignore-walk": "^8.0.0", - "proc-log": "^5.0.0" + "minimatch": "^10.0.3" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-pick-manifest": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", - "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", - "semver": "^7.3.5" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npm-pick-manifest/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "image-size": "bin/image-size.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=0.10.0" } }, - "node_modules/npm-pick-manifest/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/npm-pick-manifest/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } + "license": "MIT" }, - "node_modules/npm-registry-fetch": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", - "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/redact": "^3.0.0", - "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", - "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", - "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=0.8.19" } }, - "node_modules/npm-registry-fetch/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, - "node_modules/npm-registry-fetch/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/ini": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz", + "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 12" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.10" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true, - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", "dependencies": { - "mimic-function": "^5.0.0" + "get-east-asian-width": "^1.3.1" }, "engines": { "node": ">=18" @@ -11476,93 +6898,72 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", "dev": true, "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ordered-binary": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", - "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.12.0" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-map": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", - "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "license": "MIT", "engines": { @@ -11572,562 +6973,563 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-retry": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true, "license": "MIT", - "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" - }, "engines": { - "node": ">=16.17" + "node": ">= 8.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/gjtorikian/" } }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } + "license": "ISC" }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "BlueOak-1.0.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } }, - "node_modules/pacote": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz", - "integrity": "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "@npmcli/git": "^6.0.0", - "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^10.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", - "proc-log": "^5.0.0", - "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", - "ssri": "^12.0.0", - "tar": "^6.1.11" - }, - "bin": { - "pacote": "bin/index.js" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": ">=10" } }, - "node_modules/pacote/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "lru-cache": "^10.0.1" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" } }, - "node_modules/pacote/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/pacote/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=10" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-json/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "node_modules/jasmine-core": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.8.0.tgz", + "integrity": "sha512-Q9dqmpUAfptwyueW3+HqBOkSuYd9I/clZSSfN97wXE/Nr2ROFNCwIBEC1F6kb3QXS9Fcz0LjFYSDQT+BiwjuhA==", "dev": true, "license": "MIT" }, - "node_modules/parse-node-version": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", - "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.10" + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", - "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", - "dependencies": { - "entities": "^6.0.0" + "bin": { + "jsesc": "bin/jsesc" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">=6" } }, - "node_modules/parse5-html-rewriting-stream": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", - "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", + "node_modules/json-parse-even-better-errors": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "dev": true, "license": "MIT", - "dependencies": { - "entities": "^6.0.0", - "parse5": "^8.0.0", - "parse5-sax-parser": "^8.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/parse5-sax-parser": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", - "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "license": "MIT", - "dependencies": { - "parse5": "^8.0.0" + "bin": { + "json5": "lib/cli.js" }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "engines": { + "node": ">=6" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, - "node_modules/path-exists": { + "node_modules/jsonfile": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/karma": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", + "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 10" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "which": "^1.2.1" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "license": "MIT" + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "MIT", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" }, "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10.0.0" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/karma-coverage/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, - "license": "ISC" + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "node_modules/karma-coverage/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/karma-jasmine": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.0.1.tgz", + "integrity": "sha512-FkL1Kk+JAKmim8VWU8RXKZBpl0lLI7J8LijM0/q7oP7emfB6QMZV1Az+JgqGKSLpF0tYaav+KUVFQroZUxQTHA==", + "dev": true, "license": "MIT", + "dependencies": { + "jasmine-core": "^4.1.0" + }, "engines": { "node": ">=12" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "peerDependencies": { + "karma": "^6.0.0" } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", + "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", "dev": true, "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" } }, - "node_modules/piscina": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.3.tgz", - "integrity": "sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==", + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.1.tgz", + "integrity": "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/karma/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=20.x" - }, - "optionalDependencies": { - "@napi-rs/nice": "^1.0.4" + "node": ">=8" } }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "node_modules/karma/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=16.20.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "node_modules/karma/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/postcss-loader": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", - "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "node_modules/karma/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", "dependencies": { - "cosmiconfig": "^9.0.0", - "jiti": "^1.20.0", - "semver": "^7.5.4" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 8.10.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" + "url": "https://paulmillr.com/funding/" }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", - "dev": true, - "license": "MIT" - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "node_modules/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "node_modules/karma/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "ms": "2.0.0" } }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "node_modules/karma/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { - "postcss-selector-parser": "^7.0.0" + "is-glob": "^4.0.1" }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">= 6" } }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "node_modules/karma/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "icss-utils": "^5.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" + "node": ">= 0.8" } }, - "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "node_modules/karma/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/proc-log": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", - "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "node_modules/karma/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "license": "ISC", + "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "node_modules/karma/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "node_modules/karma/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", - "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/karma/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.6" } }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "node_modules/karma/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "node_modules/karma/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.9" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "node_modules/karma/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -12136,406 +7538,326 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", - "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "node_modules/karma/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.7.0", + "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "node_modules/karma/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "picomatch": "^2.2.1" }, "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": ">=8.10.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/karma/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "node_modules/karma/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "node": ">= 0.8" } }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "node_modules/karma/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "regenerate": "^1.4.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/regex-parser": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", - "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "node_modules/karma/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "node_modules/karma/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "jsesc": "~3.1.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/resolve-url-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", - "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "node_modules/less": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/less/-/less-4.4.2.tgz", + "integrity": "sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "optional": true, + "peer": true, "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.14", - "source-map": "0.6.1" + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" }, "engines": { - "node": ">=12" + "node": ">=14" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" } }, - "node_modules/resolve-url-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" + "pify": "^4.0.1", + "semver": "^5.6.0" }, "engines": { - "node": ">=8.9.0" + "node": ">=6" } }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/less/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "mime": "cli.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "semver": "bin/semver" } }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "node_modules/less/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "optional": true, + "peer": true, "engines": { - "node": ">= 4" + "node": ">=0.10.0" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=20.0.0" } }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "dev": true, "license": "MIT" }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "node_modules/lmdb": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.3.tgz", + "integrity": "sha512-GWV1kVi6uhrXWqe+3NXWO73OYe8fto6q8JMo0HOpk1vf8nEyFWgo4CSNJpIFzsOxOrysVUlcO48qRbQfmKd1gA==", "dev": true, - "license": "ISC", + "hasInstallScript": true, + "license": "MIT", + "optional": true, "dependencies": { - "glob": "^7.1.3" + "msgpackr": "^1.11.2", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.2.2", + "ordered-binary": "^1.5.3", + "weak-lru-cache": "^1.2.2" }, "bin": { - "rimraf": "bin.js" + "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.4.3", + "@lmdb/lmdb-darwin-x64": "3.4.3", + "@lmdb/lmdb-linux-arm": "3.4.3", + "@lmdb/lmdb-linux-arm64": "3.4.3", + "@lmdb/lmdb-linux-x64": "3.4.3", + "@lmdb/lmdb-win32-arm64": "3.4.3", + "@lmdb/lmdb-win32-x64": "3.4.3" } }, - "node_modules/rollup": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", - "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "node_modules/lmdb/node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.3", - "@rollup/rollup-android-arm64": "4.52.3", - "@rollup/rollup-darwin-arm64": "4.52.3", - "@rollup/rollup-darwin-x64": "4.52.3", - "@rollup/rollup-freebsd-arm64": "4.52.3", - "@rollup/rollup-freebsd-x64": "4.52.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", - "@rollup/rollup-linux-arm-musleabihf": "4.52.3", - "@rollup/rollup-linux-arm64-gnu": "4.52.3", - "@rollup/rollup-linux-arm64-musl": "4.52.3", - "@rollup/rollup-linux-loong64-gnu": "4.52.3", - "@rollup/rollup-linux-ppc64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-musl": "4.52.3", - "@rollup/rollup-linux-s390x-gnu": "4.52.3", - "@rollup/rollup-linux-x64-gnu": "4.52.3", - "@rollup/rollup-linux-x64-musl": "4.52.3", - "@rollup/rollup-openharmony-arm64": "4.52.3", - "@rollup/rollup-win32-arm64-msvc": "4.52.3", - "@rollup/rollup-win32-ia32-msvc": "4.52.3", - "@rollup/rollup-win32-x64-gnu": "4.52.3", - "@rollup/rollup-win32-x64-msvc": "4.52.3", - "fsevents": "~2.3.2" - } + "optional": true }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" }, "engines": { - "node": ">= 18" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, "engines": { "node": ">=18" }, @@ -12543,943 +7865,896 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "queue-microtask": "^1.2.2" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" } }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", "dependencies": { - "tslib": "^2.1.0" + "yallist": "^3.0.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" + "semver": "^7.5.3" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/sass": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", - "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", + "node_modules/make-fetch-happen": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", + "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "ssri": "^13.0.0" }, "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/sass-loader": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", - "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", + "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", "dev": true, - "license": "MIT", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", + "dev": true, + "license": "ISC", "dependencies": { - "neo-async": "^2.6.2" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "webpack": { - "optional": true - } + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "dev": true, - "license": "ISC", - "optional": true + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, "engines": { - "node": ">= 10.13.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/schema-utils/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" + "braces": "^3.0.3", + "picomatch": "^2.3.1" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": ">=8.6" } }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true, - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, + "optional": true, "engines": { - "node": ">=10" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", "bin": { - "semver": "bin/semver.js" + "mime": "cli.js" }, "engines": { - "node": ">=10" + "node": ">=4.0.0" } }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, "engines": { - "node": ">= 18" + "node": ">= 0.6" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "randombytes": "^2.1.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serve-index/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 0.6" + "node": "*" } }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">= 0.6" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 0.6" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/minipass-fetch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.0.tgz", + "integrity": "sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==", "dev": true, "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, "engines": { - "node": ">= 0.6" + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "node_modules/serve-index/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "mime-db": "1.52.0" + "minipass": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 8" } }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-index/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">= 18" + "node": ">=8" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "kind-of": "^6.0.2" + "minipass": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "shebang-regex": "^3.0.0" + "yallist": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "ISC" }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "minipass": "^7.1.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 18" } }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" + "minimist": "^1.2.6" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optional": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" } }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" + "node-gyp-build-optional-packages": "5.2.2" }, - "engines": { - "node": ">= 0.4" + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" } }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, "license": "ISC", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/sigstore": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", - "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" }, - "engines": { - "node": ">=12" + "bin": { + "needle": "bin/needle" }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "engines": { + "node": ">= 4.4.x" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" + "node": ">= 0.6" } }, - "node_modules/socket.io": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", - "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-gyp": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.1.0.tgz", + "integrity": "sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==", "dev": true, "license": "MIT", "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.6.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.2", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">=10.2.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/socket.io-adapter": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", - "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "debug": "~4.3.4", - "ws": "~8.17.1" + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "license": "Apache-2.0", + "optional": true, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=8" } }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, + "license": "ISC", "engines": { - "node": ">=10.0.0" + "node": ">=16" } }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/node-gyp/node_modules/proc-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", + "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, + "license": "ISC", "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/socket.io/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": ">= 0.6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ms": "^2.1.3" + "abbrev": "^4.0.0" }, - "engines": { - "node": ">=6.0" + "bin": { + "nopt": "bin/nopt.js" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/socket.io/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/socket.io/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/npm-bundled": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", + "integrity": "sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "mime-db": "1.52.0" + "npm-normalize-package-bin": "^4.0.0" }, "engines": { - "node": ">= 0.6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/socket.io/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/npm-install-checks": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, "engines": { - "node": ">= 0.6" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", "dev": true, - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "node_modules/npm-package-arg": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.1.tgz", + "integrity": "sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" + "hosted-git-info": "^9.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" }, "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "node_modules/npm-packlist": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.3.tgz", + "integrity": "sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" + "ignore-walk": "^8.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": ">= 14" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "node_modules/npm-packlist/node_modules/proc-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", + "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "engines": { - "node": ">= 12" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "node_modules/npm-pick-manifest": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", + "dependencies": { + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", + "semver": "^7.3.5" + }, "engines": { - "node": ">=0.10.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/source-map-loader": { + "node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", - "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" - }, + "license": "ISC", "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.72.1" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/npm-registry-fetch": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "@npmcli/redact": "^4.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^15.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/npm-registry-fetch/node_modules/proc-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.0.0.tgz", + "integrity": "sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==", "dev": true, - "license": "BSD-3-Clause", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, - "license": "Apache-2.0", + "license": "BSD-2-Clause", "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.22", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", - "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, - "license": "CC0-1.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" + "ee-first": "1.1.1" }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.8" } }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" + "wrappy": "1" } }, - "node_modules/ssri": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", - "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^7.0.3" + "mimic-function": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "node_modules/ora": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.0.0.tgz", + "integrity": "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==", "dev": true, "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.2.2", + "string-width": "^8.1.0", + "strip-ansi": "^7.1.2" + }, "engines": { - "node": ">= 0.8" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "node_modules/ordered-binary": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", + "integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", "engines": { @@ -13489,851 +8764,980 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/streamroller": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", - "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "node_modules/pacote": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.3.tgz", + "integrity": "sha512-itdFlanxO0nmQv4ORsvA9K1wv40IPfB9OmWqfaJWvoJ30VKyHsqNgDVeG+TVhI7Gk7XW8slUy7cA9r6dF5qohw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "date-format": "^4.0.14", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" + "@npmcli/git": "^7.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^4.0.0", + "ssri": "^12.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" }, "engines": { - "node": ">=8.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.10" } }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" + "entities": "^6.0.0" }, - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-8.0.0.tgz", + "integrity": "sha512-wzh11mj8KKkno1pZEu+l2EVeWsuKDfR5KNWZOTsslfUX8lPDZx77m9T0kIoAVkFtD1nx6YF8oh4BnPHvxMtNMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0", + "parse5": "^8.0.0", + "parse5-sax-parser": "^8.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/parse5-sax-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-8.0.0.tgz", + "integrity": "sha512-/dQ8UzHZwnrzs3EvDj6IkKrD/jIZyTlB+8XrHJvcjNgRdmWruNdN9i9RK/JtxakmlUdPwKubKPTCqvbTgzGhrw==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "parse5": "^8.0.0" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "ansi-regex": "^5.0.1" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", + "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=6" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/piscina": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-5.1.3.tgz", + "integrity": "sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=20.x" + }, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.4" } }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "node_modules/pkce-challenge": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=16.20.0" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=8" + "node": "^10 || ^12 || >=14" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "err-code": "^2.0.2", + "retry": "^0.12.0" }, "engines": { "node": ">=10" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { - "node": ">= 8" + "node": ">= 0.10" } }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT", + "optional": true, + "peer": true }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", "dev": true, "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, "engines": { - "node": ">= 8" + "node": ">=0.9" } }, - "node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dev": true, - "license": "ISC", + "license": "BSD-3-Clause", "dependencies": { - "yallist": "^4.0.0" + "side-channel": "^1.1.0" }, "engines": { - "node": ">=8" + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/terser": { - "version": "5.43.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.10" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">= 10.13.0" + "node": ">=0.10.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" + "url": "https://opencollective.com/express" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/thingies": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", - "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, "engines": { - "node": ">=10.18" + "node": ">= 0.4" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "^2" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.14" + "node": ">= 4" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", "dependencies": { - "is-number": "^7.0.0" + "glob": "^7.1.3" }, - "engines": { - "node": ">=8.0" + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/rolldown": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.47.tgz", + "integrity": "sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tree-dump": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", - "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" + "dependencies": { + "@oxc-project/types": "=0.96.0", + "@rolldown/pluginutils": "1.0.0-beta.47" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" + "bin": { + "rolldown": "bin/cli.mjs" }, - "peerDependencies": { - "tslib": "2" + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.47", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.47", + "@rolldown/binding-darwin-x64": "1.0.0-beta.47", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.47", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.47", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.47", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.47", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.47", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.47", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.47", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.47", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.47", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.47", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.47" } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "node_modules/rollup": { + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, "bin": { - "tree-kill": "cli.js" + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", + "fsevents": "~2.3.2" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tuf-js": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", - "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", "dependencies": { - "@tufjs/models": "3.0.1", - "debug": "^4.4.1", - "make-fetch-happen": "^14.0.3" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 18" } }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typed-assert": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", - "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.41", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", - "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "node_modules/sass": { + "version": "1.93.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz", + "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, "bin": { - "ua-parser-js": "script/cli.js" + "sass": "sass.js" }, "engines": { - "node": "*" + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, - "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "optional": true, "peer": true }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "license": "MIT", + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dev": true, "license": "MIT", "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">= 18" } }, - "node_modules/unicode-property-aliases-ecmascript": { + "node_modules/serve-static": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", - "dev": true, - "license": "ISC", "dependencies": { - "unique-slug": "^5.0.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 18" } }, - "node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4" + "shebang-regex": "^3.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">=8" } }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4.0.0" + "node": ">=8" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, - "bin": { - "update-browserslist-db": "cli.js" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "punycode": "^2.1.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/util-deprecate": { + "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/sigstore": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.0.0.tgz", + "integrity": "sha512-Gw/FgHtrLM9WP8P5lLcSGh9OQcrTruWCELAiS48ik1QbL0cH+dfjomiRTUE9zzz+D1N6rOLkwXUvVmXZAsNE0Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validate-npm-package-name": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", - "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", - "dev": true, - "license": "ISC", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.0.0", + "@sigstore/tuf": "^4.0.0", + "@sigstore/verify": "^3.0.0" + }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 6.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", "dev": true, "license": "MIT", "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" }, "engines": { - "node": ">=10.13.0" + "node": ">=10.2.0" } }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "license": "MIT", "dependencies": { - "minimalistic-assert": "^1.0.0" + "debug": "~4.3.4", + "ws": "~8.17.1" } }, - "node_modules/weak-lru-cache": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", - "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "node_modules/webpack": { - "version": "5.101.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.2.tgz", - "integrity": "sha512-4JLXU0tD6OZNVqlwzm3HGEhAHufSiyv+skb7q0d2367VDMzrU1Q/ZeepvkcHH0rZie6uqEtTQQe0OEOOluH3Mg==", + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" }, "engines": { - "node": ">=10.13.0" + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "engines": { + "node": ">=6.0" }, "peerDependenciesMeta": { - "webpack-cli": { + "supports-color": { "optional": true } } }, - "node_modules/webpack-dev-middleware": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", - "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "license": "MIT", "dependencies": { - "colorette": "^2.0.10", - "memfs": "^4.6.0", - "mime-types": "^2.1.31", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" }, - "peerDependencies": { - "webpack": "^5.0.0" + "engines": { + "node": ">=6.0" }, "peerDependenciesMeta": { - "webpack": { + "supports-color": { "optional": true } } }, - "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "node_modules/socket.io/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", @@ -14343,7 +9747,7 @@ "node": ">= 0.6" } }, - "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "node_modules/socket.io/node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", @@ -14356,641 +9760,593 @@ "node": ">= 0.6" } }, - "node_modules/webpack-dev-server": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", - "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.13", - "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.21", - "@types/express-serve-static-core": "^4.17.21", - "@types/serve-index": "^1.9.4", - "@types/serve-static": "^1.15.5", - "@types/sockjs": "^0.3.36", - "@types/ws": "^8.5.10", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.2.1", - "chokidar": "^3.6.0", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "express": "^4.21.2", - "graceful-fs": "^4.2.6", - "http-proxy-middleware": "^2.0.9", - "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", - "open": "^10.0.3", - "p-retry": "^6.2.0", - "schema-utils": "^4.2.0", - "selfsigned": "^2.4.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.4.2", - "ws": "^8.18.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/webpack-dev-server/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/webpack-dev-server/node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">= 14" } }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">= 12" } }, - "node_modules/webpack-dev-server/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/webpack-dev-server/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/webpack-dev-server/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, - "license": "MIT" + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } }, - "node_modules/webpack-dev-server/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/webpack-dev-server/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", "dev": true, - "license": "MIT" + "license": "CC0-1.0" }, - "node_modules/webpack-dev-server/node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "node_modules/ssri": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", + "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "minipass": "^7.0.3" }, "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/webpack-dev-server/node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, "engines": { "node": ">= 0.8" } }, - "node_modules/webpack-dev-server/node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webpack-dev-server/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" }, "engines": { - "node": ">= 6" + "node": ">=8.0" + } + }, + "node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "dev": true, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" + "node": ">=12" }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/webpack-dev-server/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/webpack-dev-server/node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/webpack-dev-server/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/tar": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/webpack-dev-server/node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" } }, - "node_modules/webpack-dev-server/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, "bin": { - "mime": "cli.js" + "terser": "bin/terser" }, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/webpack-dev-server/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=14.14" } }, - "node_modules/webpack-dev-server/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "is-number": "^7.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=8.0" } }, - "node_modules/webpack-dev-server/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.6" } }, - "node_modules/webpack-dev-server/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, - "license": "MIT" + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, - "node_modules/webpack-dev-server/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/tuf-js": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.0.0.tgz", + "integrity": "sha512-Lq7ieeGvXDXwpoSmOSgLWVdsGGV9J4a77oDTAPe/Ltrqnnm/ETaRlBAQTH5JatEh8KXuE6sddf9qAv1Q2282Hg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8.6" + "dependencies": { + "@tufjs/models": "4.0.0", + "debug": "^4.4.1", + "make-fetch-happen": "^15.0.0" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/webpack-dev-server/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "dependencies": { - "side-channel": "^1.0.6" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">=0.6" + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=14.17" } }, - "node_modules/webpack-dev-server/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bin": { + "ua-parser-js": "script/cli.js" }, "engines": { - "node": ">= 0.8" + "node": "*" } }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", "dev": true, "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">=20.18.1" } }, - "node_modules/webpack-dev-server/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/unique-filename": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", + "dev": true, + "license": "ISC", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "unique-slug": "^6.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/webpack-dev-server/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/unique-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", "dev": true, - "license": "MIT", + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, "engines": { - "node": ">= 0.8" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/webpack-dev-server/node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, "engines": { - "node": ">= 0.8.0" + "node": ">= 4.0.0" } }, - "node_modules/webpack-dev-server/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/webpack-dev-server/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, - "engines": { - "node": ">= 0.6" + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" } }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, "engines": { - "node": ">=18.0.0" + "node": ">=6" } }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.13.0" + "node": ">= 0.4.0" } }, - "node_modules/webpack-subresource-integrity": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", - "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "typed-assert": "^1.0.8" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", - "webpack": "^5.12.0" - }, - "peerDependenciesMeta": { - "html-webpack-plugin": { - "optional": true - } + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, - "node_modules/webpack/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "node_modules/validate-npm-package-name": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", + "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/webpack/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", "dev": true, "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">=0.8.0" + "node": ">=10.13.0" } }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } + "license": "MIT", + "optional": true }, "node_modules/which": { "version": "2.0.2", @@ -15008,13 +10364,6 @@ "node": ">= 8" } }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -15032,94 +10381,27 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=18" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/wrappy": { @@ -15151,22 +10433,6 @@ } } }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -15208,14 +10474,37 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/yargs/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -15245,13 +10534,13 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", "dev": true, "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25 || ^4" } }, "node_modules/zone.js": { diff --git a/docs/src/assets/stackblitz/package.json b/docs/src/assets/stackblitz/package.json index ff88a9f61d0c..adeef681e680 100644 --- a/docs/src/assets/stackblitz/package.json +++ b/docs/src/assets/stackblitz/package.json @@ -10,26 +10,25 @@ }, "private": true, "dependencies": { - "@angular/cdk": "^20.2.0", - "@angular/common": "^20.2.0", - "@angular/compiler": "^20.2.0", - "@angular/core": "^20.2.0", - "@angular/forms": "^20.2.0", - "@angular/localize": "^20.2.0", - "@angular/material": "^20.2.0", - "@angular/material-luxon-adapter": "^20.2.0", - "@angular/platform-browser": "^20.2.0", - "@angular/platform-browser-dynamic": "^20.2.0", - "@angular/router": "^20.2.0", + "@angular/cdk": "^21.0.0", + "@angular/common": "^21.0.0", + "@angular/compiler": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/forms": "^21.0.0", + "@angular/localize": "^21.0.0", + "@angular/material": "^21.0.0", + "@angular/material-luxon-adapter": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/router": "^21.0.0", "luxon": "^3.7.2", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^20.2.0", - "@angular/cli": "^20.2.0", - "@angular/compiler-cli": "^20.2.0", + "@angular/cli": "^21.0.0", + "@angular/build": "^21.0.0", + "@angular/compiler-cli": "^21.0.0", "@types/jasmine": "~5.1.0", "@types/luxon": "^3.0.0", "@types/node": "^12.11.1", diff --git a/docs/src/assets/stackblitz/src/test.ts b/docs/src/assets/stackblitz/src/test.ts deleted file mode 100644 index 0323f914c153..000000000000 --- a/docs/src/assets/stackblitz/src/test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -// This file is required by karma.conf.js and loads recursively all the .spec and framework files - -import 'zone.js/testing'; -import {getTestBed} from '@angular/core/testing'; -import { - BrowserDynamicTestingModule, - platformBrowserDynamicTesting, -} from '@angular/platform-browser-dynamic/testing'; - -// First, initialize the Angular testing environment. -getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); - -// Then we find all the tests. -const context = (import.meta as any).webpackContext('./', { - recursive: true, - regExp: /\.spec\.ts$/, -}); - -// And load the modules. -context.keys().map(context); diff --git a/docs/src/assets/stackblitz/tsconfig.spec.json b/docs/src/assets/stackblitz/tsconfig.spec.json index f140ce15bf10..47e3dd755170 100644 --- a/docs/src/assets/stackblitz/tsconfig.spec.json +++ b/docs/src/assets/stackblitz/tsconfig.spec.json @@ -5,6 +5,5 @@ "outDir": "./out-tsc/spec", "types": ["jasmine"] }, - "files": ["src/test.ts"], "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] } diff --git a/docs/src/styles.scss b/docs/src/styles.scss index aa13d737aeee..eb3ebfd8d76c 100644 --- a/docs/src/styles.scss +++ b/docs/src/styles.scss @@ -9,16 +9,19 @@ html { background-color: var(--mat-sys-surface); color: var(--mat-sys-on-surface); - @include mat.theme(( - color: ( - theme-type: light, - primary: mat.$azure-palette, - tertiary: mat.$blue-palette, - ), - typography: Roboto, - density: 0, - )); + @include mat.theme( + ( + color: ( + theme-type: light, + primary: mat.$azure-palette, + tertiary: mat.$blue-palette, + ), + typography: Roboto, + density: 0, + ) + ); + @include mat.system-classes(); @include cdk.a11y-visually-hidden(); } diff --git a/docs/src/styles/_markdown.scss b/docs/src/styles/_markdown.scss index 99c056889a22..51c8dc31be0d 100644 --- a/docs/src/styles/_markdown.scss +++ b/docs/src/styles/_markdown.scss @@ -44,7 +44,9 @@ font-weight: 400; } - p, ul, ol { + p, + ul, + ol { font-size: 16px; line-height: 28px; } @@ -118,4 +120,30 @@ } } + summary { + cursor: pointer; + padding: 8px; + + &:hover { + background-color: color-mix( + in srgb, + var(--mat-sys-on-surface) calc(var(--mat-sys-hover-state-layer-opacity) * 100%), + transparent + ); + } + &:focus { + background-color: color-mix( + in srgb, + var(--mat-sys-on-surface) calc(var(--mat-sys-focus-state-layer-opacity) * 100%), + transparent + ); + } + &:active { + background-color: color-mix( + in srgb, + var(--mat-sys-on-surface) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), + transparent + ); + } + } } diff --git a/docs/src/styles/_tables.scss b/docs/src/styles/_tables.scss index c9084474be8b..cf41ac2373ce 100644 --- a/docs/src/styles/_tables.scss +++ b/docs/src/styles/_tables.scss @@ -10,7 +10,6 @@ width: 100%; } - // Styles specific only to the table inside markdown. .docs-markdown > table { font-size: 14px; @@ -18,10 +17,10 @@ // Code tends to wrap inside tables which doesn't look great with the background color. code { background: transparent; + padding: 0; } } - .docs-api th, .docs-markdown > table th { max-width: 100px; diff --git a/goldens/cdk/a11y/index.api.md b/goldens/cdk/a11y/index.api.md index ea7039d26cd3..0a68560adbda 100644 --- a/goldens/cdk/a11y/index.api.md +++ b/goldens/cdk/a11y/index.api.md @@ -295,7 +295,7 @@ export interface Highlightable extends ListKeyManagerOption { // @public export class _IdGenerator { - getId(prefix: string): string; + getId(prefix: string, randomize?: boolean): string; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration<_IdGenerator, never>; // (undocumented) diff --git a/goldens/cdk/accordion/index.api.md b/goldens/cdk/accordion/index.api.md index bcb6931f8f80..1b1ed1b9af99 100644 --- a/goldens/cdk/accordion/index.api.md +++ b/goldens/cdk/accordion/index.api.md @@ -29,7 +29,9 @@ export class CdkAccordion implements OnDestroy, OnChanges { ngOnDestroy(): void; openAll(): void; readonly _openCloseAllActions: Subject; - readonly _stateChanges: Subject; + readonly _stateChanges: Subject<{ + [propName: string]: i0.SimpleChange; + }>; // (undocumented) static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) diff --git a/goldens/cdk/overlay/index.api.md b/goldens/cdk/overlay/index.api.md index d2537257ed04..5f83c88501c8 100644 --- a/goldens/cdk/overlay/index.api.md +++ b/goldens/cdk/overlay/index.api.md @@ -12,6 +12,7 @@ import { EmbeddedViewRef } from '@angular/core'; import { EnvironmentInjector } from '@angular/core'; import { EventEmitter } from '@angular/core'; import * as i0 from '@angular/core'; +import { InjectionToken } from '@angular/core'; import { Injector } from '@angular/core'; import { Location as Location_2 } from '@angular/common'; import { NgIterable } from '@angular/core'; @@ -37,6 +38,9 @@ export class BlockScrollStrategy implements ScrollStrategy { enable(): void; } +// @public +export const CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG: InjectionToken; + // @public export class CdkConnectedOverlay implements OnDestroy, OnChanges { constructor(...args: unknown[]); @@ -44,17 +48,18 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { attachOverlay(): void; backdropClass: string | string[]; readonly backdropClick: EventEmitter; + set _config(value: string | CdkConnectedOverlayConfig); readonly detach: EventEmitter; detachOverlay(): void; get dir(): Direction; disableClose: boolean; - get disposeOnNavigation(): boolean; - set disposeOnNavigation(value: boolean); + disposeOnNavigation: boolean; flexibleDimensions: boolean; growAfterOpen: boolean; hasBackdrop: boolean; height: number | string; lockPosition: boolean; + matchWidth: boolean; minHeight: number | string; minWidth: number | string; // (undocumented) @@ -68,6 +73,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { // (undocumented) static ngAcceptInputType_lockPosition: unknown; // (undocumented) + static ngAcceptInputType_matchWidth: unknown; + // (undocumented) static ngAcceptInputType_push: unknown; // (undocumented) ngOnChanges(changes: SimpleChanges): void; @@ -89,14 +96,65 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { push: boolean; scrollStrategy: ScrollStrategy; transformOriginSelector: string; + usePopover: FlexibleOverlayPopoverLocation | null; viewportMargin: ViewportMargin; width: number | string; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; } +// @public +export interface CdkConnectedOverlayConfig { + // (undocumented) + backdropClass?: string | string[]; + // (undocumented) + disableClose?: boolean; + // (undocumented) + disposeOnNavigation?: boolean; + // (undocumented) + flexibleDimensions?: boolean; + // (undocumented) + growAfterOpen?: boolean; + // (undocumented) + hasBackdrop?: boolean; + // (undocumented) + height?: number | string; + // (undocumented) + lockPosition?: boolean; + // (undocumented) + matchWidth?: boolean; + // (undocumented) + minHeight?: number | string; + // (undocumented) + minWidth?: number | string; + // (undocumented) + offsetX?: number; + // (undocumented) + offsetY?: number; + // (undocumented) + origin?: CdkOverlayOrigin | FlexibleConnectedPositionStrategyOrigin; + // (undocumented) + panelClass?: string | string[]; + // (undocumented) + positions?: ConnectedPosition[]; + // (undocumented) + positionStrategy?: FlexibleConnectedPositionStrategy; + // (undocumented) + push?: boolean; + // (undocumented) + scrollStrategy?: ScrollStrategy; + // (undocumented) + transformOriginSelector?: string; + // (undocumented) + usePopover?: FlexibleOverlayPopoverLocation | null; + // (undocumented) + viewportMargin?: ViewportMargin; + // (undocumented) + width?: number | string; +} + // @public export class CdkOverlayOrigin { constructor(...args: unknown[]); @@ -226,6 +284,10 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { // (undocumented) detach(): void; dispose(): void; + getPopoverInsertionPoint(): Element | null | { + type: 'parent'; + element: Element; + }; _origin: FlexibleConnectedPositionStrategyOrigin; positionChanges: Observable; get positions(): ConnectionPositionPair[]; @@ -237,6 +299,7 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { withFlexibleDimensions(flexibleDimensions?: boolean): this; withGrowAfterOpen(growAfterOpen?: boolean): this; withLockedPosition(isLocked?: boolean): this; + withPopoverLocation(location: FlexibleOverlayPopoverLocation): this; withPositions(positions: ConnectedPosition[]): this; withPush(canPush?: boolean): this; withScrollableContainers(scrollables: CdkScrollable[]): this; @@ -250,6 +313,12 @@ export type FlexibleConnectedPositionStrategyOrigin = ElementRef | Element | (Po height?: number; }); +// @public +export type FlexibleOverlayPopoverLocation = 'global' | 'inline' | { + type: 'parent'; + element: Element; +}; + // @public export class FullscreenOverlayContainer extends OverlayContainer implements OnDestroy { constructor(...args: unknown[]); @@ -315,6 +384,9 @@ export class Overlay { static ɵprov: i0.ɵɵInjectableDeclaration; } +// @public +export const OVERLAY_DEFAULT_CONFIG: InjectionToken; + // @public export class OverlayConfig { constructor(config?: OverlayConfig); @@ -331,6 +403,7 @@ export class OverlayConfig { panelClass?: string | string[]; positionStrategy?: PositionStrategy; scrollStrategy?: ScrollStrategy; + usePopover?: boolean; width?: number | string; } @@ -364,6 +437,12 @@ export class OverlayContainer implements OnDestroy { static ɵprov: i0.ɵɵInjectableDeclaration; } +// @public +export interface OverlayDefaultConfig { + // (undocumented) + usePopover?: boolean; +} + // @public export class OverlayKeyboardDispatcher extends BaseOverlayDispatcher { add(overlayRef: OverlayRef): void; @@ -461,6 +540,10 @@ export interface PositionStrategy { attach(overlayRef: OverlayRef): void; detach?(): void; dispose(): void; + getPopoverInsertionPoint?(): Element | null | { + type: 'parent'; + element: Element; + }; } // @public diff --git a/goldens/cdk/private/index.api.md b/goldens/cdk/private/index.api.md index 5ef9ad638b5c..dbe48ac4374c 100644 --- a/goldens/cdk/private/index.api.md +++ b/goldens/cdk/private/index.api.md @@ -16,6 +16,15 @@ export class _CdkPrivateStyleLoader { static ɵprov: i0.ɵɵInjectableDeclaration<_CdkPrivateStyleLoader>; } +// @public (undocumented) +export interface TrustedHTML { + // (undocumented) + __brand__: 'TrustedHTML'; +} + +// @public +export function trustedHTMLFromString(html: string): TrustedHTML; + // @public export class _VisuallyHiddenLoader { // (undocumented) diff --git a/goldens/material/expansion/index.api.md b/goldens/material/expansion/index.api.md index 65c13c024054..2f7066c0442c 100644 --- a/goldens/material/expansion/index.api.md +++ b/goldens/material/expansion/index.api.md @@ -95,7 +95,9 @@ export class MatExpansionPanel extends CdkAccordionItem implements AfterContentI _headerId: string; get hideToggle(): boolean; set hideToggle(value: boolean); - readonly _inputChanges: Subject; + readonly _inputChanges: Subject<{ + [propName: string]: i0.SimpleChange; + }>; _lazyContent: MatExpansionPanelContent; // (undocumented) static ngAcceptInputType_hideToggle: unknown; diff --git a/goldens/material/menu/testing/index.api.md b/goldens/material/menu/testing/index.api.md index 8cc4d315be89..43e5a2601eea 100644 --- a/goldens/material/menu/testing/index.api.md +++ b/goldens/material/menu/testing/index.api.md @@ -62,6 +62,7 @@ export class MatMenuItemHarness extends ContentContainerComponentHarness // @public export interface MenuHarnessFilters extends BaseHarnessFilters { + triggerIconName?: string | RegExp; triggerText?: string | RegExp; } diff --git a/goldens/material/select/index.api.md b/goldens/material/select/index.api.md index 2f02eff60d41..d0975c05859a 100644 --- a/goldens/material/select/index.api.md +++ b/goldens/material/select/index.api.md @@ -316,7 +316,7 @@ export class MatSelect implements AfterContentInit, OnChanges, OnDestroy, OnInit ngOnInit(): void; _onBlur(): void; _onChange: (value: any) => void; - onContainerClick(): void; + onContainerClick(event: MouseEvent): void; // (undocumented) _onFocus(): void; _onTouched: () => void; diff --git a/goldens/material/timepicker/index.api.md b/goldens/material/timepicker/index.api.md index d97326892cd0..29b17a051d43 100644 --- a/goldens/material/timepicker/index.api.md +++ b/goldens/material/timepicker/index.api.md @@ -46,6 +46,8 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent { readonly disabled: Signal; readonly disableRipple: InputSignalWithTransform; protected _getAriaLabelledby(): string | null; + // (undocumented) + _getOverlayHost(): HTMLElement | undefined; protected _handleAnimationEnd(event: AnimationEvent): void; readonly interval: InputSignalWithTransform; readonly isOpen: Signal; diff --git a/guides/theming-your-components.md b/guides/theming-your-components.md new file mode 100644 index 000000000000..d7532b4db11b --- /dev/null +++ b/guides/theming-your-components.md @@ -0,0 +1,1009 @@ + + +# Theming your components + +Angular Material provides two approaches to styling custom components to match the application's +theme: + +- **CSS variables** included by `mat.theme()`. These are prefixed with `--mat-sys` and can be used + in your component stylesheets to match the theme and design for your application. +- **Utility classes** included by `mat.system-classes()`. These utility classes wrap the CSS variables + and allow you to apply theme styles directly from your component templates. + +Both approaches are valid ways to ensure your application is consistently designed alongside Angular +Material's components. Also, by using the system tokens for applying color to your application, +you will automatically support both light and dark mode versions that can be set using the +`color-scheme` CSS property. + +This guide will focus on showing the implementation for the utility classes as a means to show +both the available classes and the underlying system token CSS variables used. + +If your application is based on the older Material Design 2 APIs with theme configs, you can still +use these CSS variables and classes. See +the [Material Design 2 Support](#material-design-2-support) section below to learn how to enable +them. + +## Colors + +Material Design uses color to create accessible, personal color schemes that communicate your +product's hierarchy, state, and brand. See Material +Design's [Color Roles](https://m3.material.io/styles/color/roles) page to learn more +about its use and purpose. + +Using the system tokens makes it easy to match the same color scheme of your application's theme. It +also makes it easy to ensure your application can correctly handle both light and dark mode +automatically. + +
+ Expand to view all available color system tokens + +```css +/* Primary */ +--mat-sys-primary +--mat-sys-on-primary +--mat-sys-primary-container +--mat-sys-on-primary-container +--mat-sys-primary-fixed +--mat-sys-on-primary-fixed +--mat-sys-on-primary-fixed-variant +--mat-sys-primary-fixed-dim +--mat-sys-inverse-primary + +/* Secondary */ +--mat-sys-secondary +--mat-sys-on-secondary +--mat-sys-secondary-container +--mat-sys-on-secondary-container +--mat-sys-secondary-fixed +--mat-sys-on-secondary-fixed +--mat-sys-on-secondary-fixed-variant +--mat-sys-secondary-fixed-dim + +/* Tertiary */ +--mat-sys-tertiary +--mat-sys-on-tertiary +--mat-sys-tertiary-container +--mat-sys-on-tertiary-container +--mat-sys-tertiary-fixed +--mat-sys-on-tertiary-fixed +--mat-sys-on-tertiary-fixed-variant +--mat-sys-tertiary-fixed-dim + +/* Error */ +--mat-sys-error +--mat-sys-on-error +--mat-sys-error-container +--mat-sys-on-error-container + +/* Surface */ +--mat-sys-surface +--mat-sys-on-surface +--mat-sys-on-surface-variant +--mat-sys-surface-bright +--mat-sys-surface-container +--mat-sys-surface-container-high +--mat-sys-surface-container-highest +--mat-sys-surface-container-low +--mat-sys-surface-container-lowest +--mat-sys-surface-dim +--mat-sys-surface-tint +--mat-sys-surface-variant +--mat-sys-inverse-surface +--mat-sys-inverse-on-surface + +/* Miscellaneous */ +--mat-sys-background +--mat-sys-on-background +--mat-sys-neutral-variant20 +--mat-sys-neutral10 +--mat-sys-outline +--mat-sys-outline-variant +--mat-sys-scrim +--mat-sys-shadow +``` +
+ +### Background + +

Primary

+ +A primary background is useful for key components across the UI, such as buttons that have greater +importance on the page. In Angular Material, this is used for the selected date in a datepicker, the +handle of a slider, and the background of a checkbox. + +Text and icons should use the `on-primary` system color token ensure good contrast and +accessibility (see [Text](#text) below). + +
+.mat-bg-primary {
+  background-color: var(--mat-sys-primary);
+}
+
+ +A primary container color background is useful for filling components that should stand out on a +surface. In Angular Material, this is used for the container of a floating action button. + +
+.mat-bg-primary-container {
+  background-color: var(--mat-sys-primary-container);
+}
+
+ +

Secondary

+ +A secondary background is useful for less prominent components in the UI that have a different color +scheme than the primary. + +Text and icons should use the `on-secondary` system color token ensure good contrast and +accessibility (see [Text](#text) below). + +
+.mat-bg-secondary {
+  background-color: var(--mat-sys-secondary);
+}
+
+ +A secondary container color background is useful for components that need less emphasis than +secondary, such as filter chips. In Angular Material, this is used for selected items in a list and +the container of a tonal button. + +
+.mat-bg-secondary-container {
+  background-color: var(--mat-sys-secondary-container);
+}
+
+ +

Error

+ +An error background is useful for indicating an error state, such as an invalid text field, or for +the background of an important notification. In Angular Material, this is used for the background of +a badge. + +Text and icons should use the `on-error` system color token ensure good contrast and accessibility ( +see [Text](#text) below). + +
+.mat-bg-error {
+  background-color: var(--mat-sys-error);
+}
+
+ +An error container color background is useful for components that need less emphasis than error, +such as a container for error text. + +
+.mat-bg-error-container {
+  background-color: var(--mat-sys-error-container);
+}
+
+ +

Surfaces

+ +When using surface backgrounds, text and icons should use the `on-surface` or `on-surface-variant` +system color tokens ensure good contrast and accessibility (see [Text](#text) below). + +A surface background is useful for general surfaces of components. In Angular Material, this is used +for the background of many components, like tables, dialogs, menus, and toolbars. + +
+.mat-bg-surface {
+  background-color: var(--mat-sys-surface);
+}
+
+ +A surface variant background is useful for surfaces that need to stand out from the main surface +color. In Angular Material, this is used for the background of a filled form field and the track of +a progress bar. + +
+.mat-bg-surface-variant {
+  background-color: var(--mat-sys-surface-variant);
+}
+
+ +The "highest" surface container background is useful for surfaces that need the most emphasis +against the main surface color. In Angular Material, this is used for the background of a filled +card. + +
+.mat-bg-surface-container-highest {
+  background-color: var(--mat-sys-surface-container-highest);
+}
+
+ +A "high" surface container background is useful for surfaces that need more emphasis against the +main surface color. In Angular Material, this is used for the background of a datepicker. + +
+.mat-bg-surface-container-high {
+  background-color: var(--mat-sys-surface-container-high);
+}
+
+ +A surface container background is useful for surfaces that need to stand out from the main surface +color. In Angular Material, this is used for the background of a menu. + +
+.mat-bg-surface-container {
+  background-color: var(--mat-sys-surface-container);
+}
+
+ +A "low" surface container background is useful for surfaces that need less emphasis against the main +surface color. In Angular Material, this is used for the background of a bottom sheet. + +
+.mat-bg-surface-container-low {
+  background-color: var(--mat-sys-surface-container-low);
+}
+
+ +The "lowest" surface container background is useful for surfaces that need the least emphasis +against the main surface color. + +
+.mat-bg-surface-container-lowest {
+  background-color: var(--mat-sys-surface-container-lowest);
+}
+
+ +An inverse surface color background is useful for making elements stand out against the default +color scheme. It is good for temporary notifications that appear above your content. In Angular +Material, this is used for the background of a snackbar and a tooltip. + +When using the inverse surface background, text and icons should use the `inverse-on-surface` system +color token ensure good contrast and accessibility (see [Text](#text) below). + +
+.mat-bg-inverse-surface {
+  background-color: var(--mat-sys-inverse-surface);
+}
+
+ +

Disabled

+ +A disabled color background is useful for disabled components. In Angular Material, this is used for +components generally filled with the primary color but are currently disabled. + +In Angular Material, text and icons often use a 38% color mix of the surface color to strongly +convey +the disabled state. This is described with the `mat-text-disabled` class in [Text](#text)). + +
+.mat-bg-disabled {
+  background-color: color-mix(in srgb, var(--mat-sys-on-surface) 12%, transparent);
+}
+
+ +### Text + +

Primary

+ +Use the primary color for text that needs to stand out. In Angular Material, this is used for the +text of a text button and the selected tab label. + +
+.mat-text-primary {
+  color: var(--mat-sys-primary);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

Secondary

+ +Use the secondary color for text that needs to stand out apart from the main theme color. + +
+.mat-text-secondary {
+  color: var(--mat-sys-secondary);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

Error

+ +Use the error color for text that indicates an issue or warning, such as validation messages. In +Angular Material, this is used for the error text in a form field. + +
+.mat-text-error {
+  color: var(--mat-sys-error);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

Disabled

+ +Use the disabled text color for elements that are disabled on either the disabled or surface +background. + +
+.mat-text-disabled {
+  color: color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

On Surface Variant

+ +Use the on-surface-variant color for text that should have a lower emphasis than the surrounding +text. This can include subheading, captions, and hint text. + +
+.mat-text-on-surface-variant {
+  color: var(--mat-sys-on-surface-variant);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

On Primary

+ +Use the on-primary color for text and icons appearing on primary backgrounds to ensure good contrast +and accessibility. + +
+.mat-text-on-primary {
+  color: var(--mat-sys-on-primary);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

On Primary Container

+ +Use the on-primary-container color for text and icons appearing on primary-container backgrounds to +ensure good contrast and accessibility. + +
+.mat-text-on-primary-container {
+  color: var(--mat-sys-on-primary-container);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

On Secondary

+ +Use the on-secondary color for text and icons appearing on secondary backgrounds to ensure good +contrast and accessibility. + +
+.mat-text-on-secondary {
+  color: var(--mat-sys-on-secondary);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

On Secondary Container

+ +Use the on-secondary-container color for text that contrasts well against a secondary-container +background. + +
+.mat-text-on-secondary-container {
+  color: var(--mat-sys-on-secondary-container);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

On Error

+ +Use the on-error color for text that contrasts well against an error background. + +
+.mat-text-on-error {
+  color: var(--mat-sys-on-error);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

On Error Container

+ +Use the on-error-container color for text that contrasts well against an error-container background. + +
+.mat-text-on-error-container {
+  color: var(--mat-sys-on-error-container);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

On Surface

+ +Use the on-surface color for text that contrasts well against a surface background. + +
+.mat-text-on-surface {
+  color: var(--mat-sys-on-surface);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

Inverse On Surface

+ +Use the inverse-on-surface color for text that contrasts well against an inverse-surface background. + +
+.mat-text-inverse-on-surface {
+  color: var(--mat-sys-inverse-on-surface);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +## Typography + +Material Design provides five categories of font types: body, display, headline, label, and title. +Each category has three sizes: small, medium, and large. Learn more about how these categories and +their sizes should be used in your application by visiting Material +Design's [Applying Type](https://m3.material.io/styles/typography/applying-type) documentation. + +
+ Expand to view all available typography system tokens + +```css +--mat-sys-body-large +--mat-sys-body-large-font +--mat-sys-body-large-line-height +--mat-sys-body-large-size +--mat-sys-body-large-tracking +--mat-sys-body-large-weight + +--mat-sys-body-medium +--mat-sys-body-medium-font +--mat-sys-body-medium-line-height +--mat-sys-body-medium-size +--mat-sys-body-medium-tracking +--mat-sys-body-medium-weight + +--mat-sys-body-small +--mat-sys-body-small-font +--mat-sys-body-small-line-height +--mat-sys-body-small-size +--mat-sys-body-small-tracking +--mat-sys-body-small-weight + +--mat-sys-display-large +--mat-sys-display-large-font +--mat-sys-display-large-line-height +--mat-sys-display-large-size +--mat-sys-display-large-tracking +--mat-sys-display-large-weight + +--mat-sys-display-medium +--mat-sys-display-medium-font +--mat-sys-display-medium-line-height +--mat-sys-display-medium-size +--mat-sys-display-medium-tracking +--mat-sys-display-medium-weight + +--mat-sys-display-small +--mat-sys-display-small-font +--mat-sys-display-small-line-height +--mat-sys-display-small-size +--mat-sys-display-small-tracking +--mat-sys-display-small-weight + +--mat-sys-headline-large +--mat-sys-headline-large-font +--mat-sys-headline-large-line-height +--mat-sys-headline-large-size +--mat-sys-headline-large-tracking +--mat-sys-headline-large-weight + +--mat-sys-headline-medium +--mat-sys-headline-medium-font +--mat-sys-headline-medium-line-height +--mat-sys-headline-medium-size +--mat-sys-headline-medium-tracking +--mat-sys-headline-medium-weight + +--mat-sys-headline-small +--mat-sys-headline-small-font +--mat-sys-headline-small-line-height +--mat-sys-headline-small-size +--mat-sys-headline-small-tracking +--mat-sys-headline-small-weight + +--mat-sys-label-large +--mat-sys-label-large-font +--mat-sys-label-large-line-height +--mat-sys-label-large-size +--mat-sys-label-large-tracking +--mat-sys-label-large-weight +--mat-sys-label-large-weight-prominent + +--mat-sys-label-medium +--mat-sys-label-medium-font +--mat-sys-label-medium-line-height +--mat-sys-label-medium-size +--mat-sys-label-medium-tracking +--mat-sys-label-medium-weight +--mat-sys-label-medium-weight-prominent + +--mat-sys-label-small +--mat-sys-label-small-font +--mat-sys-label-small-line-height +--mat-sys-label-small-size +--mat-sys-label-small-tracking +--mat-sys-label-small-weight + +--mat-sys-title-large +--mat-sys-title-large-font +--mat-sys-title-large-line-height +--mat-sys-title-large-size +--mat-sys-title-large-tracking +--mat-sys-title-large-weight + +--mat-sys-title-medium +--mat-sys-title-medium-font +--mat-sys-title-medium-line-height +--mat-sys-title-medium-size +--mat-sys-title-medium-tracking +--mat-sys-title-medium-weight + +--mat-sys-title-small +--mat-sys-title-small-font +--mat-sys-title-small-line-height +--mat-sys-title-small-size +--mat-sys-title-small-tracking +--mat-sys-title-small-weight +``` +
+ +

Body

+ + +The small body typeface is useful for captions. In Angular Material, this is used for the subscript +text in a form field and the text in a paginator. + +
+.mat-font-body-sm {
+  font: var(--mat-sys-body-small);
+  letter-spacing: var(--mat-sys-body-small-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The medium body typeface is the default body font. In Angular Material, this is used for the text in +a table row and the supporting text in a dialog. + +
+.mat-font-body-md {
+  font: var(--mat-sys-body-medium);
+  letter-spacing: var(--mat-sys-body-medium-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The large body typeface is useful for an introductory paragraph. In Angular Material, this is used +for the text in a list item and the text in a select trigger. + +
+.mat-font-body-lg {
+  font: var(--mat-sys-body-large);
+  letter-spacing: var(--mat-sys-body-large-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

Display

+ + +The small display typeface is useful for short, important text or numerals, such as in hero sections +or for marking key information. + +
+.mat-font-display-sm {
+  font: var(--mat-sys-display-small);
+  letter-spacing: var(--mat-sys-display-small-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The medium display typeface is useful for short, impactful text in hero sections or titles on larger +screens. + +
+.mat-font-display-md {
+  font: var(--mat-sys-display-medium);
+  letter-spacing: var(--mat-sys-display-medium-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The large display typeface is useful for short, high-emphasis text in hero sections or titles on +larger screens, providing the most visual weight. + +
+.mat-font-display-lg {
+  font: var(--mat-sys-display-large);
+  letter-spacing: var(--mat-sys-display-large-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

Headline

+ + +The small headline typeface is useful for a page title. In Angular Material, this is used for the +headline in a dialog. + +
+.mat-font-headline-sm {
+  font: var(--mat-sys-headline-small);
+  letter-spacing: var(--mat-sys-headline-small-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The medium headline typeface is useful for a section title. + +
+.mat-font-headline-md {
+  font: var(--mat-sys-headline-medium);
+  letter-spacing: var(--mat-sys-headline-medium-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The large headline typeface is useful for a page title on a large screen. + +
+.mat-font-headline-lg {
+  font: var(--mat-sys-headline-large);
+  letter-spacing: var(--mat-sys-headline-large-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

Label

+ + +The small label typeface is useful for text in a badge. + +
+.mat-font-label-sm {
+  font: var(--mat-sys-label-small);
+  letter-spacing: var(--mat-sys-label-small-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The medium label typeface is useful for the slider label. + +
+.mat-font-label-md {
+  font: var(--mat-sys-label-medium);
+  letter-spacing: var(--mat-sys-label-medium-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The large label typeface is useful for buttons, chips, and menu labels. + +
+.mat-font-label-lg {
+  font: var(--mat-sys-label-large);
+  letter-spacing: var(--mat-sys-label-large-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +

Title

+ + +The small title typeface is useful for a card title. In Angular Material, this is used for the +header of a table and the label of an option group. + +
+.mat-font-title-sm {
+  font: var(--mat-sys-title-small);
+  letter-spacing: var(--mat-sys-title-small-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The medium title typeface is useful for a dialog title or the primary text in a list item. In +Angular Material, this is used for the subtitle of a card and the header of an expansion panel. + +
+.mat-font-title-md {
+  font: var(--mat-sys-title-medium);
+  letter-spacing: var(--mat-sys-title-medium-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +The large title typeface is useful for a page title on a small screen. In Angular Material, this is +used for the title of a card and the title of a toolbar. + +
+.mat-font-title-lg {
+  font: var(--mat-sys-title-large);
+  letter-spacing: var(--mat-sys-title-large-tracking);
+}
+
+ +
Example text: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ +## Shape + +Material Design uses border radius to help direct attention, identify components, communicate state, +and express brand. See Material +Design's [Corner Radius Scale](https://m3.material.io/styles/shape/corner-radius-scale) +documentation to learn more. + +In Angular Material, shape is scoped to varying levels of border-radius. The following code blocks +demonstrate the levels of roundness. Their border sizes are increased to `2px` to clearly show the +border radii. + +
+ Expand to view all available corner shape system tokens + +```css +--mat-sys-corner-extra-large: 28px; +--mat-sys-corner-extra-large-top: 28px 28px 0 0; +--mat-sys-corner-extra-small: 4px; +--mat-sys-corner-extra-small-top: 4px 4px 0 0; +--mat-sys-corner-full: 9999px; +--mat-sys-corner-large: 16px; +--mat-sys-corner-large-end: 0 16px 16px 0; +--mat-sys-corner-large-start: 16px 0 0 16px; +--mat-sys-corner-large-top: 16px 16px 0 0; +--mat-sys-corner-medium: 12px; +--mat-sys-corner-none: 0; +--mat-sys-corner-small: 8px; +``` +
+ +The extra small border radius is useful for components that need a small amount of rounding, such as +a chip. In Angular Material, this is used for the shape of a snackbar and a tooltip. + +
+.mat-corner-xs {
+  border-radius: var(--mat-sys-corner-extra-small);
+}
+
+The small border radius is useful for components that need a small amount of rounding, such as a +text field. + +
+.mat-corner-sm {
+  border-radius: var(--mat-sys-corner-small);
+}
+
+The medium border radius is useful for components that need a medium amount of rounding, such as a +button. In Angular Material, this is used for the shape of a card. + +
+.mat-corner-md {
+  border-radius: var(--mat-sys-corner-medium);
+}
+
+The large border radius is useful for components that need a large amount of rounding, such as a +card. In Angular Material, this is used for the shape of a floating action button and a datepicker. + +
+.mat-corner-lg {
+  border-radius: var(--mat-sys-corner-large);
+}
+
+The extra large border radius is useful for components that need a large amount of rounding. In +Angular Material, this is used for the shape of a button toggle and the shape of a dialog. + +
+.mat-corner-xl {
+  border-radius: var(--mat-sys-corner-extra-large);
+}
+
+The full border radius is useful for components that are circular, such as a user avatar. In Angular +Material, this is used for the shape of a badge and the shape of a button. + +
+.mat-corner-full {
+  border-radius: var(--mat-sys-corner-full);
+}
+
+ +## Elevation + +Material Design uses borders and shadows to create a sense of depth and hierarchy in the UI. See +Material Design's [Applying Elevation](https://m3.material.io/styles/elevation/applying-elevation) +documentation to learn more. + +

Border

+ +
+ Expand to view the outline system tokens + +```css +--mat-sys-outline +--mat-sys-outline-variant +``` +
+ +The Material Design border is useful for components that need a visible boundary. In Angular +Material, this is used for the outline of an outlined button. + +
+.mat-border {
+  border: 1px solid var(--mat-sys-outline);
+}
+
+ +The subtle outline variant is useful for components that need a less obvious boundary. In Angular +Material, this is used for the outline of an outlined card and the color of the divider. + +
+.mat-border-subtle {
+  border: 1px solid var(--mat-sys-outline-variant);
+}
+
+ +

Shadow

+ +
+ Expand to view all available elevation system tokens + +```css +--mat-sys-level0: 0px 0px 0px 0px rgba(0, 0, 0, .2), 0px 0px 0px 0px rgba(0, 0, 0, .14), 0px 0px 0px 0px rgba(0, 0, 0, .12); +--mat-sys-level1: 0px 2px 1px -1px rgba(0, 0, 0, .2), 0px 1px 1px 0px rgba(0, 0, 0, .14), 0px 1px 3px 0px rgba(0, 0, 0, .12); +--mat-sys-level2: 0px 3px 3px -2px rgba(0, 0, 0, .2), 0px 3px 4px 0px rgba(0, 0, 0, .14), 0px 1px 8px 0px rgba(0, 0, 0, .12); +--mat-sys-level3: 0px 3px 5px -1px rgba(0, 0, 0, .2), 0px 6px 10px 0px rgba(0, 0, 0, .14), 0px 1px 18px 0px rgba(0, 0, 0, .12); +--mat-sys-level4: 0px 5px 5px -3px rgba(0, 0, 0, .2), 0px 8px 10px 1px rgba(0, 0, 0, .14), 0px 3px 14px 2px rgba(0, 0, 0, .12); +--mat-sys-level5: 0px 7px 8px -4px rgba(0, 0, 0, .2), 0px 12px 17px 2px rgba(0, 0, 0, .14), 0px 5px 22px 4px rgba(0, 0, 0, .12); +``` +
+ +Level 1 elevation can be used to slightly raise the appearance of a surface. In Angular Material, +this is used for the elevation of an elevated card and the handle of a slider. + +
+.mat-shadow-1 {
+  box-shadow: var(--mat-sys-level1);
+}
+
+ +Level 2 elevation can be used to raise the appearance of a surface. In Angular Material, this is +used for the elevation of a menu and a select panel. + +
+.mat-shadow-2 {
+  box-shadow: var(--mat-sys-level2);
+}
+
+ +Level 3 elevation is used to raise the appearance of a surface. In Angular Material, this is used +for the elevation of a floating action button. + +
+.mat-shadow-3 {
+  box-shadow: var(--mat-sys-level3);
+}
+
+ +Level 4 elevation is generally reserved for elevation changes due to interaction like focus and +hover. In Angular Material, this is used for the elevation of a hovered floating action button. + +
+.mat-shadow-4 {
+  box-shadow: var(--mat-sys-level4);
+}
+
+ +Level 5 elevation is used to greatly raise the appearance of a surface and is generally reserved for +elevation changes due to interaction like focus and hover. + +
+.mat-shadow-5 {
+  box-shadow: var(--mat-sys-level5);
+}
+
+ +## Material Design 2 Support + +This guide is compatible for applications defining their theme with the "m2" Sass APIs using +the legacy theme-config approach. To take advantage of CSS variables and the utility classes, +you can call `@include mat.m2-theme($theme)` in your theme file, which will define +system tokens according to the Material Design 2 system that matches your current theme +configuration. + +```scss +@use '@angular/material' as mat; + +$theme: mat.m2-define-light-theme(( + color: ( + primary: mat.define-palette(mat.$indigo-palette, 500), + ), + ... +)); + +html { + @include mat.core-theme($theme); + @include mat.button-theme($theme); + ... + @include mat.m2-theme($theme); + @include mat.system-classes(); +} +``` + +By using CSS variables and utility classes, you can avoid creating component-specific theme files +and mixins that extract values from the `$theme` Sass map. For example, consider the following +example of how styles used to be applied in custom components: + +```scss +@use '@angular/material' as mat; +@use 'sass:map'; + +@mixin my-component-theme($theme) { + $foreground: map.get(mat.m2-get-color-config($theme), foreground); + $background: map.get(mat.m2-get-color-config($theme), background); + $primary: map.get(mat.m2-get-color-config($theme), primary); + $typography: mat.m2-get-typography-config($config-or-theme); + + .widget-a { + background-color: mat.m2-get-color-from-palette($background, card); + color: mat.m2-get-color-from-palette($foreground, text); + @include mat.m2-typography-level($config, body-1); + } + + .widget-b { + background-color: mat.m2-get-color-from-palette($primary, default); + color: mat.m2-get-color-from-palette($primary, default-contrast); + } +} +``` + +By using the CSS variables, you can define these theme styles in your component's +stylesheet and avoid creating a separate theme file or mixin: + +```scss +.widget-a { + background-color: var(--mat-sys-surface); + color: var(--mat-sys-on-surface); + font: var(--mat-sys-body-medium); +} + +.widget-b { + background-color: var(--mat-sys-primary); + color: var(--mat-sys-on-primary); +} +``` + +Taking it one step further, you can alternatively use the utility classes to achieve the same styles +in the component template: + +```html + + + +``` diff --git a/guides/theming.md b/guides/theming.md index 78a285cc62a3..13188ce4cb1e 100644 --- a/guides/theming.md +++ b/guides/theming.md @@ -58,6 +58,20 @@ body { } ``` +You can also define a convenient set of CSS utility classes that let you apply +theme styles from your component templates. + +```scss +html { + ... + @include mat.system-classes(); +} +``` + +```html + +``` + The `mat.theme` mixin will only declare CSS variables for the categories included in the input. For example, if `typography` is not defined, then typography CSS variables will not be included in the output. @@ -357,7 +371,7 @@ typography variables to create an application-wide banner presenting important information to the user: ```scss -:host { +.my-component { background: var(--mat-sys-primary-container); color: var(--mat-sys-on-primary-container); border: 1px solid var(--mat-sys-outline-variant); @@ -365,8 +379,14 @@ information to the user: } ``` -See the [Theme Variables](https://material.angular.dev/guide/system-variables) guide for a -comprehensive list of these variables, examples of where they are used, and how +Alternatively, you can use utility classes to achieve the same styles: + +```html +
+``` + +See the [Theming your components](https://material.angular.dev/guide/theming-your-components) guide for a +comprehensive list of these variables and classes, including examples of where they are used, and how components can depend on them. ## Customizing Tokens @@ -458,6 +478,81 @@ structure and CSS classes are considered private implementation details that may change at any time. CSS variables used by the Angular Material components should be defined through the `overrides` API instead of defined explicitly. +## Strong focus indicators + +By default, most components indicate browser focus by changing their background color as described +by the Material Design specification. This behavior, however, can fall short of accessibility +requirements, such as [WCAG 4.5:1][], which require a stronger indication of browser focus. + +Angular Material supports rendering highly visible outlines on focused elements. Applications can +enable these strong focus indicators via two Sass mixins: +`strong-focus-indicators` and `strong-focus-indicators-theme`. + +The `strong-focus-indicators` mixin emits structural indicator styles for all components. This mixin +should be included exactly once in an application, similar to the `core` mixin described above. + +The `strong-focus-indicators-theme` mixin emits only the indicator's color styles. This mixin should +be included once per theme, similar to the theme mixins described above. Additionally, you can use +this mixin to change the color of the focus indicators in situations in which the default color +would not contrast sufficiently with the background color. + +The following example includes strong focus indicator styles in an application alongside the rest of +the custom theme API. + +```scss +@use '@angular/material' as mat; + +$my-theme: ( + color: mat.$violet-palette, + typography: Roboto, + density: 0 +); + +@include mat.strong-focus-indicators(); + +html { + color-scheme: light dark; + @include mat.theme($my-theme); + @include mat.strong-focus-indicators-theme($my-theme); +} +``` + +### Customizing strong focus indicators + +You can pass a configuration map to `strong-focus-indicators` to customize the appearance of the +indicators. This configuration includes `border-style`, `border-width`, and `border-radius`. + +You also can customize the color of indicators with `strong-focus-indicators-theme`. This mixin +accepts either a theme, as described earlier in this guide, or a CSS color value. When providing a +theme, the indicators will use the default hue of the primary palette. + +The following example includes strong focus indicator styles with custom settings alongside the rest +of the custom theme API. + +```scss +@use '@angular/material' as mat; + +@include mat.strong-focus-indicators(( + border-style: dotted, + border-width: 4px, + border-radius: 2px, +)); + +html { + color-scheme: light dark; + + @include mat.theme(( + color: mat.$rose-palette, + typography: Roboto, + density: 0 + )); + + @include mat.strong-focus-indicators-theme(orange); +} +``` + +[WCAG]: https://www.w3.org/WAI/standards-guidelines/wcag/glance/ + ## Shadow DOM Angular Material assumes that, by default, all theme styles are loaded as global diff --git a/integration/harness-e2e-cli/pnpm-workspace.yaml b/integration/harness-e2e-cli/pnpm-workspace.yaml index fcf4541cbe97..d37afeb21e69 100644 --- a/integration/harness-e2e-cli/pnpm-workspace.yaml +++ b/integration/harness-e2e-cli/pnpm-workspace.yaml @@ -1,3 +1,5 @@ +packages: + - . # The minimum age of a release to be considered for dependency installation. # The value is in minutes (1440 minutes = 1 day). diff --git a/integration/ng-add-standalone/pnpm-workspace.yaml b/integration/ng-add-standalone/pnpm-workspace.yaml index fcf4541cbe97..d37afeb21e69 100644 --- a/integration/ng-add-standalone/pnpm-workspace.yaml +++ b/integration/ng-add-standalone/pnpm-workspace.yaml @@ -1,3 +1,5 @@ +packages: + - . # The minimum age of a release to be considered for dependency installation. # The value is in minutes (1440 minutes = 1 day). diff --git a/integration/ng-add/pnpm-workspace.yaml b/integration/ng-add/pnpm-workspace.yaml index fcf4541cbe97..d37afeb21e69 100644 --- a/integration/ng-add/pnpm-workspace.yaml +++ b/integration/ng-add/pnpm-workspace.yaml @@ -1,3 +1,5 @@ +packages: + - . # The minimum age of a release to be considered for dependency installation. # The value is in minutes (1440 minutes = 1 day). diff --git a/package.json b/package.json index dfa4439b9dda..7ecb5b251f78 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "url": "https://github.com/angular/components.git" }, "license": "MIT", - "packageManager": "pnpm@10.18.3", + "packageManager": "pnpm@10.23.0", "engines": { "npm": "Please use pnpm instead of NPM to install dependencies", "yarn": "Please use pnpm instead of Yarn to install dependencies", - "pnpm": "10.18.3" + "pnpm": "10.23.0" }, "scripts": { "ng-dev": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only node_modules/@angular/ng-dev/bundles/cli.mjs", @@ -53,7 +53,7 @@ "ci-docs-monitor-test": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only scripts/docs-deploy/monitoring/ci-test.mts", "prepare": "husky" }, - "version": "21.0.0-next.9", + "version": "21.0.1", "dependencies": { "@angular-devkit/core": "catalog:", "@angular-devkit/schematics": "catalog:", @@ -76,7 +76,7 @@ "devDependencies": { "@angular/compiler-cli": "catalog:", "@angular/localize": "catalog:", - "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#3c4fd6f54f2c67ce5b9f1a32ce90e36ba2fada4e", + "@angular/ng-dev": "https://github.com/angular/dev-infra-private-ng-dev-builds.git#4c28145df03aff8c74d0a53f4f5602140e4d1a23", "@angular/platform-server": "catalog:", "@angular/router": "catalog:", "@babel/core": "^7.16.12", @@ -147,6 +147,7 @@ "tsutils": "^3.21.0", "typescript": "5.9.2", "vrsource-tslint-rules": "6.0.0", + "yaml": "^2.8.1", "yargs": "^18.0.0", "zx": "^8.0.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3554c443a74a..24cd3c46e4ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,53 +7,53 @@ settings: catalogs: default: '@angular-devkit/build-angular': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular-devkit/core': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular-devkit/schematics': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/cli': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/common': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/compiler': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/compiler-cli': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/core': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/forms': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/localize': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/platform-browser': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/platform-browser-dynamic': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/platform-server': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/router': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@angular/ssr': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 '@schematics/angular': - specifier: 21.0.0-next.8 - version: 21.0.0-next.8 + specifier: 21.0.1 + version: 21.0.1 rxjs: specifier: ^6.6.7 version: 6.6.7 @@ -69,25 +69,25 @@ importers: dependencies: '@angular-devkit/core': specifier: 'catalog:' - version: 21.0.0-next.8(chokidar@4.0.3) + version: 21.0.1(chokidar@4.0.3) '@angular-devkit/schematics': specifier: 'catalog:' - version: 21.0.0-next.8(chokidar@4.0.3) + version: 21.0.1(chokidar@4.0.3) '@angular/common': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) + version: 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) '@angular/compiler': specifier: 'catalog:' - version: 21.0.0-next.8 + version: 21.0.1 '@angular/core': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) + version: 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) '@angular/forms': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + version: 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) '@angular/platform-browser': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)) + version: 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)) '@types/google.maps': specifier: ^3.54.10 version: 3.58.1 @@ -121,22 +121,22 @@ importers: devDependencies: '@angular/compiler-cli': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2) + version: 21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2) '@angular/localize': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8) + version: 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1) '@angular/ng-dev': - specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#3c4fd6f54f2c67ce5b9f1a32ce90e36ba2fada4e - version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/3c4fd6f54f2c67ce5b9f1a32ce90e36ba2fada4e(@modelcontextprotocol/sdk@1.20.0) + specifier: https://github.com/angular/dev-infra-private-ng-dev-builds.git#4c28145df03aff8c74d0a53f4f5602140e4d1a23 + version: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/4c28145df03aff8c74d0a53f4f5602140e4d1a23(@modelcontextprotocol/sdk@1.21.1) '@angular/platform-server': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8)(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + version: 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1)(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) '@angular/router': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + version: 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) '@babel/core': specifier: ^7.16.12 - version: 7.28.4 + version: 7.28.5 '@bazel/bazelisk': specifier: 1.26.0 version: 1.26.0 @@ -160,13 +160,13 @@ importers: version: 22.0.0 '@rollup/plugin-commonjs': specifier: ^28.0.0 - version: 28.0.6(rollup@4.52.4) + version: 28.0.9(rollup@4.53.1) '@rollup/plugin-node-resolve': specifier: ^16.0.0 - version: 16.0.2(rollup@4.52.4) + version: 16.0.3(rollup@4.53.1) '@schematics/angular': specifier: 'catalog:' - version: 21.0.0-next.8(chokidar@4.0.3) + version: 21.0.1(chokidar@4.0.3) '@types/babel__core': specifier: ^7.1.18 version: 7.20.5 @@ -175,13 +175,13 @@ importers: version: 11.0.4 '@types/jasmine': specifier: ^5.0.0 - version: 5.1.9 + version: 5.1.12 '@types/luxon': specifier: ^3.0.0 version: 3.7.1 '@types/node': specifier: ^22.14.1 - version: 22.18.8 + version: 22.19.0 '@types/selenium-webdriver': specifier: ^3.0.17 version: 3.0.26 @@ -193,13 +193,13 @@ importers: version: 0.8.17 '@types/yargs': specifier: ^17.0.8 - version: 17.0.33 + version: 17.0.34 autoprefixer: specifier: ^10.4.2 version: 10.4.21(postcss@8.5.6) axe-core: specifier: ^4.10.3 - version: 4.10.3 + version: 4.11.0 chalk: specifier: ^5.4.1 version: 5.6.2 @@ -211,10 +211,10 @@ importers: version: 0.30.0(dgeni@0.4.14) esbuild: specifier: ^0.25.0 - version: 0.25.10 + version: 0.25.12 firebase-tools: specifier: 14.20.0 - version: 14.20.0(@types/node@22.18.8)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2) + version: 14.20.0(@types/node@22.19.0)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2) fs-extra: specifier: ^11.0.0 version: 11.3.2 @@ -229,7 +229,7 @@ importers: version: 9.1.7 jasmine: specifier: ^5.6.0 - version: 5.11.0 + version: 5.12.0 jasmine-core: specifier: 5.12.0 version: 5.12.0 @@ -262,10 +262,10 @@ importers: version: 0.30.19 marked: specifier: ^16.0.0 - version: 16.3.0 + version: 16.4.2 minimatch: specifier: ^10.0.3 - version: 10.0.3 + version: 10.1.1 parse5: specifier: ^8.0.0 version: 8.0.0 @@ -286,13 +286,13 @@ importers: version: 2.3.7 rollup: specifier: ^4.52.3 - version: 4.52.4 + version: 4.53.1 rollup-plugin-dts: specifier: 6.2.3 - version: 6.2.3(rollup@4.52.4)(typescript@5.9.2) + version: 6.2.3(rollup@4.53.1)(typescript@5.9.2) rollup-plugin-sourcemaps2: specifier: 0.5.4 - version: 0.5.4(@types/node@22.18.8)(rollup@4.52.4) + version: 0.5.4(@types/node@22.19.0)(rollup@4.53.1) sass: specifier: ^1.80.6 version: 1.93.2 @@ -301,7 +301,7 @@ importers: version: 3.6.0 semver: specifier: ^7.3.5 - version: 7.7.2 + version: 7.7.3 shelljs: specifier: ^0.10.0 version: 0.10.0 @@ -316,10 +316,10 @@ importers: version: 14.16.1 terser: specifier: ^5.10.0 - version: 5.44.0 + version: 5.44.1 ts-node: specifier: ^10.9.1 - version: 10.9.2(@types/node@22.18.8)(typescript@5.9.2) + version: 10.9.2(@types/node@22.19.0)(typescript@5.9.2) tsec: specifier: 0.2.9 version: 0.2.9(@bazel/bazelisk@1.26.0)(typescript@5.9.2) @@ -338,12 +338,15 @@ importers: vrsource-tslint-rules: specifier: 6.0.0 version: 6.0.0(tslint@6.1.3(typescript@5.9.2))(typescript@5.9.2) + yaml: + specifier: ^2.8.1 + version: 2.8.1 yargs: specifier: ^18.0.0 version: 18.0.0 zx: specifier: ^8.0.0 - version: 8.8.4 + version: 8.8.5 docs: dependencies: @@ -358,25 +361,25 @@ importers: version: link:../src/cdk-experimental '@angular/common': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) + version: 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) '@angular/compiler': specifier: 'catalog:' - version: 21.0.0-next.8 + version: 21.0.1 '@angular/components-examples': specifier: workspace:* version: link:../src/components-examples '@angular/core': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) + version: 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) '@angular/forms': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + version: 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) '@angular/google-maps': specifier: workspace:* version: link:../src/google-maps '@angular/localize': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8) + version: 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1) '@angular/material': specifier: workspace:* version: link:../src/material @@ -388,16 +391,16 @@ importers: version: link:../src/material-luxon-adapter '@angular/platform-browser': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)) + version: 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)) '@angular/platform-browser-dynamic': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler@21.0.0-next.8)(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))) + version: 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler@21.0.1)(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))) '@angular/router': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + version: 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) '@angular/ssr': specifier: 'catalog:' - version: 21.0.0-next.8(97a956334e4483b817e98b9a94c98525) + version: 21.0.1(720178cc5eba0b050aa5091f059d8960) '@angular/youtube-player': specifier: workspace:* version: link:../src/youtube-player @@ -422,13 +425,13 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 'catalog:' - version: 21.0.0-next.8(cfe8a97e2176695474f8a07e88e10fe8) + version: 21.0.1(eeb44f99cc32b2717d9a0f24d72d1e0f) '@angular/cli': specifier: 'catalog:' - version: 21.0.0-next.8(@types/node@22.18.8)(chokidar@4.0.3) + version: 21.0.1(@types/node@22.19.0)(chokidar@4.0.3) '@angular/compiler-cli': specifier: 'catalog:' - version: 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2) + version: 21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2) '@bazel/bazelisk': specifier: ^1.12.1 version: 1.26.0 @@ -437,13 +440,13 @@ importers: version: 5.1.12 '@types/node': specifier: ^22.14.1 - version: 22.18.8 + version: 22.19.0 '@types/shelljs': specifier: 0.8.17 version: 0.8.17 firebase-tools: specifier: ^14.0.0 - version: 14.17.0(@types/node@22.18.8)(bufferutil@4.0.9)(encoding@0.1.13) + version: 14.20.0(@types/node@22.19.0)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2) jasmine-core: specifier: 5.12.0 version: 5.12.0 @@ -473,7 +476,7 @@ importers: version: 2.9.1(bufferutil@4.0.9) lighthouse: specifier: ^13.0.0 - version: 13.0.0(bufferutil@4.0.9) + version: 13.0.1(bufferutil@4.0.9) lighthouse-logger: specifier: ~2.0.0 version: 2.0.2 @@ -485,7 +488,7 @@ importers: version: 7.0.0 puppeteer-core: specifier: ^24.6.1 - version: 24.23.0(bufferutil@4.0.9) + version: 24.29.1(bufferutil@4.0.9) sass: specifier: 1.93.2 version: 1.93.2 @@ -494,7 +497,7 @@ importers: version: 0.10.0 ts-node: specifier: 10.9.2 - version: 10.9.2(@types/node@22.18.8)(typescript@5.9.2) + version: 10.9.2(@types/node@22.19.0)(typescript@5.9.2) typescript: specifier: 5.9.2 version: 5.9.2 @@ -731,87 +734,87 @@ packages: '@actions/io@1.1.3': resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==} - '@algolia/abtesting@1.6.0': - resolution: {integrity: sha512-c4M/Z/KWkEG+RHpZsWKDTTlApXu3fe4vlABNcpankWBhdMe4oPZ/r4JxEr2zKUP6K+BT66tnp8UbHmgOd/vvqQ==} + '@algolia/abtesting@1.6.1': + resolution: {integrity: sha512-wV/gNRkzb7sI9vs1OneG129hwe3Q5zPj7zigz3Ps7M5Lpo2hSorrOnXNodHEOV+yXE/ks4Pd+G3CDFIjFTWhMQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-abtesting@5.40.0': - resolution: {integrity: sha512-qegVlgHtmiS8m9nEsuKUVhlw1FHsIshtt5nhNnA6EYz3g+tm9+xkVZZMzkrMLPP7kpoheHJZAwz2MYnHtwFa9A==} + '@algolia/client-abtesting@5.40.1': + resolution: {integrity: sha512-cxKNATPY5t+Mv8XAVTI57altkaPH+DZi4uMrnexPxPHODMljhGYY+GDZyHwv9a+8CbZHcY372OkxXrDMZA4Lnw==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.40.0': - resolution: {integrity: sha512-Dw2c+6KGkw7mucnnxPyyMsIGEY8+hqv6oB+viYB612OMM3l8aNaWToBZMnNvXsyP+fArwq7XGR+k3boPZyV53A==} + '@algolia/client-analytics@5.40.1': + resolution: {integrity: sha512-XP008aMffJCRGAY8/70t+hyEyvqqV7YKm502VPu0+Ji30oefrTn2al7LXkITz7CK6I4eYXWRhN6NaIUi65F1OA==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.40.0': - resolution: {integrity: sha512-dbE4+MJIDsTghG3hUYWBq7THhaAmqNqvW9g2vzwPf5edU4IRmuYpKtY3MMotes8/wdTasWG07XoaVhplJBlvdg==} + '@algolia/client-common@5.40.1': + resolution: {integrity: sha512-gWfQuQUBtzUboJv/apVGZMoxSaB0M4Imwl1c9Ap+HpCW7V0KhjBddqF2QQt5tJZCOFsfNIgBbZDGsEPaeKUosw==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.40.0': - resolution: {integrity: sha512-SH6zlROyGUCDDWg71DlCnbbZ/zEHYPZC8k901EAaBVhvY43Ju8Wa6LAcMPC4tahcDBgkG2poBy8nJZXvwEWAlQ==} + '@algolia/client-insights@5.40.1': + resolution: {integrity: sha512-RTLjST/t+lsLMouQ4zeLJq2Ss+UNkLGyNVu+yWHanx6kQ3LT5jv8UvPwyht9s7R6jCPnlSI77WnL80J32ZuyJg==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.40.0': - resolution: {integrity: sha512-EgHjJEEf7CbUL9gJHI1ULmAtAFeym2cFNSAi1uwHelWgLPcnLjYW2opruPxigOV7NcetkGu+t2pcWOWmZFuvKQ==} + '@algolia/client-personalization@5.40.1': + resolution: {integrity: sha512-2FEK6bUomBzEYkTKzD0iRs7Ljtjb45rKK/VSkyHqeJnG+77qx557IeSO0qVFE3SfzapNcoytTofnZum0BQ6r3Q==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.40.0': - resolution: {integrity: sha512-HvE1jtCag95DR41tDh7cGwrMk4X0aQXPOBIhZRmsBPolMeqRJz0kvfVw8VCKvA1uuoAkjFfTG0X0IZED+rKXoA==} + '@algolia/client-query-suggestions@5.40.1': + resolution: {integrity: sha512-Nju4NtxAvXjrV2hHZNLKVJLXjOlW6jAXHef/CwNzk1b2qIrCWDO589ELi5ZHH1uiWYoYyBXDQTtHmhaOVVoyXg==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.40.0': - resolution: {integrity: sha512-nlr/MMgoLNUHcfWC5Ns2ENrzKx9x51orPc6wJ8Ignv1DsrUmKm0LUih+Tj3J+kxYofzqQIQRU495d4xn3ozMbg==} + '@algolia/client-search@5.40.1': + resolution: {integrity: sha512-Mw6pAUF121MfngQtcUb5quZVqMC68pSYYjCRZkSITC085S3zdk+h/g7i6FxnVdbSU6OztxikSDMh1r7Z+4iPlA==} engines: {node: '>= 14.0.0'} - '@algolia/ingestion@1.40.0': - resolution: {integrity: sha512-OfHnhE+P0f+p3i90Kmshf9Epgesw5oPV1IEUOY4Mq1HV7cQk16gvklVN1EaY/T9sVavl+Vc3g4ojlfpIwZFA4g==} + '@algolia/ingestion@1.40.1': + resolution: {integrity: sha512-z+BPlhs45VURKJIxsR99NNBWpUEEqIgwt10v/fATlNxc4UlXvALdOsWzaFfe89/lbP5Bu4+mbO59nqBC87ZM/g==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.40.0': - resolution: {integrity: sha512-SWANV32PTKhBYvwKozeWP9HOnVabOixAuPdFFGoqtysTkkwutrtGI/rrh80tvG+BnQAmZX0vUmD/RqFZVfr/Yg==} + '@algolia/monitoring@1.40.1': + resolution: {integrity: sha512-VJMUMbO0wD8Rd2VVV/nlFtLJsOAQvjnVNGkMkspFiFhpBA7s/xJOb+fJvvqwKFUjbKTUA7DjiSi1ljSMYBasXg==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.40.0': - resolution: {integrity: sha512-1Qxy9I5bSb3mrhPk809DllMa561zl5hLsMR6YhIqNkqQ0OyXXQokvJ2zApSxvd39veRZZnhN+oGe+XNoNwLgkw==} + '@algolia/recommend@5.40.1': + resolution: {integrity: sha512-ehvJLadKVwTp9Scg9NfzVSlBKH34KoWOQNTaN8i1Ac64AnO6iH2apJVSP6GOxssaghZ/s8mFQsDH3QIZoluFHA==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.40.0': - resolution: {integrity: sha512-MGt94rdHfkrVjfN/KwUfWcnaeohYbWGINrPs96f5J7ZyRYpVLF+VtPQ2FmcddFvK4gnKXSu8BAi81hiIhUpm3w==} + '@algolia/requester-browser-xhr@5.40.1': + resolution: {integrity: sha512-PbidVsPurUSQIr6X9/7s34mgOMdJnn0i6p+N6Ab+lsNhY5eiu+S33kZEpZwkITYBCIbhzDLOvb7xZD3gDi+USA==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.40.0': - resolution: {integrity: sha512-wXQ05JZZ10Dr642QVAkAZ4ZZlU+lh5r6dIBGmm9WElz+1EaQ6BNYtEOTV6pkXuFYsZpeJA89JpDOiwBOP9j24w==} + '@algolia/requester-fetch@5.40.1': + resolution: {integrity: sha512-ThZ5j6uOZCF11fMw9IBkhigjOYdXGXQpj6h4k+T9UkZrF2RlKcPynFzDeRgaLdpYk8Yn3/MnFbwUmib7yxj5Lw==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.40.0': - resolution: {integrity: sha512-5qCRoySnzpbQVg2IPLGFCm4LF75pToxI5tdjOYgUMNL/um91aJ4dH3SVdBEuFlVsalxl8mh3bWPgkUmv6NpJiQ==} + '@algolia/requester-node-http@5.40.1': + resolution: {integrity: sha512-H1gYPojO6krWHnUXu/T44DrEun/Wl95PJzMXRcM/szstNQczSbwq6wIFJPI9nyE95tarZfUNU3rgorT+wZ6iCQ==} engines: {node: '>= 14.0.0'} '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@angular-devkit/architect@0.2100.0-next.8': - resolution: {integrity: sha512-tY3Q3cr+GuRwWKvXWMUqPf0h9kJNeUYwEdXyHuVbp5lLBAraL9JSXmtE6EZewprUGco5DsYQshh9zedr4AGwiw==} + '@angular-devkit/architect@0.2100.1': + resolution: {integrity: sha512-MLxTT6EE7NHuCen9yGdv9iT2vtB/fAdXTRnulOWfVa/SVmGoKawBGCNOAPpI2yA8Fb/D5xlU6ThS1ggDsiCqrQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular-devkit/build-angular@21.0.0-next.8': - resolution: {integrity: sha512-IISlniBqsSjRkSLyGliJ41xXrnzvGFiYifQTgsoq1sdl3xJIToJDgcWlDa2kOSxVslWG/HtHFF5gK8qZvgpe0A==} + '@angular-devkit/build-angular@21.0.1': + resolution: {integrity: sha512-emFfwO1/mf74G3EHbwrMsOBeecBRW6ymfqClbMNqTVUje0cezcvkvn3hS4Kl+yqIURJ6iK90lkJIfon5C3TfDQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - '@angular/compiler-cli': ^21.0.0-next.0 - '@angular/core': ^21.0.0-next.0 - '@angular/localize': ^21.0.0-next.0 - '@angular/platform-browser': ^21.0.0-next.0 - '@angular/platform-server': ^21.0.0-next.0 - '@angular/service-worker': ^21.0.0-next.0 - '@angular/ssr': ^21.0.0-next.8 + '@angular/compiler-cli': ^21.0.0 + '@angular/core': ^21.0.0 + '@angular/localize': ^21.0.0 + '@angular/platform-browser': ^21.0.0 + '@angular/platform-server': ^21.0.0 + '@angular/service-worker': ^21.0.0 + '@angular/ssr': ^21.0.1 '@web/test-runner': ^0.20.0 browser-sync: ^3.0.2 jest: ^30.2.0 jest-environment-jsdom: ^30.2.0 karma: ^6.3.0 - ng-packagr: ^21.0.0-next.0 + ng-packagr: ^21.0.0 protractor: ^7.0.0 tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 typescript: 5.9.2 @@ -845,15 +848,15 @@ packages: tailwindcss: optional: true - '@angular-devkit/build-webpack@0.2100.0-next.8': - resolution: {integrity: sha512-zANZET32iXj37Na+XQBUwO4Yhkdxxz0vEdxOs6rKN3iOdYZOR8KXOLvV2YUyMP3xOwZxDA+yAFhi/qZFypAJ4w==} + '@angular-devkit/build-webpack@0.2100.1': + resolution: {integrity: sha512-Zf/s1tr5H4kRn+4zn94/mIvkZYXI+P8yKjs3C8Fqg5lavRXp9uQ850xX1oY3Z1OEvq6TQAFfCdFnPRaMvgxKfg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: webpack: ^5.30.0 webpack-dev-server: ^5.0.2 - '@angular-devkit/core@21.0.0-next.8': - resolution: {integrity: sha512-LsIs9UUYOQUcuobiEcBqgqkKpq6pa8dQEVTTj/g42V82ZjTb36Xq3SqiEJGwwbS0gIehFRTsCvKsSgzUSan/ig==} + '@angular-devkit/core@21.0.1': + resolution: {integrity: sha512-AGdAu0hV2TLCWYHiyVSxUFbpR2chO+xA4OkRrG2YirQGcqJTmr651C4rWDkheWqeWDxMicZklqKaTw66mNSUkw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^4.0.0 @@ -861,30 +864,30 @@ packages: chokidar: optional: true - '@angular-devkit/schematics@21.0.0-next.8': - resolution: {integrity: sha512-GQx2g1pye/Z2tnvC7zNpWdzzFkJ6wTyRB2dlpZfzkS81MB0KZSouhGSByRBF9uW4KUQ/uQDWVfjIq8nrVArpJQ==} + '@angular-devkit/schematics@21.0.1': + resolution: {integrity: sha512-3koB1xJNkqMg7g6JwH2rhQO268WjnPVA852lwoLW7wzSZRpJH0kHtZsnY9FYOC2kbmAGnCWWbnPLJ5/T1wemoA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular/build@21.0.0-next.8': - resolution: {integrity: sha512-2D6KXdh8cn6lFjzqEfIlY5rwZdBSN6iqi83+fFCIC3qITcai7E4dI6R/5REuZFVo0YMa4KK9KQzFRqGPdrod9A==} + '@angular/build@21.0.1': + resolution: {integrity: sha512-AQFZWG5TtujCRs7ncajeBZpl/hLBKkuF0lZSziJL8tsgBru0hz0OobOkEuS/nb3FuCRQfva8YP2EPhLdcuo50g==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - '@angular/compiler': ^21.0.0-next.0 - '@angular/compiler-cli': ^21.0.0-next.0 - '@angular/core': ^21.0.0-next.0 - '@angular/localize': ^21.0.0-next.0 - '@angular/platform-browser': ^21.0.0-next.0 - '@angular/platform-server': ^21.0.0-next.0 - '@angular/service-worker': ^21.0.0-next.0 - '@angular/ssr': ^21.0.0-next.8 + '@angular/compiler': ^21.0.0 + '@angular/compiler-cli': ^21.0.0 + '@angular/core': ^21.0.0 + '@angular/localize': ^21.0.0 + '@angular/platform-browser': ^21.0.0 + '@angular/platform-server': ^21.0.0 + '@angular/service-worker': ^21.0.0 + '@angular/ssr': ^21.0.1 karma: ^6.4.0 less: ^4.2.0 - ng-packagr: ^21.0.0-next.0 + ng-packagr: ^21.0.0 postcss: ^8.4.0 tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 tslib: ^2.3.0 typescript: 5.9.2 - vitest: ^3.1.1 + vitest: ^4.0.8 peerDependenciesMeta: '@angular/core': optional: true @@ -911,115 +914,115 @@ packages: vitest: optional: true - '@angular/cli@21.0.0-next.8': - resolution: {integrity: sha512-+pVNpTQonKnjVAME9pvttqcOKKfR+qsZbA8YGVfT4OaneDc4Y8N/wVgz90yxOCv7axQXbXHNtx3Nx9TQiEPtGA==} + '@angular/cli@21.0.1': + resolution: {integrity: sha512-i0+7jwf19D73yAzR/lL4+eKVhooM+J055qfSaJWL5QLCF9/JSSjMPCG8I/qIGNdVr+lVmWvvxqpt7O7kR3zfUw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular/common@21.0.0-next.8': - resolution: {integrity: sha512-x9sh/uhFVFrrOwVC+FC7gufkZxs1IPOCIx5lps6mqykQ30ihZ6vQLfJqY+XJ/N1y73o50qCAVK3IpzLNBWkgMQ==} + '@angular/common@21.0.1': + resolution: {integrity: sha512-EqdTGpFp7PVdTVztO7TB6+QxdzUbYXKKT2jwG2Gg+PIQZ2A8XrLPRmGXyH/DLlc5IhnoJlLbngmBRCLCO4xWog==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/core': 21.0.0-next.8 + '@angular/core': 21.0.1 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@21.0.0-next.8': - resolution: {integrity: sha512-o1B9/B7jM2GuDrfUw1UzkJpMCZ8ycpUi2DrcHtIiOZtqBbnfodASNLmzGaW8uEbNeB0h7PdYXxdFMvVYpWQv8g==} + '@angular/compiler-cli@21.0.1': + resolution: {integrity: sha512-BxGLtL5bxlaaAs/kSN4oyXhMfvzqsj1Gc4Jauz39R4xtgOF5cIvjBtj6dJ9mD3PK0s6zaFi7WYd0YwWkxhjgMA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: - '@angular/compiler': 21.0.0-next.8 + '@angular/compiler': 21.0.1 typescript: 5.9.2 peerDependenciesMeta: typescript: optional: true - '@angular/compiler@21.0.0-next.8': - resolution: {integrity: sha512-5Fkzhs5zpCy2IhIK0Osw3RyRqlrwUdcJNGJ/UbkUJuEE4VBEFXbd1evZa/mf5YIQFNhK0WW2zY3zMABTtGMweg==} + '@angular/compiler@21.0.1': + resolution: {integrity: sha512-YRzHpThgCaC9b3xzK1Wx859ePeHEPR7ewQklUB5TYbpzVacvnJo38PcSAx/nzOmgX9y4mgyros6LzECmBb8d8w==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@angular/core@21.0.0-next.8': - resolution: {integrity: sha512-+omCS1MesfEPVuebs/wUWj6f5xeiRQWjLe70lpbuRPdEgmgOzPOgZKG8828yhPqU1XqJEUjjghfiAIdM6+tYhw==} + '@angular/core@21.0.1': + resolution: {integrity: sha512-z0G9Bwzgqr0fQVbtMgqwl+SbbiqtJD7I2xT6U5p45LetKHojcfigH29dxi/vqALPwEdgb2nSIx7RqVhoyynraQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/compiler': 21.0.0-next.8 + '@angular/compiler': 21.0.1 rxjs: ^6.5.3 || ^7.4.0 - zone.js: ~0.15.0 + zone.js: ~0.15.0 || ~0.16.0 peerDependenciesMeta: '@angular/compiler': optional: true zone.js: optional: true - '@angular/forms@21.0.0-next.8': - resolution: {integrity: sha512-Xsdyd3/76phasRu6xs79llRai4kioh/AJsD2WpZ1c9pWh4hFoYcmfTDYle0KmKPZs2+HJPUmQ6DjS152LD21OA==} + '@angular/forms@21.0.1': + resolution: {integrity: sha512-BVFPuKjxkzjzKMmpc6KxUKICpVs6J2/KzA4HjtPp/UKvdZPe8dj8vIXuc9pGf8DA4XdkjCwvv8szCgzTWi02LQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.0.0-next.8 - '@angular/core': 21.0.0-next.8 - '@angular/platform-browser': 21.0.0-next.8 + '@angular/common': 21.0.1 + '@angular/core': 21.0.1 + '@angular/platform-browser': 21.0.1 '@standard-schema/spec': ^1.0.0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/localize@21.0.0-next.8': - resolution: {integrity: sha512-fIIjunzHQSPy9yka7VWo4RnnMM/A/vQsD/zylykG4EPr4PZpuKXKd6LmEX1DBRNOUa9N1cmkrH00zkCKfy81eQ==} + '@angular/localize@21.0.1': + resolution: {integrity: sha512-ouzParJQEQO9cStBkIioyDkQ839eJaedSwNuNXK/MeTZ1NH+pDcvwgYH0Gcn5BTB7fZEY0oCXyBcVxyz37qTcA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: - '@angular/compiler': 21.0.0-next.8 - '@angular/compiler-cli': 21.0.0-next.8 + '@angular/compiler': 21.0.1 + '@angular/compiler-cli': 21.0.1 - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/3c4fd6f54f2c67ce5b9f1a32ce90e36ba2fada4e': - resolution: {tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/3c4fd6f54f2c67ce5b9f1a32ce90e36ba2fada4e} - version: 0.0.0-c584c3565b71c7a8cda81d55bb14e3e66ef934da + '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/4c28145df03aff8c74d0a53f4f5602140e4d1a23': + resolution: {tarball: https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/4c28145df03aff8c74d0a53f4f5602140e4d1a23} + version: 0.0.0-95e3a0ede6dfa1aedd34b03918ad72b18f87e5ae hasBin: true - '@angular/platform-browser-dynamic@21.0.0-next.8': - resolution: {integrity: sha512-xWbDsZy3NZArnZ5H/dr2uSvloTWTy1ABYyEG22H6fkOtwwqyjXMZY5x8WtUww7R4UZ+kAfVvvVLX3nuwE5L3jw==} + '@angular/platform-browser-dynamic@21.0.1': + resolution: {integrity: sha512-TzCKf3p1NBK1NYoPJXLScSjVeiQ52DaXf9gweNUGtCmX3EkVKf1sx4Ny1x4DxaTwB5XZn+O+L3nVLstPBj7UGA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.0.0-next.8 - '@angular/compiler': 21.0.0-next.8 - '@angular/core': 21.0.0-next.8 - '@angular/platform-browser': 21.0.0-next.8 + '@angular/common': 21.0.1 + '@angular/compiler': 21.0.1 + '@angular/core': 21.0.1 + '@angular/platform-browser': 21.0.1 - '@angular/platform-browser@21.0.0-next.8': - resolution: {integrity: sha512-nOdVbkHDGpi0pNTFd/uhyBXX/z1zzq72p3m7G3yoCME3e/soekJMK5E0yRlliL7bK2yA3gH8UjOuh1hXsExMQg==} + '@angular/platform-browser@21.0.1': + resolution: {integrity: sha512-68StH9HILKUqNhQKz6KKNHzpgk1n88CIusWlmJvnb0l6iWGf3ydq5lTMKAKiZQmSDAVP5unTGfNvIkh59GRyVg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/animations': 21.0.0-next.8 - '@angular/common': 21.0.0-next.8 - '@angular/core': 21.0.0-next.8 + '@angular/animations': 21.0.1 + '@angular/common': 21.0.1 + '@angular/core': 21.0.1 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/platform-server@21.0.0-next.8': - resolution: {integrity: sha512-Yfc4GtHh+w9LKJccI5Quo5NSnxodyZ4yy/CwcAkF3WrWbvxjjGel1Fv3U2ZU6JzDkLtkeTk7tibs9CZghdG9qQ==} + '@angular/platform-server@21.0.1': + resolution: {integrity: sha512-SfDn9u2YG2UaFtuUL35UFedTz6xiMphVD2sbLLYPpYVd3nRZUziUV3VjeZ2xM6mbZTtT4m2zA8XGUbbWVkxOPg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.0.0-next.8 - '@angular/compiler': 21.0.0-next.8 - '@angular/core': 21.0.0-next.8 - '@angular/platform-browser': 21.0.0-next.8 + '@angular/common': 21.0.1 + '@angular/compiler': 21.0.1 + '@angular/core': 21.0.1 + '@angular/platform-browser': 21.0.1 rxjs: ^6.5.3 || ^7.4.0 - '@angular/router@21.0.0-next.8': - resolution: {integrity: sha512-1tqI7+jy1JtCXUdRkemDSAFttUZkYn8ZmAHvDJLVQIz1hlkDUYNu/482J6HbOBVvnsKFO/gTdc43Z5pUKzj9eQ==} + '@angular/router@21.0.1': + resolution: {integrity: sha512-EnNbiScESZ0op9XS9qUNncWc1UcSYy90uCbDMVTTChikZt9b+e19OusFMf50zecb96VMMz+BzNY1see7Rmvx4g==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.0.0-next.8 - '@angular/core': 21.0.0-next.8 - '@angular/platform-browser': 21.0.0-next.8 + '@angular/common': 21.0.1 + '@angular/core': 21.0.1 + '@angular/platform-browser': 21.0.1 rxjs: ^6.5.3 || ^7.4.0 - '@angular/ssr@21.0.0-next.8': - resolution: {integrity: sha512-ow3Ew2MLSx7lEnJ4JCRIh8j9xW1jAW1NdhN7ZId7UN9PFCFRb5HoX8qAklKxgX8/BARpfbvgmqmpyugKal6szg==} + '@angular/ssr@21.0.1': + resolution: {integrity: sha512-rbShURMPg7Lca81cUJGOFN3vGVVVESOj4HOwtemjex75R+AEKC7Pm5q0FMhlrc+Xr0DHB1M9gMSEbmUewcByrg==} peerDependencies: - '@angular/common': ^21.0.0-next.0 - '@angular/core': ^21.0.0-next.0 - '@angular/platform-server': ^21.0.0-next.0 - '@angular/router': ^21.0.0-next.0 + '@angular/common': ^21.0.0 + '@angular/core': ^21.0.0 + '@angular/platform-server': ^21.0.0 + '@angular/router': ^21.0.0 peerDependenciesMeta: '@angular/platform-server': optional: true @@ -1027,19 +1030,22 @@ packages: '@apidevtools/json-schema-ref-parser@9.1.2': resolution: {integrity: sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==} - '@apphosting/build@0.1.6': - resolution: {integrity: sha512-nXK1wsR1tehaq9uSRDCGQmN+Dp0xbyGohssYd7g4W8ZbzHfUiab+Pabv34pHVTS03VaSVkjdNcR1g9hezi6s8g==} + '@apphosting/build@0.1.7': + resolution: {integrity: sha512-zNgQGiAWDOj6c+4ylv5ej3nLGXzMAVmzCGMqlbSarHe4bvBmZ2C5GfBRdJksedP7C9pqlwTWpxU5+GSzhJ+nKA==} hasBin: true '@apphosting/common@0.0.8': resolution: {integrity: sha512-RJu5gXs2HYV7+anxpVPpp04oXeuHbV3qn402AdXVlnuYM/uWo7aceqmngpfp6Bi376UzRqGjfpdwFHxuwsEGXQ==} + '@apphosting/common@0.0.9': + resolution: {integrity: sha512-ZbPZDcVhEN+8m0sf90PmQN4xWaKmmySnBSKKPaIOD0JvcDsRr509WenFEFlojP++VSxwFZDGG/TYsHs1FMMqpw==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} '@babel/core@7.26.10': @@ -1050,10 +1056,18 @@ packages: resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} engines: {node: '>=6.9.0'} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.28.3': resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -1062,14 +1076,14 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.3': - resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} + '@babel/helper-create-class-features-plugin@7.28.5': + resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@7.27.1': - resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==} + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1083,8 +1097,8 @@ packages: resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.27.1': - resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.27.1': @@ -1129,8 +1143,8 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': @@ -1145,13 +1159,13 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': - resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1228,8 +1242,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.28.4': - resolution: {integrity: sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==} + '@babel/plugin-transform-block-scoping@7.28.5': + resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1258,8 +1272,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@7.28.0': - resolution: {integrity: sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==} + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1294,8 +1308,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.27.1': - resolution: {integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==} + '@babel/plugin-transform-exponentiation-operator@7.28.5': + resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1330,8 +1344,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.27.1': - resolution: {integrity: sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==} + '@babel/plugin-transform-logical-assignment-operators@7.28.5': + resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1354,8 +1368,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.27.1': - resolution: {integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==} + '@babel/plugin-transform-modules-systemjs@7.28.5': + resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1408,8 +1422,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.27.1': - resolution: {integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==} + '@babel/plugin-transform-optional-chaining@7.28.5': + resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1535,12 +1549,12 @@ packages: resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} '@bazel/bazelisk@1.26.0': @@ -1595,178 +1609,334 @@ packages: resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} - '@electric-sql/pglite-tools@0.2.15': - resolution: {integrity: sha512-PPvfCaYWjYmVVFykB/KLcWDktm3YtuJEeAGL82LhEAzb5IMjUeLiCtsfH80S0N+lF267x+0D2Y1203up6Gb1FA==} + '@electric-sql/pglite-tools@0.2.19': + resolution: {integrity: sha512-Ls4ZcSymnFRlEHtDyO3k9qPXLg7awfRAE3YnXk4WLsint17JBsU4UEX8le9YE8SgPkWNnQC898SqbFGGU/5JUA==} peerDependencies: - '@electric-sql/pglite': 0.3.10 + '@electric-sql/pglite': 0.3.14 '@electric-sql/pglite@0.2.17': resolution: {integrity: sha512-qEpKRT2oUaWDH6tjRxLHjdzMqRUGYDnGZlKrnL4dJ77JVMcP2Hpo3NYnOSPKdZdeec57B6QPprCUFg0picx5Pw==} - '@electric-sql/pglite@0.3.10': - resolution: {integrity: sha512-1XtXXprd848aR4hvjNqBc3Gc86zNGmd60x+MgOUShbHYxt+J76N8A81DqTEl275T8xBD0vdTgqR/dJ4yJyz0NQ==} + '@electric-sql/pglite@0.3.14': + resolution: {integrity: sha512-3DB258dhqdsArOI1fIt7cb9RpUOgcDg5hXWVgVHAeqVQ/qxtFy605QKs4gx6mFq3jWsSPqDN8TgSEsqC3OfV9Q==} - '@emnapi/core@1.5.0': - resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} + '@emnapi/core@1.7.0': + resolution: {integrity: sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==} - '@emnapi/runtime@1.5.0': - resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + '@emnapi/runtime@1.7.0': + resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.26.0': + resolution: {integrity: sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + '@esbuild/android-arm64@0.26.0': + resolution: {integrity: sha512-DDnoJ5eoa13L8zPh87PUlRd/IyFaIKOlRbxiwcSbeumcJ7UZKdtuMCHa1Q27LWQggug6W4m28i4/O2qiQQ5NZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.26.0': + resolution: {integrity: sha512-C0hkDsYNHZkBtPxxDx177JN90/1MiCpvBNjz1f5yWJo1+5+c5zr8apjastpEG+wtPjo9FFtGG7owSsAxyKiHxA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + '@esbuild/android-x64@0.26.0': + resolution: {integrity: sha512-bKDkGXGZnj0T70cRpgmv549x38Vr2O3UWLbjT2qmIkdIWcmlg8yebcFWoT9Dku7b5OV3UqPEuNKRzlNhjwUJ9A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + '@esbuild/darwin-arm64@0.26.0': + resolution: {integrity: sha512-6Z3naJgOuAIB0RLlJkYc81An3rTlQ/IeRdrU3dOea8h/PvZSgitZV+thNuIccw0MuK1GmIAnAmd5TrMZad8FTQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + '@esbuild/darwin-x64@0.26.0': + resolution: {integrity: sha512-OPnYj0zpYW0tHusMefyaMvNYQX5pNQuSsHFTHUBNp3vVXupwqpxofcjVsUx11CQhGVkGeXjC3WLjh91hgBG2xw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.26.0': + resolution: {integrity: sha512-jix2fa6GQeZhO1sCKNaNMjfj5hbOvoL2F5t+w6gEPxALumkpOV/wq7oUBMHBn2hY2dOm+mEV/K+xfZy3mrsxNQ==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + '@esbuild/freebsd-x64@0.26.0': + resolution: {integrity: sha512-tccJaH5xHJD/239LjbVvJwf6T4kSzbk6wPFerF0uwWlkw/u7HL+wnAzAH5GB2irGhYemDgiNTp8wJzhAHQ64oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + '@esbuild/linux-arm64@0.26.0': + resolution: {integrity: sha512-IMJYN7FSkLttYyTbsbme0Ra14cBO5z47kpamo16IwggzzATFY2lcZAwkbcNkWiAduKrTgFJP7fW5cBI7FzcuNQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.26.0': + resolution: {integrity: sha512-JY8NyU31SyRmRpuc5W8PQarAx4TvuYbyxbPIpHAZdr/0g4iBr8KwQBS4kiiamGl2f42BBecHusYCsyxi7Kn8UQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.26.0': + resolution: {integrity: sha512-XITaGqGVLgk8WOHw8We9Z1L0lbLFip8LyQzKYFKO4zFo1PFaaSKsbNjvkb7O8kEXytmSGRkYpE8LLVpPJpsSlw==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + '@esbuild/linux-loong64@0.26.0': + resolution: {integrity: sha512-MkggfbDIczStUJwq9wU7gQ7kO33d8j9lWuOCDifN9t47+PeI+9m2QVh51EI/zZQ1spZtFMC1nzBJ+qNGCjJnsg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.26.0': + resolution: {integrity: sha512-fUYup12HZWAeccNLhQ5HwNBPr4zXCPgUWzEq2Rfw7UwqwfQrFZ0SR/JljaURR8xIh9t+o1lNUFTECUTmaP7yKA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + '@esbuild/linux-ppc64@0.26.0': + resolution: {integrity: sha512-MzRKhM0Ip+//VYwC8tialCiwUQ4G65WfALtJEFyU0GKJzfTYoPBw5XNWf0SLbCUYQbxTKamlVwPmcw4DgZzFxg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.26.0': + resolution: {integrity: sha512-QhCc32CwI1I4Jrg1enCv292sm3YJprW8WHHlyxJhae/dVs+KRWkbvz2Nynl5HmZDW/m9ZxrXayHzjzVNvQMGQA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + '@esbuild/linux-s390x@0.26.0': + resolution: {integrity: sha512-1D6vi6lfI18aNT1aTf2HV+RIlm6fxtlAp8eOJ4mmnbYmZ4boz8zYDar86sIYNh0wmiLJEbW/EocaKAX6Yso2fw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.26.0': + resolution: {integrity: sha512-rnDcepj7LjrKFvZkx+WrBv6wECeYACcFjdNPvVPojCPJD8nHpb3pv3AuR9CXgdnjH1O23btICj0rsp0L9wAnHA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.26.0': + resolution: {integrity: sha512-FSWmgGp0mDNjEXXFcsf12BmVrb+sZBBBlyh3LwB/B9ac3Kkc8x5D2WimYW9N7SUkolui8JzVnVlWh7ZmjCpnxw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + '@esbuild/netbsd-x64@0.26.0': + resolution: {integrity: sha512-0QfciUDFryD39QoSPUDshj4uNEjQhp73+3pbSAaxjV2qGOEDsM67P7KbJq7LzHoVl46oqhIhJ1S+skKGR7lMXA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + '@esbuild/openbsd-arm64@0.26.0': + resolution: {integrity: sha512-vmAK+nHhIZWImwJ3RNw9hX3fU4UGN/OqbSE0imqljNbUQC3GvVJ1jpwYoTfD6mmXmQaxdJY6Hn4jQbLGJKg5Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.26.0': + resolution: {integrity: sha512-GPXF7RMkJ7o9bTyUsnyNtrFMqgM3X+uM/LWw4CeHIjqc32fm0Ir6jKDnWHpj8xHFstgWDUYseSABK9KCkHGnpg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.26.0': + resolution: {integrity: sha512-nUHZ5jEYqbBthbiBksbmHTlbb5eElyVfs/s1iHQ8rLBq1eWsd5maOnDpCocw1OM8kFK747d1Xms8dXJHtduxSw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + '@esbuild/sunos-x64@0.26.0': + resolution: {integrity: sha512-TMg3KCTCYYaVO+R6P5mSORhcNDDlemUVnUbb8QkboUtOhb5JWKAzd5uMIMECJQOxHZ/R+N8HHtDF5ylzLfMiLw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + '@esbuild/win32-arm64@0.26.0': + resolution: {integrity: sha512-apqYgoAUd6ZCb9Phcs8zN32q6l0ZQzQBdVXOofa6WvHDlSOhwCWgSfVQabGViThS40Y1NA4SCvQickgZMFZRlA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.26.0': + resolution: {integrity: sha512-FGJAcImbJNZzLWu7U6WB0iKHl4RuY4TsXEwxJPl9UZLS47agIZuILZEX3Pagfw7I4J3ddflomt9f0apfaJSbaw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.26.0': + resolution: {integrity: sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1775,8 +1945,8 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} - '@firebase/ai@2.4.0': - resolution: {integrity: sha512-YilG6AJ/nYpCKtxZyvEzBRAQv5bU+2tBOKX4Ps0rNNSdxN39aT37kGhjATbk1kq1z5Lq7mkWglw/ajAF3lOWUg==} + '@firebase/ai@2.6.0': + resolution: {integrity: sha512-NGyE7NQDFznOv683Xk4+WoUv39iipa9lEfrwvvPz33ChzVbCCiB69FJQTK2BI/11pRtzYGbHo1/xMz7gxWWhJw==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x @@ -1813,19 +1983,19 @@ packages: peerDependencies: '@firebase/app': 0.x - '@firebase/app-compat@0.5.4': - resolution: {integrity: sha512-T7ifGmb+awJEcp542Ek4HtNfBxcBrnuk1ggUdqyFEdsXHdq7+wVlhvE6YukTL7NS8hIkEfL7TMAPx/uCNqt30g==} + '@firebase/app-compat@0.5.6': + resolution: {integrity: sha512-YYGARbutghQY4zZUWMYia0ib0Y/rb52y72/N0z3vglRHL7ii/AaK9SA7S/dzScVOlCdnbHXz+sc5Dq+r8fwFAg==} engines: {node: '>=20.0.0'} '@firebase/app-types@0.9.3': resolution: {integrity: sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==} - '@firebase/app@0.14.4': - resolution: {integrity: sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw==} + '@firebase/app@0.14.6': + resolution: {integrity: sha512-4uyt8BOrBsSq6i4yiOV/gG6BnnrvTeyymlNcaN/dKvyU1GoolxAafvIvaNP1RCGPlNab3OuE4MKUQuv2lH+PLQ==} engines: {node: '>=20.0.0'} - '@firebase/auth-compat@0.6.0': - resolution: {integrity: sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw==} + '@firebase/auth-compat@0.6.1': + resolution: {integrity: sha512-I0o2ZiZMnMTOQfqT22ur+zcGDVSAfdNZBHo26/Tfi8EllfR1BO7aTVo2rt/ts8o/FWsK8pOALLeVBGhZt8w/vg==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app-compat': 0.x @@ -1839,8 +2009,8 @@ packages: '@firebase/app-types': 0.x '@firebase/util': 1.x - '@firebase/auth@1.11.0': - resolution: {integrity: sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==} + '@firebase/auth@1.11.1': + resolution: {integrity: sha512-Mea0G/BwC1D0voSG+60Ylu3KZchXAFilXQ/hJXWCw3gebAu+RDINZA0dJMNeym7HFxBaBaByX8jSa7ys5+F2VA==} engines: {node: '>=20.0.0'} peerDependencies: '@firebase/app': 0.x @@ -1853,8 +2023,8 @@ packages: resolution: {integrity: sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==} engines: {node: '>=20.0.0'} - '@firebase/data-connect@0.3.11': - resolution: {integrity: sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw==} + '@firebase/data-connect@0.3.12': + resolution: {integrity: sha512-baPddcoNLj/+vYo+HSJidJUdr5W4OkhT109c5qhR8T1dJoZcyJpkv/dFpYlw/VJ3dV66vI8GHQFrmAZw/xUS4g==} peerDependencies: '@firebase/app': 0.x @@ -1985,23 +2155,23 @@ packages: '@firebase/webchannel-wrapper@1.0.5': resolution: {integrity: sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==} - '@formatjs/ecma402-abstract@2.3.5': - resolution: {integrity: sha512-1HTESOq1IUa23g1lFZEGIXsfZKZOwWmB9RROwGn+xariiQnd++wwTMvlRAbZ8wtXRHFUamJPxsKcxpSzeCvFWQ==} + '@formatjs/ecma402-abstract@2.3.6': + resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==} '@formatjs/fast-memoize@2.2.7': resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - '@formatjs/icu-messageformat-parser@2.11.3': - resolution: {integrity: sha512-H/KfWSosaiDiOaW4nHe1Fn4Cgzm+oFQ8giTmB5RJzTBNSMmd+j2NVrvvZHAmlxJHcuOelzKBLjQ2EDcyH4NSWw==} + '@formatjs/icu-messageformat-parser@2.11.4': + resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} - '@formatjs/icu-skeleton-parser@1.8.15': - resolution: {integrity: sha512-qNrKxWJmnWxin5U4A4Evy7C0rgRiNw3IqXu9OGuT31B8lDxBGl+OgT8kcq0ZVKK0gqA4l4SQB9x+SFAvLT5hcQ==} + '@formatjs/icu-skeleton-parser@1.8.16': + resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} '@formatjs/intl-localematcher@0.6.2': resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} - '@google-cloud/cloud-sql-connector@1.8.3': - resolution: {integrity: sha512-Xmh1U0cw5goTWchBqvXfcffz2B6eGUnMH+iY6NTz6A+WlsvlDtAi87NlMst4yKYNfF7gRmNnnJTcK25I6TR6cA==} + '@google-cloud/cloud-sql-connector@1.8.4': + resolution: {integrity: sha512-dX36ksbh4xrIsALCHxNYXwvRUtlZ/msQ/PL0LpSYWHnfMenRM+watAzM/XIVr8tQOtzhT7mrmrAb/W/bgayQAQ==} engines: {node: '>=18'} '@google-cloud/common@6.0.0': @@ -2048,11 +2218,11 @@ packages: resolution: {integrity: sha512-IJn+8A3QZJfe7FUtWqHVNo3xJs7KFpurCWGWCiCz3oEh+BkRymKZ1QxfAbU2yGMDzTytLGQ2IV6T2r3cuo75/w==} engines: {node: '>=18'} - '@google/genai@1.25.0': - resolution: {integrity: sha512-IBNyel/umavam98SQUfvQSvh/Rp6Ql2fysQLqPyWZr5K8d768X9AO+JZU4o+3qvFDUBA0dVYUSkxyYonVcICvA==} + '@google/genai@1.30.0': + resolution: {integrity: sha512-3MRcgczBFbUat1wIlZoLJ0vCCfXgm7Qxjh59cZi2X08RgWLtm9hKOspzp7TOg1TV2e26/MLxR2GR5yD5GmBV2w==} engines: {node: '>=20.0.0'} peerDependencies: - '@modelcontextprotocol/sdk': ^1.11.4 + '@modelcontextprotocol/sdk': ^1.20.1 peerDependenciesMeta: '@modelcontextprotocol/sdk': optional: true @@ -2061,8 +2231,8 @@ packages: resolution: {integrity: sha512-k4lXSFCFuZmWtYuW/OH/PcHimZP5P/uDLK0+ACbgoZFB8qmlgcyF0531aJt6JHIdBwCRlHXZlMW4LDC5Gqra5w==} engines: {node: '>=12.0.0'} - '@grpc/grpc-js@1.14.0': - resolution: {integrity: sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==} + '@grpc/grpc-js@1.14.1': + resolution: {integrity: sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ==} engines: {node: '>=12.10.0'} '@grpc/grpc-js@1.9.15': @@ -2079,16 +2249,16 @@ packages: engines: {node: '>=6'} hasBin: true - '@inquirer/ansi@1.0.0': - resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==} + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} engines: {node: '>=18'} - '@inquirer/ansi@1.0.1': - resolution: {integrity: sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==} - engines: {node: '>=18'} + '@inquirer/ansi@2.0.1': + resolution: {integrity: sha512-QAZUk6BBncv/XmSEZTscd8qazzjV3E0leUMrEPjxCd51QBgCKmprUGLex5DTsNtURm7LMzv+CLcd6S86xvBfYg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - '@inquirer/checkbox@4.2.4': - resolution: {integrity: sha512-2n9Vgf4HSciFq8ttKXk+qy+GsyTXPV1An6QAwe/8bkbbqvG4VW1I/ZY1pNu2rf+h9bdzMLPbRSfcNxkHBy/Ydw==} + '@inquirer/checkbox@4.3.1': + resolution: {integrity: sha512-rOcLotrptYIy59SGQhKlU0xBg1vvcVl2FdPIEclUvKHh0wo12OfGkId/01PIMJ/V+EimJ77t085YabgnQHBa5A==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2096,17 +2266,17 @@ packages: '@types/node': optional: true - '@inquirer/checkbox@4.3.0': - resolution: {integrity: sha512-5+Q3PKH35YsnoPTh75LucALdAxom6xh5D1oeY561x4cqBuH24ZFVyFREPe14xgnrtmGu3EEt1dIi60wRVSnGCw==} - engines: {node: '>=18'} + '@inquirer/checkbox@5.0.1': + resolution: {integrity: sha512-5VPFBK8jKdsjMK3DTFOlbR0+Kkd4q0AWB7VhWQn6ppv44dr3b7PU8wSJQTC5oA0f/aGW7v/ZozQJAY9zx6PKig==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/confirm@5.1.18': - resolution: {integrity: sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==} + '@inquirer/confirm@5.1.19': + resolution: {integrity: sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2114,8 +2284,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.19': - resolution: {integrity: sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==} + '@inquirer/confirm@5.1.20': + resolution: {integrity: sha512-HDGiWh2tyRZa0M1ZnEIUCQro25gW/mN8ODByicQrbR1yHx4hT+IOpozCMi5TgBtUdklLwRI2mv14eNpftDluEw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2123,17 +2293,17 @@ packages: '@types/node': optional: true - '@inquirer/core@10.2.2': - resolution: {integrity: sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==} - engines: {node: '>=18'} + '@inquirer/confirm@6.0.1': + resolution: {integrity: sha512-wD+pM7IxLn1TdcQN12Q6wcFe5VpyCuh/I2sSmqO5KjWH2R4v+GkUToHb+PsDGobOe1MtAlXMwGNkZUPc2+L6NA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/core@10.3.0': - resolution: {integrity: sha512-Uv2aPPPSK5jeCplQmQ9xadnFx2Zhj9b5Dj7bU6ZeCdDNNY11nhYy4btcSdtDguHqCT2h5oNeQTcUNSGGLA7NTA==} + '@inquirer/core@10.3.1': + resolution: {integrity: sha512-hzGKIkfomGFPgxKmnKEKeA+uCYBqC+TKtRx5LgyHRCrF6S2MliwRIjp3sUaWwVzMp7ZXVs8elB0Tfe682Rpg4w==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2141,17 +2311,17 @@ packages: '@types/node': optional: true - '@inquirer/editor@4.2.20': - resolution: {integrity: sha512-7omh5y5bK672Q+Brk4HBbnHNowOZwrb/78IFXdrEB9PfdxL3GudQyDk8O9vQ188wj3xrEebS2M9n18BjJoI83g==} - engines: {node: '>=18'} + '@inquirer/core@11.0.1': + resolution: {integrity: sha512-Tpf49h50e4KYffVUCXzkx4gWMafUi3aDQDwfVAAGBNnVcXiwJIj4m2bKlZ7Kgyf6wjt1eyXH1wDGXcAokm4Ssw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/editor@4.2.21': - resolution: {integrity: sha512-MjtjOGjr0Kh4BciaFShYpZ1s9400idOdvQ5D7u7lE6VztPFoyLcVNE5dXBmEEIQq5zi4B9h2kU+q7AVBxJMAkQ==} + '@inquirer/editor@4.2.22': + resolution: {integrity: sha512-8yYZ9TCbBKoBkzHtVNMF6PV1RJEUvMlhvmS3GxH4UvXMEHlS45jFyqFy0DU+K42jBs5slOaA78xGqqqWAx3u6A==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2159,17 +2329,17 @@ packages: '@types/node': optional: true - '@inquirer/expand@4.0.20': - resolution: {integrity: sha512-Dt9S+6qUg94fEvgn54F2Syf0Z3U8xmnBI9ATq2f5h9xt09fs2IJXSCIXyyVHwvggKWFXEY/7jATRo2K6Dkn6Ow==} - engines: {node: '>=18'} + '@inquirer/editor@5.0.1': + resolution: {integrity: sha512-zDKobHI7Ry++4noiV9Z5VfYgSVpPZoMApviIuGwLOMciQaP+dGzCO+1fcwI441riklRiZg4yURWyEoX0Zy2zZw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/expand@4.0.21': - resolution: {integrity: sha512-+mScLhIcbPFmuvU3tAGBed78XvYHSvCl6dBiYMlzCLhpr0bzGzd8tfivMMeqND6XZiaZ1tgusbUHJEfc6YzOdA==} + '@inquirer/expand@4.0.22': + resolution: {integrity: sha512-9XOjCjvioLjwlq4S4yXzhvBmAXj5tG+jvva0uqedEsQ9VD8kZ+YT7ap23i0bIXOtow+di4+u3i6u26nDqEfY4Q==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2177,25 +2347,43 @@ packages: '@types/node': optional: true - '@inquirer/external-editor@1.0.2': - resolution: {integrity: sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==} - engines: {node: '>=18'} + '@inquirer/expand@5.0.1': + resolution: {integrity: sha512-TBrTpAB6uZNnGQHtSEkbvJZIQ3dXZOrwqQSO9uUbwct3G2LitwBCE5YZj98MbQ5nzihzs5pRjY1K9RRLH4WgoA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/figures@1.0.13': - resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@2.0.1': + resolution: {integrity: sha512-BPYWJXCAK9w6R+pb2s3WyxUz9ts9SP/LDOUwA9fu7LeuyYgojz83i0DSRwezu736BgMwz14G63Xwj70hSzHohQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true - '@inquirer/figures@1.0.14': - resolution: {integrity: sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==} + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} engines: {node: '>=18'} - '@inquirer/input@4.2.4': - resolution: {integrity: sha512-cwSGpLBMwpwcZZsc6s1gThm0J+it/KIJ+1qFL2euLmSKUMGumJ5TcbMgxEjMjNHRGadouIYbiIgruKoDZk7klw==} + '@inquirer/figures@2.0.1': + resolution: {integrity: sha512-KtMxyjLCuDFqAWHmCY9qMtsZ09HnjMsm8H3OvpSIpfhHdfw3/AiGWHNrfRwbyvHPtOJpumm8wGn5fkhtvkWRsg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/input@4.3.0': + resolution: {integrity: sha512-h4fgse5zeGsBSW3cRQqu9a99OXRdRsNCvHoBqVmz40cjYjYFzcfwD0KA96BHIPlT7rZw0IpiefQIqXrjbzjS4Q==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2203,17 +2391,17 @@ packages: '@types/node': optional: true - '@inquirer/input@4.2.5': - resolution: {integrity: sha512-7GoWev7P6s7t0oJbenH0eQ0ThNdDJbEAEtVt9vsrYZ9FulIokvd823yLyhQlWHJPGce1wzP53ttfdCZmonMHyA==} - engines: {node: '>=18'} + '@inquirer/input@5.0.1': + resolution: {integrity: sha512-cEhEUohCpE2BCuLKtFFZGp4Ief05SEcqeAOq9NxzN5ThOQP8Rl5N/Nt9VEDORK1bRb2Sk/zoOyQYfysPQwyQtA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/number@3.0.20': - resolution: {integrity: sha512-bbooay64VD1Z6uMfNehED2A2YOPHSJnQLs9/4WNiV/EK+vXczf/R988itL2XLDGTgmhMF2KkiWZo+iEZmc4jqg==} + '@inquirer/number@3.0.22': + resolution: {integrity: sha512-oAdMJXz++fX58HsIEYmvuf5EdE8CfBHHXjoi9cTcQzgFoHGZE+8+Y3P38MlaRMeBvAVnkWtAxMUF6urL2zYsbg==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2221,17 +2409,17 @@ packages: '@types/node': optional: true - '@inquirer/number@3.0.21': - resolution: {integrity: sha512-5QWs0KGaNMlhbdhOSCFfKsW+/dcAVC2g4wT/z2MCiZM47uLgatC5N20kpkDQf7dHx+XFct/MJvvNGy6aYJn4Pw==} - engines: {node: '>=18'} + '@inquirer/number@4.0.1': + resolution: {integrity: sha512-4//zgBGHe8Q/FfCoUXZUrUHyK/q5dyqiwsePz3oSSPSmw1Ijo35ZkjaftnxroygcUlLYfXqm+0q08lnB5hd49A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/password@4.0.20': - resolution: {integrity: sha512-nxSaPV2cPvvoOmRygQR+h0B+Av73B01cqYLcr7NXcGXhbmsYfUb8fDdw2Us1bI2YsX+VvY7I7upgFYsyf8+Nug==} + '@inquirer/password@4.0.22': + resolution: {integrity: sha512-CbdqK1ioIr0Y3akx03k/+Twf+KSlHjn05hBL+rmubMll7PsDTGH0R4vfFkr+XrkB0FOHrjIwVP9crt49dgt+1g==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2239,17 +2427,17 @@ packages: '@types/node': optional: true - '@inquirer/password@4.0.21': - resolution: {integrity: sha512-xxeW1V5SbNFNig2pLfetsDb0svWlKuhmr7MPJZMYuDnCTkpVBI+X/doudg4pznc1/U+yYmWFFOi4hNvGgUo7EA==} - engines: {node: '>=18'} + '@inquirer/password@5.0.1': + resolution: {integrity: sha512-UJudHpd7Ia30Q+x+ctYqI9Nh6SyEkaBscpa7J6Ts38oc1CNSws0I1hJEdxbQBlxQd65z5GEJPM4EtNf6tzfWaQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/prompts@7.8.6': - resolution: {integrity: sha512-68JhkiojicX9SBUD8FE/pSKbOKtwoyaVj1kwqLfvjlVXZvOy3iaSWX4dCLsZyYx/5Ur07Fq+yuDNOen+5ce6ig==} + '@inquirer/prompts@7.10.0': + resolution: {integrity: sha512-X2HAjY9BClfFkJ2RP3iIiFxlct5JJVdaYYXhA7RKxsbc9KL+VbId79PSoUGH/OLS011NFbHHDMDcBKUj3T89+Q==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2266,17 +2454,17 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@4.1.8': - resolution: {integrity: sha512-CQ2VkIASbgI2PxdzlkeeieLRmniaUU1Aoi5ggEdm6BIyqopE9GuDXdDOj9XiwOqK5qm72oI2i6J+Gnjaa26ejg==} - engines: {node: '>=18'} + '@inquirer/prompts@8.0.1': + resolution: {integrity: sha512-MURRu/cyvLm9vchDDaVZ9u4p+ADnY0Mz3LQr0KTgihrrvuKZlqcWwlBC4lkOMvd0KKX4Wz7Ww9+uA7qEpQaqjg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/rawlist@4.1.9': - resolution: {integrity: sha512-AWpxB7MuJrRiSfTKGJ7Y68imYt8P9N3Gaa7ySdkFj1iWjr6WfbGAhdZvw/UnhFXTHITJzxGUI9k8IX7akAEBCg==} + '@inquirer/rawlist@4.1.10': + resolution: {integrity: sha512-Du4uidsgTMkoH5izgpfyauTL/ItVHOLsVdcY+wGeoGaG56BV+/JfmyoQGniyhegrDzXpfn3D+LFHaxMDRygcAw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2284,17 +2472,17 @@ packages: '@types/node': optional: true - '@inquirer/search@3.1.3': - resolution: {integrity: sha512-D5T6ioybJJH0IiSUK/JXcoRrrm8sXwzrVMjibuPs+AgxmogKslaafy1oxFiorNI4s3ElSkeQZbhYQgLqiL8h6Q==} - engines: {node: '>=18'} + '@inquirer/rawlist@5.0.1': + resolution: {integrity: sha512-vVfVHKUgH6rZmMlyd0jOuGZo0Fw1jfcOqZF96lMwlgavx7g0x7MICe316bV01EEoI+c68vMdbkTTawuw3O+Fgw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/search@3.2.0': - resolution: {integrity: sha512-a5SzB/qrXafDX1Z4AZW3CsVoiNxcIYCzYP7r9RzrfMpaLpB+yWi5U8BWagZyLmwR0pKbbL5umnGRd0RzGVI8bQ==} + '@inquirer/search@3.2.1': + resolution: {integrity: sha512-cKiuUvETublmTmaOneEermfG2tI9ABpb7fW/LqzZAnSv4ZaJnbEis05lOkiBuYX5hNdnX0Q9ryOQyrNidb55WA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2302,17 +2490,17 @@ packages: '@types/node': optional: true - '@inquirer/select@4.3.4': - resolution: {integrity: sha512-Qp20nySRmfbuJBBsgPU7E/cL62Hf250vMZRzYDcBHty2zdD1kKCnoDFWRr0WO2ZzaXp3R7a4esaVGJUx0E6zvA==} - engines: {node: '>=18'} + '@inquirer/search@4.0.1': + resolution: {integrity: sha512-XwiaK5xBvr31STX6Ji8iS3HCRysBXfL/jUbTzufdWTS6LTGtvDQA50oVETt1BJgjKyQBp9vt0VU6AmU/AnOaGA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} peerDependencies: '@types/node': '>=18' peerDependenciesMeta: '@types/node': optional: true - '@inquirer/select@4.4.0': - resolution: {integrity: sha512-kaC3FHsJZvVyIjYBs5Ih8y8Bj4P/QItQWrZW22WJax7zTN+ZPXVGuOM55vzbdCP9zKUiBd9iEJVdesujfF+cAA==} + '@inquirer/select@4.4.1': + resolution: {integrity: sha512-E9hbLU4XsNe2SAOSsFrtYtYQDVi1mfbqJrPDvXKnGlnRiApBdWMJz7r3J2Ff38AqULkPUD3XjQMD4492TymD7Q==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2320,8 +2508,17 @@ packages: '@types/node': optional: true - '@inquirer/type@3.0.9': - resolution: {integrity: sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==} + '@inquirer/select@5.0.1': + resolution: {integrity: sha512-gPByrgYoezGyKMq5KjV7Tuy1JU2ArIy6/sI8sprw0OpXope3VGQwP5FK1KD4eFFqEhKu470Dwe6/AyDPmGRA0Q==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -2329,6 +2526,15 @@ packages: '@types/node': optional: true + '@inquirer/type@4.0.1': + resolution: {integrity: sha512-odO8YwoQAw/eVu/PSPsDDVPmqO77r/Mq7zcoF5VduVqIu2wSRWUgmYb5K9WH1no0SjLnOe8MDKtDL++z6mfo2g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@isaacs/balanced-match@4.0.1': resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} engines: {node: 20 || >=22} @@ -2383,8 +2589,8 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/buffers@1.0.0': - resolution: {integrity: sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q==} + '@jsonjoy.com/buffers@1.2.1': + resolution: {integrity: sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -2395,8 +2601,8 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/json-pack@1.14.0': - resolution: {integrity: sha512-LpWbYgVnKzphN5S6uss4M25jJ/9+m6q6UJoeN6zTkK4xAGhKsiBRPVeF7OYMWonn5repMQbE5vieRXcMUrKDKw==} + '@jsonjoy.com/json-pack@1.21.0': + resolution: {integrity: sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -2416,12 +2622,12 @@ packages: '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} - '@listr2/prompt-adapter-inquirer@3.0.4': - resolution: {integrity: sha512-Dta7PtlCrlZK6UVvlV1Ym3ZHYZ6+7C4mflkI6tOudbl0kh8hoEikSkk6qFQa2tHPprz+8yLHbh5u948HQIOuKg==} + '@listr2/prompt-adapter-inquirer@3.0.5': + resolution: {integrity: sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==} engines: {node: '>=20.0.0'} peerDependencies: '@inquirer/prompts': '>= 3 < 8' - listr2: 9.0.4 + listr2: 9.0.5 '@lmdb/lmdb-darwin-arm64@3.4.3': resolution: {integrity: sha512-zR6Y45VNtW5s+A+4AyhrJk0VJKhXdkLhrySCpCu7PSdnakebsOzNxf58p5Xoq66vOSuueGAxlqDAF49HwdrSTQ==} @@ -2461,13 +2667,18 @@ packages: '@material/material-color-utilities@0.3.0': resolution: {integrity: sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==} - '@modelcontextprotocol/sdk@1.19.1': - resolution: {integrity: sha512-3Y2h3MZKjec1eAqSTBclATlX+AbC6n1LgfVzRMJLt3v6w0RCYgwLrjbxPDbhsYHt6Wdqc/aCceNJYgj448ELQQ==} + '@modelcontextprotocol/sdk@1.20.1': + resolution: {integrity: sha512-j/P+yuxXfgxb+mW7OEoRCM3G47zCTDqUPivJo/VzpjbG8I9csTXtOprCf5FfOfHK4whOJny0aHuBEON+kS7CCA==} engines: {node: '>=18'} - '@modelcontextprotocol/sdk@1.20.0': - resolution: {integrity: sha512-kOQ4+fHuT4KbR2iq2IjeV32HiihueuOf1vJkq18z08CLZ1UQrTc8BXJpVfxZkq45+inLLD+D4xx4nBjUelJa4Q==} + '@modelcontextprotocol/sdk@1.21.1': + resolution: {integrity: sha512-UyLFcJLDvUuZbGnaQqXFT32CpPpGj7VS19roLut6gkQVhb439xUzYWbsUvdI3ZPL+2hnFosuugtYWE0Mcs1rmQ==} engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} @@ -2499,8 +2710,8 @@ packages: cpu: [x64] os: [win32] - '@mswjs/interceptors@0.39.7': - resolution: {integrity: sha512-sURvQbbKsq5f8INV54YJgJEdk8oxBanqkTiXXd33rKmofFCwZLhLRszPduMZ9TA9b8/1CHc/IJmOlBHJk2Q5AQ==} + '@mswjs/interceptors@0.39.8': + resolution: {integrity: sha512-2+BzZbjRO7Ct61k8fMNHEtoKjeWI9pIlHFTqBwZ5icHpqszIgEZbjb1MW5Z0+bITTCTl3gk4PDBxs9tA/csXvA==} engines: {node: '>=18'} '@napi-rs/nice-android-arm-eabi@1.1.1': @@ -2624,11 +2835,11 @@ packages: peerDependencies: '@angular/compiler-cli': '*' - '@ngtools/webpack@21.0.0-next.8': - resolution: {integrity: sha512-VmKPwjqtBeB4SdGIhleauqOOp4DNwqO5D39HzkTbONhffGYWz+TfNbRhKEhKnMVTXkhyzaf9IpVtFW/Liyp2wA==} + '@ngtools/webpack@21.0.1': + resolution: {integrity: sha512-pzDjq+MqgDDGxSnZyRsGeoy1Ur8cHCYiwXlYpku9f6p4a+9uueEvd11iA0YWP2t7mOINyywjsJ7mihdC6PpnSQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - '@angular/compiler-cli': ^21.0.0-next.0 + '@angular/compiler-cli': ^21.0.0 typescript: 5.9.2 webpack: ^5.54.0 @@ -2665,12 +2876,12 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - '@npmcli/node-gyp@4.0.0': - resolution: {integrity: sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==} - engines: {node: ^18.17.0 || >=20.5.0} + '@npmcli/node-gyp@5.0.0': + resolution: {integrity: sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==} + engines: {node: ^20.17.0 || >=22.9.0} - '@npmcli/package-json@7.0.1': - resolution: {integrity: sha512-956YUeI0YITbk2+KnirCkD19HLzES0habV+Els+dyZaVsaM6VGSiNwnRu6t3CZaqDLz4KXy2zx+0N/Zy6YjlAA==} + '@npmcli/package-json@7.0.2': + resolution: {integrity: sha512-0ylN3U5htO1SJTmy2YI78PZZjLkKUGg7EKgukb2CRi0kzyoDr0cfjHAzi7kozVhj2V3SxN1oyKqZ2NSo40z00g==} engines: {node: ^20.17.0 || >=22.9.0} '@npmcli/promise-spawn@3.0.0': @@ -2681,62 +2892,75 @@ packages: resolution: {integrity: sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==} engines: {node: ^18.17.0 || >=20.5.0} + '@npmcli/promise-spawn@9.0.0': + resolution: {integrity: sha512-qxvGj3ZM6Zuk8YeVMY0gZHY19WN6g3OGxwR4MBaxHImfD/4zD0HpgBHNOSayEaisj/p3PyQjdQlO9tbl5ZBFZg==} + engines: {node: ^20.17.0 || >=22.9.0} + '@npmcli/redact@3.2.2': resolution: {integrity: sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==} engines: {node: ^18.17.0 || >=20.5.0} - '@npmcli/run-script@10.0.0': - resolution: {integrity: sha512-vaQj4nccJbAslopIvd49pQH2NhUp7G9pY4byUtmwhe37ZZuubGrx0eB9hW2F37uVNRuDDK6byFGXF+7JCuMSZg==} + '@npmcli/run-script@10.0.2': + resolution: {integrity: sha512-9lCTqxaoa9c9cdkzSSx+q/qaYrCrUPEwTWzLkVYg1/T8ESH3BG9vmb1zRc6ODsBVB0+gnGRSqSr01pxTS1yX3A==} engines: {node: ^20.17.0 || >=22.9.0} - '@octokit/auth-app@8.1.1': - resolution: {integrity: sha512-yW9YUy1cuqWlz8u7908ed498wJFt42VYsYWjvepjojM4BdZSp4t+5JehFds7LfvYi550O/GaUI94rgbhswvxfA==} + '@octokit/auth-app@8.1.2': + resolution: {integrity: sha512-db8VO0PqXxfzI6GdjtgEFHY9tzqUql5xMFXYA12juq8TeTgPAuiiP3zid4h50lwlIP457p5+56PnJOgd2GGBuw==} engines: {node: '>= 20'} - '@octokit/auth-oauth-app@9.0.2': - resolution: {integrity: sha512-vmjSHeuHuM+OxZLzOuoYkcY3OPZ8erJ5lfswdTmm+4XiAKB5PmCk70bA1is4uwSl/APhRVAv4KHsgevWfEKIPQ==} + '@octokit/auth-oauth-app@9.0.3': + resolution: {integrity: sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==} engines: {node: '>= 20'} - '@octokit/auth-oauth-device@8.0.2': - resolution: {integrity: sha512-KW7Ywrz7ei7JX+uClWD2DN1259fnkoKuVdhzfpQ3/GdETaCj4Tx0IjvuJrwhP/04OhcMu5yR6tjni0V6LBihdw==} + '@octokit/auth-oauth-device@8.0.3': + resolution: {integrity: sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==} engines: {node: '>= 20'} - '@octokit/auth-oauth-user@6.0.1': - resolution: {integrity: sha512-vlKsL1KUUPvwXpv574zvmRd+/4JiDFXABIZNM39+S+5j2kODzGgjk7w5WtiQ1x24kRKNaE7v9DShNbw43UA3Hw==} + '@octokit/auth-oauth-user@6.0.2': + resolution: {integrity: sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==} engines: {node: '>= 20'} '@octokit/auth-token@6.0.0': resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} engines: {node: '>= 20'} - '@octokit/core@7.0.5': - resolution: {integrity: sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==} + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} engines: {node: '>= 20'} - '@octokit/endpoint@11.0.1': - resolution: {integrity: sha512-7P1dRAZxuWAOPI7kXfio88trNi/MegQ0IJD3vfgC3b+LZo1Qe6gRJc2v0mz2USWWJOKrB2h5spXCzGbw+fAdqA==} + '@octokit/endpoint@11.0.2': + resolution: {integrity: sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==} engines: {node: '>= 20'} - '@octokit/graphql-schema@15.26.0': - resolution: {integrity: sha512-SoVbh+sXe9nsoweFbLT3tAk3XWYbYLs5ku05wij1zhyQ2U3lewdrhjo5Tb7lfaOGWNHSkPZT4uuPZp8neF7P7A==} + '@octokit/graphql-schema@15.26.1': + resolution: {integrity: sha512-RFDC2MpRBd4AxSRvUeBIVeBU7ojN/SxDfALUd7iVYOSeEK3gZaqR2MGOysj4Zh2xj2RY5fQAUT+Oqq7hWTraMA==} - '@octokit/graphql@9.0.2': - resolution: {integrity: sha512-iz6KzZ7u95Fzy9Nt2L8cG88lGRMr/qy1Q36ih/XVzMIlPDMYwaNLE/ENhqmIzgPrlNWiYJkwmveEetvxAgFBJw==} + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} engines: {node: '>= 20'} '@octokit/oauth-authorization-url@8.0.0': resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==} engines: {node: '>= 20'} - '@octokit/oauth-methods@6.0.1': - resolution: {integrity: sha512-xi6Iut3izMCFzXBJtxxJehxJmAKjE8iwj6L5+raPRwlTNKAbOOBJX7/Z8AF5apD4aXvc2skwIdOnC+CQ4QuA8Q==} + '@octokit/oauth-methods@6.0.2': + resolution: {integrity: sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==} engines: {node: '>= 20'} '@octokit/openapi-types@26.0.0': resolution: {integrity: sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==} - '@octokit/plugin-paginate-rest@13.2.0': - resolution: {integrity: sha512-YuAlyjR8o5QoRSOvMHxSJzPtogkNMgeMv2mpccrvdUGeC3MKyfi/hS+KiFwyH/iRKIKyx+eIMsDjbt3p9r2GYA==} + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/plugin-paginate-rest@13.2.1': + resolution: {integrity: sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} engines: {node: '>= 20'} peerDependencies: '@octokit/core': '>=6' @@ -2747,26 +2971,39 @@ packages: peerDependencies: '@octokit/core': '>=6' - '@octokit/plugin-rest-endpoint-methods@16.1.0': - resolution: {integrity: sha512-nCsyiKoGRnhH5LkH8hJEZb9swpqOcsW+VXv1QoyUNQXJeVODG4+xM6UICEqyqe9XFr6LkL8BIiFCPev8zMDXPw==} + '@octokit/plugin-rest-endpoint-methods@16.1.1': + resolution: {integrity: sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@17.0.0': + resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} engines: {node: '>= 20'} peerDependencies: '@octokit/core': '>=6' - '@octokit/request-error@7.0.1': - resolution: {integrity: sha512-CZpFwV4+1uBrxu7Cw8E5NCXDWFNf18MSY23TdxCBgjw1tXXHvTrZVsXlW8hgFTOLw8RQR1BBrMvYRtuyaijHMA==} + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} engines: {node: '>= 20'} - '@octokit/request@10.0.5': - resolution: {integrity: sha512-TXnouHIYLtgDhKo+N6mXATnDBkV05VwbR0TtMWpgTHIoQdRQfCSzmy/LGqR1AbRMbijq/EckC/E3/ZNcU92NaQ==} + '@octokit/request@10.0.6': + resolution: {integrity: sha512-FO+UgZCUu+pPnZAR+iKdUt64kPE7QW7ciqpldaMXaNzixz5Jld8dJ31LAUewk0cfSRkNSRKyqG438ba9c/qDlQ==} engines: {node: '>= 20'} '@octokit/rest@22.0.0': resolution: {integrity: sha512-z6tmTu9BTnw51jYGulxrlernpsQYXpui1RK21vmXn8yF5bp6iX16yfTtJYGK5Mh1qDkvDOmp2n8sRMcQmR8jiA==} engines: {node: '>= 20'} - '@octokit/types@15.0.0': - resolution: {integrity: sha512-8o6yDfmoGJUIeR9OfYU0/TUJTnMPG2r68+1yEdUeG2Fdqpj8Qetg0ziKIgcBm0RW/j29H41WP37CYCEhp6GoHQ==} + '@octokit/rest@22.0.1': + resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} + engines: {node: '>= 20'} + + '@octokit/types@15.0.2': + resolution: {integrity: sha512-rR+5VRjhYSer7sC51krfCctQhVTmjyUMAaShfPB8mscVa8tSoLyon3coxQmXu0ahJoLVWl8dSGD/3OGZlFV44Q==} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} '@open-draft/deferred-promise@2.2.0': resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} @@ -2791,8 +3028,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/context-async-hooks@2.1.0': - resolution: {integrity: sha512-zOyetmZppnwTyPrt4S7jMfXiSX9yyfF0hxlA8B5oo2TtKl+/RGCy7fi4DrBfIf3lCPrkKsRBWZZD7RFojK7FDg==} + '@opentelemetry/context-async-hooks@2.2.0': + resolution: {integrity: sha512-qRkLWiUEZNAmYapZ7KGS5C4OmBLcP/H2foXeOEaowYCR0wi89fHejrfYfbuLVCMLp/dWZXKvQusdbUEZjERfwQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -2803,8 +3040,8 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@2.1.0': - resolution: {integrity: sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ==} + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' @@ -2971,8 +3208,8 @@ packages: resolution: {integrity: sha512-4VlGgo32k2EQ2wcCY3vEU28A0O13aOtHz3Xt2/2U5FAh9EfhD6t6DqL5Z6yAnRCntbTFDU4YfbpyzSlHNWycPw==} engines: {node: '>=14'} - '@opentelemetry/semantic-conventions@1.37.0': - resolution: {integrity: sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==} + '@opentelemetry/semantic-conventions@1.38.0': + resolution: {integrity: sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==} engines: {node: '>=14'} '@opentelemetry/sql-common@0.40.1': @@ -2981,8 +3218,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 - '@oxc-project/types@0.94.0': - resolution: {integrity: sha512-+UgQT/4o59cZfH6Cp7G0hwmqEQ0wE+AdIwhikdwnhWI9Dp8CgSY081+Q3O67/wq3VJu8mgUEB93J9EHHn70fOw==} + '@oxc-project/types@0.96.0': + resolution: {integrity: sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==} '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} @@ -3096,8 +3333,8 @@ packages: resolution: {integrity: sha512-tNe7a6U4rCpxLMBaR0SIYTdjxGdL0Vwb3G1zY8++sPtHSvy7qd54u8CIB0Z+Y6t5tc9pNYMYCMwhE/wdSY7ltg==} engines: {node: '>=18.12'} - '@pnpm/dependency-path@1001.1.2': - resolution: {integrity: sha512-fih99/lY+HRRak0U0KMKAO7+nacilWMcvFTH6YDKzjCBTOhxDr6Eeap2mF7uf4ED4dnKsQVNUGmFpvaSXSuFCQ==} + '@pnpm/dependency-path@1001.1.5': + resolution: {integrity: sha512-powgYgNzuAdrZK+bx1Vxes5LRFp8ByUCcFsCeo0pQpyFbKpRDFF31FUVSE3CGs61WgL0lTBQr7ZoUSRc+BDrCw==} engines: {node: '>=18.12'} '@pnpm/graceful-fs@1000.0.1': @@ -3112,8 +3349,8 @@ packages: resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} engines: {node: '>=12'} - '@pnpm/types@1000.8.0': - resolution: {integrity: sha512-yx86CGHHquWAI0GgKIuV/RnYewcf5fVFZemC45C/K2cX0uV8GB8TUP541ZrokWola2fZx5sn1vL7xzbceRZfoQ==} + '@pnpm/types@1001.0.1': + resolution: {integrity: sha512-v5X09E6LkJFOOw9FgGITpAs7nQJtx6u3N0SNtyIC5mSeIC5SebMrrelpCz6QUTJvyXBEa1AWj2dZhYfLj59xhA==} engines: {node: '>=18.12'} '@prisma/instrumentation@6.11.1': @@ -3151,103 +3388,103 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@puppeteer/browsers@2.10.10': - resolution: {integrity: sha512-3ZG500+ZeLql8rE0hjfhkycJjDj0pI/btEh3L9IkWUYcOrgP0xCNRq3HbtbqOPbvDhFaAWD88pDFtlLv8ns8gA==} + '@puppeteer/browsers@2.10.13': + resolution: {integrity: sha512-a9Ruw3j3qlnB5a/zHRTkruppynxqaeE4H9WNj5eYGRWqw0ZauZ23f4W2ARf3hghF5doozyD+CRtt7XSYuYRI/Q==} engines: {node: '>=18'} hasBin: true - '@rolldown/binding-android-arm64@1.0.0-beta.43': - resolution: {integrity: sha512-TP8bcPOb1s6UmY5syhXrDn9k0XkYcw+XaoylTN4cJxf0JOVS2j682I3aTcpfT51hOFGr2bRwNKN9RZ19XxeQbA==} + '@rolldown/binding-android-arm64@1.0.0-beta.47': + resolution: {integrity: sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-beta.43': - resolution: {integrity: sha512-kuVWnZsE4vEjMF/10SbSUyzucIW2zmdsqFghYMqy+fsjXnRHg0luTU6qWF8IqJf4Cbpm9NEZRnjIEPpAbdiSNQ==} + '@rolldown/binding-darwin-arm64@1.0.0-beta.47': + resolution: {integrity: sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.43': - resolution: {integrity: sha512-u9Ps4sh6lcmJ3vgLtyEg/x4jlhI64U0mM93Ew+tlfFdLDe7yKyA+Fe80cpr2n1mNCeZXrvTSbZluKpXQ0GxLjw==} + '@rolldown/binding-darwin-x64@1.0.0-beta.47': + resolution: {integrity: sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.43': - resolution: {integrity: sha512-h9lUtVtXgfbk/tnicMpbFfZ3DJvk5Zn2IvmlC1/e0+nUfwoc/TFqpfrRRqcNBXk/e+xiWMSKv6b0MF8N+Rtvlg==} + '@rolldown/binding-freebsd-x64@1.0.0-beta.47': + resolution: {integrity: sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.43': - resolution: {integrity: sha512-IX2C6bA6wM2rX/RvD75ko+ix9yxPKjKGGq7pOhB8wGI4Z4fqX5B1nDHga/qMDmAdCAR1m9ymzxkmqhm/AFYf7A==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.47': + resolution: {integrity: sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.43': - resolution: {integrity: sha512-mcjd57vEj+CEQbZAzUiaxNzNgwwgOpFtZBWcINm8DNscvkXl5b/s622Z1dqGNWSdrZmdjdC6LWMvu8iHM6v9sQ==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.47': + resolution: {integrity: sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.43': - resolution: {integrity: sha512-Pa8QMwlkrztTo/1mVjZmPIQ44tCSci10TBqxzVBvXVA5CFh5EpiEi99fPSll2dHG2uT4dCOMeC6fIhyDdb0zXA==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.47': + resolution: {integrity: sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.43': - resolution: {integrity: sha512-BgynXKMjeaX4AfWLARhOKDetBOOghnSiVRjAHVvhiAaDXgdQN8e65mSmXRiVoVtD3cHXx/cfU8Gw0p0K+qYKVQ==} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.47': + resolution: {integrity: sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.43': - resolution: {integrity: sha512-VIsoPlOB/tDSAw9CySckBYysoIBqLeps1/umNSYUD8pMtalJyzMTneAVI1HrUdf4ceFmQ5vARoLIXSsPwVFxNg==} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.47': + resolution: {integrity: sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.43': - resolution: {integrity: sha512-YDXTxVJG67PqTQMKyjVJSddoPbSWJ4yRz/E3xzTLHqNrTDGY0UuhG8EMr8zsYnfH/0cPFJ3wjQd/hJWHuR6nkA==} + '@rolldown/binding-openharmony-arm64@1.0.0-beta.47': + resolution: {integrity: sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.43': - resolution: {integrity: sha512-3M+2DmorXvDuAIGYQ9Z93Oy1G9ETkejLwdXXb1uRTgKN9pMcu7N+KG2zDrJwqyxeeLIFE22AZGtSJm3PJbNu9Q==} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.47': + resolution: {integrity: sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.43': - resolution: {integrity: sha512-/B1j1pJs33y9ywtslOMxryUPHq8zIGu/OGEc2gyed0slimJ8fX2uR/SaJVhB4+NEgCFIeYDR4CX6jynAkeRuCA==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.47': + resolution: {integrity: sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.43': - resolution: {integrity: sha512-29oG1swCz7hNP+CQYrsM4EtylsKwuYzM8ljqbqC5TsQwmKat7P8ouDpImsqg/GZxFSXcPP9ezQm0Q0wQwGM3JA==} + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.47': + resolution: {integrity: sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.43': - resolution: {integrity: sha512-eWBV1Ef3gfGNehxVGCyXs7wLayRIgCmyItuCZwYYXW5bsk4EvR4n2GP5m3ohjnx7wdiY3nLmwQfH2Knb5gbNZw==} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.47': + resolution: {integrity: sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-beta.43': - resolution: {integrity: sha512-5Uxg7fQUCmfhax7FJke2+8B6cqgeUJUD9o2uXIKXhD+mG0mL6NObmVoi9wXEU1tY89mZKgAYA6fTbftx3q2ZPQ==} + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} - '@rollup/plugin-commonjs@28.0.6': - resolution: {integrity: sha512-XSQB1K7FUU5QP+3lOQmVCE3I0FcbbNvmNT4VJSj93iUjayaARrTQeoRdiYQoftAJBLrR9t2agwAd3ekaTgHNlw==} + '@rollup/plugin-commonjs@28.0.9': + resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 @@ -3255,8 +3492,8 @@ packages: rollup: optional: true - '@rollup/plugin-node-resolve@16.0.2': - resolution: {integrity: sha512-tCtHJ2BlhSoK4cCs25NMXfV7EALKr0jyasmqVCq3y9cBrKdmJhtsy1iTz36Xhk/O+pDJbzawxF4K6ZblqCnITQ==} + '@rollup/plugin-node-resolve@16.0.3': + resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^2.78.0||^3.0.0||^4.0.0 @@ -3282,129 +3519,129 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.52.4': - resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} + '@rollup/rollup-android-arm-eabi@4.53.1': + resolution: {integrity: sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.4': - resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==} + '@rollup/rollup-android-arm64@4.53.1': + resolution: {integrity: sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.4': - resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==} + '@rollup/rollup-darwin-arm64@4.53.1': + resolution: {integrity: sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.4': - resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==} + '@rollup/rollup-darwin-x64@4.53.1': + resolution: {integrity: sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.4': - resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==} + '@rollup/rollup-freebsd-arm64@4.53.1': + resolution: {integrity: sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.4': - resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==} + '@rollup/rollup-freebsd-x64@4.53.1': + resolution: {integrity: sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': - resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.53.1': + resolution: {integrity: sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.52.4': - resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} + '@rollup/rollup-linux-arm-musleabihf@4.53.1': + resolution: {integrity: sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.52.4': - resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} + '@rollup/rollup-linux-arm64-gnu@4.53.1': + resolution: {integrity: sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.52.4': - resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} + '@rollup/rollup-linux-arm64-musl@4.53.1': + resolution: {integrity: sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.52.4': - resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} + '@rollup/rollup-linux-loong64-gnu@4.53.1': + resolution: {integrity: sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-gnu@4.52.4': - resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} + '@rollup/rollup-linux-ppc64-gnu@4.53.1': + resolution: {integrity: sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.52.4': - resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} + '@rollup/rollup-linux-riscv64-gnu@4.53.1': + resolution: {integrity: sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.52.4': - resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} + '@rollup/rollup-linux-riscv64-musl@4.53.1': + resolution: {integrity: sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.52.4': - resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} + '@rollup/rollup-linux-s390x-gnu@4.53.1': + resolution: {integrity: sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.52.4': - resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} + '@rollup/rollup-linux-x64-gnu@4.53.1': + resolution: {integrity: sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.52.4': - resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} + '@rollup/rollup-linux-x64-musl@4.53.1': + resolution: {integrity: sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openharmony-arm64@4.52.4': - resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} + '@rollup/rollup-openharmony-arm64@4.53.1': + resolution: {integrity: sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.4': - resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==} + '@rollup/rollup-win32-arm64-msvc@4.53.1': + resolution: {integrity: sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.4': - resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==} + '@rollup/rollup-win32-ia32-msvc@4.53.1': + resolution: {integrity: sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.4': - resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==} + '@rollup/rollup-win32-x64-gnu@4.53.1': + resolution: {integrity: sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.4': - resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==} + '@rollup/rollup-win32-x64-msvc@4.53.1': + resolution: {integrity: sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==} cpu: [x64] os: [win32] - '@schematics/angular@21.0.0-next.8': - resolution: {integrity: sha512-9ilY3gf5Mr84abt/wTfnxNGBQOv2hPjJ61vKyIeV2mCTClsco/RctQIc4peDHToI2YMf9r7/AJZVKPUNwFoXJw==} + '@schematics/angular@21.0.1': + resolution: {integrity: sha512-m7Z/gykPxOyC5Gs9nkFkGwYTc5xLNLcVkjjZPcYszycwsWBohDREjQLZzRG86AauWFYy8mBUrTF9CD63ZqYHeQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} '@sentry/core@9.46.0': @@ -3540,8 +3777,8 @@ packages: '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} - '@types/duplexify@3.6.4': - resolution: {integrity: sha512-2eahVPsd+dy3CL6FugAzJcxoraWhUghZGEQJns1kTKfCXWKJ5iG/VkaB05wRVrDKHfOFKqb0X0kXh91eE99RZg==} + '@types/duplexify@3.6.5': + resolution: {integrity: sha512-fB56ACzlW91UdZ5F3VXplVMDngO8QaX5Y2mjvADtN01TT2TMy4WjF0Lg+tFDvt4uMBeTe4SgaD+qCrA7dL5/tA==} '@types/ejs@3.1.5': resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==} @@ -3558,11 +3795,11 @@ packages: '@types/events@3.0.3': resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==} - '@types/express-serve-static-core@4.19.6': - resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + '@types/express-serve-static-core@4.19.7': + resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==} - '@types/express@4.17.23': - resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/express@4.17.25': + resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} '@types/folder-hash@4.0.4': resolution: {integrity: sha512-c+PwHm51Dw3fXM8SDK+93PO3oXdk4XNouCCvV67lj4aijRkZz5g67myk+9wqWWnyv3go6q96hT6ywcd3XtoZiQ==} @@ -3570,8 +3807,8 @@ packages: '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} - '@types/git-raw-commits@5.0.0': - resolution: {integrity: sha512-MQIzbZxgEnKpN1kCcw9JlQIu3Wdw5c4CCCP2cUli+DYgFjzsjtGLOeUe8oqPjjrKJudOoFnNuIZb/4sYHXEWZg==} + '@types/git-raw-commits@5.0.1': + resolution: {integrity: sha512-sd4kgxJbuZF0RDy6cX7KlKSGiwqB1mqn8nriUbxt5e1F+MO/N4hJlhaYn0Omw4g2biClFpT5Mre07x7OkGt8tg==} '@types/google.maps@3.58.1': resolution: {integrity: sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ==} @@ -3579,17 +3816,14 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - '@types/http-proxy@1.17.16': - resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==} - - '@types/jasmine@5.1.11': - resolution: {integrity: sha512-eAij9lMAsosuA8cvRcqw7p2vO+LUraktQDmOUFx2jAnya8NUchr3DryHksfhZbRzU83vzNUSZhlk1cFdoePxwA==} + '@types/http-proxy@1.17.17': + resolution: {integrity: sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==} '@types/jasmine@5.1.12': resolution: {integrity: sha512-1BzPxNsFDLDfj9InVR3IeY0ZVf4o9XV+4mDqoCfyPkbsA7dYyKAPAb2co6wLFlHcvxPlt1wShm7zQdV7uTfLGA==} - '@types/jasmine@5.1.9': - resolution: {integrity: sha512-8t4HtkW4wxiPVedMpeZ63n3vlWxEIquo/zc1Tm8ElU+SqVV7+D3Na2PWaJUp179AzTragMWVwkMv7mvty0NfyQ==} + '@types/jasmine@5.1.13': + resolution: {integrity: sha512-MYCcDkruFc92LeYZux5BC0dmqo2jk+M5UIZ4/oFnAPCXN9mCcQhLyj7F3/Za7rocVyt5YRr1MmqJqFlvQ9LVcg==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -3615,11 +3849,11 @@ packages: '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@22.18.8': - resolution: {integrity: sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==} + '@types/node@22.19.0': + resolution: {integrity: sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==} - '@types/node@24.8.1': - resolution: {integrity: sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==} + '@types/node@24.10.1': + resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3633,8 +3867,8 @@ packages: '@types/pg@8.6.1': resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==} - '@types/pumpify@1.4.4': - resolution: {integrity: sha512-+cWbQUecD04MQYkjNBhPmcUIP368aloYmqm+ImdMKA8rMpxRNAhZAD6gIj+sAVTF1DliqrT/qUp6aGNi/9U3tw==} + '@types/pumpify@1.4.5': + resolution: {integrity: sha512-BGVAQyK5yJdfIII230fVYGY47V63hUNAhryuuS3b4lEN2LNwxUXFKsEf8QLDCjmZuimlj23BHppJgcrGvNtqKg==} '@types/q@0.0.32': resolution: {integrity: sha512-qYi3YV9inU/REEfxwVcGZzbS3KG/Xs90lv0Pr+lDtuVjBPGd1A+eciXzVSaRvLify132BfcvhvEjeVahrUl0Ug==} @@ -3660,17 +3894,17 @@ packages: '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - '@types/send@0.17.5': - resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} - '@types/send@1.2.0': - resolution: {integrity: sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==} + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} '@types/serve-index@1.9.4': resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==} - '@types/serve-static@1.15.9': - resolution: {integrity: sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==} + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} '@types/shelljs@0.8.17': resolution: {integrity: sha512-IDksKYmQA2W9MkQjiyptbMmcQx+8+Ol6b7h6dPU5S05JyiQDSb/nZKnrMrZqGwgV6VkVdl6/SPCKPDlMRvqECg==} @@ -3715,8 +3949,11 @@ packages: '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + '@types/yargs@17.0.34': + resolution: {integrity: sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} '@types/yarnpkg__lockfile@1.1.9': resolution: {integrity: sha512-GD4Fk15UoP5NLCNor51YdfL9MSdldKCqOC9EssrRw3HVfar9wUZ5y8Lfnp+qVD6hIinLr8ygklDYnmlnlQo12Q==} @@ -3885,8 +4122,8 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - algoliasearch@5.40.0: - resolution: {integrity: sha512-a9aIL2E3Z7uYUPMCmjMFFd5MWhn+ccTubEvnMy7rOTZCB62dXBJtz0R5BZ/TPuX3R9ocBsgWuAbGWQ+Ph4Fmlg==} + algoliasearch@5.40.1: + resolution: {integrity: sha512-iUNxcXUNg9085TJx0HJLjqtDE0r1RZ0GOGrt8KNQqQT5ugu8lZsHuMUYW/e0lHhq6xBvmktU9Bw4CXP9VQeKrg==} engines: {node: '>= 14.0.0'} amdefine@1.0.1: @@ -3900,8 +4137,8 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} - ansi-escapes@7.1.1: - resolution: {integrity: sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==} + ansi-escapes@7.2.0: + resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} engines: {node: '>=18'} ansi-html-community@0.0.8: @@ -3937,10 +4174,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - ansis@4.2.0: - resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} - engines: {node: '>=14'} - any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -4044,8 +4277,8 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - atomically@2.0.3: - resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==} + atomically@2.1.0: + resolution: {integrity: sha512-+gDffFXRW6sl/HCwbta7zK4uNqbPjv4YJEAdz7Vu+FLQHe77eZ4bvbJGi4hE0QPeJlMYMA3piXEr1UL3dAwx7Q==} autoprefixer@10.4.21: resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} @@ -4064,10 +4297,6 @@ packages: aws4@1.13.2: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - axe-core@4.10.3: - resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} - engines: {node: '>=4'} - axe-core@4.11.0: resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} engines: {node: '>=4'} @@ -4111,11 +4340,16 @@ packages: balanced-match@2.0.0: resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - bare-events@2.7.0: - resolution: {integrity: sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==} + bare-events@2.8.2: + resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true - bare-fs@4.4.5: - resolution: {integrity: sha512-TCtu93KGLu6/aiGWzMr12TmSRS6nKdfhAnzTQRbXoSWxkbb9eRd53jQ51jG7g1gYjjtto3hbBrrhzg6djcgiKg==} + bare-fs@4.5.0: + resolution: {integrity: sha512-GljgCjeupKZJNetTqxKaQArLK10vpmK28or0+RwWjEl5Rk+/xG3wkpmkv+WrcBm3q1BwHKlnhXzR8O37kcvkXQ==} engines: {bare: '>=1.16.0'} peerDependencies: bare-buffer: '*' @@ -4141,8 +4375,8 @@ packages: bare-events: optional: true - bare-url@2.2.2: - resolution: {integrity: sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA==} + bare-url@2.3.2: + resolution: {integrity: sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4151,8 +4385,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.8.12: - resolution: {integrity: sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==} + baseline-browser-mapping@2.8.25: + resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==} hasBin: true basic-auth-connect@1.1.0: @@ -4231,8 +4465,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.26.3: - resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -4325,8 +4559,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001747: - resolution: {integrity: sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==} + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} canonical-path@0.0.2: resolution: {integrity: sha512-y8EIEvL+IW81S4hRQWCRFtly+g1cc1G+wxHpjhYR9jI2+JJjWiaKnkH8mmvNHOMOAd9fzgARDO3AEzjuR51qaA==} @@ -4373,8 +4607,8 @@ packages: character-entities-legacy@1.1.4: resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} - chardet@2.1.0: - resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} checkpoint-stream@0.1.2: resolution: {integrity: sha512-eYXIcydL3mPjjEVLxHdi1ISgTwmxGJZ8vyJ3lYVvFTDRyTOZMTbKZdRJqiA7Gi1rPcwOyyzcrZmGLL8ff7e69w==} @@ -4404,8 +4638,8 @@ packages: resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} engines: {node: '>=6.0'} - chromium-bidi@9.1.0: - resolution: {integrity: sha512-rlUzQ4WzIAWdIbY/viPShhZU2n21CxDUgazXVbw4Hu1MwaeUSEksSeM6DqPgpRjCLXRk702AVRxJxoOz0dw4OA==} + chromium-bidi@10.5.1: + resolution: {integrity: sha512-rlj6OyhKhVTnk4aENcUme3Jl9h+cq4oXu4AzBcvr8RMmT6BR4a3zSNT9dbIfXr9/BS6ibzRyDhowuw4n2GgzsQ==} peerDependencies: devtools-protocol: '*' @@ -4452,8 +4686,8 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} - cli-truncate@5.1.0: - resolution: {integrity: sha512-7JDGG+4Zp0CsknDCedl0DYdaeOhc46QNpXi3NLQblkZpXXgA6LncLDUUyvrjSvZeF3VRQa+KiMGomazQrC1V8g==} + cli-truncate@5.1.1: + resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} engines: {node: '>=20'} cli-width@4.1.0: @@ -4618,8 +4852,8 @@ packages: resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} engines: {node: '>=18'} - conventional-commits-parser@6.2.0: - resolution: {integrity: sha512-uLnoLeIW4XaoFtH37qEcg/SXMJmKF4vi7V0H2rnPueg+VEtFGA/asSCNTcq4M/GQ6QmlzchAEtOoDTtKqWeHag==} + conventional-commits-parser@6.2.1: + resolution: {integrity: sha512-20pyHgnO40rvfI0NGF/xiEoFMkXDtkF8FwHvk5BokoFoCuTQRI8vrNCNFWUOfuolKJMm1tPCHc8GgYEtr1XRNA==} engines: {node: '>=18'} hasBin: true @@ -4653,8 +4887,8 @@ packages: peerDependencies: webpack: ^5.1.0 - core-js-compat@3.45.1: - resolution: {integrity: sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==} + core-js-compat@3.46.0: + resolution: {integrity: sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==} core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -4927,15 +5161,15 @@ packages: engines: {node: '>=0.10'} hasBin: true - detect-libc@2.1.1: - resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - devtools-protocol@0.0.1508733: - resolution: {integrity: sha512-QJ1R5gtck6nDcdM+nlsaJXcelPEI7ZxSMw1ujHpO1c4+9l+Nue5qlebi9xO1Z2MGr92bFOQTW7/rrheh5hHxDg==} + devtools-protocol@0.0.1521046: + resolution: {integrity: sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==} devtools-protocol@0.0.1527314: resolution: {integrity: sha512-UohCFOlzpPPD/IcsxM0k4lVZp/GfhPVJ6l2No5XX+LknpGisPWJe17oOHQhZTHf6ThUFIMwHO6bSEZUq/6oP7w==} @@ -5037,11 +5271,11 @@ packages: engines: {node: '>=0.10.0'} hasBin: true - electron-to-chromium@1.5.230: - resolution: {integrity: sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==} + electron-to-chromium@1.5.249: + resolution: {integrity: sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==} - emoji-regex@10.5.0: - resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -5158,13 +5392,18 @@ packages: es6-promisify@5.0.0: resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - esbuild-wasm@0.25.10: - resolution: {integrity: sha512-IyyfrTA2iiOh/uhlaJj0aUDgW42lFhr29ZeKouVNOz/8mLyuqWbEuVst+B4RBH18pb3AcOHnaOgyskAbsVOe3A==} + esbuild-wasm@0.26.0: + resolution: {integrity: sha512-9rZuermDo9ZbWvKBv/vDRaRciCpR4L3rEbZLDs5kDq3TrCHRQZaQipQeV9wK/btpLBzNUBujTrd1uorDxbL/GA==} engines: {node: '>=18'} hasBin: true - esbuild@0.25.10: - resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.26.0: + resolution: {integrity: sha512-3Hq7jri+tRrVWha+ZeIVhl4qJRha/XjRNSopvTsOaCvfPHrflTYTcUFcEjMKdxofsXXsdc4zjg5NOTnL4Gl57Q==} engines: {node: '>=18'} hasBin: true @@ -5300,8 +5539,8 @@ packages: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} - exponential-backoff@3.1.2: - resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} express-rate-limit@7.5.1: resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} @@ -5420,18 +5659,13 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - firebase-tools@14.17.0: - resolution: {integrity: sha512-BWOfB9kokuRZbfRpr1AJrMiJHzyp8TjTNmYlgmHnGcBOAwtDinaeceqiKwLyZ6SucnOWj6Ca73DubqbAj+DmKw==} - engines: {node: '>=20.0.0 || >=22.0.0'} - hasBin: true - firebase-tools@14.20.0: resolution: {integrity: sha512-8zduD1/tpm8tEf4V6dOdveLHVttPUdnubZeZksdvRcetJuffNuU+Ugr4/JsObhxJP0n0Npb2MvPMHln6xKHMXw==} engines: {node: '>=20.0.0 || >=22.0.0'} hasBin: true - firebase@12.4.0: - resolution: {integrity: sha512-/chNgDQ6ppPPGOQO4jctxOa/5JeQxuhaxA7Y90K0I+n/wPfoO8mRveedhVUdo7ExLcWUivnnow/ouSLYSI5Icw==} + firebase@12.6.0: + resolution: {integrity: sha512-8ZD1Gcv916Qp8/nsFH2+QMIrfX/76ti6cJwxQUENLXXnKlOX/IJZaU2Y3bdYf5r1mbownrQKfnWtrt+MVgdwLA==} flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} @@ -5555,8 +5789,8 @@ packages: resolution: {integrity: sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==} engines: {node: '>=14'} - gaxios@7.1.2: - resolution: {integrity: sha512-/Szrn8nr+2TsQT1Gp8iIe/BEytJmbyfrbFh419DfGQSkEgNEhbPi7JRJuughjkTzPWgU9gBQf5AVu3DbHt0OXA==} + gaxios@7.1.3: + resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} engines: {node: '>=18'} gaze@1.1.3: @@ -5567,8 +5801,8 @@ packages: resolution: {integrity: sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==} engines: {node: '>=14'} - gcp-metadata@7.0.1: - resolution: {integrity: sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==} + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} engines: {node: '>=18'} generator-function@2.0.1: @@ -5607,8 +5841,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} get-uri@6.0.5: resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} @@ -5697,8 +5931,8 @@ packages: resolution: {integrity: sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==} engines: {node: '>= 0.10'} - google-auth-library@10.4.0: - resolution: {integrity: sha512-CmIrSy1bqMQUsPmA9+hcSbAXL80cFhu40cGMUjCaLpNKVzzvi+0uAHq8GNZxkoGYIsTX4ZQ7e4aInAqWxgn4fg==} + google-auth-library@10.5.0: + resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} engines: {node: '>=18'} google-auth-library@9.15.1: @@ -5709,16 +5943,16 @@ packages: resolution: {integrity: sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==} engines: {node: '>=14'} - google-gax@5.0.4: - resolution: {integrity: sha512-HmQ6zIYBs2EikTk+kjeHmtHprNTEpsnVaKONw9cwZZwUNCkUb+D5RYrJpCxyjdvIDvJp3wLbVReolJLRZRms1g==} + google-gax@5.0.5: + resolution: {integrity: sha512-VuC6nVnPVfo/M1WudLoS4Y3dTepVndZatUmeb0nUNmfzft6mKSy8ffDh4h5qxR7L9lslDxNpWPYsuPrFboOmTw==} engines: {node: '>=18'} google-logging-utils@0.0.2: resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} engines: {node: '>=14'} - google-logging-utils@1.1.1: - resolution: {integrity: sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A==} + google-logging-utils@1.1.2: + resolution: {integrity: sha512-YsFPGVgDFf4IzSwbwIR0iaFJQFmR5Jp7V1WuYSjuRgAm9yWqsMhKE9YPlL+wvFLnc/wMiFV4SQUD9Y/JMpxIxQ==} engines: {node: '>=14'} googleapis-common@8.0.2-rc.0: @@ -5741,8 +5975,8 @@ packages: peerDependencies: graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - graphql@16.11.0: - resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + graphql@16.12.0: + resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} grpc-gcp@1.0.1: @@ -5864,8 +6098,8 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} - hosted-git-info@9.0.0: - resolution: {integrity: sha512-gEf705MZLrDPkbbhi8PnoO4ZwYgKoNL+ISZ3AjZMht2r3N5tuTwncyDi6Fv2/qDnMmZxgs0yI8WDOyR8q3G+SQ==} + hosted-git-info@9.0.2: + resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} engines: {node: ^20.17.0 || >=22.9.0} hpack.js@2.1.6: @@ -6019,15 +6253,15 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - immutable@5.1.3: - resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==} + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - import-in-the-middle@1.14.4: - resolution: {integrity: sha512-eWjxh735SJLFJJDs5X82JQ2405OdJeAHDBnaoFCfdr5GVc7AWc9xU7KbrF+3Xd5F2ccP1aQFKtY+65X6EfKZ7A==} + import-in-the-middle@1.15.0: + resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} import-lazy@2.1.0: resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} @@ -6077,11 +6311,11 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - intl-messageformat@10.7.17: - resolution: {integrity: sha512-0Ugaf65B2J76rb31drgNF1l6bGEDkbIiYc2Glx6jaZINHnwa5kDRGy8KXYuA+/8P4G0c9prAFhfVhQJJfzUuvQ==} + intl-messageformat@10.7.18: + resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} - ip-address@10.0.1: - resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} ip-regex@4.3.0: @@ -6457,12 +6691,12 @@ packages: jasmine-core@4.6.1: resolution: {integrity: sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==} - jasmine-core@5.11.0: - resolution: {integrity: sha512-MPJ8L5yyNul0F2SuEsLASwESXQjJvBXnKu31JWFyRZSvuv2B79K4GDWN3pSqvLheUNh7Fyb6dXwd4rsz95O2Kg==} - jasmine-core@5.12.0: resolution: {integrity: sha512-QqO4pX33GEML5JoGQU6BM5NHKPgEsg+TXp3jCIDek9MbfEp2JUYEFBo9EF1+hegWy/bCHS1m5nP0BOp18G6rVA==} + jasmine-core@5.12.1: + resolution: {integrity: sha512-P/UbRZ0LKwXe7wEpwDheuhunPwITn4oPALhrJEQJo6756EwNGnsK/TSQrWojBB4cQDQ+VaxWYws9tFNDuiMh2Q==} + jasmine-reporters@2.5.2: resolution: {integrity: sha512-qdewRUuFOSiWhiyWZX8Yx3YNQ9JG51ntBEO4ekLQRpktxFTwUHy24a86zD/Oi2BRTKksEdfWQZcQFqzjqIkPig==} @@ -6473,10 +6707,6 @@ packages: resolution: {integrity: sha512-KbdGQTf5jbZgltoHs31XGiChAPumMSY64OZMWLNYnEnMfG5uwGBhffePwuskexjT+/Jea/gU3qAU8344hNohSw==} hasBin: true - jasmine@5.11.0: - resolution: {integrity: sha512-MhIYY2pLfRA5hhIvY72ZLilwKeZEBuTyIUv9JDB+b+pEYehsJDW2obKF2dmMtWaFG6pDiFiAUNphpZ7SW7fFMA==} - hasBin: true - jasmine@5.12.0: resolution: {integrity: sha512-KmKeTNuH8rgAuPRL5AUsXWSdJVlDu+pgqi2dLXoZUSH/g3kR+7Ho8B7hEhwDu0fu1PLuiXZtfaxmQ/mB5wqihw==} hasBin: true @@ -6537,9 +6767,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-parse-even-better-errors@4.0.0: - resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} - engines: {node: ^18.17.0 || >=20.5.0} + json-parse-even-better-errors@5.0.0: + resolution: {integrity: sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==} + engines: {node: ^20.17.0 || >=22.9.0} json-parse-helpfulerror@1.0.3: resolution: {integrity: sha512-XgP0FGR77+QhUxjXkwOMkC94k3WtqEBfcnjWqhRd82qTat4SWKRE+9kUnynz/shm3I4ea2+qISvTIeGTNU7kJg==} @@ -6669,8 +6899,8 @@ packages: kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - launch-editor@2.11.1: - resolution: {integrity: sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==} + launch-editor@2.12.0: + resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} lazystream@1.0.1: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} @@ -6732,16 +6962,16 @@ packages: lighthouse-stack-packs@1.12.3: resolution: {integrity: sha512-d8IsOpE83kbANgnM+Tp8+x6HcMpX9o2ITBiUERssgzAIFdZCQzs/f4k6D0DLQTE59enml9mbAOU52Wu35exWtg==} - lighthouse@13.0.0: - resolution: {integrity: sha512-s9koINEnVGm4n4oej4G3NET4e8i1XjPYCxEqp+TDlwUUkS5jpeOiFbIRLvPol/kgaeYce7BBqg8m52V7MEW7Lw==} + lighthouse@13.0.1: + resolution: {integrity: sha512-SsxFXPE0DoUv6rH3hva0luh0pbpyIx9McBQ1WUpqCYFMtArODT6l9Zpu1K3XSdkeMQ2/zFcMN5o3pPVhfVwnCA==} engines: {node: '>=22.19'} hasBin: true lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - listr2@9.0.4: - resolution: {integrity: sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ==} + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} lmdb@3.4.3: @@ -6752,8 +6982,8 @@ packages: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} - loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} engines: {node: '>=6.11.5'} loader-utils@2.0.4: @@ -6943,8 +7173,8 @@ packages: engines: {node: '>= 18'} hasBin: true - marked@16.3.0: - resolution: {integrity: sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==} + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} engines: {node: '>= 20'} hasBin: true @@ -6966,8 +7196,8 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - memfs@4.48.1: - resolution: {integrity: sha512-vWO+1ROkhOALF1UnT9aNOOflq5oFDlqwTXaPg6duo07fBLxSH0+bcF0TY1lbA1zTNKyGgDxgaDdKx5MaewLX5A==} + memfs@4.50.0: + resolution: {integrity: sha512-N0LUYQMUA1yS5tJKmMtU9yprPm6ZIg24yr/OVv/7t6q0kKDIho4cBbXRi1XKttUmNYDYgF/q45qrKE/UhGO0CA==} memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} @@ -7050,8 +7280,8 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} engines: {node: 20 || >=22} minimatch@3.0.8: @@ -7176,15 +7406,15 @@ packages: resolution: {integrity: sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ==} engines: {node: '>=18'} - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nan@2.23.0: - resolution: {integrity: sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==} + nan@2.23.1: + resolution: {integrity: sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw==} nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} @@ -7269,13 +7499,13 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-gyp@11.4.2: - resolution: {integrity: sha512-3gD+6zsrLQH7DyYOUIutaauuXrcyxeTPyQuZQCQoNPZMHMMS5m4y0xclNpvYzoK3VNzuyxT6eF4mkIL4WSZ1eQ==} + node-gyp@11.5.0: + resolution: {integrity: sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==} engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - node-releases@2.0.23: - resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} nopt@3.0.6: resolution: {integrity: sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==} @@ -7309,9 +7539,9 @@ packages: resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - npm-install-checks@7.1.2: - resolution: {integrity: sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==} - engines: {node: ^18.17.0 || >=20.5.0} + npm-install-checks@8.0.0: + resolution: {integrity: sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==} + engines: {node: ^20.17.0 || >=22.9.0} npm-normalize-package-bin@3.0.1: resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} @@ -7321,6 +7551,10 @@ packages: resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} engines: {node: ^18.17.0 || >=20.5.0} + npm-normalize-package-bin@5.0.0: + resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} + engines: {node: ^20.17.0 || >=22.9.0} + npm-package-arg@11.0.3: resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} engines: {node: ^16.14.0 || >=18.0.0} @@ -7329,20 +7563,20 @@ packages: resolution: {integrity: sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==} engines: {node: ^20.17.0 || >=22.9.0} - npm-packlist@10.0.2: - resolution: {integrity: sha512-DrIWNiWT0FTdDRjGOYfEEZUNe1IzaSZ+up7qBTKnrQDySpdmuOQvytrqQlpK5QrCA4IThMvL4wTumqaa1ZvVIQ==} + npm-packlist@10.0.3: + resolution: {integrity: sha512-zPukTwJMOu5X5uvm0fztwS5Zxyvmk38H/LfidkOMt3gbZVCyro2cD/ETzwzVPcWZA3JOyPznfUN/nkyFiyUbxg==} engines: {node: ^20.17.0 || >=22.9.0} - npm-pick-manifest@11.0.1: - resolution: {integrity: sha512-HnU7FYSWbo7dTVHtK0G+BXbZ0aIfxz/aUCVLN0979Ec6rGUX5cJ6RbgVx5fqb5G31ufz+BVFA7y1SkRTPVNoVQ==} + npm-pick-manifest@11.0.3: + resolution: {integrity: sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==} engines: {node: ^20.17.0 || >=22.9.0} npm-pick-manifest@9.1.0: resolution: {integrity: sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==} engines: {node: ^16.14.0 || >=18.0.0} - npm-registry-fetch@19.0.0: - resolution: {integrity: sha512-DFxSAemHUwT/POaXAOY4NJmEWBPB0oKbwD6FFDE9hnt1nORkt/FXvgjD4hQjoKoHw9u0Ezws9SPXwV7xE/Gyww==} + npm-registry-fetch@19.1.0: + resolution: {integrity: sha512-xyZLfs7TxPu/WKjHUs0jZOPinzBAI32kEUel6za0vH+JUTnFZ5zbHI1ZoGZRDm6oMjADtrli6FxtMlk/5ABPNw==} engines: {node: ^20.17.0 || >=22.9.0} npm-run-all@4.1.5: @@ -7612,8 +7846,8 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.0: - resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + path-scurry@2.0.1: + resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} path-to-regexp@0.1.12: @@ -7834,6 +8068,10 @@ packages: resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} engines: {node: ^18.17.0 || >=20.5.0} + proc-log@6.0.0: + resolution: {integrity: sha512-KG/XsTDN901PNfPfAMmj6N/Ywg9tM+bHK8pAz+27fS4N4Pcr+4zoYBOcGSBu6ceXYNPxkLpa4ohtfxV1XcLAfA==} + engines: {node: ^20.17.0 || >=22.9.0} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -7870,8 +8108,8 @@ packages: resolution: {integrity: sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==} engines: {node: '>=14.0.0'} - proto3-json-serializer@3.0.2: - resolution: {integrity: sha512-AnMIfnoK2Ml3F/ZVl5PxcwIoefMxj4U/lomJ5/B2eIGdxw4UkbV1YamtsMQsEkZATdMCKMbnI1iG9RQaJbxBGw==} + proto3-json-serializer@3.0.4: + resolution: {integrity: sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==} engines: {node: '>=18'} protobufjs@7.4.0: @@ -7930,8 +8168,8 @@ packages: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} engines: {node: '>=8'} - puppeteer-core@24.23.0: - resolution: {integrity: sha512-yl25C59gb14sOdIiSnJ08XiPP+O2RjuyZmEG+RjYmCXO7au0jcLf7fRiyii96dXGUBW7Zwei/mVKfxMx/POeFw==} + puppeteer-core@24.29.1: + resolution: {integrity: sha512-ErJ9qKCK+bdLvBa7QVSQTBSPm8KZbl1yC/WvhrZ0ut27hDf2QBzjDsn1IukzE1i1KtZ7NYGETOV4W1beoo9izA==} engines: {node: '>=18'} q@1.4.1: @@ -7999,8 +8237,8 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - re2@1.22.1: - resolution: {integrity: sha512-E4J0EtgyNLdIr0wTg0dQPefuiqNY29KaLacytiUAYYRzxCG+zOkWoUygt1rI+TA1LrhN49/njrfSO1DHtVC5Vw==} + re2@1.22.3: + resolution: {integrity: sha512-002aE82U91DiaUA16U6vbiJusvPXn1OWiQukOxJkVUTXbzrSuQbFNHYKcGw8QK/uifRCfjl2Hd/vXYDanKkmaQ==} read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} @@ -8139,8 +8377,8 @@ packages: resolve@1.1.7: resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} hasBin: true @@ -8194,12 +8432,16 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + robots-parser@3.0.1: resolution: {integrity: sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ==} engines: {node: '>=10.0.0'} - rolldown@1.0.0-beta.43: - resolution: {integrity: sha512-6RcqyRx0tY1MlRLnjXPp/849Rl/CPFhzpGGwNPEPjKwqBMqPq/Rbbkxasa8s0x+IkUk46ty4jazb5skZ/Vgdhw==} + rolldown@1.0.0-beta.47: + resolution: {integrity: sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -8220,8 +8462,8 @@ packages: '@types/node': optional: true - rollup@4.52.4: - resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==} + rollup@4.53.1: + resolution: {integrity: sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -8310,8 +8552,8 @@ packages: saucelabs@1.5.0: resolution: {integrity: sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==} - sax@1.4.1: - resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + sax@1.4.3: + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} schema-utils@4.3.3: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} @@ -8340,11 +8582,6 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -8741,8 +8978,11 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - stubborn-fs@1.2.5: - resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} + stubborn-fs@2.0.0: + resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} + + stubborn-utils@1.0.2: + resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} stubs@3.0.0: resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} @@ -8820,8 +9060,8 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - tar@7.5.1: - resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} + tar@7.5.2: + resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} tcp-port-used@1.0.2: @@ -8860,6 +9100,11 @@ packages: engines: {node: '>=10'} hasBin: true + terser@5.44.1: + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} + engines: {node: '>=10'} + hasBin: true + text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} @@ -8882,6 +9127,9 @@ packages: third-party-web@0.27.0: resolution: {integrity: sha512-h0JYX+dO2Zr3abCQpS6/uFjujaOjA1DyDzGQ41+oFn9VW/ARiq9g5ln7qEP9+BTzDpOMyIfsfj4OvfgXAsMUSA==} + third-party-web@0.29.0: + resolution: {integrity: sha512-nBDSJw5B7Sl1YfsATG2XkW5qgUPODbJhXw++BKygi9w6O/NKS98/uY/nR/DxDq2axEjL6halHW1v+jhm/j1DBQ==} + through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} @@ -9128,13 +9376,17 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.14.0: - resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} undici@5.29.0: resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} engines: {node: '>=14.0'} + undici@7.16.0: + resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + engines: {node: '>=20.18.1'} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -9201,8 +9453,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -9293,8 +9545,8 @@ packages: vfile@3.0.1: resolution: {integrity: sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==} - vite@7.1.10: - resolution: {integrity: sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==} + vite@7.2.2: + resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -9366,8 +9618,8 @@ packages: web-vitals@4.2.4: resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} - webdriver-bidi-protocol@0.3.6: - resolution: {integrity: sha512-mlGndEOA9yK9YAbvtxaPTqdi/kaCWYYfwrZvGzcmkr/3lWM+tQj53BxtpVd6qbC6+E5OnHXgCcAhre6AkXzxjA==} + webdriver-bidi-protocol@0.3.8: + resolution: {integrity: sha512-21Yi2GhGntMc671vNBCjiAeEVknXjVRoyu+k+9xOMShu+ZQfpGQwnBqbNz/Sv4GXZ6JmutlPAi2nIJcrymAWuQ==} webdriver-js-extender@2.1.0: resolution: {integrity: sha512-lcUKrjbBfCK6MNsh7xaY2UAUmZwe+/ib03AjVOpFobX4O7+83BUveSrLfU0Qsyb1DaKJdQRbuU+kM9aZ6QUhiQ==} @@ -9445,8 +9697,8 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - when-exit@2.1.4: - resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==} + when-exit@2.1.5: + resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} @@ -9486,6 +9738,11 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true + which@6.0.0: + resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} @@ -9697,8 +9954,8 @@ packages: zone.js@0.15.1: resolution: {integrity: sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==} - zx@8.8.4: - resolution: {integrity: sha512-44GcD+ZlM/v1OQtbwnSxLPcoE1ZEUICmR+RSbJZLAqfIixNLuMjLyh0DcS75OyfJ/sWYAwCWDmDvJ4hdnANAPQ==} + zx@8.8.5: + resolution: {integrity: sha512-SNgDF5L0gfN7FwVOdEFguY3orU5AkfFZm9B5YSHog/UDHv+lvmd82ZAsOenOkQixigwH2+yyH198AwNdKhj+RA==} engines: {node: '>= 12.17.0'} hasBin: true @@ -9720,110 +9977,110 @@ snapshots: '@actions/io@1.1.3': {} - '@algolia/abtesting@1.6.0': + '@algolia/abtesting@1.6.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-abtesting@5.40.0': + '@algolia/client-abtesting@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-analytics@5.40.0': + '@algolia/client-analytics@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-common@5.40.0': {} + '@algolia/client-common@5.40.1': {} - '@algolia/client-insights@5.40.0': + '@algolia/client-insights@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-personalization@5.40.0': + '@algolia/client-personalization@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-query-suggestions@5.40.0': + '@algolia/client-query-suggestions@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/client-search@5.40.0': + '@algolia/client-search@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/ingestion@1.40.0': + '@algolia/ingestion@1.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/monitoring@1.40.0': + '@algolia/monitoring@1.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/recommend@5.40.0': + '@algolia/recommend@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + '@algolia/client-common': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 - '@algolia/requester-browser-xhr@5.40.0': + '@algolia/requester-browser-xhr@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 + '@algolia/client-common': 5.40.1 - '@algolia/requester-fetch@5.40.0': + '@algolia/requester-fetch@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 + '@algolia/client-common': 5.40.1 - '@algolia/requester-node-http@5.40.0': + '@algolia/requester-node-http@5.40.1': dependencies: - '@algolia/client-common': 5.40.0 + '@algolia/client-common': 5.40.1 '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@angular-devkit/architect@0.2100.0-next.8(chokidar@4.0.3)': + '@angular-devkit/architect@0.2100.1(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 21.0.0-next.8(chokidar@4.0.3) + '@angular-devkit/core': 21.0.1(chokidar@4.0.3) rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@21.0.0-next.8(cfe8a97e2176695474f8a07e88e10fe8)': + '@angular-devkit/build-angular@21.0.1(eeb44f99cc32b2717d9a0f24d72d1e0f)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.2100.0-next.8(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.2100.0-next.8(chokidar@4.0.3)(webpack-dev-server@5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.25.10)))(webpack@5.102.1(esbuild@0.25.10)) - '@angular-devkit/core': 21.0.0-next.8(chokidar@4.0.3) - '@angular/build': 21.0.0-next.8(b79efbffc17c55a8c1fe4809ca38affa) - '@angular/compiler-cli': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2) + '@angular-devkit/architect': 0.2100.1(chokidar@4.0.3) + '@angular-devkit/build-webpack': 0.2100.1(chokidar@4.0.3)(webpack-dev-server@5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.26.0)))(webpack@5.102.1(esbuild@0.26.0)) + '@angular-devkit/core': 21.0.1(chokidar@4.0.3) + '@angular/build': 21.0.1(4102bd41b95b86437844a27094717d62) + '@angular/compiler-cli': 21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2) '@babel/core': 7.28.4 '@babel/generator': 7.28.3 '@babel/helper-annotate-as-pure': 7.27.3 @@ -9834,53 +10091,53 @@ snapshots: '@babel/preset-env': 7.28.3(@babel/core@7.28.4) '@babel/runtime': 7.28.4 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.102.1(esbuild@0.25.10)) + '@ngtools/webpack': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.102.1(esbuild@0.26.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.21(postcss@8.5.6) - babel-loader: 10.0.0(@babel/core@7.28.4)(webpack@5.102.1(esbuild@0.25.10)) - browserslist: 4.26.3 - copy-webpack-plugin: 13.0.1(webpack@5.102.1(esbuild@0.25.10)) - css-loader: 7.1.2(webpack@5.102.1(esbuild@0.25.10)) - esbuild-wasm: 0.25.10 + babel-loader: 10.0.0(@babel/core@7.28.4)(webpack@5.102.1(esbuild@0.26.0)) + browserslist: 4.27.0 + copy-webpack-plugin: 13.0.1(webpack@5.102.1(esbuild@0.26.0)) + css-loader: 7.1.2(webpack@5.102.1(esbuild@0.26.0)) + esbuild-wasm: 0.26.0 http-proxy-middleware: 3.0.5 istanbul-lib-instrument: 6.0.3 jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.4.2 - less-loader: 12.3.0(less@4.4.2)(webpack@5.102.1(esbuild@0.25.10)) - license-webpack-plugin: 4.0.2(webpack@5.102.1(esbuild@0.25.10)) + less-loader: 12.3.0(less@4.4.2)(webpack@5.102.1(esbuild@0.26.0)) + license-webpack-plugin: 4.0.2(webpack@5.102.1(esbuild@0.26.0)) loader-utils: 3.3.1 - mini-css-extract-plugin: 2.9.4(webpack@5.102.1(esbuild@0.25.10)) + mini-css-extract-plugin: 2.9.4(webpack@5.102.1(esbuild@0.26.0)) open: 10.2.0 ora: 9.0.0 picomatch: 4.0.3 piscina: 5.1.3 postcss: 8.5.6 - postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.2)(webpack@5.102.1(esbuild@0.25.10)) + postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.2)(webpack@5.102.1(esbuild@0.26.0)) resolve-url-loader: 5.0.0 rxjs: 7.8.2 sass: 1.93.2 - sass-loader: 16.0.5(sass@1.93.2)(webpack@5.102.1(esbuild@0.25.10)) + sass-loader: 16.0.5(sass@1.93.2)(webpack@5.102.1(esbuild@0.26.0)) semver: 7.7.3 - source-map-loader: 5.0.0(webpack@5.102.1(esbuild@0.25.10)) + source-map-loader: 5.0.0(webpack@5.102.1(esbuild@0.26.0)) source-map-support: 0.5.21 terser: 5.44.0 tinyglobby: 0.2.15 tree-kill: 1.2.2 tslib: 2.8.1 typescript: 5.9.2 - webpack: 5.102.1(esbuild@0.25.10) - webpack-dev-middleware: 7.4.5(webpack@5.102.1(esbuild@0.25.10)) - webpack-dev-server: 5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.25.10)) + webpack: 5.102.1(esbuild@0.26.0) + webpack-dev-middleware: 7.4.5(webpack@5.102.1(esbuild@0.26.0)) + webpack-dev-server: 5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.26.0)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(webpack@5.102.1(esbuild@0.25.10)) + webpack-subresource-integrity: 5.1.0(webpack@5.102.1(esbuild@0.26.0)) optionalDependencies: - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@angular/localize': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8) - '@angular/platform-browser': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)) - '@angular/platform-server': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8)(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) - '@angular/ssr': 21.0.0-next.8(97a956334e4483b817e98b9a94c98525) - esbuild: 0.25.10 + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@angular/localize': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1) + '@angular/platform-browser': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)) + '@angular/platform-server': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1)(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + '@angular/ssr': 21.0.1(720178cc5eba0b050aa5091f059d8960) + esbuild: 0.26.0 karma: 6.4.4(bufferutil@4.0.9) protractor: 7.0.0 transitivePeerDependencies: @@ -9906,16 +10163,16 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-webpack@0.2100.0-next.8(chokidar@4.0.3)(webpack-dev-server@5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.25.10)))(webpack@5.102.1(esbuild@0.25.10))': + '@angular-devkit/build-webpack@0.2100.1(chokidar@4.0.3)(webpack-dev-server@5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.26.0)))(webpack@5.102.1(esbuild@0.26.0))': dependencies: - '@angular-devkit/architect': 0.2100.0-next.8(chokidar@4.0.3) + '@angular-devkit/architect': 0.2100.1(chokidar@4.0.3) rxjs: 7.8.2 - webpack: 5.102.1(esbuild@0.25.10) - webpack-dev-server: 5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.25.10)) + webpack: 5.102.1(esbuild@0.26.0) + webpack-dev-server: 5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.26.0)) transitivePeerDependencies: - chokidar - '@angular-devkit/core@21.0.0-next.8(chokidar@4.0.3)': + '@angular-devkit/core@21.0.1(chokidar@4.0.3)': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1 @@ -9926,9 +10183,9 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics@21.0.0-next.8(chokidar@4.0.3)': + '@angular-devkit/schematics@21.0.1(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 21.0.0-next.8(chokidar@4.0.3) + '@angular-devkit/core': 21.0.1(chokidar@4.0.3) jsonc-parser: 3.3.1 magic-string: 0.30.19 ora: 9.0.0 @@ -9936,44 +10193,45 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular/build@21.0.0-next.8(b79efbffc17c55a8c1fe4809ca38affa)': + '@angular/build@21.0.1(4102bd41b95b86437844a27094717d62)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.2100.0-next.8(chokidar@4.0.3) - '@angular/compiler': 21.0.0-next.8 - '@angular/compiler-cli': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2) + '@angular-devkit/architect': 0.2100.1(chokidar@4.0.3) + '@angular/compiler': 21.0.1 + '@angular/compiler-cli': 21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2) '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 5.1.19(@types/node@22.18.8) - '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.1.10(@types/node@22.18.8)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + '@inquirer/confirm': 5.1.19(@types/node@22.19.0) + '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) beasties: 0.3.5 - browserslist: 4.26.3 - esbuild: 0.25.10 + browserslist: 4.27.0 + esbuild: 0.26.0 https-proxy-agent: 7.0.6(supports-color@10.2.2) istanbul-lib-instrument: 6.0.3 jsonc-parser: 3.3.1 - listr2: 9.0.4 + listr2: 9.0.5 magic-string: 0.30.19 mrmime: 2.0.1 parse5-html-rewriting-stream: 8.0.0 picomatch: 4.0.3 piscina: 5.1.3 - rolldown: 1.0.0-beta.43 + rolldown: 1.0.0-beta.47 sass: 1.93.2 semver: 7.7.3 source-map-support: 0.5.21 tinyglobby: 0.2.15 tslib: 2.8.1 typescript: 5.9.2 - vite: 7.1.10(@types/node@22.18.8)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + undici: 7.16.0 + vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) watchpack: 2.4.4 optionalDependencies: - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@angular/localize': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8) - '@angular/platform-browser': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)) - '@angular/platform-server': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8)(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) - '@angular/ssr': 21.0.0-next.8(97a956334e4483b817e98b9a94c98525) + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@angular/localize': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1) + '@angular/platform-browser': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)) + '@angular/platform-server': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1)(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + '@angular/ssr': 21.0.1(720178cc5eba0b050aa5091f059d8960) karma: 6.4.4(bufferutil@4.0.9) less: 4.4.2 lmdb: 3.4.3 @@ -9991,23 +10249,24 @@ snapshots: - tsx - yaml - '@angular/cli@21.0.0-next.8(@types/node@22.18.8)(chokidar@4.0.3)': + '@angular/cli@21.0.1(@types/node@22.19.0)(chokidar@4.0.3)': dependencies: - '@angular-devkit/architect': 0.2100.0-next.8(chokidar@4.0.3) - '@angular-devkit/core': 21.0.0-next.8(chokidar@4.0.3) - '@angular-devkit/schematics': 21.0.0-next.8(chokidar@4.0.3) - '@inquirer/prompts': 7.9.0(@types/node@22.18.8) - '@listr2/prompt-adapter-inquirer': 3.0.4(@inquirer/prompts@7.9.0(@types/node@22.18.8))(@types/node@22.18.8)(listr2@9.0.4) - '@modelcontextprotocol/sdk': 1.20.0 - '@schematics/angular': 21.0.0-next.8(chokidar@4.0.3) + '@angular-devkit/architect': 0.2100.1(chokidar@4.0.3) + '@angular-devkit/core': 21.0.1(chokidar@4.0.3) + '@angular-devkit/schematics': 21.0.1(chokidar@4.0.3) + '@inquirer/prompts': 7.9.0(@types/node@22.19.0) + '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.9.0(@types/node@22.19.0))(@types/node@22.19.0)(listr2@9.0.5) + '@modelcontextprotocol/sdk': 1.20.1 + '@schematics/angular': 21.0.1(chokidar@4.0.3) '@yarnpkg/lockfile': 1.1.0 - algoliasearch: 5.40.0 + algoliasearch: 5.40.1 ini: 5.0.0 jsonc-parser: 3.3.1 - listr2: 9.0.4 + listr2: 9.0.5 npm-package-arg: 13.0.1 pacote: 21.0.3 - resolve: 1.22.10 + parse5-html-rewriting-stream: 8.0.0 + resolve: 1.22.11 semver: 7.7.3 yargs: 18.0.0 zod: 3.25.76 @@ -10016,19 +10275,19 @@ snapshots: - chokidar - supports-color - '@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7)': + '@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7)': dependencies: - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2)) + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2)) rxjs: 6.6.7 tslib: 2.8.1 transitivePeerDependencies: - '@angular/compiler-cli' - supports-color - '@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2)': + '@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2)': dependencies: - '@angular/compiler': 21.0.0-next.8 + '@angular/compiler': 21.0.1 '@babel/core': 7.28.4 '@jridgewell/sourcemap-codec': 1.5.5 chokidar: 4.0.3 @@ -10042,88 +10301,87 @@ snapshots: transitivePeerDependencies: - supports-color - '@angular/compiler@21.0.0-next.8': + '@angular/compiler@21.0.1': dependencies: tslib: 2.8.1 - '@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)': + '@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)': dependencies: rxjs: 6.6.7 tslib: 2.8.1 optionalDependencies: - '@angular/compiler': 21.0.0-next.8 + '@angular/compiler': 21.0.1 zone.js: 0.15.1 - '@angular/forms@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7)': + '@angular/forms@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7)': dependencies: - '@angular/common': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@angular/platform-browser': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)) - '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2)) + '@angular/common': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@angular/platform-browser': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2)) rxjs: 6.6.7 tslib: 2.8.1 transitivePeerDependencies: - '@angular/compiler-cli' - supports-color - '@angular/localize@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8)': + '@angular/localize@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1)': dependencies: - '@angular/compiler': 21.0.0-next.8 - '@angular/compiler-cli': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2) + '@angular/compiler': 21.0.1 + '@angular/compiler-cli': 21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2) '@babel/core': 7.28.4 - '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2)) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2)) '@types/babel__core': 7.20.5 tinyglobby: 0.2.15 yargs: 18.0.0 transitivePeerDependencies: - supports-color - '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/3c4fd6f54f2c67ce5b9f1a32ce90e36ba2fada4e(@modelcontextprotocol/sdk@1.20.0)': + '@angular/ng-dev@https://codeload.github.com/angular/dev-infra-private-ng-dev-builds/tar.gz/4c28145df03aff8c74d0a53f4f5602140e4d1a23(@modelcontextprotocol/sdk@1.21.1)': dependencies: '@actions/core': 1.11.1 '@google-cloud/spanner': 8.0.0(supports-color@10.2.2) - '@google/genai': 1.25.0(@modelcontextprotocol/sdk@1.20.0)(bufferutil@4.0.9)(encoding@0.1.13)(supports-color@10.2.2)(utf-8-validate@6.0.5) - '@inquirer/prompts': 7.9.0(@types/node@24.8.1) - '@inquirer/type': 3.0.9(@types/node@24.8.1) - '@octokit/auth-app': 8.1.1 - '@octokit/core': 7.0.5 - '@octokit/graphql': 9.0.2 - '@octokit/graphql-schema': 15.26.0 - '@octokit/openapi-types': 26.0.0 - '@octokit/plugin-paginate-rest': 13.2.0(@octokit/core@7.0.5) - '@octokit/plugin-rest-endpoint-methods': 16.1.0(@octokit/core@7.0.5) - '@octokit/request-error': 7.0.1 - '@octokit/rest': 22.0.0 - '@octokit/types': 15.0.0 - '@pnpm/dependency-path': 1001.1.2 + '@google/genai': 1.30.0(@modelcontextprotocol/sdk@1.21.1)(bufferutil@4.0.9)(supports-color@10.2.2)(utf-8-validate@6.0.5) + '@inquirer/prompts': 8.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) + '@octokit/auth-app': 8.1.2 + '@octokit/core': 7.0.6 + '@octokit/graphql': 9.0.3 + '@octokit/graphql-schema': 15.26.1 + '@octokit/openapi-types': 27.0.0 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + '@octokit/request-error': 7.1.0 + '@octokit/rest': 22.0.1 + '@octokit/types': 16.0.0 + '@pnpm/dependency-path': 1001.1.5 '@types/cli-progress': 3.11.6 '@types/ejs': 3.1.5 '@types/events': 3.0.3 '@types/folder-hash': 4.0.4 - '@types/git-raw-commits': 5.0.0 - '@types/jasmine': 5.1.12 - '@types/node': 24.8.1 + '@types/git-raw-commits': 5.0.1 + '@types/jasmine': 5.1.13 + '@types/node': 24.10.1 '@types/semver': 7.7.1 '@types/which': 3.0.4 - '@types/yargs': 17.0.33 + '@types/yargs': 17.0.35 '@types/yarnpkg__lockfile': 1.1.9 '@yarnpkg/lockfile': 1.1.0 bufferutil: 4.0.9 - chalk: 5.6.2 cli-progress: 3.12.0 conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.0 + conventional-commits-parser: 6.2.1 ejs: 3.1.10 encoding: 0.1.13 fast-glob: 3.3.3 - firebase: 12.4.0 + firebase: 12.6.0 folder-hash: 4.1.1(supports-color@10.2.2) - git-raw-commits: 5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.0) + git-raw-commits: 5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1) jasmine: 5.12.0 - jasmine-core: 5.12.0 + jasmine-core: 5.12.1 jasmine-reporters: 2.5.2 jsonc-parser: 3.3.1 - minimatch: 10.0.3 + minimatch: 10.1.1 multimatch: 7.0.0 nock: 14.0.10 semver: 7.7.3 @@ -10132,38 +10390,38 @@ snapshots: typed-graphqlify: 3.1.6 typescript: 5.9.2 utf-8-validate: 6.0.5 - which: 5.0.0 + which: 6.0.0 yaml: 2.8.1 yargs: 18.0.0 transitivePeerDependencies: - '@modelcontextprotocol/sdk' - '@react-native-async-storage/async-storage' - '@angular/platform-browser-dynamic@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler@21.0.0-next.8)(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))': + '@angular/platform-browser-dynamic@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler@21.0.1)(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))': dependencies: - '@angular/common': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) - '@angular/compiler': 21.0.0-next.8 - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@angular/platform-browser': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)) + '@angular/common': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) + '@angular/compiler': 21.0.1 + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@angular/platform-browser': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)) tslib: 2.8.1 - '@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))': + '@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))': dependencies: - '@angular/common': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2)) + '@angular/common': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2)) tslib: 2.8.1 transitivePeerDependencies: - '@angular/compiler-cli' - supports-color - '@angular/platform-server@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8)(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7)': + '@angular/platform-server@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1)(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7)': dependencies: - '@angular/common': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) - '@angular/compiler': 21.0.0-next.8 - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@angular/platform-browser': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)) - '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2)) + '@angular/common': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) + '@angular/compiler': 21.0.1 + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@angular/platform-browser': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2)) rxjs: 6.6.7 tslib: 2.8.1 xhr2: 0.2.1 @@ -10171,26 +10429,26 @@ snapshots: - '@angular/compiler-cli' - supports-color - '@angular/router@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7)': + '@angular/router@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7)': dependencies: - '@angular/common': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@angular/platform-browser': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)) - '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2)) + '@angular/common': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@angular/platform-browser': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)) + '@nginfra/angular-linking': 1.0.9(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2)) rxjs: 6.6.7 tslib: 2.8.1 transitivePeerDependencies: - '@angular/compiler-cli' - supports-color - '@angular/ssr@21.0.0-next.8(97a956334e4483b817e98b9a94c98525)': + '@angular/ssr@21.0.1(720178cc5eba0b050aa5091f059d8960)': dependencies: - '@angular/common': 21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) - '@angular/core': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1) - '@angular/router': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + '@angular/common': 21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7) + '@angular/core': 21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1) + '@angular/router': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) tslib: 2.8.1 optionalDependencies: - '@angular/platform-server': 21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/compiler@21.0.0-next.8)(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.0-next.8(@angular/common@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(@angular/core@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) + '@angular/platform-server': 21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/compiler@21.0.1)(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(@angular/platform-browser@21.0.1(@angular/common@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1))(rxjs@6.6.7))(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(@angular/core@21.0.1(@angular/compiler@21.0.1)(rxjs@6.6.7)(zone.js@0.15.1)))(rxjs@6.6.7) '@apidevtools/json-schema-ref-parser@9.1.2': dependencies: @@ -10199,14 +10457,14 @@ snapshots: call-me-maybe: 1.0.2 js-yaml: 4.1.0 - '@apphosting/build@0.1.6(@types/node@22.18.8)(typescript@5.9.2)': + '@apphosting/build@0.1.7(@types/node@22.19.0)(typescript@5.9.2)': dependencies: - '@apphosting/common': 0.0.8 + '@apphosting/common': 0.0.9 '@npmcli/promise-spawn': 3.0.0 colorette: 2.0.20 commander: 11.1.0 npm-pick-manifest: 9.1.0 - ts-node: 10.9.2(@types/node@22.18.8)(typescript@5.9.2) + ts-node: 10.9.2(@types/node@22.19.0)(typescript@5.9.2) transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -10215,26 +10473,28 @@ snapshots: '@apphosting/common@0.0.8': {} + '@apphosting/common@0.0.9': {} + '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.28.5': {} '@babel/core@7.26.10': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.28.3(@babel/core@7.26.10) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 convert-source-map: 2.0.0 debug: 4.4.3(supports-color@10.2.2) gensync: 1.0.0-beta.2 @@ -10246,14 +10506,34 @@ snapshots: '@babel/core@7.28.4': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@10.2.2) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3(supports-color@10.2.2) @@ -10265,38 +10545,46 @@ snapshots: '@babel/generator@7.28.3': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.3 + browserslist: 4.27.0 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.4)': + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.28.4)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 @@ -10310,23 +10598,23 @@ snapshots: '@babel/helper-plugin-utils': 7.27.1 debug: 4.4.3(supports-color@10.2.2) lodash.debounce: 4.0.8 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.27.1': + '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color @@ -10334,8 +10622,8 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10343,14 +10631,23 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/helper-plugin-utils@7.27.1': {} @@ -10359,58 +10656,58 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.3 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helper-split-export-declaration@7.24.7': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.27.1': {} '@babel/helper-wrap-function@7.28.3': dependencies: '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/parser@7.28.4': + '@babel/parser@7.28.5': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10429,7 +10726,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.4) transitivePeerDependencies: - supports-color @@ -10437,7 +10734,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10458,7 +10755,7 @@ snapshots: '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.4)': @@ -10471,7 +10768,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10489,7 +10786,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-block-scoping@7.28.4(@babel/core@7.28.4)': + '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 @@ -10497,7 +10794,7 @@ snapshots: '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -10505,7 +10802,7 @@ snapshots: '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -10518,7 +10815,7 @@ snapshots: '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10528,18 +10825,18 @@ snapshots: '@babel/helper-plugin-utils': 7.27.1 '@babel/template': 7.27.2 - '@babel/plugin-transform-destructuring@7.28.0(@babel/core@7.28.4)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.4)': @@ -10550,7 +10847,7 @@ snapshots: '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.4)': @@ -10562,11 +10859,11 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 @@ -10589,7 +10886,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10603,7 +10900,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 @@ -10629,13 +10926,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10650,7 +10947,7 @@ snapshots: '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.4)': @@ -10673,9 +10970,9 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color @@ -10692,7 +10989,7 @@ snapshots: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 @@ -10708,7 +11005,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -10717,7 +11014,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.4) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color @@ -10735,7 +11032,7 @@ snapshots: '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.4)': @@ -10791,29 +11088,29 @@ snapshots: '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.4)': dependencies: '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/preset-env@7.28.3(@babel/core@7.28.4)': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.4) '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.4) '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.4) '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.4) @@ -10826,28 +11123,28 @@ snapshots: '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.4) '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-block-scoping': 7.28.4(@babel/core@7.28.4) + '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.4) '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.4) '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.4) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.4) - '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-exponentiation-operator': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.4) @@ -10856,7 +11153,7 @@ snapshots: '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.4) '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.4) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.4) @@ -10877,7 +11174,7 @@ snapshots: babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.4) babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.4) babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.4) - core-js-compat: 3.45.1 + core-js-compat: 3.46.0 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -10886,7 +11183,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 esutils: 2.0.3 '@babel/runtime@7.28.4': {} @@ -10894,25 +11191,25 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 - '@babel/traverse@7.28.4': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color - '@babel/types@7.28.4': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@bazel/bazelisk@1.26.0': {} @@ -10926,13 +11223,13 @@ snapshots: '@colors/colors@1.6.0': {} - '@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.0)': + '@conventional-changelog/git-client@1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1)': dependencies: '@types/semver': 7.7.1 semver: 7.7.3 optionalDependencies: conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.0 + conventional-commits-parser: 6.2.1 '@cspotcode/source-map-support@0.8.1': dependencies: @@ -10950,21 +11247,21 @@ snapshots: '@discoveryjs/json-ext@0.6.3': {} - '@electric-sql/pglite-tools@0.2.15(@electric-sql/pglite@0.3.10)': + '@electric-sql/pglite-tools@0.2.19(@electric-sql/pglite@0.3.14)': dependencies: - '@electric-sql/pglite': 0.3.10 + '@electric-sql/pglite': 0.3.14 '@electric-sql/pglite@0.2.17': {} - '@electric-sql/pglite@0.3.10': {} + '@electric-sql/pglite@0.3.14': {} - '@emnapi/core@1.5.0': + '@emnapi/core@1.7.0': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.5.0': + '@emnapi/runtime@1.7.0': dependencies: tslib: 2.8.1 optional: true @@ -10974,89 +11271,167 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.25.10': + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.26.0': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.26.0': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.26.0': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.26.0': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.26.0': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.26.0': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/android-arm64@0.25.10': + '@esbuild/freebsd-arm64@0.26.0': optional: true - '@esbuild/android-arm@0.25.10': + '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/android-x64@0.25.10': + '@esbuild/freebsd-x64@0.26.0': optional: true - '@esbuild/darwin-arm64@0.25.10': + '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/darwin-x64@0.25.10': + '@esbuild/linux-arm64@0.26.0': optional: true - '@esbuild/freebsd-arm64@0.25.10': + '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/freebsd-x64@0.25.10': + '@esbuild/linux-arm@0.26.0': optional: true - '@esbuild/linux-arm64@0.25.10': + '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-arm@0.25.10': + '@esbuild/linux-ia32@0.26.0': optional: true - '@esbuild/linux-ia32@0.25.10': + '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-loong64@0.25.10': + '@esbuild/linux-loong64@0.26.0': optional: true - '@esbuild/linux-mips64el@0.25.10': + '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-ppc64@0.25.10': + '@esbuild/linux-mips64el@0.26.0': optional: true - '@esbuild/linux-riscv64@0.25.10': + '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-s390x@0.25.10': + '@esbuild/linux-ppc64@0.26.0': optional: true - '@esbuild/linux-x64@0.25.10': + '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.25.10': + '@esbuild/linux-riscv64@0.26.0': optional: true - '@esbuild/netbsd-x64@0.25.10': + '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.25.10': + '@esbuild/linux-s390x@0.26.0': optional: true - '@esbuild/openbsd-x64@0.25.10': + '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.25.10': + '@esbuild/linux-x64@0.26.0': optional: true - '@esbuild/sunos-x64@0.25.10': + '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.25.10': + '@esbuild/netbsd-arm64@0.26.0': optional: true - '@esbuild/win32-ia32@0.25.10': + '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/win32-x64@0.25.10': + '@esbuild/netbsd-x64@0.26.0': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.26.0': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.26.0': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.26.0': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.26.0': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.26.0': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.26.0': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.26.0': optional: true '@fastify/busboy@2.1.1': {} - '@firebase/ai@2.4.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.4)': + '@firebase/ai@2.6.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/app-check-interop-types': 0.3.3 '@firebase/app-types': 0.9.3 '@firebase/component': 0.7.0 @@ -11064,11 +11439,11 @@ snapshots: '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/analytics-compat@0.2.25(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4)': + '@firebase/analytics-compat@0.2.25(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6)': dependencies: - '@firebase/analytics': 0.10.19(@firebase/app@0.14.4) + '@firebase/analytics': 0.10.19(@firebase/app@0.14.6) '@firebase/analytics-types': 0.8.3 - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -11077,20 +11452,20 @@ snapshots: '@firebase/analytics-types@0.8.3': {} - '@firebase/analytics@0.10.19(@firebase/app@0.14.4)': + '@firebase/analytics@0.10.19(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.4) + '@firebase/installations': 0.6.19(@firebase/app@0.14.6) '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/app-check-compat@0.4.0(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4)': + '@firebase/app-check-compat@0.4.0(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-check': 0.11.0(@firebase/app@0.14.4) + '@firebase/app-check': 0.11.0(@firebase/app@0.14.6) '@firebase/app-check-types': 0.5.3 - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 @@ -11102,17 +11477,17 @@ snapshots: '@firebase/app-check-types@0.5.3': {} - '@firebase/app-check@0.11.0(@firebase/app@0.14.4)': + '@firebase/app-check@0.11.0(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/app-compat@0.5.4': + '@firebase/app-compat@0.5.6': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 @@ -11120,7 +11495,7 @@ snapshots: '@firebase/app-types@0.9.3': {} - '@firebase/app@0.14.4': + '@firebase/app@0.14.6': dependencies: '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 @@ -11128,10 +11503,10 @@ snapshots: idb: 7.1.1 tslib: 2.8.1 - '@firebase/auth-compat@0.6.0(@firebase/app-compat@0.5.4)(@firebase/app-types@0.9.3)(@firebase/app@0.14.4)': + '@firebase/auth-compat@0.6.1(@firebase/app-compat@0.5.6)(@firebase/app-types@0.9.3)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-compat': 0.5.4 - '@firebase/auth': 1.11.0(@firebase/app@0.14.4) + '@firebase/app-compat': 0.5.6 + '@firebase/auth': 1.11.1(@firebase/app@0.14.6) '@firebase/auth-types': 0.13.0(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) '@firebase/component': 0.7.0 '@firebase/util': 1.13.0 @@ -11148,9 +11523,9 @@ snapshots: '@firebase/app-types': 0.9.3 '@firebase/util': 1.13.0 - '@firebase/auth@1.11.0(@firebase/app@0.14.4)': + '@firebase/auth@1.11.1(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 @@ -11161,9 +11536,9 @@ snapshots: '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/data-connect@0.3.11(@firebase/app@0.14.4)': + '@firebase/data-connect@0.3.12(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/auth-interop-types': 0.2.4 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 @@ -11194,11 +11569,11 @@ snapshots: faye-websocket: 0.11.4 tslib: 2.8.1 - '@firebase/firestore-compat@0.4.2(@firebase/app-compat@0.5.4)(@firebase/app-types@0.9.3)(@firebase/app@0.14.4)': + '@firebase/firestore-compat@0.4.2(@firebase/app-compat@0.5.6)(@firebase/app-types@0.9.3)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 - '@firebase/firestore': 4.9.2(@firebase/app@0.14.4) + '@firebase/firestore': 4.9.2(@firebase/app@0.14.6) '@firebase/firestore-types': 3.0.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -11211,9 +11586,9 @@ snapshots: '@firebase/app-types': 0.9.3 '@firebase/util': 1.13.0 - '@firebase/firestore@4.9.2(@firebase/app@0.14.4)': + '@firebase/firestore@4.9.2(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 @@ -11222,11 +11597,11 @@ snapshots: '@grpc/proto-loader': 0.7.15 tslib: 2.8.1 - '@firebase/functions-compat@0.4.1(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4)': + '@firebase/functions-compat@0.4.1(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 - '@firebase/functions': 0.13.1(@firebase/app@0.14.4) + '@firebase/functions': 0.13.1(@firebase/app@0.14.6) '@firebase/functions-types': 0.6.3 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -11235,9 +11610,9 @@ snapshots: '@firebase/functions-types@0.6.3': {} - '@firebase/functions@0.13.1(@firebase/app@0.14.4)': + '@firebase/functions@0.13.1(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/app-check-interop-types': 0.3.3 '@firebase/auth-interop-types': 0.2.4 '@firebase/component': 0.7.0 @@ -11245,11 +11620,11 @@ snapshots: '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/installations-compat@0.2.19(@firebase/app-compat@0.5.4)(@firebase/app-types@0.9.3)(@firebase/app@0.14.4)': + '@firebase/installations-compat@0.2.19(@firebase/app-compat@0.5.6)(@firebase/app-types@0.9.3)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.4) + '@firebase/installations': 0.6.19(@firebase/app@0.14.6) '@firebase/installations-types': 0.5.3(@firebase/app-types@0.9.3) '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -11261,9 +11636,9 @@ snapshots: dependencies: '@firebase/app-types': 0.9.3 - '@firebase/installations@0.6.19(@firebase/app@0.14.4)': + '@firebase/installations@0.6.19(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 '@firebase/util': 1.13.0 idb: 7.1.1 @@ -11273,11 +11648,11 @@ snapshots: dependencies: tslib: 2.8.1 - '@firebase/messaging-compat@0.2.23(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4)': + '@firebase/messaging-compat@0.2.23(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 - '@firebase/messaging': 0.12.23(@firebase/app@0.14.4) + '@firebase/messaging': 0.12.23(@firebase/app@0.14.6) '@firebase/util': 1.13.0 tslib: 2.8.1 transitivePeerDependencies: @@ -11285,22 +11660,22 @@ snapshots: '@firebase/messaging-interop-types@0.2.3': {} - '@firebase/messaging@0.12.23(@firebase/app@0.14.4)': + '@firebase/messaging@0.12.23(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.4) + '@firebase/installations': 0.6.19(@firebase/app@0.14.6) '@firebase/messaging-interop-types': 0.2.3 '@firebase/util': 1.13.0 idb: 7.1.1 tslib: 2.8.1 - '@firebase/performance-compat@0.2.22(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4)': + '@firebase/performance-compat@0.2.22(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 - '@firebase/performance': 0.7.9(@firebase/app@0.14.4) + '@firebase/performance': 0.7.9(@firebase/app@0.14.6) '@firebase/performance-types': 0.2.3 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -11309,22 +11684,22 @@ snapshots: '@firebase/performance-types@0.2.3': {} - '@firebase/performance@0.7.9(@firebase/app@0.14.4)': + '@firebase/performance@0.7.9(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.4) + '@firebase/installations': 0.6.19(@firebase/app@0.14.6) '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 web-vitals: 4.2.4 - '@firebase/remote-config-compat@0.2.20(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4)': + '@firebase/remote-config-compat@0.2.20(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 '@firebase/logger': 0.5.0 - '@firebase/remote-config': 0.7.0(@firebase/app@0.14.4) + '@firebase/remote-config': 0.7.0(@firebase/app@0.14.6) '@firebase/remote-config-types': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -11333,20 +11708,20 @@ snapshots: '@firebase/remote-config-types@0.5.0': {} - '@firebase/remote-config@0.7.0(@firebase/app@0.14.4)': + '@firebase/remote-config@0.7.0(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 - '@firebase/installations': 0.6.19(@firebase/app@0.14.4) + '@firebase/installations': 0.6.19(@firebase/app@0.14.6) '@firebase/logger': 0.5.0 '@firebase/util': 1.13.0 tslib: 2.8.1 - '@firebase/storage-compat@0.4.0(@firebase/app-compat@0.5.4)(@firebase/app-types@0.9.3)(@firebase/app@0.14.4)': + '@firebase/storage-compat@0.4.0(@firebase/app-compat@0.5.6)(@firebase/app-types@0.9.3)(@firebase/app@0.14.6)': dependencies: - '@firebase/app-compat': 0.5.4 + '@firebase/app-compat': 0.5.6 '@firebase/component': 0.7.0 - '@firebase/storage': 0.14.0(@firebase/app@0.14.4) + '@firebase/storage': 0.14.0(@firebase/app@0.14.6) '@firebase/storage-types': 0.8.3(@firebase/app-types@0.9.3)(@firebase/util@1.13.0) '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -11359,9 +11734,9 @@ snapshots: '@firebase/app-types': 0.9.3 '@firebase/util': 1.13.0 - '@firebase/storage@0.14.0(@firebase/app@0.14.4)': + '@firebase/storage@0.14.0(@firebase/app@0.14.6)': dependencies: - '@firebase/app': 0.14.4 + '@firebase/app': 0.14.6 '@firebase/component': 0.7.0 '@firebase/util': 1.13.0 tslib: 2.8.1 @@ -11372,7 +11747,7 @@ snapshots: '@firebase/webchannel-wrapper@1.0.5': {} - '@formatjs/ecma402-abstract@2.3.5': + '@formatjs/ecma402-abstract@2.3.6': dependencies: '@formatjs/fast-memoize': 2.2.7 '@formatjs/intl-localematcher': 0.6.2 @@ -11383,26 +11758,26 @@ snapshots: dependencies: tslib: 2.8.1 - '@formatjs/icu-messageformat-parser@2.11.3': + '@formatjs/icu-messageformat-parser@2.11.4': dependencies: - '@formatjs/ecma402-abstract': 2.3.5 - '@formatjs/icu-skeleton-parser': 1.8.15 + '@formatjs/ecma402-abstract': 2.3.6 + '@formatjs/icu-skeleton-parser': 1.8.16 tslib: 2.8.1 - '@formatjs/icu-skeleton-parser@1.8.15': + '@formatjs/icu-skeleton-parser@1.8.16': dependencies: - '@formatjs/ecma402-abstract': 2.3.5 + '@formatjs/ecma402-abstract': 2.3.6 tslib: 2.8.1 '@formatjs/intl-localematcher@0.6.2': dependencies: tslib: 2.8.1 - '@google-cloud/cloud-sql-connector@1.8.3': + '@google-cloud/cloud-sql-connector@1.8.4': dependencies: '@googleapis/sqladmin': 31.1.0 - gaxios: 7.1.2(supports-color@10.2.2) - google-auth-library: 10.4.0(supports-color@10.2.2) + gaxios: 7.1.3(supports-color@10.2.2) + google-auth-library: 10.5.0(supports-color@10.2.2) p-throttle: 7.0.0 transitivePeerDependencies: - supports-color @@ -11414,7 +11789,7 @@ snapshots: arrify: 2.0.1 duplexify: 4.1.3 extend: 3.0.2 - google-auth-library: 10.4.0(supports-color@10.2.2) + google-auth-library: 10.5.0(supports-color@10.2.2) html-entities: 2.6.0 retry-request: 8.0.2(supports-color@10.2.2) teeny-request: 10.1.0(supports-color@10.2.2) @@ -11450,7 +11825,7 @@ snapshots: '@opentelemetry/semantic-conventions': 1.30.0 arrify: 2.0.1 extend: 3.0.2 - google-auth-library: 9.15.1(encoding@0.1.13)(supports-color@10.2.2) + google-auth-library: 9.15.1(encoding@0.1.13) google-gax: 4.6.1(encoding@0.1.13) heap-js: 2.7.1 is-stream-ended: 0.1.4 @@ -11468,9 +11843,9 @@ snapshots: '@google-cloud/promisify': 5.0.0 '@grpc/proto-loader': 0.7.15 '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/context-async-hooks': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.38.0 '@types/big.js': 6.2.2 '@types/stack-trace': 0.0.33 big.js: 7.0.1 @@ -11478,8 +11853,8 @@ snapshots: duplexify: 4.1.3 events-intercept: 2.0.0 extend: 3.0.2 - google-auth-library: 10.4.0(supports-color@10.2.2) - google-gax: 5.0.4(supports-color@10.2.2) + google-auth-library: 10.5.0(supports-color@10.2.2) + google-gax: 5.0.5(supports-color@10.2.2) grpc-gcp: 1.0.1 is: 3.3.2 lodash.snakecase: 4.1.1 @@ -11495,15 +11870,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@google/genai@1.25.0(@modelcontextprotocol/sdk@1.20.0)(bufferutil@4.0.9)(encoding@0.1.13)(supports-color@10.2.2)(utf-8-validate@6.0.5)': + '@google/genai@1.30.0(@modelcontextprotocol/sdk@1.21.1)(bufferutil@4.0.9)(supports-color@10.2.2)(utf-8-validate@6.0.5)': dependencies: - google-auth-library: 9.15.1(encoding@0.1.13)(supports-color@10.2.2) + google-auth-library: 10.5.0(supports-color@10.2.2) ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) optionalDependencies: - '@modelcontextprotocol/sdk': 1.20.0 + '@modelcontextprotocol/sdk': 1.21.1 transitivePeerDependencies: - bufferutil - - encoding - supports-color - utf-8-validate @@ -11513,7 +11887,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@grpc/grpc-js@1.14.0': + '@grpc/grpc-js@1.14.1': dependencies: '@grpc/proto-loader': 0.8.0 '@js-sdsl/ordered-map': 4.4.2 @@ -11521,7 +11895,7 @@ snapshots: '@grpc/grpc-js@1.9.15': dependencies: '@grpc/proto-loader': 0.7.15 - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@grpc/proto-loader@0.7.15': dependencies: @@ -11537,365 +11911,271 @@ snapshots: protobufjs: 7.5.4 yargs: 17.7.2 - '@inquirer/ansi@1.0.0': {} + '@inquirer/ansi@1.0.2': {} - '@inquirer/ansi@1.0.1': {} + '@inquirer/ansi@2.0.1': {} - '@inquirer/checkbox@4.2.4(@types/node@22.18.8)': + '@inquirer/checkbox@4.3.1(@types/node@22.19.0)': dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/checkbox@4.3.0(@types/node@22.18.8)': + '@inquirer/checkbox@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@22.18.8) - yoctocolors-cjs: 2.1.3 + '@inquirer/ansi': 2.0.1 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/figures': 2.0.1 + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.1 - '@inquirer/checkbox@4.3.0(@types/node@24.8.1)': + '@inquirer/confirm@5.1.19(@types/node@22.19.0)': dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@24.8.1) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.8.1 - - '@inquirer/confirm@5.1.18(@types/node@22.18.8)': - dependencies: - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/confirm@5.1.19(@types/node@22.18.8)': + '@inquirer/confirm@5.1.20(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/confirm@5.1.19(@types/node@24.8.1)': + '@inquirer/confirm@6.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/type': 3.0.9(@types/node@24.8.1) + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.8.1 + '@types/node': 24.10.1 - '@inquirer/core@10.2.2(@types/node@22.18.8)': + '@inquirer/core@10.3.1(@types/node@22.19.0)': dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.0) cli-width: 4.1.0 - mute-stream: 2.0.0 + mute-stream: 3.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/core@10.3.0(@types/node@22.18.8)': + '@inquirer/core@11.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/ansi': 2.0.1 + '@inquirer/figures': 2.0.1 + '@inquirer/type': 4.0.1(@types/node@24.10.1) cli-width: 4.1.0 - mute-stream: 2.0.0 + mute-stream: 3.0.0 signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.18.8 - - '@inquirer/core@10.3.0(@types/node@24.8.1)': - dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@24.8.1) - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.8.1 - - '@inquirer/editor@4.2.20(@types/node@22.18.8)': - dependencies: - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/external-editor': 1.0.2(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) - optionalDependencies: - '@types/node': 22.18.8 - - '@inquirer/editor@4.2.21(@types/node@22.18.8)': - dependencies: - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/external-editor': 1.0.2(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + wrap-ansi: 9.0.2 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.1 - '@inquirer/editor@4.2.21(@types/node@24.8.1)': + '@inquirer/editor@4.2.22(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/external-editor': 1.0.2(@types/node@24.8.1) - '@inquirer/type': 3.0.9(@types/node@24.8.1) + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/external-editor': 1.0.3(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) optionalDependencies: - '@types/node': 24.8.1 + '@types/node': 22.19.0 - '@inquirer/expand@4.0.20(@types/node@22.18.8)': + '@inquirer/editor@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/external-editor': 2.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.1 - '@inquirer/expand@4.0.21(@types/node@22.18.8)': + '@inquirer/expand@4.0.22(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/expand@4.0.21(@types/node@24.8.1)': + '@inquirer/expand@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/type': 3.0.9(@types/node@24.8.1) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.8.1 + '@types/node': 24.10.1 - '@inquirer/external-editor@1.0.2(@types/node@22.18.8)': + '@inquirer/external-editor@1.0.3(@types/node@22.19.0)': dependencies: - chardet: 2.1.0 + chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/external-editor@1.0.2(@types/node@24.8.1)': + '@inquirer/external-editor@2.0.1(@types/node@24.10.1)': dependencies: - chardet: 2.1.0 + chardet: 2.1.1 iconv-lite: 0.7.0 optionalDependencies: - '@types/node': 24.8.1 - - '@inquirer/figures@1.0.13': {} + '@types/node': 24.10.1 - '@inquirer/figures@1.0.14': {} + '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.2.4(@types/node@22.18.8)': - dependencies: - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) - optionalDependencies: - '@types/node': 22.18.8 - - '@inquirer/input@4.2.5(@types/node@22.18.8)': - dependencies: - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) - optionalDependencies: - '@types/node': 22.18.8 - - '@inquirer/input@4.2.5(@types/node@24.8.1)': - dependencies: - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/type': 3.0.9(@types/node@24.8.1) - optionalDependencies: - '@types/node': 24.8.1 + '@inquirer/figures@2.0.1': {} - '@inquirer/number@3.0.20(@types/node@22.18.8)': + '@inquirer/input@4.3.0(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/number@3.0.21(@types/node@22.18.8)': + '@inquirer/input@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.1 - '@inquirer/number@3.0.21(@types/node@24.8.1)': + '@inquirer/number@3.0.22(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/type': 3.0.9(@types/node@24.8.1) + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) optionalDependencies: - '@types/node': 24.8.1 + '@types/node': 22.19.0 - '@inquirer/password@4.0.20(@types/node@22.18.8)': + '@inquirer/number@4.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.1 - '@inquirer/password@4.0.21(@types/node@22.18.8)': + '@inquirer/password@4.0.22(@types/node@22.19.0)': dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/password@4.0.21(@types/node@24.8.1)': + '@inquirer/password@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/type': 3.0.9(@types/node@24.8.1) - optionalDependencies: - '@types/node': 24.8.1 - - '@inquirer/prompts@7.8.6(@types/node@22.18.8)': - dependencies: - '@inquirer/checkbox': 4.2.4(@types/node@22.18.8) - '@inquirer/confirm': 5.1.18(@types/node@22.18.8) - '@inquirer/editor': 4.2.20(@types/node@22.18.8) - '@inquirer/expand': 4.0.20(@types/node@22.18.8) - '@inquirer/input': 4.2.4(@types/node@22.18.8) - '@inquirer/number': 3.0.20(@types/node@22.18.8) - '@inquirer/password': 4.0.20(@types/node@22.18.8) - '@inquirer/rawlist': 4.1.8(@types/node@22.18.8) - '@inquirer/search': 3.1.3(@types/node@22.18.8) - '@inquirer/select': 4.3.4(@types/node@22.18.8) + '@inquirer/ansi': 2.0.1 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 22.18.8 - - '@inquirer/prompts@7.9.0(@types/node@22.18.8)': - dependencies: - '@inquirer/checkbox': 4.3.0(@types/node@22.18.8) - '@inquirer/confirm': 5.1.19(@types/node@22.18.8) - '@inquirer/editor': 4.2.21(@types/node@22.18.8) - '@inquirer/expand': 4.0.21(@types/node@22.18.8) - '@inquirer/input': 4.2.5(@types/node@22.18.8) - '@inquirer/number': 3.0.21(@types/node@22.18.8) - '@inquirer/password': 4.0.21(@types/node@22.18.8) - '@inquirer/rawlist': 4.1.9(@types/node@22.18.8) - '@inquirer/search': 3.2.0(@types/node@22.18.8) - '@inquirer/select': 4.4.0(@types/node@22.18.8) + '@types/node': 24.10.1 + + '@inquirer/prompts@7.10.0(@types/node@22.19.0)': + dependencies: + '@inquirer/checkbox': 4.3.1(@types/node@22.19.0) + '@inquirer/confirm': 5.1.20(@types/node@22.19.0) + '@inquirer/editor': 4.2.22(@types/node@22.19.0) + '@inquirer/expand': 4.0.22(@types/node@22.19.0) + '@inquirer/input': 4.3.0(@types/node@22.19.0) + '@inquirer/number': 3.0.22(@types/node@22.19.0) + '@inquirer/password': 4.0.22(@types/node@22.19.0) + '@inquirer/rawlist': 4.1.10(@types/node@22.19.0) + '@inquirer/search': 3.2.1(@types/node@22.19.0) + '@inquirer/select': 4.4.1(@types/node@22.19.0) optionalDependencies: - '@types/node': 22.18.8 - - '@inquirer/prompts@7.9.0(@types/node@24.8.1)': - dependencies: - '@inquirer/checkbox': 4.3.0(@types/node@24.8.1) - '@inquirer/confirm': 5.1.19(@types/node@24.8.1) - '@inquirer/editor': 4.2.21(@types/node@24.8.1) - '@inquirer/expand': 4.0.21(@types/node@24.8.1) - '@inquirer/input': 4.2.5(@types/node@24.8.1) - '@inquirer/number': 3.0.21(@types/node@24.8.1) - '@inquirer/password': 4.0.21(@types/node@24.8.1) - '@inquirer/rawlist': 4.1.9(@types/node@24.8.1) - '@inquirer/search': 3.2.0(@types/node@24.8.1) - '@inquirer/select': 4.4.0(@types/node@24.8.1) + '@types/node': 22.19.0 + + '@inquirer/prompts@7.9.0(@types/node@22.19.0)': + dependencies: + '@inquirer/checkbox': 4.3.1(@types/node@22.19.0) + '@inquirer/confirm': 5.1.20(@types/node@22.19.0) + '@inquirer/editor': 4.2.22(@types/node@22.19.0) + '@inquirer/expand': 4.0.22(@types/node@22.19.0) + '@inquirer/input': 4.3.0(@types/node@22.19.0) + '@inquirer/number': 3.0.22(@types/node@22.19.0) + '@inquirer/password': 4.0.22(@types/node@22.19.0) + '@inquirer/rawlist': 4.1.10(@types/node@22.19.0) + '@inquirer/search': 3.2.1(@types/node@22.19.0) + '@inquirer/select': 4.4.1(@types/node@22.19.0) optionalDependencies: - '@types/node': 24.8.1 - - '@inquirer/rawlist@4.1.8(@types/node@22.18.8)': - dependencies: - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) - yoctocolors-cjs: 2.1.3 + '@types/node': 22.19.0 + + '@inquirer/prompts@8.0.1(@types/node@24.10.1)': + dependencies: + '@inquirer/checkbox': 5.0.1(@types/node@24.10.1) + '@inquirer/confirm': 6.0.1(@types/node@24.10.1) + '@inquirer/editor': 5.0.1(@types/node@24.10.1) + '@inquirer/expand': 5.0.1(@types/node@24.10.1) + '@inquirer/input': 5.0.1(@types/node@24.10.1) + '@inquirer/number': 4.0.1(@types/node@24.10.1) + '@inquirer/password': 5.0.1(@types/node@24.10.1) + '@inquirer/rawlist': 5.0.1(@types/node@24.10.1) + '@inquirer/search': 4.0.1(@types/node@24.10.1) + '@inquirer/select': 5.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.1 - '@inquirer/rawlist@4.1.9(@types/node@22.18.8)': + '@inquirer/rawlist@4.1.10(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/rawlist@4.1.9(@types/node@24.8.1)': + '@inquirer/rawlist@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/type': 3.0.9(@types/node@24.8.1) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.8.1 + '@types/node': 24.10.1 - '@inquirer/search@3.1.3(@types/node@22.18.8)': + '@inquirer/search@3.2.1(@types/node@22.19.0)': dependencies: - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/search@3.2.0(@types/node@22.18.8)': + '@inquirer/search@4.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@22.18.8) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 22.18.8 - - '@inquirer/search@3.2.0(@types/node@24.8.1)': - dependencies: - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@24.8.1) - yoctocolors-cjs: 2.1.3 - optionalDependencies: - '@types/node': 24.8.1 - - '@inquirer/select@4.3.4(@types/node@22.18.8)': - dependencies: - '@inquirer/ansi': 1.0.0 - '@inquirer/core': 10.2.2(@types/node@22.18.8) - '@inquirer/figures': 1.0.13 - '@inquirer/type': 3.0.9(@types/node@22.18.8) - yoctocolors-cjs: 2.1.3 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/figures': 2.0.1 + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 24.10.1 - '@inquirer/select@4.4.0(@types/node@22.18.8)': + '@inquirer/select@4.4.1(@types/node@22.19.0)': dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/core': 10.3.0(@types/node@22.18.8) - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@22.18.8) + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.1(@types/node@22.19.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.19.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/select@4.4.0(@types/node@24.8.1)': + '@inquirer/select@5.0.1(@types/node@24.10.1)': dependencies: - '@inquirer/ansi': 1.0.1 - '@inquirer/core': 10.3.0(@types/node@24.8.1) - '@inquirer/figures': 1.0.14 - '@inquirer/type': 3.0.9(@types/node@24.8.1) - yoctocolors-cjs: 2.1.3 + '@inquirer/ansi': 2.0.1 + '@inquirer/core': 11.0.1(@types/node@24.10.1) + '@inquirer/figures': 2.0.1 + '@inquirer/type': 4.0.1(@types/node@24.10.1) optionalDependencies: - '@types/node': 24.8.1 + '@types/node': 24.10.1 - '@inquirer/type@3.0.9(@types/node@22.18.8)': + '@inquirer/type@3.0.10(@types/node@22.19.0)': optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@inquirer/type@3.0.9(@types/node@24.8.1)': + '@inquirer/type@4.0.1(@types/node@24.10.1)': optionalDependencies: - '@types/node': 24.8.1 + '@types/node': 24.10.1 '@isaacs/balanced-match@4.0.1': {} @@ -11955,7 +12235,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/buffers@1.0.0(tslib@2.8.1)': + '@jsonjoy.com/buffers@1.2.1(tslib@2.8.1)': dependencies: tslib: 2.8.1 @@ -11963,15 +12243,16 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/json-pack@1.14.0(tslib@2.8.1)': + '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': dependencies: '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) - '@jsonjoy.com/buffers': 1.0.0(tslib@2.8.1) + '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) '@jsonjoy.com/json-pointer': 1.0.2(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) hyperdyperid: 1.2.0 thingies: 2.5.0(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 '@jsonjoy.com/json-pointer@1.0.2(tslib@2.8.1)': @@ -11982,17 +12263,17 @@ snapshots: '@jsonjoy.com/util@1.9.0(tslib@2.8.1)': dependencies: - '@jsonjoy.com/buffers': 1.0.0(tslib@2.8.1) + '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) tslib: 2.8.1 '@leichtgewicht/ip-codec@2.0.5': {} - '@listr2/prompt-adapter-inquirer@3.0.4(@inquirer/prompts@7.9.0(@types/node@22.18.8))(@types/node@22.18.8)(listr2@9.0.4)': + '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.9.0(@types/node@22.19.0))(@types/node@22.19.0)(listr2@9.0.5)': dependencies: - '@inquirer/prompts': 7.9.0(@types/node@22.18.8) - '@inquirer/type': 3.0.9(@types/node@22.18.8) - listr2: 9.0.4 + '@inquirer/prompts': 7.9.0(@types/node@22.19.0) + '@inquirer/type': 3.0.10(@types/node@22.19.0) + listr2: 9.0.5 transitivePeerDependencies: - '@types/node' @@ -12019,7 +12300,7 @@ snapshots: '@material/material-color-utilities@0.3.0': {} - '@modelcontextprotocol/sdk@1.19.1': + '@modelcontextprotocol/sdk@1.20.1': dependencies: ajv: 6.12.6 content-type: 1.0.5 @@ -12036,9 +12317,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@modelcontextprotocol/sdk@1.20.0': + '@modelcontextprotocol/sdk@1.21.1': dependencies: - ajv: 6.12.6 + ajv: 8.17.1 + ajv-formats: 3.0.1 content-type: 1.0.5 cors: 2.8.5 cross-spawn: 7.0.6 @@ -12071,7 +12353,7 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': optional: true - '@mswjs/interceptors@0.39.7': + '@mswjs/interceptors@0.39.8': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -12154,27 +12436,27 @@ snapshots: '@napi-rs/wasm-runtime@1.0.7': dependencies: - '@emnapi/core': 1.5.0 - '@emnapi/runtime': 1.5.0 + '@emnapi/core': 1.7.0 + '@emnapi/runtime': 1.7.0 '@tybys/wasm-util': 0.10.1 optional: true - '@nginfra/angular-linking@1.0.9(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))': + '@nginfra/angular-linking@1.0.9(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))': dependencies: - '@angular/compiler-cli': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2) + '@angular/compiler-cli': 21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2) '@babel/core': 7.26.10 '@types/babel__core': 7.20.5 - '@types/node': 22.18.8 + '@types/node': 22.19.0 tinyglobby: 0.2.12 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@ngtools/webpack@21.0.0-next.8(@angular/compiler-cli@21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.102.1(esbuild@0.25.10))': + '@ngtools/webpack@21.0.1(@angular/compiler-cli@21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2))(typescript@5.9.2)(webpack@5.102.1(esbuild@0.26.0))': dependencies: - '@angular/compiler-cli': 21.0.0-next.8(@angular/compiler@21.0.0-next.8)(typescript@5.9.2) + '@angular/compiler-cli': 21.0.1(@angular/compiler@21.0.1)(typescript@5.9.2) typescript: 5.9.2 - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) '@nodelib/fs.scandir@2.1.5': dependencies: @@ -12217,7 +12499,7 @@ snapshots: '@npmcli/promise-spawn': 8.0.3 ini: 5.0.0 lru-cache: 11.2.2 - npm-pick-manifest: 11.0.1 + npm-pick-manifest: 11.0.3 proc-log: 5.0.0 promise-retry: 2.0.1 semver: 7.7.3 @@ -12228,15 +12510,15 @@ snapshots: npm-bundled: 4.0.0 npm-normalize-package-bin: 4.0.0 - '@npmcli/node-gyp@4.0.0': {} + '@npmcli/node-gyp@5.0.0': {} - '@npmcli/package-json@7.0.1': + '@npmcli/package-json@7.0.2': dependencies: '@npmcli/git': 7.0.0 glob: 11.0.3 - hosted-git-info: 9.0.0 - json-parse-even-better-errors: 4.0.0 - proc-log: 5.0.0 + hosted-git-info: 9.0.2 + json-parse-even-better-errors: 5.0.0 + proc-log: 6.0.0 semver: 7.7.3 validate-npm-package-license: 3.0.4 @@ -12248,129 +12530,156 @@ snapshots: dependencies: which: 5.0.0 + '@npmcli/promise-spawn@9.0.0': + dependencies: + which: 5.0.0 + '@npmcli/redact@3.2.2': {} - '@npmcli/run-script@10.0.0': + '@npmcli/run-script@10.0.2': dependencies: - '@npmcli/node-gyp': 4.0.0 - '@npmcli/package-json': 7.0.1 - '@npmcli/promise-spawn': 8.0.3 - node-gyp: 11.4.2 - proc-log: 5.0.0 + '@npmcli/node-gyp': 5.0.0 + '@npmcli/package-json': 7.0.2 + '@npmcli/promise-spawn': 9.0.0 + node-gyp: 11.5.0 + proc-log: 6.0.0 which: 5.0.0 transitivePeerDependencies: - supports-color - '@octokit/auth-app@8.1.1': + '@octokit/auth-app@8.1.2': dependencies: - '@octokit/auth-oauth-app': 9.0.2 - '@octokit/auth-oauth-user': 6.0.1 - '@octokit/request': 10.0.5 - '@octokit/request-error': 7.0.1 - '@octokit/types': 15.0.0 + '@octokit/auth-oauth-app': 9.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.6 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 toad-cache: 3.7.0 universal-github-app-jwt: 2.2.2 universal-user-agent: 7.0.3 - '@octokit/auth-oauth-app@9.0.2': + '@octokit/auth-oauth-app@9.0.3': dependencies: - '@octokit/auth-oauth-device': 8.0.2 - '@octokit/auth-oauth-user': 6.0.1 - '@octokit/request': 10.0.5 - '@octokit/types': 15.0.0 + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/auth-oauth-user': 6.0.2 + '@octokit/request': 10.0.6 + '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 - '@octokit/auth-oauth-device@8.0.2': + '@octokit/auth-oauth-device@8.0.3': dependencies: - '@octokit/oauth-methods': 6.0.1 - '@octokit/request': 10.0.5 - '@octokit/types': 15.0.0 + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.6 + '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 - '@octokit/auth-oauth-user@6.0.1': + '@octokit/auth-oauth-user@6.0.2': dependencies: - '@octokit/auth-oauth-device': 8.0.2 - '@octokit/oauth-methods': 6.0.1 - '@octokit/request': 10.0.5 - '@octokit/types': 15.0.0 + '@octokit/auth-oauth-device': 8.0.3 + '@octokit/oauth-methods': 6.0.2 + '@octokit/request': 10.0.6 + '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 '@octokit/auth-token@6.0.0': {} - '@octokit/core@7.0.5': + '@octokit/core@7.0.6': dependencies: '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.2 - '@octokit/request': 10.0.5 - '@octokit/request-error': 7.0.1 - '@octokit/types': 15.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.6 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 - '@octokit/endpoint@11.0.1': + '@octokit/endpoint@11.0.2': dependencies: - '@octokit/types': 15.0.0 + '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 - '@octokit/graphql-schema@15.26.0': + '@octokit/graphql-schema@15.26.1': dependencies: - graphql: 16.11.0 - graphql-tag: 2.12.6(graphql@16.11.0) + graphql: 16.12.0 + graphql-tag: 2.12.6(graphql@16.12.0) - '@octokit/graphql@9.0.2': + '@octokit/graphql@9.0.3': dependencies: - '@octokit/request': 10.0.5 - '@octokit/types': 15.0.0 + '@octokit/request': 10.0.6 + '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 '@octokit/oauth-authorization-url@8.0.0': {} - '@octokit/oauth-methods@6.0.1': + '@octokit/oauth-methods@6.0.2': dependencies: '@octokit/oauth-authorization-url': 8.0.0 - '@octokit/request': 10.0.5 - '@octokit/request-error': 7.0.1 - '@octokit/types': 15.0.0 + '@octokit/request': 10.0.6 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 '@octokit/openapi-types@26.0.0': {} - '@octokit/plugin-paginate-rest@13.2.0(@octokit/core@7.0.5)': + '@octokit/openapi-types@27.0.0': {} + + '@octokit/plugin-paginate-rest@13.2.1(@octokit/core@7.0.6)': dependencies: - '@octokit/core': 7.0.5 - '@octokit/types': 15.0.0 + '@octokit/core': 7.0.6 + '@octokit/types': 15.0.2 - '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.5)': + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': dependencies: - '@octokit/core': 7.0.5 + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 - '@octokit/plugin-rest-endpoint-methods@16.1.0(@octokit/core@7.0.5)': + '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.6)': dependencies: - '@octokit/core': 7.0.5 - '@octokit/types': 15.0.0 + '@octokit/core': 7.0.6 - '@octokit/request-error@7.0.1': + '@octokit/plugin-rest-endpoint-methods@16.1.1(@octokit/core@7.0.6)': dependencies: - '@octokit/types': 15.0.0 + '@octokit/core': 7.0.6 + '@octokit/types': 15.0.2 - '@octokit/request@10.0.5': + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': dependencies: - '@octokit/endpoint': 11.0.1 - '@octokit/request-error': 7.0.1 - '@octokit/types': 15.0.0 + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/request-error@7.1.0': + dependencies: + '@octokit/types': 16.0.0 + + '@octokit/request@10.0.6': + dependencies: + '@octokit/endpoint': 11.0.2 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 fast-content-type-parse: 3.0.0 universal-user-agent: 7.0.3 '@octokit/rest@22.0.0': dependencies: - '@octokit/core': 7.0.5 - '@octokit/plugin-paginate-rest': 13.2.0(@octokit/core@7.0.5) - '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.5) - '@octokit/plugin-rest-endpoint-methods': 16.1.0(@octokit/core@7.0.5) + '@octokit/core': 7.0.6 + '@octokit/plugin-paginate-rest': 13.2.1(@octokit/core@7.0.6) + '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 16.1.1(@octokit/core@7.0.6) + + '@octokit/rest@22.0.1': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) - '@octokit/types@15.0.0': + '@octokit/types@15.0.2': dependencies: '@octokit/openapi-types': 26.0.0 + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + '@open-draft/deferred-promise@2.2.0': {} '@open-draft/logger@0.3.0': @@ -12390,7 +12699,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/context-async-hooks@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -12399,17 +12708,17 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.28.0 - '@opentelemetry/core@2.1.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/instrumentation-amqplib@0.46.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12418,7 +12727,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@types/connect': 3.4.38 transitivePeerDependencies: - supports-color @@ -12435,7 +12744,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12466,7 +12775,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12486,7 +12795,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12494,7 +12803,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12502,7 +12811,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12511,7 +12820,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12526,7 +12835,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12535,7 +12844,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12543,7 +12852,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -12552,7 +12861,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@types/mysql': 2.15.26 transitivePeerDependencies: - supports-color @@ -12562,7 +12871,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) '@types/pg': 8.6.1 '@types/pg-pool': 2.0.6 @@ -12574,7 +12883,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 transitivePeerDependencies: - supports-color @@ -12582,7 +12891,7 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@types/tedious': 4.0.14 transitivePeerDependencies: - supports-color @@ -12600,7 +12909,7 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.57.2 '@types/shimmer': 1.2.0 - import-in-the-middle: 1.14.4 + import-in-the-middle: 1.15.0 require-in-the-middle: 7.5.2 semver: 7.7.3 shimmer: 1.2.1 @@ -12626,14 +12935,14 @@ snapshots: '@opentelemetry/semantic-conventions@1.30.0': {} - '@opentelemetry/semantic-conventions@1.37.0': {} + '@opentelemetry/semantic-conventions@1.38.0': {} '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@oxc-project/types@0.94.0': {} + '@oxc-project/types@0.96.0': {} '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -12699,7 +13008,7 @@ snapshots: '@paulirish/trace_engine@0.0.61': dependencies: legacy-javascript: 0.0.1 - third-party-web: 0.27.0 + third-party-web: 0.29.0 '@phenomnomnominal/tsquery@4.2.0(typescript@5.9.2)': dependencies: @@ -12719,10 +13028,10 @@ snapshots: '@pnpm/crypto.polyfill@1000.1.0': {} - '@pnpm/dependency-path@1001.1.2': + '@pnpm/dependency-path@1001.1.5': dependencies: '@pnpm/crypto.hash': 1000.2.1 - '@pnpm/types': 1000.8.0 + '@pnpm/types': 1001.0.1 semver: 7.7.3 '@pnpm/graceful-fs@1000.0.1': @@ -12739,7 +13048,7 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@pnpm/types@1000.8.0': {} + '@pnpm/types@1001.0.1': {} '@prisma/instrumentation@6.11.1(@opentelemetry/api@1.9.0)': dependencies: @@ -12771,7 +13080,7 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@puppeteer/browsers@2.10.10': + '@puppeteer/browsers@2.10.13': dependencies: debug: 4.4.3(supports-color@10.2.2) extract-zip: 2.0.1 @@ -12781,59 +13090,60 @@ snapshots: tar-fs: 3.1.1 yargs: 17.7.2 transitivePeerDependencies: + - bare-abort-controller - bare-buffer - react-native-b4a - supports-color - '@rolldown/binding-android-arm64@1.0.0-beta.43': + '@rolldown/binding-android-arm64@1.0.0-beta.47': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.43': + '@rolldown/binding-darwin-arm64@1.0.0-beta.47': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.43': + '@rolldown/binding-darwin-x64@1.0.0-beta.47': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.43': + '@rolldown/binding-freebsd-x64@1.0.0-beta.47': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.43': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.47': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.43': + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.47': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.43': + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.47': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.43': + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.47': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.43': + '@rolldown/binding-linux-x64-musl@1.0.0-beta.47': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.43': + '@rolldown/binding-openharmony-arm64@1.0.0-beta.47': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.43': + '@rolldown/binding-wasm32-wasi@1.0.0-beta.47': dependencies: '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.43': + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.47': optional: true - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.43': + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.47': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.43': + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.47': optional: true - '@rolldown/pluginutils@1.0.0-beta.43': {} + '@rolldown/pluginutils@1.0.0-beta.47': {} - '@rollup/plugin-commonjs@28.0.6(rollup@4.52.4)': + '@rollup/plugin-commonjs@28.0.9(rollup@4.53.1)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.52.4) + '@rollup/pluginutils': 5.3.0(rollup@4.53.1) commondir: 1.0.1 estree-walker: 2.0.2 fdir: 6.5.0(picomatch@4.0.3) @@ -12841,111 +13151,111 @@ snapshots: magic-string: 0.30.19 picomatch: 4.0.3 optionalDependencies: - rollup: 4.52.4 + rollup: 4.53.1 - '@rollup/plugin-node-resolve@16.0.2(rollup@4.52.4)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.53.1)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.52.4) + '@rollup/pluginutils': 5.3.0(rollup@4.53.1) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 - resolve: 1.22.10 + resolve: 1.22.11 optionalDependencies: - rollup: 4.52.4 + rollup: 4.53.1 - '@rollup/pluginutils@5.2.0(rollup@4.52.4)': + '@rollup/pluginutils@5.2.0(rollup@4.53.1)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.52.4 + rollup: 4.53.1 - '@rollup/pluginutils@5.3.0(rollup@4.52.4)': + '@rollup/pluginutils@5.3.0(rollup@4.53.1)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.52.4 + rollup: 4.53.1 - '@rollup/rollup-android-arm-eabi@4.52.4': + '@rollup/rollup-android-arm-eabi@4.53.1': optional: true - '@rollup/rollup-android-arm64@4.52.4': + '@rollup/rollup-android-arm64@4.53.1': optional: true - '@rollup/rollup-darwin-arm64@4.52.4': + '@rollup/rollup-darwin-arm64@4.53.1': optional: true - '@rollup/rollup-darwin-x64@4.52.4': + '@rollup/rollup-darwin-x64@4.53.1': optional: true - '@rollup/rollup-freebsd-arm64@4.52.4': + '@rollup/rollup-freebsd-arm64@4.53.1': optional: true - '@rollup/rollup-freebsd-x64@4.52.4': + '@rollup/rollup-freebsd-x64@4.53.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + '@rollup/rollup-linux-arm-gnueabihf@4.53.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.4': + '@rollup/rollup-linux-arm-musleabihf@4.53.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.4': + '@rollup/rollup-linux-arm64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.4': + '@rollup/rollup-linux-arm64-musl@4.53.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.4': + '@rollup/rollup-linux-loong64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.4': + '@rollup/rollup-linux-ppc64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.4': + '@rollup/rollup-linux-riscv64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.4': + '@rollup/rollup-linux-riscv64-musl@4.53.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.4': + '@rollup/rollup-linux-s390x-gnu@4.53.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.4': + '@rollup/rollup-linux-x64-gnu@4.53.1': optional: true - '@rollup/rollup-linux-x64-musl@4.52.4': + '@rollup/rollup-linux-x64-musl@4.53.1': optional: true - '@rollup/rollup-openharmony-arm64@4.52.4': + '@rollup/rollup-openharmony-arm64@4.53.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.4': + '@rollup/rollup-win32-arm64-msvc@4.53.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.4': + '@rollup/rollup-win32-ia32-msvc@4.53.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.4': + '@rollup/rollup-win32-x64-gnu@4.53.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.4': + '@rollup/rollup-win32-x64-msvc@4.53.1': optional: true - '@schematics/angular@21.0.0-next.8(chokidar@4.0.3)': + '@schematics/angular@21.0.1(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 21.0.0-next.8(chokidar@4.0.3) - '@angular-devkit/schematics': 21.0.0-next.8(chokidar@4.0.3) + '@angular-devkit/core': 21.0.1(chokidar@4.0.3) + '@angular-devkit/schematics': 21.0.1(chokidar@4.0.3) jsonc-parser: 3.3.1 transitivePeerDependencies: - chokidar '@sentry/core@9.46.0': {} - '@sentry/node-core@9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)': + '@sentry/node-core@9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) @@ -12953,10 +13263,10 @@ snapshots: '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@sentry/core': 9.46.0 - '@sentry/opentelemetry': 9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) - import-in-the-middle: 1.14.4 + '@sentry/opentelemetry': 9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) + import-in-the-middle: 1.15.0 '@sentry/node@9.46.0': dependencies: @@ -12988,23 +13298,23 @@ snapshots: '@opentelemetry/instrumentation-undici': 0.10.1(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@prisma/instrumentation': 6.11.1(@opentelemetry/api@1.9.0) '@sentry/core': 9.46.0 - '@sentry/node-core': 9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) - '@sentry/opentelemetry': 9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0) - import-in-the-middle: 1.14.4 + '@sentry/node-core': 9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) + '@sentry/opentelemetry': 9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0) + import-in-the-middle: 1.15.0 minimatch: 9.0.5 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.37.0)': + '@sentry/opentelemetry@9.46.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.38.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.37.0 + '@opentelemetry/semantic-conventions': 1.38.0 '@sentry/core': 9.46.0 '@sigstore/bundle@4.0.0': @@ -13076,58 +13386,58 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 '@types/big.js@6.2.2': {} '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/bonjour@3.5.13': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/caseless@0.12.5': {} '@types/cli-progress@3.11.6': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 4.19.6 - '@types/node': 22.18.8 + '@types/express-serve-static-core': 4.19.7 + '@types/node': 22.19.0 '@types/connect@3.4.38': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/cors@2.8.19': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@types/duplexify@3.6.4': + '@types/duplexify@3.6.5': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/ejs@3.1.5': {} @@ -13145,50 +13455,48 @@ snapshots: '@types/events@3.0.3': {} - '@types/express-serve-static-core@4.19.6': + '@types/express-serve-static-core@4.19.7': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 - '@types/send': 1.2.0 + '@types/send': 1.2.1 - '@types/express@4.17.23': + '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.6 + '@types/express-serve-static-core': 4.19.7 '@types/qs': 6.14.0 - '@types/serve-static': 1.15.9 + '@types/serve-static': 1.15.10 '@types/folder-hash@4.0.4': {} '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@types/git-raw-commits@5.0.0': + '@types/git-raw-commits@5.0.1': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/google.maps@3.58.1': {} '@types/http-errors@2.0.5': {} - '@types/http-proxy@1.17.16': + '@types/http-proxy@1.17.17': dependencies: - '@types/node': 22.18.8 - - '@types/jasmine@5.1.11': {} + '@types/node': 22.19.0 '@types/jasmine@5.1.12': {} - '@types/jasmine@5.1.9': {} + '@types/jasmine@5.1.13': {} '@types/json-schema@7.0.15': {} '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/long@4.0.2': {} @@ -13200,19 +13508,19 @@ snapshots: '@types/mysql@2.15.26': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/node-forge@1.3.14': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@types/node@22.18.8': + '@types/node@22.19.0': dependencies: undici-types: 6.21.0 - '@types/node@24.8.1': + '@types/node@24.10.1': dependencies: - undici-types: 7.14.0 + undici-types: 7.16.0 '@types/normalize-package-data@2.4.4': {} @@ -13224,14 +13532,14 @@ snapshots: '@types/pg@8.6.1': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 pg-protocol: 1.10.3 pg-types: 2.2.0 - '@types/pumpify@1.4.4': + '@types/pumpify@1.4.5': dependencies: - '@types/duplexify': 3.6.4 - '@types/node': 22.18.8 + '@types/duplexify': 3.6.5 + '@types/node': 22.19.0 '@types/q@0.0.32': {} @@ -13242,7 +13550,7 @@ snapshots: '@types/request@2.48.13': dependencies: '@types/caseless': 0.12.5 - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/tough-cookie': 4.0.5 form-data: 2.5.5 @@ -13254,41 +13562,41 @@ snapshots: '@types/semver@7.7.1': {} - '@types/send@0.17.5': + '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 22.18.8 + '@types/node': 22.19.0 - '@types/send@1.2.0': + '@types/send@1.2.1': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/serve-index@1.9.4': dependencies: - '@types/express': 4.17.23 + '@types/express': 4.17.25 - '@types/serve-static@1.15.9': + '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 22.18.8 - '@types/send': 0.17.5 + '@types/node': 22.19.0 + '@types/send': 0.17.6 '@types/shelljs@0.8.17': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 glob: 11.0.3 '@types/shimmer@1.2.0': {} '@types/sockjs@0.3.36': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/stack-trace@0.0.33': {} '@types/tedious@4.0.14': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/tough-cookie@4.0.5': {} @@ -13304,7 +13612,7 @@ snapshots: '@types/vfile@3.0.2': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/unist': 2.0.11 '@types/vfile-message': 2.0.0 @@ -13312,11 +13620,15 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 '@types/yargs-parser@21.0.3': {} - '@types/yargs@17.0.33': + '@types/yargs@17.0.34': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@types/yargs@17.0.35': dependencies: '@types/yargs-parser': 21.0.3 @@ -13324,14 +13636,14 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 optional: true '@types/youtube@0.1.2': {} - '@vitejs/plugin-basic-ssl@2.1.0(vite@7.1.10(@types/node@22.18.8)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: - vite: 7.1.10(@types/node@22.18.8)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vite: 7.2.2(@types/node@22.19.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) '@webassemblyjs/ast@1.14.1': dependencies: @@ -13503,22 +13815,22 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - algoliasearch@5.40.0: - dependencies: - '@algolia/abtesting': 1.6.0 - '@algolia/client-abtesting': 5.40.0 - '@algolia/client-analytics': 5.40.0 - '@algolia/client-common': 5.40.0 - '@algolia/client-insights': 5.40.0 - '@algolia/client-personalization': 5.40.0 - '@algolia/client-query-suggestions': 5.40.0 - '@algolia/client-search': 5.40.0 - '@algolia/ingestion': 1.40.0 - '@algolia/monitoring': 1.40.0 - '@algolia/recommend': 5.40.0 - '@algolia/requester-browser-xhr': 5.40.0 - '@algolia/requester-fetch': 5.40.0 - '@algolia/requester-node-http': 5.40.0 + algoliasearch@5.40.1: + dependencies: + '@algolia/abtesting': 1.6.1 + '@algolia/client-abtesting': 5.40.1 + '@algolia/client-analytics': 5.40.1 + '@algolia/client-common': 5.40.1 + '@algolia/client-insights': 5.40.1 + '@algolia/client-personalization': 5.40.1 + '@algolia/client-query-suggestions': 5.40.1 + '@algolia/client-search': 5.40.1 + '@algolia/ingestion': 1.40.1 + '@algolia/monitoring': 1.40.1 + '@algolia/recommend': 5.40.1 + '@algolia/requester-browser-xhr': 5.40.1 + '@algolia/requester-fetch': 5.40.1 + '@algolia/requester-node-http': 5.40.1 amdefine@1.0.1: optional: true @@ -13529,7 +13841,7 @@ snapshots: ansi-colors@4.1.3: {} - ansi-escapes@7.1.1: + ansi-escapes@7.2.0: dependencies: environment: 1.1.0 @@ -13553,8 +13865,6 @@ snapshots: ansi-styles@6.2.3: {} - ansis@4.2.0: {} - any-promise@1.3.0: {} anymatch@3.1.3: @@ -13582,6 +13892,7 @@ snapshots: tar-stream: 3.1.7 zip-stream: 6.0.1 transitivePeerDependencies: + - bare-abort-controller - react-native-b4a arg@4.1.3: {} @@ -13655,15 +13966,15 @@ snapshots: asynckit@0.4.0: {} - atomically@2.0.3: + atomically@2.1.0: dependencies: - stubborn-fs: 1.2.5 - when-exit: 2.1.4 + stubborn-fs: 2.0.0 + when-exit: 2.1.5 autoprefixer@10.4.21(postcss@8.5.6): dependencies: - browserslist: 4.26.3 - caniuse-lite: 1.0.30001747 + browserslist: 4.27.0 + caniuse-lite: 1.0.30001754 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -13678,21 +13989,19 @@ snapshots: aws4@1.13.2: {} - axe-core@4.10.3: {} - axe-core@4.11.0: {} b4a@1.7.3: {} - babel-loader@10.0.0(@babel/core@7.28.4)(webpack@5.102.1(esbuild@0.25.10)): + babel-loader@10.0.0(@babel/core@7.28.4)(webpack@5.102.1(esbuild@0.26.0)): dependencies: '@babel/core': 7.28.4 find-up: 5.0.0 - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.4): dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/core': 7.28.4 '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) semver: 6.3.1 @@ -13703,7 +14012,7 @@ snapshots: dependencies: '@babel/core': 7.28.4 '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) - core-js-compat: 3.45.1 + core-js-compat: 3.46.0 transitivePeerDependencies: - supports-color @@ -13720,16 +14029,17 @@ snapshots: balanced-match@2.0.0: {} - bare-events@2.7.0: {} + bare-events@2.8.2: {} - bare-fs@4.4.5: + bare-fs@4.5.0: dependencies: - bare-events: 2.7.0 + bare-events: 2.8.2 bare-path: 3.0.0 - bare-stream: 2.7.0(bare-events@2.7.0) - bare-url: 2.2.2 + bare-stream: 2.7.0(bare-events@2.8.2) + bare-url: 2.3.2 fast-fifo: 1.3.2 transitivePeerDependencies: + - bare-abort-controller - react-native-b4a optional: true @@ -13741,16 +14051,17 @@ snapshots: bare-os: 3.6.2 optional: true - bare-stream@2.7.0(bare-events@2.7.0): + bare-stream@2.7.0(bare-events@2.8.2): dependencies: streamx: 2.23.0 optionalDependencies: - bare-events: 2.7.0 + bare-events: 2.8.2 transitivePeerDependencies: + - bare-abort-controller - react-native-b4a optional: true - bare-url@2.2.2: + bare-url@2.3.2: dependencies: bare-path: 3.0.0 optional: true @@ -13759,7 +14070,7 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.8.12: {} + baseline-browser-mapping@2.8.25: {} basic-auth-connect@1.1.0: dependencies: @@ -13876,13 +14187,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.26.3: + browserslist@4.27.0: dependencies: - baseline-browser-mapping: 2.8.12 - caniuse-lite: 1.0.30001747 - electron-to-chromium: 1.5.230 - node-releases: 2.0.23 - update-browserslist-db: 1.1.3(browserslist@4.26.3) + baseline-browser-mapping: 2.8.25 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.249 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.27.0) browserstack-local@1.5.8: dependencies: @@ -13950,7 +14261,7 @@ snapshots: minipass-pipeline: 1.2.4 p-map: 7.0.3 ssri: 12.0.0 - tar: 7.5.1 + tar: 7.5.2 unique-filename: 4.0.0 cacache@20.0.1: @@ -14003,7 +14314,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001747: {} + caniuse-lite@1.0.30001754: {} canonical-path@0.0.2: {} @@ -14065,11 +14376,11 @@ snapshots: character-entities-legacy@1.1.4: {} - chardet@2.1.0: {} + chardet@2.1.1: {} checkpoint-stream@0.1.2: dependencies: - '@types/pumpify': 1.4.4 + '@types/pumpify': 1.4.5 events-intercept: 2.0.0 pumpify: 1.5.1 split-array-stream: 1.0.3 @@ -14097,7 +14408,7 @@ snapshots: chrome-launcher@1.2.1: dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 2.0.2 @@ -14106,9 +14417,9 @@ snapshots: chrome-trace-event@1.0.4: {} - chromium-bidi@9.1.0(devtools-protocol@0.0.1508733): + chromium-bidi@10.5.1(devtools-protocol@0.0.1521046): dependencies: - devtools-protocol: 0.0.1508733 + devtools-protocol: 0.0.1521046 mitt: 3.0.1 zod: 3.25.76 @@ -14153,7 +14464,7 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 - cli-truncate@5.1.0: + cli-truncate@5.1.1: dependencies: slice-ansi: 7.1.2 string-width: 8.1.0 @@ -14289,7 +14600,7 @@ snapshots: configstore@7.1.0: dependencies: - atomically: 2.0.3 + atomically: 2.1.0 dot-prop: 9.0.0 graceful-fs: 4.2.11 xdg-basedir: 5.1.0 @@ -14333,7 +14644,7 @@ snapshots: conventional-commits-filter@5.0.0: {} - conventional-commits-parser@6.2.0: + conventional-commits-parser@6.2.1: dependencies: meow: 13.2.0 @@ -14353,18 +14664,18 @@ snapshots: dependencies: is-what: 3.14.1 - copy-webpack-plugin@13.0.1(webpack@5.102.1(esbuild@0.25.10)): + copy-webpack-plugin@13.0.1(webpack@5.102.1(esbuild@0.26.0)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 6.0.2 tinyglobby: 0.2.15 - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) - core-js-compat@3.45.1: + core-js-compat@3.46.0: dependencies: - browserslist: 4.26.3 + browserslist: 4.27.0 core-util-is@1.0.2: {} @@ -14425,7 +14736,7 @@ snapshots: css-functions-list@3.2.3: {} - css-loader@7.1.2(webpack@5.102.1(esbuild@0.25.10)): + css-loader@7.1.2(webpack@5.102.1(esbuild@0.26.0)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -14436,7 +14747,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.3 optionalDependencies: - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) css-select@6.0.0: dependencies: @@ -14596,12 +14907,12 @@ snapshots: detect-libc@1.0.3: optional: true - detect-libc@2.1.1: + detect-libc@2.1.2: optional: true detect-node@2.1.0: {} - devtools-protocol@0.0.1508733: {} + devtools-protocol@0.0.1521046: {} devtools-protocol@0.0.1527314: {} @@ -14748,9 +15059,9 @@ snapshots: dependencies: jake: 10.9.4 - electron-to-chromium@1.5.230: {} + electron-to-chromium@1.5.249: {} - emoji-regex@10.5.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -14779,7 +15090,7 @@ snapshots: engine.io@6.6.4(bufferutil@4.0.9): dependencies: '@types/cors': 2.8.19 - '@types/node': 22.18.8 + '@types/node': 22.19.0 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -14918,36 +15229,65 @@ snapshots: dependencies: es6-promise: 4.2.8 - esbuild-wasm@0.25.10: {} + esbuild-wasm@0.26.0: {} - esbuild@0.25.10: + esbuild@0.25.12: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.10 - '@esbuild/android-arm': 0.25.10 - '@esbuild/android-arm64': 0.25.10 - '@esbuild/android-x64': 0.25.10 - '@esbuild/darwin-arm64': 0.25.10 - '@esbuild/darwin-x64': 0.25.10 - '@esbuild/freebsd-arm64': 0.25.10 - '@esbuild/freebsd-x64': 0.25.10 - '@esbuild/linux-arm': 0.25.10 - '@esbuild/linux-arm64': 0.25.10 - '@esbuild/linux-ia32': 0.25.10 - '@esbuild/linux-loong64': 0.25.10 - '@esbuild/linux-mips64el': 0.25.10 - '@esbuild/linux-ppc64': 0.25.10 - '@esbuild/linux-riscv64': 0.25.10 - '@esbuild/linux-s390x': 0.25.10 - '@esbuild/linux-x64': 0.25.10 - '@esbuild/netbsd-arm64': 0.25.10 - '@esbuild/netbsd-x64': 0.25.10 - '@esbuild/openbsd-arm64': 0.25.10 - '@esbuild/openbsd-x64': 0.25.10 - '@esbuild/openharmony-arm64': 0.25.10 - '@esbuild/sunos-x64': 0.25.10 - '@esbuild/win32-arm64': 0.25.10 - '@esbuild/win32-ia32': 0.25.10 - '@esbuild/win32-x64': 0.25.10 + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.26.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.26.0 + '@esbuild/android-arm': 0.26.0 + '@esbuild/android-arm64': 0.26.0 + '@esbuild/android-x64': 0.26.0 + '@esbuild/darwin-arm64': 0.26.0 + '@esbuild/darwin-x64': 0.26.0 + '@esbuild/freebsd-arm64': 0.26.0 + '@esbuild/freebsd-x64': 0.26.0 + '@esbuild/linux-arm': 0.26.0 + '@esbuild/linux-arm64': 0.26.0 + '@esbuild/linux-ia32': 0.26.0 + '@esbuild/linux-loong64': 0.26.0 + '@esbuild/linux-mips64el': 0.26.0 + '@esbuild/linux-ppc64': 0.26.0 + '@esbuild/linux-riscv64': 0.26.0 + '@esbuild/linux-s390x': 0.26.0 + '@esbuild/linux-x64': 0.26.0 + '@esbuild/netbsd-arm64': 0.26.0 + '@esbuild/netbsd-x64': 0.26.0 + '@esbuild/openbsd-arm64': 0.26.0 + '@esbuild/openbsd-x64': 0.26.0 + '@esbuild/openharmony-arm64': 0.26.0 + '@esbuild/sunos-x64': 0.26.0 + '@esbuild/win32-arm64': 0.26.0 + '@esbuild/win32-ia32': 0.26.0 + '@esbuild/win32-x64': 0.26.0 escalade@3.2.0: {} @@ -15035,7 +15375,9 @@ snapshots: events-universal@1.0.1: dependencies: - bare-events: 2.7.0 + bare-events: 2.8.2 + transitivePeerDependencies: + - bare-abort-controller events@3.3.0: {} @@ -15086,7 +15428,7 @@ snapshots: exit@0.1.2: {} - exponential-backoff@3.1.2: {} + exponential-backoff@3.1.3: {} express-rate-limit@7.5.1(express@5.1.0): dependencies: @@ -15280,101 +15622,16 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - firebase-tools@14.17.0(@types/node@22.18.8)(bufferutil@4.0.9)(encoding@0.1.13): - dependencies: - '@electric-sql/pglite': 0.3.10 - '@electric-sql/pglite-tools': 0.2.15(@electric-sql/pglite@0.3.10) - '@google-cloud/cloud-sql-connector': 1.8.3 - '@google-cloud/pubsub': 4.11.0(encoding@0.1.13) - '@inquirer/prompts': 7.8.6(@types/node@22.18.8) - '@modelcontextprotocol/sdk': 1.19.1 - abort-controller: 3.0.0 - ajv: 8.17.1 - ajv-formats: 3.0.1 - archiver: 7.0.1 - async-lock: 1.4.1 - body-parser: 1.20.3 - chokidar: 3.6.0 - cjson: 0.3.3 - cli-table3: 0.6.5 - colorette: 2.0.20 - commander: 5.1.0 - configstore: 5.0.1 - cors: 2.8.5 - cross-env: 7.0.3 - cross-spawn: 7.0.6 - csv-parse: 5.6.0 - deep-equal-in-any-order: 2.1.0 - exegesis: 4.3.0 - exegesis-express: 4.0.0 - express: 4.21.2 - filesize: 6.4.0 - form-data: 4.0.4 - fs-extra: 10.1.0 - fuzzy: 0.1.3 - gaxios: 6.7.1(encoding@0.1.13)(supports-color@10.2.2) - glob: 10.4.5 - google-auth-library: 9.15.1(encoding@0.1.13)(supports-color@10.2.2) - ignore: 7.0.5 - js-yaml: 3.14.1 - jsonwebtoken: 9.0.2 - leven: 3.1.0 - libsodium-wrappers: 0.7.15 - lodash: 4.17.21 - lsofi: 1.0.0 - marked: 13.0.3 - marked-terminal: 7.3.0(marked@13.0.3) - mime: 2.6.0 - minimatch: 3.1.2 - morgan: 1.10.1 - node-fetch: 2.7.0(encoding@0.1.13) - open: 6.4.0 - ora: 5.4.1 - p-limit: 3.1.0 - pg: 8.16.3 - pg-gateway: 0.3.0-beta.4 - pglite-2: '@electric-sql/pglite@0.2.17' - portfinder: 1.0.38 - progress: 2.0.3 - proxy-agent: 6.5.0 - retry: 0.13.1 - semver: 7.7.2 - sql-formatter: 15.6.10 - stream-chain: 2.2.5 - stream-json: 1.9.1 - superstatic: 9.2.0(encoding@0.1.13) - tar: 6.2.1 - tcp-port-used: 1.0.2 - tmp: 0.2.5 - triple-beam: 1.4.1 - universal-analytics: 0.5.3 - update-notifier-cjs: 5.1.7(encoding@0.1.13) - uuid: 8.3.2 - winston: 3.18.3 - winston-transport: 4.9.0 - ws: 7.5.10(bufferutil@4.0.9) - yaml: 2.8.1 - zod: 3.25.76 - zod-to-json-schema: 3.24.6(zod@3.25.76) - transitivePeerDependencies: - - '@types/node' - - bufferutil - - encoding - - pg-native - - react-native-b4a - - supports-color - - utf-8-validate - - firebase-tools@14.20.0(@types/node@22.18.8)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2): + firebase-tools@14.20.0(@types/node@22.19.0)(bufferutil@4.0.9)(encoding@0.1.13)(typescript@5.9.2): dependencies: - '@apphosting/build': 0.1.6(@types/node@22.18.8)(typescript@5.9.2) + '@apphosting/build': 0.1.7(@types/node@22.19.0)(typescript@5.9.2) '@apphosting/common': 0.0.8 - '@electric-sql/pglite': 0.3.10 - '@electric-sql/pglite-tools': 0.2.15(@electric-sql/pglite@0.3.10) - '@google-cloud/cloud-sql-connector': 1.8.3 + '@electric-sql/pglite': 0.3.14 + '@electric-sql/pglite-tools': 0.2.19(@electric-sql/pglite@0.3.14) + '@google-cloud/cloud-sql-connector': 1.8.4 '@google-cloud/pubsub': 4.11.0(encoding@0.1.13) - '@inquirer/prompts': 7.9.0(@types/node@22.18.8) - '@modelcontextprotocol/sdk': 1.20.0 + '@inquirer/prompts': 7.10.0(@types/node@22.19.0) + '@modelcontextprotocol/sdk': 1.21.1 abort-controller: 3.0.0 ajv: 8.17.1 ajv-formats: 3.0.1 @@ -15399,9 +15656,9 @@ snapshots: form-data: 4.0.4 fs-extra: 10.1.0 fuzzy: 0.1.3 - gaxios: 6.7.1(encoding@0.1.13)(supports-color@10.2.2) + gaxios: 6.7.1(encoding@0.1.13) glob: 10.4.5 - google-auth-library: 9.15.1(encoding@0.1.13)(supports-color@10.2.2) + google-auth-library: 9.15.1(encoding@0.1.13) ignore: 7.0.5 js-yaml: 3.14.1 jsonwebtoken: 9.0.2 @@ -15444,9 +15701,11 @@ snapshots: zod: 3.25.76 zod-to-json-schema: 3.24.6(zod@3.25.76) transitivePeerDependencies: + - '@cfworker/json-schema' - '@swc/core' - '@swc/wasm' - '@types/node' + - bare-abort-controller - bufferutil - encoding - pg-native @@ -15455,35 +15714,35 @@ snapshots: - typescript - utf-8-validate - firebase@12.4.0: + firebase@12.6.0: dependencies: - '@firebase/ai': 2.4.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.4) - '@firebase/analytics': 0.10.19(@firebase/app@0.14.4) - '@firebase/analytics-compat': 0.2.25(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4) - '@firebase/app': 0.14.4 - '@firebase/app-check': 0.11.0(@firebase/app@0.14.4) - '@firebase/app-check-compat': 0.4.0(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4) - '@firebase/app-compat': 0.5.4 + '@firebase/ai': 2.6.0(@firebase/app-types@0.9.3)(@firebase/app@0.14.6) + '@firebase/analytics': 0.10.19(@firebase/app@0.14.6) + '@firebase/analytics-compat': 0.2.25(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6) + '@firebase/app': 0.14.6 + '@firebase/app-check': 0.11.0(@firebase/app@0.14.6) + '@firebase/app-check-compat': 0.4.0(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6) + '@firebase/app-compat': 0.5.6 '@firebase/app-types': 0.9.3 - '@firebase/auth': 1.11.0(@firebase/app@0.14.4) - '@firebase/auth-compat': 0.6.0(@firebase/app-compat@0.5.4)(@firebase/app-types@0.9.3)(@firebase/app@0.14.4) - '@firebase/data-connect': 0.3.11(@firebase/app@0.14.4) + '@firebase/auth': 1.11.1(@firebase/app@0.14.6) + '@firebase/auth-compat': 0.6.1(@firebase/app-compat@0.5.6)(@firebase/app-types@0.9.3)(@firebase/app@0.14.6) + '@firebase/data-connect': 0.3.12(@firebase/app@0.14.6) '@firebase/database': 1.1.0 '@firebase/database-compat': 2.1.0 - '@firebase/firestore': 4.9.2(@firebase/app@0.14.4) - '@firebase/firestore-compat': 0.4.2(@firebase/app-compat@0.5.4)(@firebase/app-types@0.9.3)(@firebase/app@0.14.4) - '@firebase/functions': 0.13.1(@firebase/app@0.14.4) - '@firebase/functions-compat': 0.4.1(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4) - '@firebase/installations': 0.6.19(@firebase/app@0.14.4) - '@firebase/installations-compat': 0.2.19(@firebase/app-compat@0.5.4)(@firebase/app-types@0.9.3)(@firebase/app@0.14.4) - '@firebase/messaging': 0.12.23(@firebase/app@0.14.4) - '@firebase/messaging-compat': 0.2.23(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4) - '@firebase/performance': 0.7.9(@firebase/app@0.14.4) - '@firebase/performance-compat': 0.2.22(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4) - '@firebase/remote-config': 0.7.0(@firebase/app@0.14.4) - '@firebase/remote-config-compat': 0.2.20(@firebase/app-compat@0.5.4)(@firebase/app@0.14.4) - '@firebase/storage': 0.14.0(@firebase/app@0.14.4) - '@firebase/storage-compat': 0.4.0(@firebase/app-compat@0.5.4)(@firebase/app-types@0.9.3)(@firebase/app@0.14.4) + '@firebase/firestore': 4.9.2(@firebase/app@0.14.6) + '@firebase/firestore-compat': 0.4.2(@firebase/app-compat@0.5.6)(@firebase/app-types@0.9.3)(@firebase/app@0.14.6) + '@firebase/functions': 0.13.1(@firebase/app@0.14.6) + '@firebase/functions-compat': 0.4.1(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6) + '@firebase/installations': 0.6.19(@firebase/app@0.14.6) + '@firebase/installations-compat': 0.2.19(@firebase/app-compat@0.5.6)(@firebase/app-types@0.9.3)(@firebase/app@0.14.6) + '@firebase/messaging': 0.12.23(@firebase/app@0.14.6) + '@firebase/messaging-compat': 0.2.23(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6) + '@firebase/performance': 0.7.9(@firebase/app@0.14.6) + '@firebase/performance-compat': 0.2.22(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6) + '@firebase/remote-config': 0.7.0(@firebase/app@0.14.6) + '@firebase/remote-config-compat': 0.2.20(@firebase/app-compat@0.5.6)(@firebase/app@0.14.6) + '@firebase/storage': 0.14.0(@firebase/app@0.14.6) + '@firebase/storage-compat': 0.4.0(@firebase/app-compat@0.5.6)(@firebase/app-types@0.9.3)(@firebase/app@0.14.6) '@firebase/util': 1.13.0 transitivePeerDependencies: - '@react-native-async-storage/async-storage' @@ -15607,7 +15866,7 @@ snapshots: fuzzy@0.1.3: {} - gaxios@6.7.1(encoding@0.1.13)(supports-color@10.2.2): + gaxios@6.7.1(encoding@0.1.13): dependencies: extend: 3.0.2 https-proxy-agent: 7.0.6(supports-color@10.2.2) @@ -15618,11 +15877,12 @@ snapshots: - encoding - supports-color - gaxios@7.1.2(supports-color@10.2.2): + gaxios@7.1.3(supports-color@10.2.2): dependencies: extend: 3.0.2 https-proxy-agent: 7.0.6(supports-color@10.2.2) node-fetch: 3.3.2 + rimraf: 5.0.10 transitivePeerDependencies: - supports-color @@ -15630,19 +15890,19 @@ snapshots: dependencies: globule: 1.3.4 - gcp-metadata@6.1.1(encoding@0.1.13)(supports-color@10.2.2): + gcp-metadata@6.1.1(encoding@0.1.13): dependencies: - gaxios: 6.7.1(encoding@0.1.13)(supports-color@10.2.2) + gaxios: 6.7.1(encoding@0.1.13) google-logging-utils: 0.0.2 json-bigint: 1.0.0 transitivePeerDependencies: - encoding - supports-color - gcp-metadata@7.0.1(supports-color@10.2.2): + gcp-metadata@8.1.2(supports-color@10.2.2): dependencies: - gaxios: 7.1.2(supports-color@10.2.2) - google-logging-utils: 1.1.1 + gaxios: 7.1.3(supports-color@10.2.2) + google-logging-utils: 1.1.2 json-bigint: 1.0.0 transitivePeerDependencies: - supports-color @@ -15685,7 +15945,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.10.1: + get-tsconfig@4.13.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -15701,9 +15961,9 @@ snapshots: dependencies: assert-plus: 1.0.0 - git-raw-commits@5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.0): + git-raw-commits@5.0.0(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1): dependencies: - '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.0) + '@conventional-changelog/git-client': 1.0.1(conventional-commits-filter@5.0.0)(conventional-commits-parser@6.2.1) meow: 13.2.0 transitivePeerDependencies: - conventional-commits-filter @@ -15744,10 +16004,10 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.1.1 - minimatch: 10.0.3 + minimatch: 10.1.1 minipass: 7.1.2 package-json-from-dist: 1.0.1 - path-scurry: 2.0.0 + path-scurry: 2.0.1 glob@5.0.15: dependencies: @@ -15820,25 +16080,25 @@ snapshots: lodash: 4.17.21 minimatch: 3.0.8 - google-auth-library@10.4.0(supports-color@10.2.2): + google-auth-library@10.5.0(supports-color@10.2.2): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 7.1.2(supports-color@10.2.2) - gcp-metadata: 7.0.1(supports-color@10.2.2) - google-logging-utils: 1.1.1 + gaxios: 7.1.3(supports-color@10.2.2) + gcp-metadata: 8.1.2(supports-color@10.2.2) + google-logging-utils: 1.1.2 gtoken: 8.0.0(supports-color@10.2.2) jws: 4.0.0 transitivePeerDependencies: - supports-color - google-auth-library@9.15.1(encoding@0.1.13)(supports-color@10.2.2): + google-auth-library@9.15.1(encoding@0.1.13): dependencies: base64-js: 1.5.1 ecdsa-sig-formatter: 1.0.11 - gaxios: 6.7.1(encoding@0.1.13)(supports-color@10.2.2) - gcp-metadata: 6.1.1(encoding@0.1.13)(supports-color@10.2.2) - gtoken: 7.1.0(encoding@0.1.13)(supports-color@10.2.2) + gaxios: 6.7.1(encoding@0.1.13) + gcp-metadata: 6.1.1(encoding@0.1.13) + gtoken: 7.1.0(encoding@0.1.13) jws: 4.0.0 transitivePeerDependencies: - encoding @@ -15846,12 +16106,12 @@ snapshots: google-gax@4.6.1(encoding@0.1.13): dependencies: - '@grpc/grpc-js': 1.14.0 + '@grpc/grpc-js': 1.14.1 '@grpc/proto-loader': 0.7.15 '@types/long': 4.0.2 abort-controller: 3.0.0 duplexify: 4.1.3 - google-auth-library: 9.15.1(encoding@0.1.13)(supports-color@10.2.2) + google-auth-library: 9.15.1(encoding@0.1.13) node-fetch: 2.7.0(encoding@0.1.13) object-hash: 3.0.0 proto3-json-serializer: 2.0.2 @@ -15862,30 +16122,31 @@ snapshots: - encoding - supports-color - google-gax@5.0.4(supports-color@10.2.2): + google-gax@5.0.5(supports-color@10.2.2): dependencies: - '@grpc/grpc-js': 1.14.0 + '@grpc/grpc-js': 1.14.1 '@grpc/proto-loader': 0.8.0 duplexify: 4.1.3 - google-auth-library: 10.4.0(supports-color@10.2.2) - google-logging-utils: 1.1.1 + google-auth-library: 10.5.0(supports-color@10.2.2) + google-logging-utils: 1.1.2 node-fetch: 3.3.2 object-hash: 3.0.0 - proto3-json-serializer: 3.0.2 + proto3-json-serializer: 3.0.4 protobufjs: 7.5.4 retry-request: 8.0.2(supports-color@10.2.2) + rimraf: 5.0.10 transitivePeerDependencies: - supports-color google-logging-utils@0.0.2: {} - google-logging-utils@1.1.1: {} + google-logging-utils@1.1.2: {} googleapis-common@8.0.2-rc.0: dependencies: extend: 3.0.2 - gaxios: 7.1.2(supports-color@10.2.2) - google-auth-library: 10.4.0(supports-color@10.2.2) + gaxios: 7.1.3(supports-color@10.2.2) + google-auth-library: 10.5.0(supports-color@10.2.2) qs: 6.14.0 url-template: 2.0.8 transitivePeerDependencies: @@ -15897,21 +16158,21 @@ snapshots: graceful-fs@4.2.11: {} - graphql-tag@2.12.6(graphql@16.11.0): + graphql-tag@2.12.6(graphql@16.12.0): dependencies: - graphql: 16.11.0 + graphql: 16.12.0 tslib: 2.8.1 - graphql@16.11.0: {} + graphql@16.12.0: {} grpc-gcp@1.0.1: dependencies: - '@grpc/grpc-js': 1.14.0 + '@grpc/grpc-js': 1.14.1 protobufjs: 7.4.0 - gtoken@7.1.0(encoding@0.1.13)(supports-color@10.2.2): + gtoken@7.1.0(encoding@0.1.13): dependencies: - gaxios: 6.7.1(encoding@0.1.13)(supports-color@10.2.2) + gaxios: 6.7.1(encoding@0.1.13) jws: 4.0.0 transitivePeerDependencies: - encoding @@ -15919,7 +16180,7 @@ snapshots: gtoken@8.0.0(supports-color@10.2.2): dependencies: - gaxios: 7.1.2(supports-color@10.2.2) + gaxios: 7.1.3(supports-color@10.2.2) jws: 4.0.0 transitivePeerDependencies: - supports-color @@ -16031,7 +16292,7 @@ snapshots: dependencies: lru-cache: 10.4.3 - hosted-git-info@9.0.0: + hosted-git-info@9.0.2: dependencies: lru-cache: 11.2.2 @@ -16114,21 +16375,21 @@ snapshots: transitivePeerDependencies: - supports-color - http-proxy-middleware@2.0.9(@types/express@4.17.23): + http-proxy-middleware@2.0.9(@types/express@4.17.25): dependencies: - '@types/http-proxy': 1.17.16 + '@types/http-proxy': 1.17.17 http-proxy: 1.18.1(debug@4.4.3) is-glob: 4.0.3 is-plain-obj: 3.0.0 micromatch: 4.0.8 optionalDependencies: - '@types/express': 4.17.23 + '@types/express': 4.17.25 transitivePeerDependencies: - debug http-proxy-middleware@3.0.5: dependencies: - '@types/http-proxy': 1.17.16 + '@types/http-proxy': 1.17.17 debug: 4.4.3(supports-color@10.2.2) http-proxy: 1.18.1(debug@4.4.3) is-glob: 4.0.3 @@ -16200,7 +16461,7 @@ snapshots: ignore-walk@8.0.0: dependencies: - minimatch: 10.0.3 + minimatch: 10.1.1 ignore@5.3.2: {} @@ -16213,14 +16474,14 @@ snapshots: immediate@3.0.6: {} - immutable@5.1.3: {} + immutable@5.1.4: {} import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@1.14.4: + import-in-the-middle@1.15.0: dependencies: acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) @@ -16261,14 +16522,14 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - intl-messageformat@10.7.17: + intl-messageformat@10.7.18: dependencies: - '@formatjs/ecma402-abstract': 2.3.5 + '@formatjs/ecma402-abstract': 2.3.6 '@formatjs/fast-memoize': 2.2.7 - '@formatjs/icu-messageformat-parser': 2.11.3 + '@formatjs/icu-messageformat-parser': 2.11.4 tslib: 2.8.1 - ip-address@10.0.1: {} + ip-address@10.1.0: {} ip-regex@4.3.0: {} @@ -16545,8 +16806,8 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -16555,8 +16816,8 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.7.3 @@ -16619,10 +16880,10 @@ snapshots: jasmine-core@4.6.1: {} - jasmine-core@5.11.0: {} - jasmine-core@5.12.0: {} + jasmine-core@5.12.1: {} + jasmine-reporters@2.5.2: dependencies: '@xmldom/xmldom': 0.8.11 @@ -16638,11 +16899,6 @@ snapshots: glob: 7.2.3 jasmine-core: 2.8.0 - jasmine@5.11.0: - dependencies: - glob: 10.4.5 - jasmine-core: 5.11.0 - jasmine@5.12.0: dependencies: glob: 10.4.5 @@ -16652,7 +16908,7 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -16695,7 +16951,7 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - json-parse-even-better-errors@4.0.0: {} + json-parse-even-better-errors@5.0.0: {} json-parse-helpfulerror@1.0.3: dependencies: @@ -16880,7 +17136,7 @@ snapshots: kuler@2.0.0: {} - launch-editor@2.11.1: + launch-editor@2.12.0: dependencies: picocolors: 1.1.1 shell-quote: 1.8.3 @@ -16891,11 +17147,11 @@ snapshots: legacy-javascript@0.0.1: {} - less-loader@12.3.0(less@4.4.2)(webpack@5.102.1(esbuild@0.25.10)): + less-loader@12.3.0(less@4.4.2)(webpack@5.102.1(esbuild@0.26.0)): dependencies: less: 4.4.2 optionalDependencies: - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) less@4.4.2: dependencies: @@ -16924,11 +17180,11 @@ snapshots: libsodium@0.7.15: {} - license-webpack-plugin@4.0.2(webpack@5.102.1(esbuild@0.25.10)): + license-webpack-plugin@4.0.2(webpack@5.102.1(esbuild@0.26.0)): dependencies: webpack-sources: 3.3.3 optionalDependencies: - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) lie@3.3.0: dependencies: @@ -16965,7 +17221,7 @@ snapshots: lighthouse-stack-packs@1.12.3: {} - lighthouse@13.0.0(bufferutil@4.0.9): + lighthouse@13.0.1(bufferutil@4.0.9): dependencies: '@paulirish/trace_engine': 0.0.61 '@sentry/node': 9.46.0 @@ -16976,7 +17232,7 @@ snapshots: devtools-protocol: 0.0.1527314 enquirer: 2.4.1 http-link-header: 1.1.3 - intl-messageformat: 10.7.17 + intl-messageformat: 10.7.18 jpeg-js: 0.4.4 js-library-detector: 6.7.0 lighthouse-logger: 2.0.2 @@ -16984,7 +17240,7 @@ snapshots: lodash-es: 4.17.21 lookup-closest-locale: 6.2.0 open: 8.4.2 - puppeteer-core: 24.23.0(bufferutil@4.0.9) + puppeteer-core: 24.29.1(bufferutil@4.0.9) robots-parser: 3.0.1 speedline-core: 1.4.3 third-party-web: 0.27.0 @@ -16993,6 +17249,7 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 transitivePeerDependencies: + - bare-abort-controller - bare-buffer - bufferutil - react-native-b4a @@ -17001,9 +17258,9 @@ snapshots: lines-and-columns@1.2.4: {} - listr2@9.0.4: + listr2@9.0.5: dependencies: - cli-truncate: 5.1.0 + cli-truncate: 5.1.1 colorette: 2.0.20 eventemitter3: 5.0.1 log-update: 6.1.0 @@ -17034,7 +17291,7 @@ snapshots: pify: 3.0.0 strip-bom: 3.0.0 - loader-runner@4.3.0: {} + loader-runner@4.3.1: {} loader-utils@2.0.4: dependencies: @@ -17111,7 +17368,7 @@ snapshots: log-update@6.1.0: dependencies: - ansi-escapes: 7.1.1 + ansi-escapes: 7.2.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 strip-ansi: 7.1.2 @@ -17227,7 +17484,7 @@ snapshots: marked-terminal@7.3.0(marked@13.0.3): dependencies: - ansi-escapes: 7.1.1 + ansi-escapes: 7.2.0 ansi-regex: 6.2.2 chalk: 5.6.2 cli-highlight: 2.1.11 @@ -17240,7 +17497,7 @@ snapshots: marked@13.0.3: {} - marked@16.3.0: {} + marked@16.4.2: {} marky@1.3.0: {} @@ -17252,9 +17509,9 @@ snapshots: media-typer@1.1.0: {} - memfs@4.48.1: + memfs@4.50.0: dependencies: - '@jsonjoy.com/json-pack': 1.14.0(tslib@2.8.1) + '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) thingies: 2.5.0(tslib@2.8.1) @@ -17317,15 +17574,15 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.4(webpack@5.102.1(esbuild@0.25.10)): + mini-css-extract-plugin@2.9.4(webpack@5.102.1(esbuild@0.26.0)): dependencies: schema-utils: 4.3.3 tapable: 2.3.0 - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) minimalistic-assert@1.0.1: {} - minimatch@10.0.3: + minimatch@10.1.1: dependencies: '@isaacs/brace-expansion': 5.0.0 @@ -17462,7 +17719,7 @@ snapshots: array-union: 3.0.1 minimatch: 9.0.5 - mute-stream@2.0.0: {} + mute-stream@3.0.0: {} mz@2.7.0: dependencies: @@ -17470,7 +17727,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nan@2.23.0: + nan@2.23.1: optional: true nanoid@3.3.11: {} @@ -17485,7 +17742,7 @@ snapshots: needle@3.3.1: dependencies: iconv-lite: 0.6.3 - sax: 1.4.1 + sax: 1.4.3 optional: true negotiator@0.6.3: {} @@ -17506,7 +17763,7 @@ snapshots: nock@14.0.10: dependencies: - '@mswjs/interceptors': 0.39.7 + '@mswjs/interceptors': 0.39.8 json-stringify-safe: 5.0.1 propagate: 2.0.1 @@ -17541,27 +17798,27 @@ snapshots: node-gyp-build-optional-packages@5.2.2: dependencies: - detect-libc: 2.1.1 + detect-libc: 2.1.2 optional: true node-gyp-build@4.8.4: {} - node-gyp@11.4.2: + node-gyp@11.5.0: dependencies: env-paths: 2.2.1 - exponential-backoff: 3.1.2 + exponential-backoff: 3.1.3 graceful-fs: 4.2.11 make-fetch-happen: 14.0.3 nopt: 8.1.0 proc-log: 5.0.0 semver: 7.7.3 - tar: 7.5.1 + tar: 7.5.2 tinyglobby: 0.2.15 which: 5.0.0 transitivePeerDependencies: - supports-color - node-releases@2.0.23: {} + node-releases@2.0.27: {} nopt@3.0.6: dependencies: @@ -17574,7 +17831,7 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.10 + resolve: 1.22.11 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -17597,7 +17854,7 @@ snapshots: dependencies: semver: 7.7.3 - npm-install-checks@7.1.2: + npm-install-checks@8.0.0: dependencies: semver: 7.7.3 @@ -17605,6 +17862,8 @@ snapshots: npm-normalize-package-bin@4.0.0: {} + npm-normalize-package-bin@5.0.0: {} + npm-package-arg@11.0.3: dependencies: hosted-git-info: 7.0.2 @@ -17614,20 +17873,20 @@ snapshots: npm-package-arg@13.0.1: dependencies: - hosted-git-info: 9.0.0 + hosted-git-info: 9.0.2 proc-log: 5.0.0 semver: 7.7.3 validate-npm-package-name: 6.0.2 - npm-packlist@10.0.2: + npm-packlist@10.0.3: dependencies: ignore-walk: 8.0.0 - proc-log: 5.0.0 + proc-log: 6.0.0 - npm-pick-manifest@11.0.1: + npm-pick-manifest@11.0.3: dependencies: - npm-install-checks: 7.1.2 - npm-normalize-package-bin: 4.0.0 + npm-install-checks: 8.0.0 + npm-normalize-package-bin: 5.0.0 npm-package-arg: 13.0.1 semver: 7.7.3 @@ -17638,7 +17897,7 @@ snapshots: npm-package-arg: 11.0.3 semver: 7.7.3 - npm-registry-fetch@19.0.0: + npm-registry-fetch@19.1.0: dependencies: '@npmcli/redact': 3.2.2 jsonparse: 1.3.1 @@ -17860,21 +18119,21 @@ snapshots: dependencies: '@npmcli/git': 7.0.0 '@npmcli/installed-package-contents': 3.0.0 - '@npmcli/package-json': 7.0.1 + '@npmcli/package-json': 7.0.2 '@npmcli/promise-spawn': 8.0.3 - '@npmcli/run-script': 10.0.0 + '@npmcli/run-script': 10.0.2 cacache: 20.0.1 fs-minipass: 3.0.3 minipass: 7.1.2 npm-package-arg: 13.0.1 - npm-packlist: 10.0.2 - npm-pick-manifest: 11.0.1 - npm-registry-fetch: 19.0.0 + npm-packlist: 10.0.3 + npm-pick-manifest: 11.0.3 + npm-registry-fetch: 19.1.0 proc-log: 5.0.0 promise-retry: 2.0.1 sigstore: 4.0.0 ssri: 12.0.0 - tar: 7.5.1 + tar: 7.5.2 transitivePeerDependencies: - supports-color @@ -17954,7 +18213,7 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 - path-scurry@2.0.0: + path-scurry@2.0.1: dependencies: lru-cache: 11.2.2 minipass: 7.1.2 @@ -18054,14 +18313,14 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.2)(webpack@5.102.1(esbuild@0.25.10)): + postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.2)(webpack@5.102.1(esbuild@0.26.0)): dependencies: cosmiconfig: 9.0.0(typescript@5.9.2) jiti: 2.6.1 postcss: 8.5.6 semver: 7.7.3 optionalDependencies: - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) transitivePeerDependencies: - typescript @@ -18134,6 +18393,8 @@ snapshots: proc-log@5.0.0: {} + proc-log@6.0.0: {} + process-nextick-args@2.0.1: {} process@0.11.10: {} @@ -18164,7 +18425,7 @@ snapshots: dependencies: protobufjs: 7.5.4 - proto3-json-serializer@3.0.2: + proto3-json-serializer@3.0.4: dependencies: protobufjs: 7.5.4 @@ -18180,7 +18441,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.18.8 + '@types/node': 22.19.0 long: 5.3.2 protobufjs@7.5.4: @@ -18195,7 +18456,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 22.18.8 + '@types/node': 22.19.0 long: 5.3.2 protractor@7.0.0: @@ -18273,16 +18534,17 @@ snapshots: dependencies: escape-goat: 2.1.1 - puppeteer-core@24.23.0(bufferutil@4.0.9): + puppeteer-core@24.29.1(bufferutil@4.0.9): dependencies: - '@puppeteer/browsers': 2.10.10 - chromium-bidi: 9.1.0(devtools-protocol@0.0.1508733) + '@puppeteer/browsers': 2.10.13 + chromium-bidi: 10.5.1(devtools-protocol@0.0.1521046) debug: 4.4.3(supports-color@10.2.2) - devtools-protocol: 0.0.1508733 + devtools-protocol: 0.0.1521046 typed-query-selector: 2.12.0 - webdriver-bidi-protocol: 0.3.6 + webdriver-bidi-protocol: 0.3.8 ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: + - bare-abort-controller - bare-buffer - bufferutil - react-native-b4a @@ -18343,11 +18605,11 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - re2@1.22.1: + re2@1.22.3: dependencies: install-artifact-from-github: 1.4.0 - nan: 2.23.0 - node-gyp: 11.4.2 + nan: 2.23.1 + node-gyp: 11.5.0 transitivePeerDependencies: - supports-color optional: true @@ -18513,7 +18775,7 @@ snapshots: dependencies: debug: 4.4.3(supports-color@10.2.2) module-details-from-path: 1.0.4 - resolve: 1.22.10 + resolve: 1.22.11 transitivePeerDependencies: - supports-color @@ -18539,7 +18801,7 @@ snapshots: resolve@1.1.7: {} - resolve@1.22.10: + resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 @@ -18593,70 +18855,73 @@ snapshots: dependencies: glob: 7.2.3 + rimraf@5.0.10: + dependencies: + glob: 10.4.5 + robots-parser@3.0.1: {} - rolldown@1.0.0-beta.43: + rolldown@1.0.0-beta.47: dependencies: - '@oxc-project/types': 0.94.0 - '@rolldown/pluginutils': 1.0.0-beta.43 - ansis: 4.2.0 + '@oxc-project/types': 0.96.0 + '@rolldown/pluginutils': 1.0.0-beta.47 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.43 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.43 - '@rolldown/binding-darwin-x64': 1.0.0-beta.43 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.43 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.43 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.43 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.43 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.43 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.43 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.43 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.43 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.43 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.43 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.43 - - rollup-plugin-dts@6.2.3(rollup@4.52.4)(typescript@5.9.2): + '@rolldown/binding-android-arm64': 1.0.0-beta.47 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.47 + '@rolldown/binding-darwin-x64': 1.0.0-beta.47 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.47 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.47 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.47 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.47 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.47 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.47 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.47 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.47 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.47 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.47 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.47 + + rollup-plugin-dts@6.2.3(rollup@4.53.1)(typescript@5.9.2): dependencies: magic-string: 0.30.19 - rollup: 4.52.4 + rollup: 4.53.1 typescript: 5.9.2 optionalDependencies: '@babel/code-frame': 7.27.1 - rollup-plugin-sourcemaps2@0.5.4(@types/node@22.18.8)(rollup@4.52.4): + rollup-plugin-sourcemaps2@0.5.4(@types/node@22.19.0)(rollup@4.53.1): dependencies: - '@rollup/pluginutils': 5.2.0(rollup@4.52.4) - rollup: 4.52.4 + '@rollup/pluginutils': 5.2.0(rollup@4.53.1) + rollup: 4.53.1 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 - rollup@4.52.4: + rollup@4.53.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.4 - '@rollup/rollup-android-arm64': 4.52.4 - '@rollup/rollup-darwin-arm64': 4.52.4 - '@rollup/rollup-darwin-x64': 4.52.4 - '@rollup/rollup-freebsd-arm64': 4.52.4 - '@rollup/rollup-freebsd-x64': 4.52.4 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.4 - '@rollup/rollup-linux-arm-musleabihf': 4.52.4 - '@rollup/rollup-linux-arm64-gnu': 4.52.4 - '@rollup/rollup-linux-arm64-musl': 4.52.4 - '@rollup/rollup-linux-loong64-gnu': 4.52.4 - '@rollup/rollup-linux-ppc64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-gnu': 4.52.4 - '@rollup/rollup-linux-riscv64-musl': 4.52.4 - '@rollup/rollup-linux-s390x-gnu': 4.52.4 - '@rollup/rollup-linux-x64-gnu': 4.52.4 - '@rollup/rollup-linux-x64-musl': 4.52.4 - '@rollup/rollup-openharmony-arm64': 4.52.4 - '@rollup/rollup-win32-arm64-msvc': 4.52.4 - '@rollup/rollup-win32-ia32-msvc': 4.52.4 - '@rollup/rollup-win32-x64-gnu': 4.52.4 - '@rollup/rollup-win32-x64-msvc': 4.52.4 + '@rollup/rollup-android-arm-eabi': 4.53.1 + '@rollup/rollup-android-arm64': 4.53.1 + '@rollup/rollup-darwin-arm64': 4.53.1 + '@rollup/rollup-darwin-x64': 4.53.1 + '@rollup/rollup-freebsd-arm64': 4.53.1 + '@rollup/rollup-freebsd-x64': 4.53.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.53.1 + '@rollup/rollup-linux-arm-musleabihf': 4.53.1 + '@rollup/rollup-linux-arm64-gnu': 4.53.1 + '@rollup/rollup-linux-arm64-musl': 4.53.1 + '@rollup/rollup-linux-loong64-gnu': 4.53.1 + '@rollup/rollup-linux-ppc64-gnu': 4.53.1 + '@rollup/rollup-linux-riscv64-gnu': 4.53.1 + '@rollup/rollup-linux-riscv64-musl': 4.53.1 + '@rollup/rollup-linux-s390x-gnu': 4.53.1 + '@rollup/rollup-linux-x64-gnu': 4.53.1 + '@rollup/rollup-linux-x64-musl': 4.53.1 + '@rollup/rollup-openharmony-arm64': 4.53.1 + '@rollup/rollup-win32-arm64-msvc': 4.53.1 + '@rollup/rollup-win32-ia32-msvc': 4.53.1 + '@rollup/rollup-win32-x64-gnu': 4.53.1 + '@rollup/rollup-win32-x64-msvc': 4.53.1 fsevents: 2.3.3 router@2.2.0: @@ -18677,9 +18942,9 @@ snapshots: rxjs-report-usage@1.0.6: dependencies: - '@babel/parser': 7.28.4 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 bent: 7.3.12 chalk: 4.1.2 glob: 7.2.3 @@ -18691,9 +18956,9 @@ snapshots: dependencies: '@phenomnomnominal/tsquery': 4.2.0(typescript@5.9.2) decamelize: 4.0.0 - resolve: 1.22.10 + resolve: 1.22.11 rxjs-report-usage: 1.0.6 - semver: 7.7.2 + semver: 7.7.3 tslib: 2.8.1 tslint: 6.1.3(typescript@5.9.2) tsutils: 3.21.0(typescript@5.9.2) @@ -18739,17 +19004,17 @@ snapshots: safevalues@1.2.0: {} - sass-loader@16.0.5(sass@1.93.2)(webpack@5.102.1(esbuild@0.25.10)): + sass-loader@16.0.5(sass@1.93.2)(webpack@5.102.1(esbuild@0.26.0)): dependencies: neo-async: 2.6.2 optionalDependencies: sass: 1.93.2 - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) sass@1.93.2: dependencies: chokidar: 4.0.3 - immutable: 5.1.3 + immutable: 5.1.4 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.1 @@ -18760,7 +19025,7 @@ snapshots: transitivePeerDependencies: - supports-color - sax@1.4.1: {} + sax@1.4.3: {} schema-utils@4.3.3: dependencies: @@ -18791,8 +19056,6 @@ snapshots: semver@6.3.1: {} - semver@7.7.2: {} - semver@7.7.3: {} send@0.17.2: @@ -19066,7 +19329,7 @@ snapshots: socks@2.8.7: dependencies: - ip-address: 10.0.1 + ip-address: 10.1.0 smart-buffer: 4.2.0 sort-any@2.0.0: @@ -19075,11 +19338,11 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.102.1(esbuild@0.25.10)): + source-map-loader@5.0.0(webpack@5.102.1(esbuild@0.26.0)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) source-map-support@0.4.18: dependencies: @@ -19142,7 +19405,7 @@ snapshots: speedline-core@1.4.3: dependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 image-ssim: 0.2.0 jpeg-js: 0.4.4 @@ -19235,6 +19498,7 @@ snapshots: fast-fifo: 1.3.2 text-decoder: 1.2.3 transitivePeerDependencies: + - bare-abort-controller - react-native-b4a strict-event-emitter@0.5.1: {} @@ -19253,7 +19517,7 @@ snapshots: string-width@7.2.0: dependencies: - emoji-regex: 10.5.0 + emoji-regex: 10.6.0 get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 @@ -19332,7 +19596,11 @@ snapshots: strip-json-comments@3.1.1: {} - stubborn-fs@1.2.5: {} + stubborn-fs@2.0.0: + dependencies: + stubborn-utils: 1.0.2 + + stubborn-utils@1.0.2: {} stubs@3.0.0: {} @@ -19401,7 +19669,7 @@ snapshots: router: 2.2.0 update-notifier-cjs: 5.1.7(encoding@0.1.13) optionalDependencies: - re2: 1.22.1 + re2: 1.22.3 transitivePeerDependencies: - encoding - supports-color @@ -19460,9 +19728,10 @@ snapshots: pump: 3.0.3 tar-stream: 3.1.7 optionalDependencies: - bare-fs: 4.4.5 + bare-fs: 4.5.0 bare-path: 3.0.0 transitivePeerDependencies: + - bare-abort-controller - bare-buffer - react-native-b4a @@ -19472,6 +19741,7 @@ snapshots: fast-fifo: 1.3.2 streamx: 2.23.0 transitivePeerDependencies: + - bare-abort-controller - react-native-b4a tar@6.2.1: @@ -19483,7 +19753,7 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - tar@7.5.1: + tar@7.5.2: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -19522,16 +19792,16 @@ snapshots: dependencies: rimraf: 2.5.4 - terser-webpack-plugin@5.3.14(esbuild@0.25.10)(webpack@5.102.1(esbuild@0.25.10)): + terser-webpack-plugin@5.3.14(esbuild@0.26.0)(webpack@5.102.1(esbuild@0.26.0)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.44.0 - webpack: 5.102.1(esbuild@0.25.10) + terser: 5.44.1 + webpack: 5.102.1(esbuild@0.26.0) optionalDependencies: - esbuild: 0.25.10 + esbuild: 0.26.0 terser@5.44.0: dependencies: @@ -19540,6 +19810,13 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + terser@5.44.1: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + text-decoder@1.2.3: dependencies: b4a: 1.7.3 @@ -19562,6 +19839,8 @@ snapshots: third-party-web@0.27.0: {} + third-party-web@0.29.0: {} + through2@2.0.5: dependencies: readable-stream: 2.3.8 @@ -19633,14 +19912,14 @@ snapshots: trough@1.0.5: {} - ts-node@10.9.2(@types/node@22.18.8)(typescript@5.9.2): + ts-node@10.9.2(@types/node@22.19.0)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.18.8 + '@types/node': 22.19.0 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -19655,7 +19934,7 @@ snapshots: dependencies: '@bazel/bazelisk': 1.26.0 glob: 11.0.3 - minimatch: 10.0.3 + minimatch: 10.1.1 typescript: 5.9.2 tsickle@0.46.3(typescript@5.9.2): @@ -19678,7 +19957,7 @@ snapshots: js-yaml: 3.14.1 minimatch: 3.1.2 mkdirp: 0.5.6 - resolve: 1.22.10 + resolve: 1.22.11 semver: 5.7.2 tslib: 1.14.1 tsutils: 2.29.0(typescript@5.9.2) @@ -19688,7 +19967,7 @@ snapshots: tsutils-etc@1.4.2(tsutils@3.21.0(typescript@5.9.2))(typescript@5.9.2): dependencies: - '@types/yargs': 17.0.33 + '@types/yargs': 17.0.34 tsutils: 3.21.0(typescript@5.9.2) typescript: 5.9.2 yargs: 17.7.2 @@ -19705,8 +19984,8 @@ snapshots: tsx@4.20.6: dependencies: - esbuild: 0.25.10 - get-tsconfig: 4.10.1 + esbuild: 0.25.12 + get-tsconfig: 4.13.0 optionalDependencies: fsevents: 2.3.3 @@ -19812,12 +20091,14 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.14.0: {} + undici-types@7.16.0: {} undici@5.29.0: dependencies: '@fastify/busboy': 2.1.1 + undici@7.16.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-emoji-modifier-base@1.0.0: {} @@ -19879,9 +20160,9 @@ snapshots: unpipe@1.0.0: {} - update-browserslist-db@1.1.3(browserslist@4.26.3): + update-browserslist-db@1.1.4(browserslist@4.27.0): dependencies: - browserslist: 4.26.3 + browserslist: 4.27.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -19979,16 +20260,16 @@ snapshots: unist-util-stringify-position: 1.1.2 vfile-message: 1.1.1 - vite@7.1.10(@types/node@22.18.8)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): + vite@7.2.2(@types/node@22.19.0)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): dependencies: - esbuild: 0.25.10 + esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.4 + rollup: 4.53.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 22.18.8 + '@types/node': 22.19.0 fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.2 @@ -20026,7 +20307,7 @@ snapshots: web-vitals@4.2.4: {} - webdriver-bidi-protocol@0.3.6: {} + webdriver-bidi-protocol@0.3.8: {} webdriver-js-extender@2.1.0: dependencies: @@ -20049,25 +20330,25 @@ snapshots: webidl-conversions@3.0.1: {} - webpack-dev-middleware@7.4.5(webpack@5.102.1(esbuild@0.25.10)): + webpack-dev-middleware@7.4.5(webpack@5.102.1(esbuild@0.26.0)): dependencies: colorette: 2.0.20 - memfs: 4.48.1 + memfs: 4.50.0 mime-types: 3.0.1 on-finished: 2.4.1 range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) - webpack-dev-server@5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.25.10)): + webpack-dev-server@5.2.2(bufferutil@4.0.9)(webpack@5.102.1(esbuild@0.26.0)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 - '@types/express': 4.17.23 - '@types/express-serve-static-core': 4.19.6 + '@types/express': 4.17.25 + '@types/express-serve-static-core': 4.19.7 '@types/serve-index': 1.9.4 - '@types/serve-static': 1.15.9 + '@types/serve-static': 1.15.10 '@types/sockjs': 0.3.36 '@types/ws': 8.18.1 ansi-html-community: 0.0.8 @@ -20078,9 +20359,9 @@ snapshots: connect-history-api-fallback: 2.0.0 express: 4.21.2 graceful-fs: 4.2.11 - http-proxy-middleware: 2.0.9(@types/express@4.17.23) + http-proxy-middleware: 2.0.9(@types/express@4.17.25) ipaddr.js: 2.2.0 - launch-editor: 2.11.1 + launch-editor: 2.12.0 open: 10.2.0 p-retry: 6.2.1 schema-utils: 4.3.3 @@ -20088,10 +20369,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(webpack@5.102.1(esbuild@0.25.10)) + webpack-dev-middleware: 7.4.5(webpack@5.102.1(esbuild@0.26.0)) ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) optionalDependencies: - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) transitivePeerDependencies: - bufferutil - debug @@ -20106,12 +20387,12 @@ snapshots: webpack-sources@3.3.3: {} - webpack-subresource-integrity@5.1.0(webpack@5.102.1(esbuild@0.25.10)): + webpack-subresource-integrity@5.1.0(webpack@5.102.1(esbuild@0.26.0)): dependencies: typed-assert: 1.0.9 - webpack: 5.102.1(esbuild@0.25.10) + webpack: 5.102.1(esbuild@0.26.0) - webpack@5.102.1(esbuild@0.25.10): + webpack@5.102.1(esbuild@0.26.0): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -20121,7 +20402,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.14.1 acorn: 8.15.0 acorn-import-phases: 1.0.4(acorn@8.15.0) - browserslist: 4.26.3 + browserslist: 4.27.0 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 @@ -20130,12 +20411,12 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 + loader-runner: 4.3.1 mime-types: 2.1.35 neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.14(esbuild@0.25.10)(webpack@5.102.1(esbuild@0.25.10)) + terser-webpack-plugin: 5.3.14(esbuild@0.26.0)(webpack@5.102.1(esbuild@0.26.0)) watchpack: 2.4.4 webpack-sources: 3.3.3 transitivePeerDependencies: @@ -20158,7 +20439,7 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - when-exit@2.1.4: {} + when-exit@2.1.5: {} which-boxed-primitive@1.1.1: dependencies: @@ -20219,6 +20500,10 @@ snapshots: dependencies: isexe: 3.1.1 + which@6.0.0: + dependencies: + isexe: 3.1.1 + widest-line@3.1.0: dependencies: string-width: 4.2.3 @@ -20323,7 +20608,7 @@ snapshots: xml2js@0.4.23: dependencies: - sax: 1.4.1 + sax: 1.4.3 xmlbuilder: 11.0.1 xmlbuilder@11.0.1: {} @@ -20425,4 +20710,4 @@ snapshots: zone.js@0.15.1: {} - zx@8.8.4: {} + zx@8.8.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c70514a65a67..b693dab59b1d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -18,23 +18,23 @@ packages: - src/youtube-player catalog: - '@angular-devkit/build-angular': 21.0.0-next.8 - '@angular-devkit/core': 21.0.0-next.8 - '@angular-devkit/schematics': 21.0.0-next.8 - '@angular/build': 21.0.0-next.8 - '@angular/cli': 21.0.0-next.8 - '@angular/common': 21.0.0-next.8 - '@angular/compiler-cli': 21.0.0-next.8 - '@angular/compiler': 21.0.0-next.8 - '@angular/core': 21.0.0-next.8 - '@angular/ssr': 21.0.0-next.8 - '@angular/forms': 21.0.0-next.8 - '@angular/localize': 21.0.0-next.8 - '@angular/platform-browser': 21.0.0-next.8 - '@angular/platform-browser-dynamic': 21.0.0-next.8 - '@angular/platform-server': 21.0.0-next.8 - '@angular/router': 21.0.0-next.8 - '@schematics/angular': 21.0.0-next.8 + '@angular-devkit/build-angular': 21.0.1 + '@angular-devkit/core': 21.0.1 + '@angular-devkit/schematics': 21.0.1 + '@angular/build': 21.0.1 + '@angular/cli': 21.0.1 + '@angular/common': 21.0.1 + '@angular/compiler-cli': 21.0.1 + '@angular/compiler': 21.0.1 + '@angular/core': 21.0.1 + '@angular/ssr': 21.0.1 + '@angular/forms': 21.0.1 + '@angular/localize': 21.0.1 + '@angular/platform-browser': 21.0.1 + '@angular/platform-browser-dynamic': 21.0.1 + '@angular/platform-server': 21.0.1 + '@angular/router': 21.0.1 + '@schematics/angular': 21.0.1 'rxjs': ^6.6.7 # The minimum age of a release to be considered for dependency installation. diff --git a/scripts/deploy/publish-build-artifacts.sh b/scripts/deploy/publish-build-artifacts.sh index 770993e481a3..c8e4a1cb39a8 100755 --- a/scripts/deploy/publish-build-artifacts.sh +++ b/scripts/deploy/publish-build-artifacts.sh @@ -17,14 +17,14 @@ fi # Release packages that need to published as snapshots. PACKAGES=( - # aria TODO(crisbeto): enable this once there's a builds repo for ARIA + aria cdk cdk-experimental material material-experimental material-moment-adapter - # material-luxon-adapter TODO(crisbeto): enable this once we have a builds repo - # material-date-fns-adapter TODO(crisbeto): enable this once we have a builds repo + material-luxon-adapter + material-date-fns-adapter google-maps youtube-player ) diff --git a/src/aria/BUILD.bazel b/src/aria/BUILD.bazel index 39343a966e93..2c73d0293de5 100644 --- a/src/aria/BUILD.bazel +++ b/src/aria/BUILD.bazel @@ -1,3 +1,4 @@ +load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory") load("@npm//:defs.bzl", "npm_link_all_packages") load("//src/aria:config.bzl", "ARIA_TARGETS") load("//tools:defaults.bzl", "ng_package", "ts_project") @@ -19,9 +20,15 @@ ng_package( name = "npm_package", package_name = "@angular/aria", srcs = ["package.json"], + nested_packages = [ + ":adev_assets", + ], package_deps = [ ":node_modules/@angular/cdk", ], + replace_prefixes = { + "adev_assets/": "_adev_assets/", + }, tags = ["release-package"], visibility = [ "//:__pkg__", @@ -31,3 +38,20 @@ ng_package( ], deps = ARIA_TARGETS, ) + +copy_to_directory( + name = "adev_assets", + srcs = [ + "//src/aria/accordion:json_api", + "//src/aria/combobox:json_api", + "//src/aria/grid:json_api", + "//src/aria/listbox:json_api", + "//src/aria/menu:json_api", + "//src/aria/tabs:json_api", + "//src/aria/toolbar:json_api", + "//src/aria/tree:json_api", + ], + replace_prefixes = { + "**/": "", + }, +) diff --git a/src/aria/accordion/BUILD.bazel b/src/aria/accordion/BUILD.bazel index 2876dab751ed..b278ac8a3e7b 100644 --- a/src/aria/accordion/BUILD.bazel +++ b/src/aria/accordion/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite") +load("//tools/adev-api-extraction:extract_api_to_json.bzl", "extract_api_to_json") package(default_visibility = ["//visibility:public"]) @@ -10,8 +11,7 @@ ng_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/deferred-content", - "//src/aria/ui-patterns", + "//src/aria/private", "//src/cdk/a11y", "//src/cdk/bidi", ], @@ -35,3 +35,23 @@ ng_web_test_suite( name = "unit_tests", deps = [":unit_test_sources"], ) + +filegroup( + name = "source-files", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), +) + +extract_api_to_json( + name = "json_api", + srcs = [ + ":source-files", + ], + entry_point = ":index.ts", + module_name = "@angular/aria/accordion", + output_name = "aria-accordion.json", + private_modules = [""], + repo = "angular/components", +) diff --git a/src/aria/accordion/accordion.spec.ts b/src/aria/accordion/accordion.spec.ts index a462023a5db2..5470a56d0305 100644 --- a/src/aria/accordion/accordion.spec.ts +++ b/src/aria/accordion/accordion.spec.ts @@ -1,4 +1,4 @@ -import {Component, DebugElement, signal, model} from '@angular/core'; +import {Component, DebugElement, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; @@ -7,10 +7,8 @@ import {AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent} from describe('AccordionGroup', () => { let fixture: ComponentFixture; - let groupDebugElement: DebugElement; let triggerDebugElements: DebugElement[]; let panelDebugElements: DebugElement[]; - let groupInstance: AccordionGroup; let triggerElements: HTMLElement[]; let panelElements: HTMLElement[]; @@ -32,28 +30,25 @@ describe('AccordionGroup', () => { const endKey = (target: HTMLElement) => keydown(target, 'End'); interface SetupOptions { - initialValue?: string[]; multiExpandable?: boolean; disabledGroup?: boolean; + expandedItemValues?: string[]; disabledItemValues?: string[]; - skipDisabled?: boolean; + softDisabled?: boolean; wrap?: boolean; } function configureAccordionComponent(opts: SetupOptions = {}) { const testComponent = fixture.componentInstance as AccordionGroupExample; - if (opts.initialValue !== undefined) { - testComponent.value.set(opts.initialValue); - } if (opts.multiExpandable !== undefined) { testComponent.multiExpandable.set(opts.multiExpandable); } if (opts.disabledGroup !== undefined) { testComponent.disabledGroup.set(opts.disabledGroup); } - if (opts.skipDisabled !== undefined) { - testComponent.skipDisabled.set(opts.skipDisabled); + if (opts.softDisabled !== undefined) { + testComponent.softDisabled.set(opts.softDisabled); } if (opts.wrap !== undefined) { testComponent.wrap.set(opts.wrap); @@ -61,17 +56,18 @@ describe('AccordionGroup', () => { if (opts.disabledItemValues !== undefined) { opts.disabledItemValues.forEach(value => testComponent.disableItem(value, true)); } + if (opts.expandedItemValues !== undefined) { + opts.expandedItemValues.forEach(value => testComponent.expandItem(value, true)); + } fixture.detectChanges(); defineTestVariables(fixture); } function defineTestVariables(currentFixture: ComponentFixture) { - groupDebugElement = currentFixture.debugElement.query(By.directive(AccordionGroup)); triggerDebugElements = currentFixture.debugElement.queryAll(By.directive(AccordionTrigger)); panelDebugElements = currentFixture.debugElement.queryAll(By.directive(AccordionPanel)); - groupInstance = groupDebugElement.injector.get(AccordionGroup); triggerElements = triggerDebugElements.map(el => el.nativeElement); panelElements = panelDebugElements.map(el => el.nativeElement); } @@ -109,7 +105,7 @@ describe('AccordionGroup', () => { }); it('should have aria-expanded="false" when collapsed', () => { - configureAccordionComponent({initialValue: []}); + configureAccordionComponent(); expect(triggerElements[0].getAttribute('aria-expanded')).toBe('false'); expect(triggerElements[1].getAttribute('aria-expanded')).toBe('false'); expect(triggerElements[2].getAttribute('aria-expanded')).toBe('false'); @@ -154,7 +150,7 @@ describe('AccordionGroup', () => { }); it('should have "inert" attribute when collapsed', () => { - configureAccordionComponent({initialValue: []}); + configureAccordionComponent(); expect(panelElements[0].hasAttribute('inert')).toBeTrue(); expect(panelElements[1].hasAttribute('inert')).toBeTrue(); expect(panelElements[2].hasAttribute('inert')).toBeTrue(); @@ -168,36 +164,32 @@ describe('AccordionGroup', () => { configureAccordionComponent({multiExpandable: false}); }); - it('should expand panel on trigger click and update value', () => { + it('should expand panel on trigger click and update expanded panels', () => { click(triggerElements[0]); expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); expect(panelElements[0].hasAttribute('inert')).toBeFalse(); - expect(groupInstance.value()).toEqual(['item-1']); }); - it('should collapes panel on trigger click and update value', () => { + it('should collapes panel on trigger click and update expanded panels', () => { click(triggerElements[0]); click(triggerElements[0]); // Collapse expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); expect(panelElements[0].hasAttribute('inert')).toBeTrue(); - expect(groupInstance.value()).toEqual([]); }); it('should expand one and collapse others', () => { click(triggerElements[0]); expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); - expect(groupInstance.value()).toEqual(['item-1']); click(triggerElements[1]); expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); expect(panelElements[0].hasAttribute('inert')).toBeTrue(); expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); expect(panelElements[1].hasAttribute('inert')).toBeFalse(); - expect(groupInstance.value()).toEqual(['item-2']); }); it('should allow setting initial value', () => { - configureAccordionComponent({initialValue: ['item-2'], multiExpandable: false}); + configureAccordionComponent({expandedItemValues: ['item-2'], multiExpandable: false}); expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); expect(isTriggerExpanded(triggerElements[2])).toBeFalse(); @@ -221,16 +213,19 @@ describe('AccordionGroup', () => { it('should collapse an item without affecting others', () => { click(triggerElements[0]); click(triggerElements[1]); - expect(groupInstance.value()).toEqual(jasmine.arrayWithExactContents(['item-1', 'item-2'])); + expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); + expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); click(triggerElements[0]); expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); expect(isTriggerExpanded(triggerElements[1])).toBeTrue(); - expect(groupInstance.value()).toEqual(['item-2']); }); it('should allow setting initial multiple values', () => { - configureAccordionComponent({initialValue: ['item-1', 'item-3'], multiExpandable: true}); + configureAccordionComponent({ + expandedItemValues: ['item-1', 'item-3'], + multiExpandable: true, + }); expect(isTriggerExpanded(triggerElements[0])).toBeTrue(); expect(isTriggerExpanded(triggerElements[1])).toBeFalse(); expect(isTriggerExpanded(triggerElements[2])).toBeTrue(); @@ -242,7 +237,6 @@ describe('AccordionGroup', () => { configureAccordionComponent({disabledItemValues: ['item-1']}); click(triggerElements[0]); expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); - expect(groupInstance.value()).toEqual([]); expect(triggerElements[0].getAttribute('aria-disabled')).toBe('true'); }); @@ -250,7 +244,6 @@ describe('AccordionGroup', () => { configureAccordionComponent({disabledGroup: true}); click(triggerElements[0]); expect(isTriggerExpanded(triggerElements[0])).toBeFalse(); - expect(groupInstance.value()).toEqual([]); click(triggerElements[1]); expect(isTriggerExpanded(triggerElements[1])).toBeFalse(); }); @@ -342,17 +335,17 @@ describe('AccordionGroup', () => { }); }); - describe('skipDisabled behavior', () => { - it('should skip disabled items if skipDisabled=true', () => { - configureAccordionComponent({skipDisabled: true, disabledItemValues: ['item-2']}); + describe('softDisabled behavior', () => { + it('should skip disabled items if softDisabled=false', () => { + configureAccordionComponent({softDisabled: false, disabledItemValues: ['item-2']}); expect(isTriggerActive(triggerElements[0])).toBeTrue(); downArrowKey(triggerElements[0]); expect(isTriggerActive(triggerElements[2])).toBeTrue(); }); - it('should focus disabled items if skipDisabled=false', () => { - configureAccordionComponent({skipDisabled: false, disabledItemValues: ['item-2']}); + it('should focus disabled items if softDisabled=true', () => { + configureAccordionComponent({softDisabled: true, disabledItemValues: ['item-2']}); expect(isTriggerActive(triggerElements[0])).toBeTrue(); downArrowKey(triggerElements[0]); @@ -382,22 +375,22 @@ describe('AccordionGroup', () => { template: `
- @for (item of items(); track item.value) { + @for (item of items(); track item.panelId) {
{{ item.content }} @@ -411,20 +404,43 @@ describe('AccordionGroup', () => { }) class AccordionGroupExample { items = signal([ - {value: 'item-1', header: 'Item 1 Header', content: 'Item 1 Content', disabled: false}, - {value: 'item-2', header: 'Item 2 Header', content: 'Item 2 Content', disabled: false}, - {value: 'item-3', header: 'Item 3 Header', content: 'Item 3 Content', disabled: false}, + { + panelId: 'item-1', + header: 'Item 1 Header', + content: 'Item 1 Content', + disabled: false, + expanded: false, + }, + { + panelId: 'item-2', + header: 'Item 2 Header', + content: 'Item 2 Content', + disabled: false, + expanded: false, + }, + { + panelId: 'item-3', + header: 'Item 3 Header', + content: 'Item 3 Content', + disabled: false, + expanded: false, + }, ]); - value = model([]); multiExpandable = signal(false); disabledGroup = signal(false); - skipDisabled = signal(true); + softDisabled = signal(true); wrap = signal(false); disableItem(itemValue: string, disabled: boolean) { this.items.update(items => - items.map(item => (item.value === itemValue ? {...item, disabled} : item)), + items.map(item => (item.panelId === itemValue ? {...item, disabled} : item)), + ); + } + + expandItem(itemValue: string, expanded: boolean) { + this.items.update(items => + items.map(item => (item.panelId === itemValue ? {...item, expanded} : item)), ); } } diff --git a/src/aria/accordion/accordion.ts b/src/aria/accordion/accordion.ts index 504c430a957f..3c873ffc95c1 100644 --- a/src/aria/accordion/accordion.ts +++ b/src/aria/accordion/accordion.ts @@ -21,16 +21,33 @@ import { } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; -import {DeferredContent, DeferredContentAware} from '@angular/aria/deferred-content'; import { + DeferredContent, + DeferredContentAware, AccordionGroupPattern, AccordionPanelPattern, AccordionTriggerPattern, -} from '@angular/aria/ui-patterns'; +} from '@angular/aria/private'; /** - * Represents the content panel of an accordion item. It is controlled by an - * associated `AccordionTrigger`. + * The content panel of an accordion item that is conditionally visible. + * + * This directive is a container for the content that is shown or hidden. It requires + * a `panelId` that must match the `panelId` of its corresponding `ngAccordionTrigger`. + * The content within the panel should be provided using an `ng-template` with the + * `ngAccordionContent` directive so that the content is not rendered on the page until the trigger + * is expanded. It applies `role="region"` for accessibility and uses the `inert` attribute to hide + * its content from assistive technologies when not visible. + * + * ```html + *
+ * + *

This content is lazily rendered and will be shown when the panel is expanded.

+ *
+ *
+ * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngAccordionPanel]', @@ -42,11 +59,10 @@ import { }, ], host: { - 'class': 'ng-accordion-panel', 'role': 'region', - '[attr.id]': 'pattern.id()', - '[attr.aria-labelledby]': 'pattern.accordionTrigger()?.id()', - '[attr.inert]': 'pattern.hidden() ? true : null', + '[attr.id]': '_pattern.id()', + '[attr.aria-labelledby]': '_pattern.accordionTrigger()?.id()', + '[attr.inert]': '!visible() ? true : null', }, }) export class AccordionPanel { @@ -54,139 +70,219 @@ export class AccordionPanel { private readonly _deferredContentAware = inject(DeferredContentAware); /** A global unique identifier for the panel. */ - private readonly _id = inject(_IdGenerator).getId('accordion-trigger-'); + readonly id = input(inject(_IdGenerator).getId('ng-accordion-panel-', true)); + + /** A local unique identifier for the panel, used to match with its trigger's `panelId`. */ + readonly panelId = input.required(); - /** A local unique identifier for the panel, used to match with its trigger's value. */ - value = input.required(); + /** Whether the accordion panel is visible. True if the associated trigger is expanded. */ + readonly visible = computed(() => !this._pattern.hidden()); /** The parent accordion trigger pattern that controls this panel. This is set by AccordionGroup. */ - readonly accordionTrigger: WritableSignal = + readonly _accordionTriggerPattern: WritableSignal = signal(undefined); /** The UI pattern instance for this panel. */ - readonly pattern: AccordionPanelPattern = new AccordionPanelPattern({ - id: () => this._id, - value: this.value, - accordionTrigger: () => this.accordionTrigger(), + readonly _pattern: AccordionPanelPattern = new AccordionPanelPattern({ + id: this.id, + panelId: this.panelId, + accordionTrigger: () => this._accordionTriggerPattern(), }); constructor() { // Connect the panel's hidden state to the DeferredContentAware's visibility. afterRenderEffect(() => { - this._deferredContentAware.contentVisible.set(!this.pattern.hidden()); + this._deferredContentAware.contentVisible.set(this.visible()); }); } + + /** Expands this item. */ + expand() { + this._accordionTriggerPattern()?.open(); + } + + /** Collapses this item. */ + collapse() { + this._accordionTriggerPattern()?.close(); + } + + /** Toggles the expansion state of this item. */ + toggle() { + this._accordionTriggerPattern()?.toggle(); + } } /** - * Represents the trigger button for an accordion item. It controls the expansion - * state of an associated `AccordionPanel`. + * The trigger that toggles the visibility of its associated `ngAccordionPanel`. + * + * This directive requires a `panelId` that must match the `panelId` of the `ngAccordionPanel` it + * controls. When clicked, it will expand or collapse the panel. It also handles keyboard + * interactions for navigation within the `ngAccordionGroup`. It applies `role="button"` and manages + * `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility. + * The `disabled` input can be used to disable the trigger. + * + * ```html + * + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngAccordionTrigger]', exportAs: 'ngAccordionTrigger', host: { - 'class': 'ng-accordion-trigger', - '[attr.data-active]': 'pattern.active()', + '[attr.data-active]': 'active()', 'role': 'button', - '[id]': 'pattern.id()', - '[attr.aria-expanded]': 'pattern.expanded()', - '[attr.aria-controls]': 'pattern.controls()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.disabled]': 'hardDisabled() ? true : null', - '[attr.tabindex]': 'pattern.tabindex()', - '(keydown)': 'pattern.onKeydown($event)', - '(pointerdown)': 'pattern.onPointerdown($event)', - '(focusin)': 'pattern.onFocus($event)', + '[id]': '_pattern.id()', + '[attr.aria-expanded]': 'expanded()', + '[attr.aria-controls]': '_pattern.controls()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.disabled]': '_pattern.hardDisabled() ? true : null', + '[attr.tabindex]': '_pattern.tabIndex()', }, }) export class AccordionTrigger { - /** A global unique identifier for the trigger. */ - private readonly _id = inject(_IdGenerator).getId('ng-accordion-trigger-'); - /** A reference to the trigger element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the trigger element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The parent AccordionGroup. */ private readonly _accordionGroup = inject(AccordionGroup); - /** A local unique identifier for the trigger, used to match with its panel's value. */ - value = input.required(); + /** A unique identifier for the widget. */ + readonly id = input(inject(_IdGenerator).getId('ng-accordion-trigger-', true)); + + /** A local unique identifier for the trigger, used to match with its panel's `panelId`. */ + readonly panelId = input.required(); /** Whether the trigger is disabled. */ - disabled = input(false, {transform: booleanAttribute}); + readonly disabled = input(false, {transform: booleanAttribute}); - /** - * Whether this trigger is completely inaccessible. - * - * TODO(ok7sai): Consider move this to UI patterns. - */ - readonly hardDisabled = computed(() => this.pattern.disabled() && this.pattern.tabindex() < 0); + /** Whether the corresponding panel is expanded. */ + readonly expanded = model(false); + + /** Whether the trigger is active. */ + readonly active = computed(() => this._pattern.active()); /** The accordion panel pattern controlled by this trigger. This is set by AccordionGroup. */ - readonly accordionPanel: WritableSignal = signal(undefined); + readonly _accordionPanelPattern: WritableSignal = + signal(undefined); /** The UI pattern instance for this trigger. */ - readonly pattern: AccordionTriggerPattern = new AccordionTriggerPattern({ - id: () => this._id, - value: this.value, - disabled: this.disabled, - element: () => this._elementRef.nativeElement, - accordionGroup: computed(() => this._accordionGroup.pattern), - accordionPanel: this.accordionPanel, + readonly _pattern: AccordionTriggerPattern = new AccordionTriggerPattern({ + ...this, + accordionGroup: computed(() => this._accordionGroup._pattern), + accordionPanel: this._accordionPanelPattern, + element: () => this.element, }); + + /** Expands this item. */ + expand() { + this._pattern.open(); + } + + /** Collapses this item. */ + collapse() { + this._pattern.close(); + } + + /** Toggles the expansion state of this item. */ + toggle() { + this._pattern.toggle(); + } } /** - * Container for a group of accordion items. It manages the overall state and + * A container for a group of accordion items. It manages the overall state and * interactions of the accordion, such as keyboard navigation and expansion mode. + * + * The `ngAccordionGroup` serves as the root of a group of accordion triggers and panels, + * coordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it. + * It supports both single and multiple expansion modes. + * + * ```html + *
+ *
+ *

+ * + *

+ *
+ * + *

Content for Item 1.

+ *
+ *
+ *
+ *
+ *

+ * + *

+ *
+ * + *

Content for Item 2.

+ *
+ *
+ *
+ *
+ * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngAccordionGroup]', exportAs: 'ngAccordionGroup', host: { - 'class': 'ng-accordion-group', + '(keydown)': '_pattern.onKeydown($event)', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(focusin)': '_pattern.onFocus($event)', }, }) export class AccordionGroup { /** A reference to the group element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the group element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The AccordionTriggers nested inside this group. */ - protected readonly _triggers = contentChildren(AccordionTrigger, {descendants: true}); + private readonly _triggers = contentChildren(AccordionTrigger, {descendants: true}); + + /** The AccordionTrigger patterns nested inside this group. */ + private readonly _triggerPatterns = computed(() => this._triggers().map(t => t._pattern)); /** The AccordionPanels nested inside this group. */ - protected readonly _panels = contentChildren(AccordionPanel, {descendants: true}); + private readonly _panels = contentChildren(AccordionPanel, {descendants: true}); /** The text direction (ltr or rtl). */ readonly textDirection = inject(Directionality).valueSignal; /** Whether the entire accordion group is disabled. */ - disabled = input(false, {transform: booleanAttribute}); + readonly disabled = input(false, {transform: booleanAttribute}); /** Whether multiple accordion items can be expanded simultaneously. */ - multiExpandable = input(true, {transform: booleanAttribute}); - - /** The values of the current selected/expanded accordions. */ - value = model([]); + readonly multiExpandable = input(true, {transform: booleanAttribute}); - /** Whether disabled items should be skipped during keyboard navigation. */ - skipDisabled = input(true, {transform: booleanAttribute}); + /** + * Whether to allow disabled items to receive focus. When `true`, disabled items are + * focusable but not interactive. When `false`, disabled items are skipped during navigation. + */ + readonly softDisabled = input(true, {transform: booleanAttribute}); /** Whether keyboard navigation should wrap around from the last item to the first, and vice-versa. */ - wrap = input(false, {transform: booleanAttribute}); + readonly wrap = input(false, {transform: booleanAttribute}); /** The UI pattern instance for this accordion group. */ - readonly pattern: AccordionGroupPattern = new AccordionGroupPattern({ + readonly _pattern: AccordionGroupPattern = new AccordionGroupPattern({ ...this, - // TODO(ok7sai): Consider making `activeItem` an internal state in the pattern and call - // `setDefaultState` in the CDK. activeItem: signal(undefined), - items: computed(() => this._triggers().map(trigger => trigger.pattern)), - expandedIds: this.value, + items: this._triggerPatterns, // TODO(ok7sai): Investigate whether an accordion should support horizontal mode. orientation: () => 'vertical', - element: () => this._elementRef.nativeElement, + getItem: e => this._getItem(e), + element: () => this.element, }); constructor() { @@ -196,19 +292,59 @@ export class AccordionGroup { const panels = this._panels(); for (const trigger of triggers) { - const panel = panels.find(p => p.value() === trigger.value()); - trigger.accordionPanel.set(panel?.pattern); + const panel = panels.find(p => p.panelId() === trigger.panelId()); + trigger._accordionPanelPattern.set(panel?._pattern); if (panel) { - panel.accordionTrigger.set(trigger.pattern); + panel._accordionTriggerPattern.set(trigger._pattern); } } }); } + + /** Expands all accordion panels if multi-expandable. */ + expandAll() { + this._pattern.expansionBehavior.openAll(); + } + + /** Collapses all accordion panels. */ + collapseAll() { + this._pattern.expansionBehavior.closeAll(); + } + + /** Gets the trigger pattern for a given element. */ + private _getItem(element: Element | null | undefined): AccordionTriggerPattern | undefined { + let target = element; + + while (target) { + const pattern = this._triggerPatterns().find(t => t.element() === target); + if (pattern) { + return pattern; + } + + target = target.parentElement?.closest('[ngAccordionTrigger]'); + } + + return undefined; + } } /** - * A structural directive that marks the `ng-template` to be used as the content - * for a `AccordionPanel`. This content can be lazily loaded. + * A structural directive that provides a mechanism for lazily rendering the content for an + * `ngAccordionPanel`. + * + * This directive should be applied to an `ng-template` inside an `ngAccordionPanel`. + * It allows the content of the panel to be lazily rendered, improving performance + * by only creating the content when the panel is first expanded. + * + * ```html + *
+ * + *

This is the content that will be displayed inside the panel.

+ *
+ *
+ * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: 'ng-template[ngAccordionContent]', diff --git a/src/aria/combobox/BUILD.bazel b/src/aria/combobox/BUILD.bazel index 410b5174c435..f3b4c41a0cce 100644 --- a/src/aria/combobox/BUILD.bazel +++ b/src/aria/combobox/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") +load("//tools/adev-api-extraction:extract_api_to_json.bzl", "extract_api_to_json") package(default_visibility = ["//visibility:public"]) @@ -10,8 +11,7 @@ ng_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/deferred-content", - "//src/aria/ui-patterns", + "//src/aria/private", "//src/cdk/bidi", ], ) @@ -39,3 +39,23 @@ ng_web_test_suite( name = "unit_tests", deps = [":unit_test_sources"], ) + +filegroup( + name = "source-files", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), +) + +extract_api_to_json( + name = "json_api", + srcs = [ + ":source-files", + ], + entry_point = ":index.ts", + module_name = "@angular/aria/combobox", + output_name = "aria-combobox.json", + private_modules = [""], + repo = "angular/components", +) diff --git a/src/aria/combobox/combobox.spec.ts b/src/aria/combobox/combobox.spec.ts index 4eae17061f5e..0cbeca485a38 100644 --- a/src/aria/combobox/combobox.spec.ts +++ b/src/aria/combobox/combobox.spec.ts @@ -1,5 +1,5 @@ import {Component, computed, DebugElement, signal} from '@angular/core'; -import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Combobox, ComboboxInput, ComboboxPopup, ComboboxPopupContainer} from '../combobox'; import {Listbox, Option} from '../listbox'; @@ -33,7 +33,7 @@ describe('Combobox', () => { const click = (element: HTMLElement, eventInit?: PointerEventInit) => { focus(); - element.dispatchEvent(new PointerEvent('pointerup', {bubbles: true, ...eventInit})); + element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); fixture.detectChanges(); }; @@ -52,14 +52,18 @@ describe('Combobox', () => { const enter = (modifierKeys?: {}) => keydown('Enter', modifierKeys); const escape = (modifierKeys?: {}) => keydown('Escape', modifierKeys); - function setupCombobox(opts: {filterMode?: 'manual' | 'auto-select' | 'highlight'} = {}) { - TestBed.configureTestingModule({}); + function setupCombobox( + opts: {readonly?: boolean; filterMode?: 'manual' | 'auto-select' | 'highlight'} = {}, + ) { fixture = TestBed.createComponent(ComboboxListboxExample); const testComponent = fixture.componentInstance; if (opts.filterMode) { testComponent.filterMode.set(opts.filterMode); } + if (opts.readonly) { + testComponent.readonly.set(true); + } fixture.detectChanges(); defineTestVariables(); @@ -206,12 +210,6 @@ describe('Combobox', () => { describe('Expansion', () => { beforeEach(() => setupCombobox()); - it('should open on click', () => { - focus(); - click(inputElement); - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); - }); - it('should open on ArrowDown', () => { focus(); keydown('ArrowDown'); @@ -242,17 +240,19 @@ describe('Combobox', () => { expect(inputElement.getAttribute('aria-expanded')).toBe('true'); }); - it('should clear the completion string and not close on escape when a completion is present', () => { + it('should close then clear the completion string', () => { fixture.componentInstance.filterMode.set('highlight'); focus(); - input('A'); + input('Ala'); expect(inputElement.value).toBe('Alabama'); expect(inputElement.getAttribute('aria-expanded')).toBe('true'); escape(); - expect(inputElement.value).toBe('A'); - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + expect(inputElement.value).toBe('Alabama'); + expect(inputElement.selectionEnd).toBe(7); + expect(inputElement.selectionStart).toBe(3); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); // close escape(); - expect(inputElement.value).toBe('A'); + expect(inputElement.value).toBe(''); // clear input expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); @@ -280,7 +280,7 @@ describe('Combobox', () => { click(options[0]); fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['Alabama']); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); expect(inputElement.value).toBe('Alabama'); }); @@ -289,7 +289,7 @@ describe('Combobox', () => { down(); enter(); - expect(fixture.componentInstance.value()).toEqual(['Alabama']); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); expect(inputElement.value).toBe('Alabama'); }); @@ -297,7 +297,7 @@ describe('Combobox', () => { down(); down(); - expect(fixture.componentInstance.value()).toEqual([]); + expect(fixture.componentInstance.values()).toEqual([]); }); it('should select on focusout if the input text exactly matches an item', () => { @@ -305,7 +305,7 @@ describe('Combobox', () => { input('Alabama'); blur(); - expect(fixture.componentInstance.value()).toEqual(['Alabama']); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); }); it('should not select on focusout if the input text does not match an item', () => { @@ -313,7 +313,7 @@ describe('Combobox', () => { input('Appl'); blur(); - expect(fixture.componentInstance.value()).toEqual([]); + expect(fixture.componentInstance.values()).toEqual([]); expect(inputElement.value).toBe('Appl'); }); }); @@ -327,7 +327,7 @@ describe('Combobox', () => { click(options[1]); fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['Alaska']); + expect(fixture.componentInstance.values()).toEqual(['Alaska']); expect(inputElement.value).toBe('Alaska'); }); @@ -336,26 +336,26 @@ describe('Combobox', () => { down(); enter(); - expect(fixture.componentInstance.value()).toEqual(['Alaska']); + expect(fixture.componentInstance.values()).toEqual(['Alaska']); expect(inputElement.value).toBe('Alaska'); }); it('should select on navigation', () => { down(); - expect(fixture.componentInstance.value()).toEqual(['Alabama']); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); down(); - expect(fixture.componentInstance.value()).toEqual(['Alaska']); + expect(fixture.componentInstance.values()).toEqual(['Alaska']); down(); - expect(fixture.componentInstance.value()).toEqual(['Arizona']); + expect(fixture.componentInstance.values()).toEqual(['Arizona']); }); it('should select the first option on input', () => { focus(); input('W'); - expect(fixture.componentInstance.value()).toEqual(['Washington']); + expect(fixture.componentInstance.values()).toEqual(['Washington']); }); it('should commit the selected option on focusout', () => { @@ -364,7 +364,7 @@ describe('Combobox', () => { blur(); expect(inputElement.value).toBe('Georgia'); - expect(fixture.componentInstance.value()).toEqual(['Georgia']); + expect(fixture.componentInstance.values()).toEqual(['Georgia']); }); }); @@ -377,7 +377,7 @@ describe('Combobox', () => { click(options[2]); fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['Arizona']); + expect(fixture.componentInstance.values()).toEqual(['Arizona']); expect(inputElement.value).toBe('Arizona'); }); @@ -387,16 +387,16 @@ describe('Combobox', () => { down(); enter(); - expect(fixture.componentInstance.value()).toEqual(['Arizona']); + expect(fixture.componentInstance.values()).toEqual(['Arizona']); expect(inputElement.value).toBe('Arizona'); }); it('should select on navigation', () => { down(); - expect(fixture.componentInstance.value()).toEqual(['Alabama']); + expect(fixture.componentInstance.values()).toEqual(['Alabama']); down(); - expect(fixture.componentInstance.value()).toEqual(['Alaska']); + expect(fixture.componentInstance.values()).toEqual(['Alaska']); }); it('should update input value on navigation', () => { @@ -411,18 +411,36 @@ describe('Combobox', () => { focus(); input('Cali'); - expect(fixture.componentInstance.value()).toEqual(['California']); + expect(fixture.componentInstance.values()).toEqual(['California']); }); - it('should insert a highlighted completion string on input', fakeAsync(() => { + it('should insert a highlighted completion string on input', () => { focus(); input('A'); - tick(); expect(inputElement.value).toBe('Alabama'); expect(inputElement.selectionStart).toBe(1); expect(inputElement.selectionEnd).toBe(7); - })); + }); + + it('should not insert a completion string on backspace', () => { + focus(); + input('New'); + + expect(inputElement.value).toBe('New Hampshire'); + expect(inputElement.selectionStart).toBe(3); + expect(inputElement.selectionEnd).toBe(13); + }); + + it('should insert a completion string even if the items are not changed', () => { + focus(); + input('New'); + + input('New '); + expect(inputElement.value).toBe('New Hampshire'); + expect(inputElement.selectionStart).toBe(4); + expect(inputElement.selectionEnd).toBe(13); + }); it('should commit the selected option on focusout', () => { focus(); @@ -430,7 +448,7 @@ describe('Combobox', () => { blur(); expect(inputElement.value).toBe('California'); - expect(fixture.componentInstance.value()).toEqual(['California']); + expect(fixture.componentInstance.values()).toEqual(['California']); }); }); }); @@ -438,15 +456,15 @@ describe('Combobox', () => { // TODO(wagnermaciel): Add unit tests for disabled options. describe('Filtering', () => { - beforeEach(() => setupCombobox()); - it('should lazily render options', () => { + setupCombobox(); expect(getOptions().length).toBe(0); focus(); expect(getOptions().length).toBe(50); }); it('should filter the options based on the input value', () => { + setupCombobox(); focus(); input('New'); @@ -459,6 +477,7 @@ describe('Combobox', () => { }); it('should show no options if nothing matches', () => { + setupCombobox(); focus(); input('xyz'); const options = getOptions(); @@ -466,6 +485,7 @@ describe('Combobox', () => { }); it('should show all options when the input is cleared', () => { + setupCombobox(); focus(); input('Alabama'); expect(getOptions().length).toBe(1); @@ -473,6 +493,51 @@ describe('Combobox', () => { input(''); expect(getOptions().length).toBe(50); }); + + it('should determine the highlighted state on open', () => { + setupCombobox({filterMode: 'highlight'}); + focus(); + input('N'); + expect(inputElement.value).toBe('Nebraska'); + expect(inputElement.selectionEnd).toBe(8); + expect(inputElement.selectionStart).toBe(1); + expect(getOptions().length).toBe(8); + + escape(); // close + inputElement.selectionStart = 2; // Change highlighting + down(); // open + + expect(inputElement.value).toBe('Nebraska'); + expect(inputElement.selectionEnd).toBe(8); + expect(inputElement.selectionStart).toBe(2); + expect(getOptions().length).toBe(6); + + escape(); // close + inputElement.selectionStart = 3; // Change highlighting + down(); // open + + expect(getOptions().length).toBe(1); + }); + }); + + describe('Readonly', () => { + beforeEach(() => setupCombobox({readonly: true})); + + it('should close on selection', () => { + focus(); + down(); + click(getOption('Alabama')!); + expect(inputElement.value).toBe('Alabama'); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); + + it('should close on escape', () => { + focus(); + down(); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + escape(); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); }); // describe('with programmatic value changes', () => { @@ -483,7 +548,7 @@ describe('Combobox', () => { // focus(); // fixture.componentInstance.value.set(['Banana']); // fixture.detectChanges(); - // expect(fixture.componentInstance.value()).toEqual(['Banana']); + // expect(fixture.componentInstance.values()).toEqual(['Banana']); // const bananaOption = getOption('Banana')!; // expect(bananaOption.getAttribute('aria-selected')).toBe('true'); // }); @@ -518,7 +583,7 @@ describe('Combobox', () => { const click = (element: HTMLElement, eventInit?: PointerEventInit) => { focus(); - element.dispatchEvent(new PointerEvent('pointerup', {bubbles: true, ...eventInit})); + element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); fixture.detectChanges(); }; @@ -539,14 +604,18 @@ describe('Combobox', () => { const enter = (modifierKeys?: {}) => keydown('Enter', modifierKeys); const escape = (modifierKeys?: {}) => keydown('Escape', modifierKeys); - function setupCombobox(opts: {filterMode?: 'manual' | 'auto-select' | 'highlight'} = {}) { - TestBed.configureTestingModule({}); + function setupCombobox( + opts: {readonly?: boolean; filterMode?: 'manual' | 'auto-select' | 'highlight'} = {}, + ) { fixture = TestBed.createComponent(ComboboxTreeExample); const testComponent = fixture.componentInstance; if (opts.filterMode) { testComponent.filterMode.set(opts.filterMode); } + if (opts.readonly) { + testComponent.readonly.set(true); + } fixture.detectChanges(); defineTestVariables(); @@ -715,7 +784,7 @@ describe('Combobox', () => { click(item); fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['April']); + expect(fixture.componentInstance.values()).toEqual(['April']); expect(inputElement.value).toBe('April'); }); @@ -723,7 +792,7 @@ describe('Combobox', () => { down(); enter(); - expect(fixture.componentInstance.value()).toEqual(['Winter']); + expect(fixture.componentInstance.values()).toEqual(['Winter']); expect(inputElement.value).toBe('Winter'); }); @@ -732,14 +801,14 @@ describe('Combobox', () => { input('November'); blur(); - expect(fixture.componentInstance.value()).toEqual(['November']); + expect(fixture.componentInstance.values()).toEqual(['November']); }); it('should not select on navigation', () => { down(); down(); - expect(fixture.componentInstance.value()).toEqual([]); + expect(fixture.componentInstance.values()).toEqual([]); }); it('should not select on focusout if the input text does not match an item', () => { @@ -747,7 +816,7 @@ describe('Combobox', () => { input('Appl'); blur(); - expect(fixture.componentInstance.value()).toEqual([]); + expect(fixture.componentInstance.values()).toEqual([]); expect(inputElement.value).toBe('Appl'); }); }); @@ -763,7 +832,7 @@ describe('Combobox', () => { click(item); fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['February']); + expect(fixture.componentInstance.values()).toEqual(['February']); expect(inputElement.value).toBe('February'); }); @@ -772,22 +841,22 @@ describe('Combobox', () => { down(); enter(); - expect(fixture.componentInstance.value()).toEqual(['Spring']); + expect(fixture.componentInstance.values()).toEqual(['Spring']); expect(inputElement.value).toBe('Spring'); }); it('should select on navigation', () => { down(); - expect(fixture.componentInstance.value()).toEqual(['Winter']); + expect(fixture.componentInstance.values()).toEqual(['Winter']); down(); - expect(fixture.componentInstance.value()).toEqual(['Spring']); + expect(fixture.componentInstance.values()).toEqual(['Spring']); }); it('should select the first option on input', () => { focus(); input('Dec'); - expect(fixture.componentInstance.value()).toEqual(['December']); + expect(fixture.componentInstance.values()).toEqual(['December']); }); it('should commit the selected option on focusout', () => { @@ -796,7 +865,7 @@ describe('Combobox', () => { blur(); expect(inputElement.value).toBe('June'); - expect(fixture.componentInstance.value()).toEqual(['June']); + expect(fixture.componentInstance.values()).toEqual(['June']); }); }); @@ -811,7 +880,7 @@ describe('Combobox', () => { click(item); fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['February']); + expect(fixture.componentInstance.values()).toEqual(['February']); expect(inputElement.value).toBe('February'); }); @@ -820,16 +889,16 @@ describe('Combobox', () => { down(); enter(); - expect(fixture.componentInstance.value()).toEqual(['Spring']); + expect(fixture.componentInstance.values()).toEqual(['Spring']); expect(inputElement.value).toBe('Spring'); }); it('should select on navigation', () => { down(); - expect(fixture.componentInstance.value()).toEqual(['Winter']); + expect(fixture.componentInstance.values()).toEqual(['Winter']); down(); - expect(fixture.componentInstance.value()).toEqual(['Spring']); + expect(fixture.componentInstance.values()).toEqual(['Spring']); }); it('should update input value on navigation', () => { @@ -844,18 +913,17 @@ describe('Combobox', () => { focus(); input('Sept'); - expect(fixture.componentInstance.value()).toEqual(['September']); + expect(fixture.componentInstance.values()).toEqual(['September']); }); - it('should insert a highlighted completion string on input', fakeAsync(() => { + it('should insert a highlighted completion string on input', () => { focus(); input('Feb'); - tick(); expect(inputElement.value).toBe('February'); expect(inputElement.selectionStart).toBe(3); expect(inputElement.selectionEnd).toBe(8); - })); + }); it('should commit the selected option on focusout', () => { focus(); @@ -863,7 +931,7 @@ describe('Combobox', () => { blur(); expect(inputElement.value).toBe('January'); - expect(fixture.componentInstance.value()).toEqual(['January']); + expect(fixture.componentInstance.values()).toEqual(['January']); }); }); }); @@ -871,12 +939,6 @@ describe('Combobox', () => { describe('Expansion', () => { beforeEach(() => setupCombobox()); - it('should open on click', () => { - focus(); - click(inputElement); - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); - }); - it('should open on ArrowDown', () => { focus(); keydown('ArrowDown'); @@ -907,17 +969,17 @@ describe('Combobox', () => { expect(inputElement.getAttribute('aria-expanded')).toBe('true'); }); - it('should clear the completion string and not close on escape when a completion is present', () => { + it('should close then clear the completion string', () => { fixture.componentInstance.filterMode.set('highlight'); focus(); input('Mar'); expect(inputElement.value).toBe('March'); expect(inputElement.getAttribute('aria-expanded')).toBe('true'); escape(); - expect(inputElement.value).toBe('Mar'); - expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + expect(inputElement.value).toBe('March'); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); // close escape(); - expect(inputElement.value).toBe('Mar'); + expect(inputElement.value).toBe(''); // clear input expect(inputElement.getAttribute('aria-expanded')).toBe('false'); }); @@ -996,12 +1058,34 @@ describe('Combobox', () => { it('should update the selected item when the value is set programmatically', () => { setupCombobox(); focus(); - fixture.componentInstance.value.set(['August']); + fixture.componentInstance.values.set(['August']); fixture.detectChanges(); - expect(fixture.componentInstance.value()).toEqual(['August']); + expect(fixture.componentInstance.values()).toEqual(['August']); expect(getTreeItem('August')!.getAttribute('aria-selected')).toBe('true'); }); }); + + describe('Readonly', () => { + beforeEach(() => setupCombobox({readonly: true})); + + it('should close on selection', () => { + focus(); + down(); + right(); + right(); + enter(); + expect(inputElement.value).toBe('December'); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); + + it('should close on escape', () => { + focus(); + down(); + expect(inputElement.getAttribute('aria-expanded')).toBe('true'); + escape(); + expect(inputElement.getAttribute('aria-expanded')).toBe('false'); + }); + }); }); }); @@ -1010,6 +1094,7 @@ describe('Combobox', () => {
{ /> -
+
@for (option of options(); track option) {
{ imports: [Combobox, ComboboxInput, ComboboxPopup, ComboboxPopupContainer, Listbox, Option], }) class ComboboxListboxExample { - value = signal([]); - - filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual'); - + readonly = signal(false); searchString = signal(''); + values = signal([]); + filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual'); options = computed(() => states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), @@ -1052,6 +1136,7 @@ class ComboboxListboxExample {
@@ -1062,7 +1147,7 @@ class ComboboxListboxExample { /> -
    +
      ([]); - - filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual'); - + readonly = signal(false); searchString = signal(''); - + values = signal([]); nodes = computed(() => this.filterTreeNodes(TREE_NODES)); + filterMode = signal<'manual' | 'auto-select' | 'highlight'>('manual'); firstMatch = computed(() => { const flatNodes = this.flattenTreeNodes(this.nodes()); @@ -1141,12 +1224,12 @@ class ComboboxTreeExample { } } -export interface TreeNode { +interface TreeNode { name: string; children?: TreeNode[]; } -export const TREE_NODES = [ +const TREE_NODES = [ { name: 'Winter', children: [{name: 'December'}, {name: 'January'}, {name: 'February'}], diff --git a/src/aria/combobox/combobox.ts b/src/aria/combobox/combobox.ts index 2c0af9581db7..de7ec30ec647 100644 --- a/src/aria/combobox/combobox.ts +++ b/src/aria/combobox/combobox.ts @@ -8,9 +8,12 @@ import { afterRenderEffect, + booleanAttribute, + computed, contentChild, Directive, ElementRef, + forwardRef, inject, input, model, @@ -18,15 +21,47 @@ import { untracked, WritableSignal, } from '@angular/core'; -import {DeferredContent, DeferredContentAware} from '@angular/aria/deferred-content'; import { + DeferredContent, + DeferredContentAware, ComboboxPattern, ComboboxListboxControls, ComboboxTreeControls, -} from '@angular/aria/ui-patterns'; + ComboboxDialogPattern, +} from '@angular/aria/private'; import {Directionality} from '@angular/cdk/bidi'; import {toSignal} from '@angular/core/rxjs-interop'; +/** + * The container element that wraps a combobox input and popup, and orchestrates its behavior. + * + * The `ngCombobox` directive is the main entry point for creating a combobox and customizing its + * behavior. It coordinates the interactions between the `ngComboboxInput` and the popup, which + * is defined by a `ng-template` with the `ngComboboxPopupContainer` directive. If using the + * `CdkOverlay`, the `cdkConnectedOverlay` directive takes the place of `ngComboboxPopupContainer`. + * + * ```html + *
      + * + * + * + *
      + * @for (option of filteredOptions(); track option) { + *
      + * {{option}} + *
      + * } + *
      + *
      + *
      + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: '[ngCombobox]', exportAs: 'ngCombobox', @@ -37,12 +72,12 @@ import {toSignal} from '@angular/core/rxjs-interop'; }, ], host: { - '[attr.data-expanded]': 'pattern.expanded()', - '(input)': 'pattern.onInput($event)', - '(keydown)': 'pattern.onKeydown($event)', - '(pointerup)': 'pattern.onPointerup($event)', - '(focusin)': 'pattern.onFocusIn()', - '(focusout)': 'pattern.onFocusOut($event)', + '[attr.data-expanded]': 'expanded()', + '(input)': '_pattern.onInput($event)', + '(keydown)': '_pattern.onKeydown($event)', + '(click)': '_pattern.onClick($event)', + '(focusin)': '_pattern.onFocusIn()', + '(focusout)': '_pattern.onFocusOut($event)', }, }) export class Combobox { @@ -57,32 +92,51 @@ export class Combobox { /** The element that the combobox is attached to. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the combobox element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The DeferredContentAware host directive. */ private readonly _deferredContentAware = inject(DeferredContentAware, {optional: true}); /** The combobox popup. */ - readonly popup = contentChild>(ComboboxPopup); + readonly popup = contentChild>( + // We need a `forwardRef` here, because the popup class is declared further down + // in the same file. When the reference is written to Angular's metadata this can + // cause an attempt to access the class before it's defined. + forwardRef(() => ComboboxPopup), + ); - /** The filter mode for the combobox. */ + /** + * The filter mode for the combobox. + * - `manual`: The consumer is responsible for filtering the options. + * - `auto-select`: The combobox automatically selects the first matching option. + * - `highlight`: The combobox highlights matching text in the options without changing selection. + */ filterMode = input<'manual' | 'auto-select' | 'highlight'>('manual'); - /** Whether the combobox is focused. */ - readonly isFocused = signal(false); - - /** Whether the listbox has received focus yet. */ - private _hasBeenFocused = signal(false); - /** Whether the combobox is disabled. */ - readonly disabled = input(false); + readonly disabled = input(false, {transform: booleanAttribute}); /** Whether the combobox is read-only. */ - readonly readonly = input(false); + readonly readonly = input(false, {transform: booleanAttribute}); /** The value of the first matching item in the popup. */ readonly firstMatch = input(undefined); + /** Whether the combobox is expanded. */ + readonly expanded = computed(() => this.alwaysExpanded() || this._pattern.expanded()); + + // TODO: Maybe make expanded a signal that can be passed in? + // Or an "always expanded" option? + + /** Whether the combobox popup should always be expanded, regardless of user interaction. */ + readonly alwaysExpanded = input(false, {transform: booleanAttribute}); + + /** Input element connected to the combobox, if any. */ + readonly inputElement = computed(() => this._pattern.inputs.inputEl()); + /** The combobox ui pattern. */ - readonly pattern = new ComboboxPattern({ + readonly _pattern = new ComboboxPattern({ ...this, textDirection: this.textDirection, disabled: this.disabled, @@ -90,41 +144,77 @@ export class Combobox { inputValue: signal(''), inputEl: signal(undefined), containerEl: () => this._elementRef.nativeElement, - popupControls: () => this.popup()?.controls(), + popupControls: () => this.popup()?._controls(), }); constructor() { afterRenderEffect(() => { - if (!this._deferredContentAware?.contentVisible() && this.pattern.isFocused()) { - this._deferredContentAware?.contentVisible.set(true); + if (this.alwaysExpanded()) { + this._pattern.expanded.set(true); } }); afterRenderEffect(() => { - if (!this._hasBeenFocused() && this.pattern.isFocused()) { - this._hasBeenFocused.set(true); + if ( + !this._deferredContentAware?.contentVisible() && + (this._pattern.isFocused() || this.alwaysExpanded()) + ) { + this._deferredContentAware?.contentVisible.set(true); } }); } + + /** Opens the combobox to the selected item. */ + open() { + this._pattern.open({selected: true}); + } + + /** Closes the combobox. */ + close() { + this._pattern.close(); + } } +/** + * An input that is part of a combobox. It is responsible for displaying the + * current value and handling user input for filtering and selection. + * + * This directive should be applied to an `` element within an `ngCombobox` + * container. It automatically handles keyboard interactions, such as opening the + * popup and navigating through the options. + * + * ```html + * + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: 'input[ngComboboxInput]', exportAs: 'ngComboboxInput', host: { 'role': 'combobox', '[value]': 'value()', - '[attr.aria-expanded]': 'combobox.pattern.expanded()', - '[attr.aria-activedescendant]': 'combobox.pattern.activedescendant()', - '[attr.aria-controls]': 'combobox.pattern.popupId()', - '[attr.aria-haspopup]': 'combobox.pattern.hasPopup()', - '[attr.aria-autocomplete]': 'combobox.pattern.autocomplete()', + '[attr.aria-disabled]': 'combobox._pattern.disabled()', + '[attr.aria-expanded]': 'combobox._pattern.expanded()', + '[attr.aria-activedescendant]': 'combobox._pattern.activeDescendant()', + '[attr.aria-controls]': 'combobox._pattern.popupId()', + '[attr.aria-haspopup]': 'combobox._pattern.hasPopup()', + '[attr.aria-autocomplete]': 'combobox._pattern.autocomplete()', + '[attr.readonly]': 'combobox._pattern.readonly()', }, }) export class ComboboxInput { /** The element that the combobox is attached to. */ private readonly _elementRef = inject>(ElementRef); + /** A reference to the input element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The combobox that the input belongs to. */ readonly combobox = inject(Combobox); @@ -132,19 +222,55 @@ export class ComboboxInput { value = model(''); constructor() { - (this.combobox.pattern.inputs.inputEl as WritableSignal).set( + (this.combobox._pattern.inputs.inputEl as WritableSignal).set( this._elementRef.nativeElement, ); - this.combobox.pattern.inputs.inputValue = this.value; + this.combobox._pattern.inputs.inputValue = this.value; + + const controls = this.combobox.popup()?._controls(); + if (controls instanceof ComboboxDialogPattern) { + return; + } /** Focuses & selects the first item in the combobox if the user changes the input value. */ afterRenderEffect(() => { - this.combobox.popup()?.controls()?.items(); - untracked(() => this.combobox.pattern.onFilter()); + this.value(); + controls?.items(); + untracked(() => this.combobox._pattern.onFilter()); }); } } +/** + * A structural directive that marks the `ng-template` to be used as the popup + * for a combobox. This content is conditionally rendered. + * + * The content of the popup can be a `ngListbox`, `ngTree`, or `role="dialog"`, allowing for + * flexible and complex combobox implementations. The consumer is responsible for + * implementing the filtering logic based on the `ngComboboxInput`'s value. + * + * ```html + * + *
      + * + *
      + *
      + * ``` + * + * When using CdkOverlay, this directive can be replaced by `cdkConnectedOverlay`. + * + * ```html + * + *
      + * + *
      + *
      + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: 'ng-template[ngComboboxPopupContainer]', exportAs: 'ngComboboxPopupContainer', @@ -152,6 +278,16 @@ export class ComboboxInput { }) export class ComboboxPopupContainer {} +/** + * Identifies an element as a popup for an `ngCombobox`. + * + * This directive acts as a bridge, allowing the `ngCombobox` to discover and interact + * with the underlying control (e.g., `ngListbox`, `ngTree`, or `ngComboboxDialog`) that + * manages the options. It's primarily used as a host directive and is responsible for + * exposing the popup's control pattern to the parent combobox. + * + * @developerPreview 21.0 + */ @Directive({ selector: '[ngComboboxPopup]', exportAs: 'ngComboboxPopup', @@ -160,8 +296,78 @@ export class ComboboxPopup { /** The combobox that the popup belongs to. */ readonly combobox = inject>(Combobox, {optional: true}); - /** The controls the popup exposes to the combobox. */ - readonly controls = signal< - ComboboxListboxControls | ComboboxTreeControls | undefined + /** The popup controls exposed to the combobox. */ + readonly _controls = signal< + | ComboboxListboxControls + | ComboboxTreeControls + | ComboboxDialogPattern + | undefined >(undefined); } + +/** + * Integrates a native `` element with the combobox, allowing for + * a modal or non-modal popup experience. It handles the opening and closing of the dialog + * based on the combobox's expanded state. + * + * ```html + * + * + * + * + * + * ``` + * + * @developerPreview 21.0 + */ +@Directive({ + selector: 'dialog[ngComboboxDialog]', + exportAs: 'ngComboboxDialog', + host: { + '[attr.data-open]': 'combobox._pattern.expanded()', + '(keydown)': '_pattern.onKeydown($event)', + '(click)': '_pattern.onClick($event)', + }, + hostDirectives: [ComboboxPopup], +}) +export class ComboboxDialog { + /** The dialog element. */ + private readonly _elementRef = inject(ElementRef); + + /** A reference to the dialog element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + + /** The combobox that the dialog belongs to. */ + readonly combobox = inject(Combobox); + + /** A reference to the parent combobox popup, if one exists. */ + private readonly _popup = inject>(ComboboxPopup, { + optional: true, + }); + + _pattern: ComboboxDialogPattern; + + constructor() { + this._pattern = new ComboboxDialogPattern({ + id: () => '', + element: () => this._elementRef.nativeElement, + combobox: this.combobox._pattern, + }); + + if (this._popup) { + this._popup._controls.set(this._pattern); + } + + afterRenderEffect(() => { + if (this._elementRef) { + this.combobox._pattern.expanded() + ? this._elementRef.nativeElement.showModal() + : this._elementRef.nativeElement.close(); + } + }); + } + + close() { + this._popup?.combobox?._pattern.close(); + } +} diff --git a/src/aria/combobox/index.ts b/src/aria/combobox/index.ts index c859f6cc01de..70513ea9f2ec 100644 --- a/src/aria/combobox/index.ts +++ b/src/aria/combobox/index.ts @@ -6,4 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -export {Combobox, ComboboxInput, ComboboxPopup, ComboboxPopupContainer} from './combobox'; +export { + Combobox, + ComboboxDialog, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from './combobox'; diff --git a/src/aria/config.bzl b/src/aria/config.bzl index 8c41015f75d4..291412b5a3fb 100644 --- a/src/aria/config.bzl +++ b/src/aria/config.bzl @@ -2,15 +2,13 @@ ARIA_ENTRYPOINTS = [ "accordion", "combobox", - "deferred-content", "grid", "listbox", "menu", - "radio-group", "tabs", "toolbar", "tree", - "ui-patterns", + "private", ] # List of all entry-point targets of the Angular Aria package. diff --git a/src/aria/grid/BUILD.bazel b/src/aria/grid/BUILD.bazel index 2d33b4879ba6..caebede7730f 100644 --- a/src/aria/grid/BUILD.bazel +++ b/src/aria/grid/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ng_project") +load("//tools/adev-api-extraction:extract_api_to_json.bzl", "extract_api_to_json") package(default_visibility = ["//visibility:public"]) @@ -10,9 +11,28 @@ ng_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/deferred-content", - "//src/aria/ui-patterns", + "//src/aria/private", "//src/cdk/a11y", "//src/cdk/bidi", ], ) + +filegroup( + name = "source-files", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), +) + +extract_api_to_json( + name = "json_api", + srcs = [ + ":source-files", + ], + entry_point = ":index.ts", + module_name = "@angular/aria/grid", + output_name = "aria-grid.json", + private_modules = [""], + repo = "angular/components", +) diff --git a/src/aria/grid/grid.ts b/src/aria/grid/grid.ts index 72adc8c974a4..42ae2af98569 100644 --- a/src/aria/grid/grid.ts +++ b/src/aria/grid/grid.ts @@ -11,49 +11,72 @@ import { afterRenderEffect, booleanAttribute, computed, - contentChild, contentChildren, Directive, ElementRef, inject, input, + output, model, Signal, } from '@angular/core'; -import {GridPattern, GridRowPattern, GridCellPattern, GridCellWidgetPattern} from '../ui-patterns'; +import {Directionality} from '@angular/cdk/bidi'; +import {GridPattern, GridRowPattern, GridCellPattern, GridCellWidgetPattern} from '../private'; -/** A directive that provides grid-based navigation and selection behavior. */ +/** + * The container for a grid. It provides keyboard navigation and focus management for the grid's + * rows and cells. It manages the overall behavior of the grid, including focus + * wrapping, selection, and disabled states. + * + * ```html + * + * @for (row of gridData; track row) { + * + * @for (cell of row; track cell) { + * + * } + * + * } + *
      + * {{cell.value}} + *
      + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: '[ngGrid]', exportAs: 'ngGrid', host: { - 'class': 'grid', 'role': 'grid', - '[tabindex]': 'pattern.tabIndex()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-activedescendant]': 'pattern.activeDescendant()', - '(keydown)': 'pattern.onKeydown($event)', - '(pointerdown)': 'pattern.onPointerdown($event)', - '(pointermove)': 'pattern.onPointermove($event)', - '(pointerup)': 'pattern.onPointerup($event)', - '(focusin)': 'pattern.onFocusIn($event)', - '(focusout)': 'pattern.onFocusOut($event)', + '[tabindex]': '_pattern.tabIndex()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-activedescendant]': '_pattern.activeDescendant()', + '(keydown)': '_pattern.onKeydown($event)', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(pointermove)': '_pattern.onPointermove($event)', + '(pointerup)': '_pattern.onPointerup($event)', + '(focusin)': '_pattern.onFocusIn($event)', + '(focusout)': '_pattern.onFocusOut($event)', }, }) export class Grid { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The rows that make up the grid. */ - private readonly _rows = contentChildren(GridRow); + private readonly _rows = contentChildren(GridRow, {descendants: true}); /** The UI patterns for the rows in the grid. */ private readonly _rowPatterns: Signal = computed(() => - this._rows().map(r => r.pattern), + this._rows().map(r => r._pattern), ); - /** The host native element. */ - readonly element = computed(() => this._elementRef.nativeElement); + /** Text direction. */ + readonly textDirection = inject(Directionality).valueSignal; /** Whether selection is enabled for the grid. */ readonly enableSelection = input(false, {transform: booleanAttribute}); @@ -61,136 +84,196 @@ export class Grid { /** Whether the grid is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); - /** Whether to skip disabled items during navigation. */ - readonly skipDisabled = input(true, {transform: booleanAttribute}); - - /** The focus strategy used by the grid. */ + /** + * Whether to allow disabled items to receive focus. When `true`, disabled items are + * focusable but not interactive. When `false`, disabled items are skipped during navigation. + */ + readonly softDisabled = input(true, {transform: booleanAttribute}); + + /** + * The focus strategy used by the grid. + * - `roving`: Focus is moved to the active cell using `tabindex`. + * - `activedescendant`: Focus remains on the grid container, and `aria-activedescendant` is used to indicate the active cell. + */ readonly focusMode = input<'roving' | 'activedescendant'>('roving'); - /** The wrapping behavior for keyboard navigation along the row axis. */ + /** + * The wrapping behavior for keyboard navigation along the row axis. + * - `continuous`: Navigation wraps from the last row to the first, and vice-versa. + * - `loop`: Navigation wraps within the current row. + * - `nowrap`: Navigation stops at the first/last item in the row. + */ readonly rowWrap = input<'continuous' | 'loop' | 'nowrap'>('loop'); - /** The wrapping behavior for keyboard navigation along the column axis. */ + /** + * The wrapping behavior for keyboard navigation along the column axis. + * - `continuous`: Navigation wraps from the last column to the first, and vice-versa. + * - `loop`: Navigation wraps within the current column. + * - `nowrap`: Navigation stops at the first/last item in the column. + */ readonly colWrap = input<'continuous' | 'loop' | 'nowrap'>('loop'); + /** Whether multiple cells in the grid can be selected. */ + readonly multi = input(false, {transform: booleanAttribute}); + + /** + * The selection strategy used by the grid. + * - `follow`: The focused cell is automatically selected. + * - `explicit`: Cells are selected explicitly by the user (e.g., via click or spacebar). + */ + readonly selectionMode = input<'follow' | 'explicit'>('follow'); + + /** Whether enable range selections (with modifier keys or dragging). */ + readonly enableRangeSelection = input(false, {transform: booleanAttribute}); + /** The UI pattern for the grid. */ - readonly pattern = new GridPattern({ + readonly _pattern = new GridPattern({ ...this, rows: this._rowPatterns, getCell: e => this._getCell(e), + element: () => this.element, }); constructor() { - afterRenderEffect(() => this.pattern.resetStateEffect()); - afterRenderEffect(() => this.pattern.focusEffect()); + afterRenderEffect(() => this._pattern.setDefaultStateEffect()); + afterRenderEffect(() => this._pattern.resetStateEffect()); + afterRenderEffect(() => this._pattern.resetFocusEffect()); + afterRenderEffect(() => this._pattern.restoreFocusEffect()); + afterRenderEffect(() => this._pattern.focusEffect()); } /** Gets the cell pattern for a given element. */ - private _getCell(element: Element): GridCellPattern | undefined { - const cellElement = element.closest('[ngGridCell]'); - if (cellElement === undefined) return; - - const widgetElement = element.closest('[ngGridCellWidget]'); - for (const row of this._rowPatterns()) { - for (const cell of row.inputs.cells()) { - if ( - cell.element() === cellElement || - (widgetElement !== undefined && cell.element() === widgetElement) - ) { - return cell; + private _getCell(element: Element | null | undefined): GridCellPattern | undefined { + let target = element; + + while (target) { + for (const row of this._rowPatterns()) { + for (const cell of row.inputs.cells()) { + if (cell.element() === target) { + return cell; + } } } + + target = target.parentElement?.closest('[ngGridCell]'); } - return; + + return undefined; } } -/** A directive that represents a row in a grid. */ +/** + * Represents a row within a grid. It is a container for `ngGridCell` directives. + * + * ```html + * + * + * + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: '[ngGridRow]', exportAs: 'ngGridRow', host: { - 'class': 'grid-row', - '[attr.role]': 'role()', + 'role': 'row', + '[attr.aria-rowindex]': '_pattern.rowIndex()', }, }) export class GridRow { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The cells that make up this row. */ - private readonly _cells = contentChildren(GridCell); + private readonly _cells = contentChildren(GridCell, {descendants: true}); /** The UI patterns for the cells in this row. */ private readonly _cellPatterns: Signal = computed(() => - this._cells().map(c => c.pattern), + this._cells().map(c => c._pattern), ); /** The parent grid. */ private readonly _grid = inject(Grid); /** The parent grid UI pattern. */ - readonly grid = computed(() => this._grid.pattern); - - /** The host native element. */ - readonly element = computed(() => this._elementRef.nativeElement); - - /** The ARIA role for the row. */ - readonly role = input<'row' | 'rowheader'>('row'); + readonly _gridPattern = computed(() => this._grid._pattern); /** The index of this row within the grid. */ readonly rowIndex = input(); /** The UI pattern for the grid row. */ - readonly pattern = new GridRowPattern({ + readonly _pattern = new GridRowPattern({ ...this, cells: this._cellPatterns, + grid: this._gridPattern, }); } -/** A directive that represents a cell in a grid. */ +/** + * Represents a cell within a grid row. It is the primary focusable element + * within the grid. It can be disabled and can have its selection state managed + * through the `selected` input. + * + * ```html + * + * Cell Content + * + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: '[ngGridCell]', exportAs: 'ngGridCell', host: { - 'class': 'grid-cell', '[attr.role]': 'role()', - '[attr.id]': 'pattern.id()', - '[attr.rowspan]': 'pattern.rowSpan()', - '[attr.colspan]': 'pattern.colSpan()', - '[attr.data-active]': 'pattern.active()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-rowspan]': 'pattern.rowSpan()', - '[attr.aria-colspan]': 'pattern.colSpan()', - '[attr.aria-rowindex]': 'pattern.ariaRowIndex()', - '[attr.aria-colindex]': 'pattern.ariaColIndex()', - '[attr.aria-selected]': 'pattern.ariaSelected()', - '[tabindex]': 'pattern.tabIndex()', + '[attr.id]': '_pattern.id()', + '[attr.rowspan]': '_pattern.rowSpan()', + '[attr.colspan]': '_pattern.colSpan()', + '[attr.data-active]': 'active()', + '[attr.data-anchor]': '_pattern.anchor()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-rowspan]': '_pattern.rowSpan()', + '[attr.aria-colspan]': '_pattern.colSpan()', + '[attr.aria-rowindex]': '_pattern.ariaRowIndex()', + '[attr.aria-colindex]': '_pattern.ariaColIndex()', + '[attr.aria-selected]': '_pattern.ariaSelected()', + '[tabindex]': '_tabIndex()', }, }) export class GridCell { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); - /** The widget contained within this cell, if any. */ - private readonly _widgets = contentChild(GridCellWidget); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + + /** Whether the cell is currently active (focused). */ + readonly active = computed(() => this._pattern.active()); + + /** The widgets contained within this cell, if any. */ + private readonly _widgets = contentChildren(GridCellWidget, {descendants: true}); /** The UI pattern for the widget in this cell. */ - private readonly _widgetPattern: Signal = computed( - () => this._widgets()?.pattern, + private readonly _widgetPatterns: Signal = computed(() => + this._widgets().map(w => w._pattern), ); /** The parent row. */ private readonly _row = inject(GridRow); - /** A unique identifier for the cell. */ - private readonly _id = inject(_IdGenerator).getId('ng-grid-cell-'); + /** Text direction. */ + readonly textDirection = inject(Directionality).valueSignal; - /** The host native element. */ - readonly element = computed(() => this._elementRef.nativeElement); + /** A unique identifier for the cell. */ + readonly id = input(inject(_IdGenerator).getId('ng-grid-cell-', true)); /** The ARIA role for the cell. */ - readonly role = input<'gridcell' | 'columnheader'>('gridcell'); + readonly role = input<'gridcell' | 'columnheader' | 'rowheader'>('gridcell'); /** The number of rows the cell should span. */ readonly rowSpan = input(1); @@ -213,47 +296,160 @@ export class GridCell { /** Whether the cell is selectable. */ readonly selectable = input(true); + /** Orientation of the widgets in the cell. */ + readonly orientation = input<'vertical' | 'horizontal'>('horizontal'); + + /** Whether widgets navigation wraps. */ + readonly wrap = input(true, {transform: booleanAttribute}); + + /** The tabindex override. */ + readonly tabindex = input(); + + /** + * The tabindex value set to the element. + * If a focus target exists then return -1. Unless an override. + */ + protected readonly _tabIndex: Signal = computed( + () => this.tabindex() ?? this._pattern.tabIndex(), + ); + /** The UI pattern for the grid cell. */ - readonly pattern = new GridCellPattern({ + readonly _pattern = new GridCellPattern({ ...this, - id: () => this._id, - grid: this._row.grid, - row: () => this._row.pattern, - widget: this._widgetPattern, + grid: this._row._gridPattern, + row: () => this._row._pattern, + widgets: this._widgetPatterns, + getWidget: e => this._getWidget(e), + element: () => this.element, }); + + constructor() {} + + /** Gets the cell widget pattern for a given element. */ + private _getWidget(element: Element | null | undefined): GridCellWidgetPattern | undefined { + let target = element; + + while (target) { + const pattern = this._widgetPatterns().find(w => w.element() === target); + if (pattern) { + return pattern; + } + + target = target.parentElement?.closest('[ngGridCellWidget]'); + } + + return undefined; + } } -/** A directive that represents a widget inside a grid cell. */ +/** + * Represents an interactive element inside a `GridCell`. It allows for pausing grid navigation to + * interact with the widget. + * + * When the user interacts with the widget (e.g., by typing in an input or opening a menu), grid + * navigation is temporarily suspended to allow the widget to handle keyboard + * events. + * + * ```html + * + * + * + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: '[ngGridCellWidget]', exportAs: 'ngGridCellWidget', host: { - 'class': 'grid-cell-widget', - '[attr.data-active]': 'pattern.active()', - '[tabindex]': 'pattern.tabIndex()', + '[attr.data-active]': 'active()', + '[attr.data-active-control]': 'isActivated() ? "widget" : "cell"', + '[tabindex]': '_tabIndex()', }, }) export class GridCellWidget { /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + + /** Whether the widget is currently active (focused). */ + readonly active = computed(() => this._pattern.active()); + /** The parent cell. */ private readonly _cell = inject(GridCell); - /** The host native element. */ - readonly element = computed(() => this._elementRef.nativeElement); + /** A unique identifier for the widget. */ + readonly id = input(inject(_IdGenerator).getId('ng-grid-cell-widget-', true)); - /** Whether the widget is activated and the grid navigation should be paused. */ - readonly activate = model(false); + /** The type of widget, which determines how it is activated. */ + readonly widgetType = input<'simple' | 'complex' | 'editable'>('simple'); + + /** Whether the widget is disabled. */ + readonly disabled = input(false, {transform: booleanAttribute}); + + /** The target that will receive focus instead of the widget. */ + readonly focusTarget = input(); + + /** Emits when the widget is activated. */ + readonly onActivate = output(); + + /** Emits when the widget is deactivated. */ + readonly onDeactivate = output(); + + /** The tabindex override. */ + readonly tabindex = input(); + + /** + * The tabindex value set to the element. + * If a focus target exists then return -1. Unless an override. + */ + protected readonly _tabIndex: Signal = computed( + () => this.tabindex() ?? (this.focusTarget() ? -1 : this._pattern.tabIndex()), + ); /** The UI pattern for the grid cell widget. */ - readonly pattern = new GridCellWidgetPattern({ + readonly _pattern = new GridCellWidgetPattern({ ...this, - cell: () => this._cell.pattern, + element: () => this.element, + cell: () => this._cell._pattern, + focusTarget: computed(() => { + if (this.focusTarget() instanceof ElementRef) { + return (this.focusTarget() as ElementRef).nativeElement; + } + return this.focusTarget(); + }), }); - /** Focuses the widget. */ - focus(): void { - this.element().focus(); + /** Whether the widget is activated. */ + get isActivated(): Signal { + return this._pattern.isActivated.asReadonly(); + } + + constructor() { + afterRenderEffect(() => { + const activateEvent = this._pattern.lastActivateEvent(); + if (activateEvent) { + this.onActivate.emit(activateEvent); + } + }); + + afterRenderEffect(() => { + const deactivateEvent = this._pattern.lastDeactivateEvent(); + if (deactivateEvent) { + this.onDeactivate.emit(deactivateEvent); + } + }); + } + + /** Activates the widget. */ + activate(): void { + this._pattern.activate(); + } + + /** Deactivates the widget. */ + deactivate(): void { + this._pattern.deactivate(); } } diff --git a/src/aria/listbox/BUILD.bazel b/src/aria/listbox/BUILD.bazel index 62c65c2faac8..f45a26819303 100644 --- a/src/aria/listbox/BUILD.bazel +++ b/src/aria/listbox/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite") +load("//tools/adev-api-extraction:extract_api_to_json.bzl", "extract_api_to_json") package(default_visibility = ["//visibility:public"]) @@ -11,7 +12,7 @@ ng_project( deps = [ "//:node_modules/@angular/core", "//src/aria/combobox", - "//src/aria/ui-patterns", + "//src/aria/private", "//src/cdk/a11y", "//src/cdk/bidi", ], @@ -37,3 +38,23 @@ ng_web_test_suite( name = "unit_tests", deps = [":unit_test_sources"], ) + +filegroup( + name = "source-files", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), +) + +extract_api_to_json( + name = "json_api", + srcs = [ + ":source-files", + ], + entry_point = ":index.ts", + module_name = "@angular/aria/listbox", + output_name = "aria-listbox.json", + private_modules = [""], + repo = "angular/components", +) diff --git a/src/aria/listbox/listbox.spec.ts b/src/aria/listbox/listbox.spec.ts index 6241478357f3..5bba549e061a 100644 --- a/src/aria/listbox/listbox.spec.ts +++ b/src/aria/listbox/listbox.spec.ts @@ -1,6 +1,6 @@ import {Component, DebugElement, signal} from '@angular/core'; import {Listbox, Option} from './listbox'; -import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Direction} from '@angular/cdk/bidi'; import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; @@ -52,8 +52,8 @@ describe('Listbox', () => { orientation?: 'horizontal' | 'vertical'; disabled?: boolean; readonly?: boolean; - value?: number[]; - skipDisabled?: boolean; + values?: number[]; + softDisabled?: boolean; focusMode?: 'roving' | 'activedescendant'; multi?: boolean; wrap?: boolean; @@ -73,8 +73,8 @@ describe('Listbox', () => { if (opts?.orientation !== undefined) testComponent.orientation = opts.orientation; if (opts?.disabled !== undefined) testComponent.disabled = opts.disabled; if (opts?.readonly !== undefined) testComponent.readonly = opts.readonly; - if (opts?.value !== undefined) testComponent.value = opts.value; - if (opts?.skipDisabled !== undefined) testComponent.skipDisabled = opts.skipDisabled; + if (opts?.values !== undefined) testComponent.values = opts.values; + if (opts?.softDisabled !== undefined) testComponent.softDisabled = opts.softDisabled; if (opts?.focusMode !== undefined) testComponent.focusMode = opts.focusMode; if (opts?.multi !== undefined) testComponent.multi = opts.multi; if (opts?.wrap !== undefined) testComponent.wrap = opts.wrap; @@ -173,7 +173,7 @@ describe('Listbox', () => { }); it('should set aria-selected to "true" for selected options', () => { - setupListbox({multi: true, value: [1, 3]}); + setupListbox({multi: true, values: [1, 3]}); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); expect(optionElements[2].getAttribute('aria-selected')).toBe('false'); @@ -200,7 +200,7 @@ describe('Listbox', () => { expect(listboxElement.getAttribute('tabindex')).toBe('0'); }); - it('should set initial focus (tabindex="0") on the first non-disabled option if no value is set', () => { + it('should set initial focus (tabindex="0") on the first non-disabled option if no values are set', () => { setupListbox({focusMode: 'roving'}); expect(optionElements[0].getAttribute('tabindex')).toBe('0'); expect(optionElements[1].getAttribute('tabindex')).toBe('-1'); @@ -210,7 +210,7 @@ describe('Listbox', () => { }); it('should set initial focus (tabindex="0") on the first selected option', () => { - setupListbox({focusMode: 'roving', value: [2]}); + setupListbox({focusMode: 'roving', values: [2]}); expect(optionElements[0].getAttribute('tabindex')).toBe('-1'); expect(optionElements[1].getAttribute('tabindex')).toBe('-1'); expect(optionElements[2].getAttribute('tabindex')).toBe('0'); @@ -218,8 +218,23 @@ describe('Listbox', () => { expect(optionElements[4].getAttribute('tabindex')).toBe('-1'); }); - it('should set initial focus (tabindex="0") on the first non-disabled option if selected option is disabled', () => { - setupListbox({focusMode: 'roving', value: [1], disabledOptions: [1]}); + it('should set initial focus (tabindex="0") on the first non-disabled option if selected option is disabled when softDisabled is false', () => { + setupListbox({ + focusMode: 'roving', + values: [1], + disabledOptions: [0], + softDisabled: false, + }); + expect(optionElements[0].getAttribute('tabindex')).toBe('-1'); + expect(optionElements[1].getAttribute('tabindex')).toBe('0'); + }); + + it('should set initial focus (tabindex="0") on the first option if selected option is disabled', () => { + setupListbox({ + focusMode: 'roving', + values: [0], + disabledOptions: [0], + }); expect(optionElements[0].getAttribute('tabindex')).toBe('0'); expect(optionElements[1].getAttribute('tabindex')).toBe('-1'); }); @@ -242,15 +257,25 @@ describe('Listbox', () => { }); it('should set aria-activedescendant to the ID of the first selected option', () => { - setupListbox({focusMode: 'activedescendant', value: [2]}); + setupListbox({focusMode: 'activedescendant', values: [2]}); expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[2].id); }); it('should set aria-activedescendant to the ID of the first non-disabled option if selected option is disabled', () => { - setupListbox({focusMode: 'activedescendant', value: [1], disabledOptions: [1]}); + setupListbox({focusMode: 'activedescendant', values: [0], disabledOptions: [0]}); expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[0].id); }); + it('should set aria-activedescendant to the ID of the first non-disabled option if selected option is disabled when softDisabled is false', () => { + setupListbox({ + focusMode: 'activedescendant', + values: [1], + disabledOptions: [0], + softDisabled: false, + }); + expect(listboxElement.getAttribute('aria-activedescendant')).toBe(optionElements[1].id); + }); + it('should set tabindex="-1" for all options', () => { setupListbox({focusMode: 'activedescendant'}); expect(optionElements[0].getAttribute('tabindex')).toBe('-1'); @@ -264,28 +289,28 @@ describe('Listbox', () => { describe('value and selection', () => { it('should select the options corresponding to the value input', () => { - setupListbox({multi: true, value: [1, 3]}); + setupListbox({multi: true, values: [1, 3]}); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); expect(optionElements[3].getAttribute('aria-selected')).toBe('true'); - expect(listboxInstance.value()).toEqual([1, 3]); + expect(listboxInstance.values()).toEqual([1, 3]); }); it('should update the value model when an option is selected via UI (single select)', () => { setupListbox({multi: false}); click(1); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); click(2); - expect(listboxInstance.value()).toEqual([2]); + expect(listboxInstance.values()).toEqual([2]); }); it('should update the value model when options are selected via UI (multi select)', () => { setupListbox({multi: true}); click(1); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); click(3); - expect(listboxInstance.value()).toEqual([1, 3]); + expect(listboxInstance.values()).toEqual([1, 3]); click(1); - expect(listboxInstance.value()).toEqual([3]); + expect(listboxInstance.values()).toEqual([3]); }); describe('pointer interactions', () => { @@ -293,14 +318,14 @@ describe('Listbox', () => { it('should select an option on click', () => { setupListbox({multi: false}); click(1); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); it('should select a new option and deselect the old one on click', () => { - setupListbox({multi: false, value: [0]}); + setupListbox({multi: false, values: [0]}); click(1); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); @@ -309,30 +334,30 @@ describe('Listbox', () => { describe('multi select', () => { describe('selection follows focus', () => { it('should select only the clicked option with a simple click', () => { - setupListbox({multi: true, selectionMode: 'follow', value: [0]}); + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); click(1); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); it('should toggle the selected state of an option with ctrl + click', () => { - setupListbox({multi: true, selectionMode: 'follow', value: [0]}); + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); click(1, {ctrlKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); + expect(listboxInstance.values().sort()).toEqual([0, 1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('true'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); click(0, {ctrlKey: true}); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); it('should select a range starting from the first option on shift + click', () => { - setupListbox({multi: true, selectionMode: 'follow', value: [0]}); + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); click(2, {shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2]); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2]); expect(optionElements[0].getAttribute('aria-selected')).toBe('true'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); expect(optionElements[2].getAttribute('aria-selected')).toBe('true'); @@ -342,46 +367,46 @@ describe('Listbox', () => { setupListbox({multi: true, selectionMode: 'follow'}); click(1); click(3, {shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([1, 2, 3]); + expect(listboxInstance.values().sort()).toEqual([1, 2, 3]); }); it('should not select disabled options on shift + click', () => { setupListbox({multi: true, selectionMode: 'follow', disabledOptions: [1]}); click(2, {shiftKey: true}); - expect(listboxInstance.value()).toEqual([0, 2]); + expect(listboxInstance.values()).toEqual([0, 2]); }); }); describe('explicit selection', () => { it('should toggle selection of the clicked option with a simple click', () => { - setupListbox({multi: true, selectionMode: 'explicit', value: [0]}); + setupListbox({multi: true, selectionMode: 'explicit', values: [0]}); click(1); - expect(listboxInstance.value().sort()).toEqual([0, 1]); + expect(listboxInstance.values().sort()).toEqual([0, 1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('true'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); click(0); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); }); it('should select a range starting from the first option on shift + click', () => { - setupListbox({multi: true, selectionMode: 'explicit', value: [0]}); + setupListbox({multi: true, selectionMode: 'explicit', values: [0]}); click(2, {shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2]); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2]); }); it('should select a range starting from the current active option on shift + click', () => { setupListbox({multi: true, selectionMode: 'explicit'}); click(1); click(3, {shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([1, 2, 3]); + expect(listboxInstance.values().sort()).toEqual([1, 2, 3]); }); it('should not select disabled options on shift + click', () => { setupListbox({multi: true, selectionMode: 'follow', disabledOptions: [1]}); click(2, {shiftKey: true}); - expect(listboxInstance.value()).toEqual([0, 2]); + expect(listboxInstance.values()).toEqual([0, 2]); }); }); }); @@ -393,30 +418,30 @@ describe('Listbox', () => { it('should select the next option on ArrowDown', () => { setupListbox({multi: false, selectionMode: 'follow'}); down(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); down(); - expect(listboxInstance.value()).toEqual([2]); + expect(listboxInstance.values()).toEqual([2]); expect(optionElements[2].getAttribute('aria-selected')).toBe('true'); }); it('should select the previous option on ArrowUp', () => { - setupListbox({multi: false, selectionMode: 'follow', value: [2]}); + setupListbox({multi: false, selectionMode: 'follow', values: [2]}); up(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); it('should select the first option on Home', () => { - setupListbox({multi: false, selectionMode: 'follow', value: [2]}); + setupListbox({multi: false, selectionMode: 'follow', values: [2]}); home(); - expect(listboxInstance.value()).toEqual([0]); + expect(listboxInstance.values()).toEqual([0]); }); it('should select the last option on End', () => { - setupListbox({multi: false, selectionMode: 'follow', value: [2]}); + setupListbox({multi: false, selectionMode: 'follow', values: [2]}); end(); - expect(listboxInstance.value()).toEqual([4]); + expect(listboxInstance.values()).toEqual([4]); }); }); @@ -427,7 +452,7 @@ describe('Listbox', () => { up(); home(); end(); - expect(listboxInstance.value()).toEqual([]); + expect(listboxInstance.values()).toEqual([]); expect(optionElements[1].getAttribute('aria-selected')).toBe('false'); }); @@ -435,12 +460,12 @@ describe('Listbox', () => { setupListbox({multi: false, selectionMode: 'explicit'}); down(); space(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); down(); down(); space(); - expect(listboxInstance.value()).toEqual([3]); + expect(listboxInstance.values()).toEqual([3]); expect(optionElements[1].getAttribute('aria-selected')).toBe('false'); expect(optionElements[3].getAttribute('aria-selected')).toBe('true'); }); @@ -449,7 +474,7 @@ describe('Listbox', () => { setupListbox({multi: false, selectionMode: 'explicit'}); down(); enter(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); }); @@ -458,46 +483,46 @@ describe('Listbox', () => { describe('multi select', () => { describe('selection follows focus', () => { it('should select only the focused option on ArrowDown (no modifier)', () => { - setupListbox({multi: true, selectionMode: 'follow', value: [0]}); + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); down(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); expect(optionElements[0].getAttribute('aria-selected')).toBe('false'); expect(optionElements[1].getAttribute('aria-selected')).toBe('true'); }); it('should move focus but not change selection on ctrl + ArrowDown, then toggle with ctrl + Space', () => { - setupListbox({multi: true, selectionMode: 'follow', value: [0]}); + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); down({ctrlKey: true}); - expect(listboxInstance.value()).toEqual([0]); + expect(listboxInstance.values()).toEqual([0]); space({ctrlKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); + expect(listboxInstance.values().sort()).toEqual([0, 1]); }); it('should toggle selection of the focused item on ctrl + Space', () => { - setupListbox({multi: true, selectionMode: 'follow', value: [0]}); + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); space({ctrlKey: true}); - expect(listboxInstance.value()).toEqual([]); + expect(listboxInstance.values()).toEqual([]); down(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); space({ctrlKey: true}); - expect(listboxInstance.value()).toEqual([]); + expect(listboxInstance.values()).toEqual([]); }); it('should extend selection on shift + ArrowDown', () => { - setupListbox({multi: true, selectionMode: 'follow', value: [0]}); + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); + expect(listboxInstance.values().sort()).toEqual([0, 1]); down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2]); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2]); }); it('should select all on Ctrl+A, then select active on second Ctrl+A', () => { - setupListbox({multi: true, selectionMode: 'follow', value: [0]}); + setupListbox({multi: true, selectionMode: 'follow', values: [0]}); keydown('A', {ctrlKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2, 3, 4]); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2, 3, 4]); keydown('A', {ctrlKey: true}); - expect(listboxInstance.value()).toEqual([0]); + expect(listboxInstance.values()).toEqual([0]); }); }); @@ -505,43 +530,43 @@ describe('Listbox', () => { it('should move focus but not select on ArrowDown', () => { setupListbox({multi: true, selectionMode: 'explicit'}); down(); - expect(listboxInstance.value()).toEqual([]); + expect(listboxInstance.values()).toEqual([]); }); it('should toggle selection of the focused item on Space', () => { setupListbox({multi: true, selectionMode: 'explicit'}); down(); space(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); down(); space(); - expect(listboxInstance.value().sort()).toEqual([1, 2]); + expect(listboxInstance.values().sort()).toEqual([1, 2]); space(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); }); it('should toggle selection of the focused item on Enter', () => { setupListbox({multi: true, selectionMode: 'explicit'}); down(); enter(); - expect(listboxInstance.value()).toEqual([1]); + expect(listboxInstance.values()).toEqual([1]); }); it('should extend selection on Shift+ArrowDown', () => { setupListbox({multi: true, selectionMode: 'explicit'}); down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1]); + expect(listboxInstance.values().sort()).toEqual([0, 1]); down({shiftKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2]); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2]); }); it('should toggle selection of all options on Ctrl+A', () => { - setupListbox({multi: true, selectionMode: 'explicit', value: [0]}); + setupListbox({multi: true, selectionMode: 'explicit', values: [0]}); keydown('A', {ctrlKey: true}); - expect(listboxInstance.value().sort()).toEqual([0, 1, 2, 3, 4]); + expect(listboxInstance.values().sort()).toEqual([0, 1, 2, 3, 4]); keydown('A', {ctrlKey: true}); - expect(listboxInstance.value()).toEqual([]); + expect(listboxInstance.values()).toEqual([]); }); }); }); @@ -553,17 +578,17 @@ describe('Listbox', () => { isFocused: (index: number) => boolean, ) { describe(`keyboard navigation (focusMode="${focusMode}")`, () => { - it('should move focus to the last enabled option on End', () => { + it('should move focus to the last focusable option on End', () => { setupListbox({focusMode, disabledOptions: [4]}); end(); - expect(isFocused(3)).toBe(true); + expect(isFocused(4)).toBe(true); }); - it('should move focus to the first enabled option on Home', () => { + it('should move focus to the first focusable option on Home', () => { setupListbox({focusMode, disabledOptions: [0]}); end(); home(); - expect(isFocused(1)).toBe(true); + expect(isFocused(0)).toBe(true); }); it('should allow keyboard navigation if the group is readonly', () => { @@ -593,27 +618,39 @@ describe('Listbox', () => { expect(isFocused(1)).toBe(true); }); - it('should skip disabled options with ArrowDown (skipDisabled="true")', () => { + it('should skip disabled options with ArrowDown (softDisabled="false")', () => { setupListbox({ focusMode, orientation: 'vertical', - skipDisabled: true, + softDisabled: false, disabledOptions: [1, 2], }); down(); expect(isFocused(3)).toBe(true); }); - it('should not skip disabled options with ArrowDown (skipDisabled="false")', () => { + it('should not skip disabled options with ArrowDown (softDisabled="true")', () => { setupListbox({ focusMode, orientation: 'vertical', - skipDisabled: false, + softDisabled: true, disabledOptions: [1, 2], }); down(); expect(isFocused(1)).toBe(true); }); + + it('should not be focusable ArrowDown when completely disabled', () => { + setupListbox({ + focusMode, + orientation: 'vertical', + softDisabled: true, + disabled: true, + }); + + down(); + expect(isFocused(0)).toBe(false); + }); }); describe('horizontal orientation', () => { @@ -641,7 +678,7 @@ describe('Listbox', () => { }); it('should move focus to the clicked disabled option', () => { - setupListbox({focusMode, disabledOptions: [2], skipDisabled: false}); + setupListbox({focusMode, disabledOptions: [2], softDisabled: true}); click(2); expect(isFocused(2)).toBe(true); }); @@ -674,7 +711,7 @@ describe('Listbox', () => { setupListbox({options: getOptions(), focusMode, selectionMode: 'follow'}); type('O'); expect(isFocused(4)).toBe(true); - expect(listboxInstance.value()).toEqual([4]); + expect(listboxInstance.values()).toEqual([4]); expect(optionElements[4].getAttribute('aria-selected')).toBe('true'); }); @@ -682,28 +719,29 @@ describe('Listbox', () => { setupListbox({options: getOptions(), focusMode, selectionMode: 'explicit'}); type('O'); expect(isFocused(4)).toBe(true); - expect(listboxInstance.value()).toEqual([]); + expect(listboxInstance.values()).toEqual([]); expect(optionElements[4].getAttribute('aria-selected')).toBe('false'); }); - it('should reset search term after typeaheadDelay', fakeAsync(() => { - setupListbox({options: getOptions(), focusMode, typeaheadDelay: 0.1}); + it('should reset search term after typeaheadDelay', async () => { + setupListbox({options: getOptions(), focusMode, typeaheadDelay: 100}); type('A'); expect(isFocused(1)).toBe(true); - tick(100); + await new Promise(resolve => setTimeout(resolve, 100)); + type('A'); expect(isFocused(0)).toBe(true); - })); + }); - it('should skip disabled options with typeahead (skipDisabled=true)', () => { - setupListbox({options: getOptions(), focusMode, disabledOptions: [2], skipDisabled: true}); + it('should skip disabled options with typeahead (softDisabled=false)', () => { + setupListbox({options: getOptions(), focusMode, disabledOptions: [2], softDisabled: false}); type('B'); expect(isFocused(3)).toBe(true); }); - it('should focus disabled options with typeahead if skipDisabled=false', () => { - setupListbox({options: getOptions(), focusMode, disabledOptions: [2], skipDisabled: false}); + it('should focus disabled options with typeahead if softDisabled=true', () => { + setupListbox({options: getOptions(), focusMode, disabledOptions: [2], softDisabled: true}); type('B'); expect(isFocused(2)).toBe(true); }); @@ -728,7 +766,7 @@ describe('Listbox', () => { expect(optionElements.length).toBe(0); expect(() => down()).not.toThrow(); expect(() => space()).not.toThrow(); - expect(listboxInstance.value()).toEqual([]); + expect(listboxInstance.values()).toEqual([]); }); }); }); @@ -744,12 +782,12 @@ interface TestOption {
        - *
      • Item 1
      • - *
      • Item 2
      • - *
      • Item 3
      • + *
          + * @for (item of items; track item.id) { + *
        • + * {{item.name}} + *
        • + * } *
        * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngListbox]', exportAs: 'ngListbox', host: { 'role': 'listbox', - 'class': 'ng-listbox', '[attr.id]': 'id()', - '[attr.tabindex]': 'pattern.tabindex()', - '[attr.aria-readonly]': 'pattern.readonly()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-orientation]': 'pattern.orientation()', - '[attr.aria-multiselectable]': 'pattern.multi()', - '[attr.aria-activedescendant]': 'pattern.activedescendant()', - '(keydown)': 'pattern.onKeydown($event)', - '(pointerdown)': 'pattern.onPointerdown($event)', - '(focusin)': 'onFocus()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.aria-readonly]': '_pattern.readonly()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-orientation]': '_pattern.orientation()', + '[attr.aria-multiselectable]': '_pattern.multi()', + '[attr.aria-activedescendant]': '_pattern.activeDescendant()', + '(keydown)': '_pattern.onKeydown($event)', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(focusin)': '_onFocus()', }, - hostDirectives: [{directive: ComboboxPopup}], + hostDirectives: [ComboboxPopup], }) export class Listbox { /** A unique identifier for the listbox. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-listbox-'); - - // TODO(wagnermaciel): https://github.com/angular/components/pull/30495#discussion_r1972601144. - /** A unique identifier for the listbox. */ - protected id = computed(() => this._generatedId); + readonly id = input(inject(_IdGenerator).getId('ng-listbox-', true)); /** A reference to the parent combobox popup, if one exists. */ private readonly _popup = inject>(ComboboxPopup, { optional: true, }); - /** A reference to the listbox element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The directionality (LTR / RTL) context for the application (or a subtree of it). */ private readonly _directionality = inject(Directionality); /** The Options nested inside of the Listbox. */ - private readonly _options = contentChildren(Option, {descendants: true}); + private readonly _options = contentChildren( + // We need a `forwardRef` here, because the option class is declared further down + // in the same file. When the reference is written to Angular's metadata this can + // cause an attempt to access the class before it's defined. + forwardRef(() => Option), + {descendants: true}, + ); /** A signal wrapper for directionality. */ protected textDirection = toSignal(this._directionality.change, { @@ -86,7 +96,7 @@ export class Listbox { }); /** The Option UIPatterns of the child Options. */ - protected items = computed(() => this._options().map(option => option.pattern)); + protected items = computed(() => this._options().map(option => option._pattern)); /** Whether the list is vertically or horizontally oriented. */ orientation = input<'vertical' | 'horizontal'>('vertical'); @@ -97,17 +107,28 @@ export class Listbox { /** Whether focus should wrap when navigating. */ wrap = input(true, {transform: booleanAttribute}); - /** Whether disabled items in the list should be skipped when navigating. */ - skipDisabled = input(true, {transform: booleanAttribute}); - - /** The focus strategy used by the list. */ + /** + * Whether to allow disabled items to receive focus. When `true`, disabled items are + * focusable but not interactive. When `false`, disabled items are skipped during navigation. + */ + softDisabled = input(true, {transform: booleanAttribute}); + + /** + * The focus strategy used by the list. + * - `roving`: Focus is moved to the active item using `tabindex`. + * - `activedescendant`: Focus remains on the listbox container, and `aria-activedescendant` is used to indicate the active item. + */ focusMode = input<'roving' | 'activedescendant'>('roving'); - /** The selection strategy used by the list. */ + /** + * The selection strategy used by the list. + * - `follow`: The focused item is automatically selected. + * - `explicit`: Items are selected explicitly by the user (e.g., via click or spacebar). + */ selectionMode = input<'follow' | 'explicit'>('follow'); /** The amount of time before the typeahead search is reset. */ - typeaheadDelay = input(0.5); // Picked arbitrarily. + typeaheadDelay = input(500); // Picked arbitrarily. /** Whether the listbox is disabled. */ disabled = input(false, {transform: booleanAttribute}); @@ -115,11 +136,11 @@ export class Listbox { /** Whether the listbox is readonly. */ readonly = input(false, {transform: booleanAttribute}); - /** The values of the current selected items. */ - value = model([]); + /** The values of the currently selected items. */ + values = model([]); /** The Listbox UIPattern. */ - pattern: ListboxPattern; + readonly _pattern: ListboxPattern; /** Whether the listbox has received focus yet. */ private _hasFocused = signal(false); @@ -132,20 +153,20 @@ export class Listbox { activeItem: signal(undefined), textDirection: this.textDirection, element: () => this._elementRef.nativeElement, - combobox: () => this._popup?.combobox?.pattern, + combobox: () => this._popup?.combobox?._pattern, }; - this.pattern = this._popup?.combobox + this._pattern = this._popup?.combobox ? new ComboboxListboxPattern(inputs) : new ListboxPattern(inputs); if (this._popup) { - this._popup.controls.set(this.pattern as ComboboxListboxPattern); + this._popup._controls.set(this._pattern as ComboboxListboxPattern); } afterRenderEffect(() => { if (typeof ngDevMode === 'undefined' || ngDevMode) { - const violations = this.pattern.validate(); + const violations = this._pattern.validate(); for (const violation of violations) { console.error(violation); } @@ -154,7 +175,7 @@ export class Listbox { afterRenderEffect(() => { if (!this._hasFocused()) { - this.pattern.setDefaultState(); + this._pattern.setDefaultState(); } }); @@ -165,64 +186,85 @@ export class Listbox { const activeItem = untracked(() => inputs.activeItem()); if (!items.some(i => i === activeItem) && activeItem) { - this.pattern.listBehavior.unfocus(); + this._pattern.listBehavior.unfocus(); } }); - // Ensure that the value is always in sync with the available options. + // Ensure that the values are always in sync with the available options. afterRenderEffect(() => { const items = inputs.items(); - const value = untracked(() => this.value()); + const values = untracked(() => this.values()); - if (items && value.some(v => !items.some(i => i.value() === v))) { - this.value.set(value.filter(v => items.some(i => i.value() === v))); + if (items && values.some(v => !items.some(i => i.value() === v))) { + this.values.set(values.filter(v => items.some(i => i.value() === v))); } }); } - onFocus() { + _onFocus() { this._hasFocused.set(true); } + + scrollActiveItemIntoView(options: ScrollIntoViewOptions = {block: 'nearest'}) { + this._pattern.inputs.activeItem()?.element()?.scrollIntoView(options); + } + + /** Navigates to the first item in the listbox. */ + gotoFirst() { + this._pattern.listBehavior.first(); + } } -/** A selectable option in a Listbox. */ +/** + * A selectable option in an `ngListbox`. + * + * This directive should be applied to an element (e.g., `
      • `, `
        `) within an + * `ngListbox`. The `value` input is used to identify the option, and the `label` input provides + * the accessible name for the option. + * + * ```html + *
      • + * Item Name + *
      • + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: '[ngOption]', exportAs: 'ngOption', host: { 'role': 'option', - 'class': 'ng-option', - '[attr.data-active]': 'pattern.active()', - '[attr.id]': 'pattern.id()', - '[attr.tabindex]': 'pattern.tabindex()', - '[attr.aria-selected]': 'pattern.selected()', - '[attr.aria-disabled]': 'pattern.disabled()', + '[attr.data-active]': 'active()', + '[attr.id]': '_pattern.id()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.aria-selected]': '_pattern.selected()', + '[attr.aria-disabled]': '_pattern.disabled()', }, }) export class Option { - /** A reference to the option element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + + /** Whether the option is currently active (focused). */ + active = computed(() => this._pattern.active()); + /** The parent Listbox. */ private readonly _listbox = inject(Listbox); /** A unique identifier for the option. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-option-'); - - // TODO(wagnermaciel): https://github.com/angular/components/pull/30495#discussion_r1972601144. - /** A unique identifier for the option. */ - protected id = computed(() => this._generatedId); + readonly id = input(inject(_IdGenerator).getId('ng-option-', true)); // TODO(wagnermaciel): See if we want to change how we handle this since textContent is not // reactive. See https://github.com/angular/components/pull/30495#discussion_r1961260216. /** The text used by the typeahead search. */ - protected searchTerm = computed(() => this.label() ?? this.element().textContent); + protected searchTerm = computed(() => this.label() ?? this.element.textContent); /** The parent Listbox UIPattern. */ - protected listbox = computed(() => this._listbox.pattern); - - /** A reference to the option element to be focused on navigation. */ - protected element = computed(() => this._elementRef.nativeElement); + private readonly _listboxPattern = computed(() => this._listbox._pattern); /** The value of the option. */ value = input.required(); @@ -233,13 +275,16 @@ export class Option { /** The text used by the typeahead search. */ label = input(); + /** Whether the option is selected. */ + readonly selected = computed(() => this._pattern.selected()); + /** The Option UIPattern. */ - pattern = new OptionPattern({ + readonly _pattern = new OptionPattern({ ...this, id: this.id, value: this.value, - listbox: this.listbox, - element: this.element, - searchTerm: this.searchTerm, + listbox: this._listboxPattern, + element: () => this.element, + searchTerm: () => this.searchTerm() ?? '', }); } diff --git a/src/aria/menu/BUILD.bazel b/src/aria/menu/BUILD.bazel index 10c8ec376f4e..8d77abf0b0e3 100644 --- a/src/aria/menu/BUILD.bazel +++ b/src/aria/menu/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite") +load("//tools/adev-api-extraction:extract_api_to_json.bzl", "extract_api_to_json") package(default_visibility = ["//visibility:public"]) @@ -10,7 +11,7 @@ ng_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns", + "//src/aria/private", "//src/cdk/a11y", "//src/cdk/bidi", ], @@ -36,3 +37,23 @@ ng_web_test_suite( name = "unit_tests", deps = [":unit_test_sources"], ) + +filegroup( + name = "source-files", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), +) + +extract_api_to_json( + name = "json_api", + srcs = [ + ":source-files", + ], + entry_point = ":index.ts", + module_name = "@angular/aria/menu", + output_name = "aria-menu.json", + private_modules = [""], + repo = "angular/components", +) diff --git a/src/aria/menu/index.ts b/src/aria/menu/index.ts index 135a0a300210..baacdeadfc02 100644 --- a/src/aria/menu/index.ts +++ b/src/aria/menu/index.ts @@ -6,4 +6,4 @@ * found in the LICENSE file at https://angular.dev/license */ -export {Menu, MenuBar, MenuItem, MenuTrigger} from './menu'; +export {Menu, MenuBar, MenuContent, MenuItem, MenuTrigger} from './menu'; diff --git a/src/aria/menu/menu.spec.ts b/src/aria/menu/menu.spec.ts index 9ed1e34e958f..e6066ea053d9 100644 --- a/src/aria/menu/menu.spec.ts +++ b/src/aria/menu/menu.spec.ts @@ -1,7 +1,8 @@ import {Component, DebugElement} from '@angular/core'; -import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing'; +import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Menu, MenuBar, MenuItem, MenuTrigger} from './menu'; +import {provideFakeDirectionality} from '@angular/cdk/testing/private'; describe('Standalone Menu Pattern', () => { let fixture: ComponentFixture; @@ -17,8 +18,9 @@ describe('Standalone Menu Pattern', () => { fixture.detectChanges(); }; - const mouseover = (element: Element) => { + const mouseover = async (element: Element) => { element.dispatchEvent(new MouseEvent('mouseover', {bubbles: true})); + await new Promise(resolve => setTimeout(resolve, 0)); fixture.detectChanges(); }; @@ -37,8 +39,10 @@ describe('Standalone Menu Pattern', () => { fixture.detectChanges(); }; - function setupMenu() { - TestBed.configureTestingModule({}); + function setupMenu(opts?: {textDirection: 'ltr' | 'rtl'}) { + TestBed.configureTestingModule({ + providers: [provideFakeDirectionality(opts?.textDirection ?? 'ltr')], + }); fixture = TestBed.createComponent(StandaloneMenuExample); fixture.detectChanges(); getItem('Apple')?.focus(); @@ -102,15 +106,15 @@ describe('Standalone Menu Pattern', () => { }); describe('Typeahead', () => { - it('should move the active item to the next item that starts with the typed character', fakeAsync(() => { + it('should move the active item to the next item that starts with the typed character', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); keydown(apple!, 'b'); expect(document.activeElement).toBe(banana); - })); + }); - it('should support multi-character typeahead', fakeAsync(() => { + it('should support multi-character typeahead', () => { const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); @@ -118,13 +122,11 @@ describe('Standalone Menu Pattern', () => { keydown(apple!, 'b'); expect(document.activeElement).toBe(banana); - tick(100); keydown(document.activeElement!, 'e'); - expect(document.activeElement).toBe(berries); - })); + }); - it('should wrap when reaching the end of the list during typeahead', fakeAsync(() => { + it('should wrap when reaching the end of the list during typeahead', () => { const apple = getItem('Apple'); const cherry = getItem('Cherry'); @@ -135,14 +137,14 @@ describe('Standalone Menu Pattern', () => { // Type 'a', which should wrap to 'Apple' keydown(document.activeElement!, 'a'); expect(document.activeElement).toBe(apple); - })); + }); - it('should not move the active item if no item matches the typed character', fakeAsync(() => { + it('should not move the active item if no item matches the typed character', () => { const apple = getItem('Apple'); keydown(apple!, 'z'); expect(document.activeElement).toBe(apple); - })); + }); }); }); @@ -151,49 +153,49 @@ describe('Standalone Menu Pattern', () => { it('should select an item on click', () => { const banana = getItem('Banana'); - spyOn(fixture.componentInstance, 'onSubmit'); + spyOn(fixture.componentInstance, 'onSelect'); click(banana!); - expect(fixture.componentInstance.onSubmit).toHaveBeenCalledWith('Banana'); + expect(fixture.componentInstance.onSelect).toHaveBeenCalledWith('Banana'); }); it('should select an item on enter', () => { const banana = getItem('Banana'); - spyOn(fixture.componentInstance, 'onSubmit'); + spyOn(fixture.componentInstance, 'onSelect'); keydown(document.activeElement!, 'ArrowDown'); // Move focus to Banana expect(document.activeElement).toBe(banana); keydown(banana!, 'Enter'); - expect(fixture.componentInstance.onSubmit).toHaveBeenCalledWith('Banana'); + expect(fixture.componentInstance.onSelect).toHaveBeenCalledWith('Banana'); }); it('should select an item on space', () => { const banana = getItem('Banana'); - spyOn(fixture.componentInstance, 'onSubmit'); + spyOn(fixture.componentInstance, 'onSelect'); keydown(document.activeElement!, 'ArrowDown'); // Move focus to Banana expect(document.activeElement).toBe(banana); keydown(banana!, ' '); - expect(fixture.componentInstance.onSubmit).toHaveBeenCalledWith('Banana'); + expect(fixture.componentInstance.onSelect).toHaveBeenCalledWith('Banana'); }); it('should not select a disabled item', () => { const cherry = getItem('Cherry'); - spyOn(fixture.componentInstance, 'onSubmit'); + spyOn(fixture.componentInstance, 'onSelect'); click(cherry!); - expect(fixture.componentInstance.onSubmit).not.toHaveBeenCalled(); + expect(fixture.componentInstance.onSelect).not.toHaveBeenCalled(); keydown(document.activeElement!, 'End'); expect(document.activeElement).toBe(cherry); keydown(cherry!, 'Enter'); - expect(fixture.componentInstance.onSubmit).not.toHaveBeenCalled(); + expect(fixture.componentInstance.onSelect).not.toHaveBeenCalled(); keydown(cherry!, ' '); - expect(fixture.componentInstance.onSubmit).not.toHaveBeenCalled(); + expect(fixture.componentInstance.onSelect).not.toHaveBeenCalled(); }); }); @@ -308,26 +310,25 @@ describe('Standalone Menu Pattern', () => { expect(document.activeElement).toBe(berries); }); - it('should open submenu on mouseover', fakeAsync(() => { + it('should open submenu on mouseover', async () => { const berries = getItem('Berries'); - mouseover(berries!); - tick(); + await mouseover(berries!); expect(isSubmenuExpanded()).toBe(true); - })); + }); it('should close on selecting an item on click', () => { - spyOn(fixture.componentInstance, 'onSubmit'); + spyOn(fixture.componentInstance, 'onSelect'); click(getItem('Berries')!); // open submenu expect(isSubmenuExpanded()).toBe(true); click(getItem('Blueberry')!); - expect(fixture.componentInstance.onSubmit).toHaveBeenCalledWith('Blueberry'); + expect(fixture.componentInstance.onSelect).toHaveBeenCalledWith('Blueberry'); expect(isSubmenuExpanded()).toBe(false); }); it('should close on selecting an item on enter', () => { - spyOn(fixture.componentInstance, 'onSubmit'); + spyOn(fixture.componentInstance, 'onSelect'); const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); @@ -341,12 +342,12 @@ describe('Standalone Menu Pattern', () => { keydown(blueberry!, 'Enter'); - expect(fixture.componentInstance.onSubmit).toHaveBeenCalledWith('Blueberry'); + expect(fixture.componentInstance.onSelect).toHaveBeenCalledWith('Blueberry'); expect(isSubmenuExpanded()).toBe(false); }); it('should close on selecting an item on space', () => { - spyOn(fixture.componentInstance, 'onSubmit'); + spyOn(fixture.componentInstance, 'onSelect'); const apple = getItem('Apple'); const banana = getItem('Banana'); const berries = getItem('Berries'); @@ -360,7 +361,7 @@ describe('Standalone Menu Pattern', () => { keydown(blueberry!, ' '); - expect(fixture.componentInstance.onSubmit).toHaveBeenCalledWith('Blueberry'); + expect(fixture.componentInstance.onSelect).toHaveBeenCalledWith('Blueberry'); expect(isSubmenuExpanded()).toBe(false); }); @@ -385,34 +386,68 @@ describe('Standalone Menu Pattern', () => { externalElement.remove(); }); - it('should close an unfocused submenu on mouse out', fakeAsync(() => { + it('should close an unfocused submenu on mouse out', async () => { const berries = getItem('Berries'); const submenu = getSubmenu(); - mouseover(berries!); - tick(); + await mouseover(berries!); expect(isSubmenuExpanded()).toBe(true); mouseout(berries!); mouseout(submenu!); - tick(500); expect(isSubmenuExpanded()).toBe(false); - })); + }); - it('should not close an unfocused submenu on mouse out if the parent menu is hovered', fakeAsync(() => { + it('should not close an unfocused submenu on mouse out if the parent menu is hovered', async () => { const berries = getItem('Berries'); const submenu = getSubmenu(); - mouseover(berries!); - tick(); + await mouseover(berries!); expect(isSubmenuExpanded()).toBe(true); mouseout(berries!); mouseover(submenu!); - tick(500); expect(isSubmenuExpanded()).toBe(true); - })); + }); + }); + + describe('RTL', () => { + function isSubmenuExpanded(): boolean { + const berries = getItem('Berries'); + return berries?.getAttribute('aria-expanded') === 'true'; + } + + beforeEach(() => setupMenu({textDirection: 'rtl'})); + + it('should open submenu on arrow left', () => { + const apple = getItem('Apple'); + const banana = getItem('Banana'); + const berries = getItem('Berries'); + const blueberry = getItem('Blueberry'); + + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'ArrowLeft'); + + expect(isSubmenuExpanded()).toBe(true); + expect(document.activeElement).toBe(blueberry); + }); + + it('should close submenu on arrow right', () => { + const apple = getItem('Apple'); + const banana = getItem('Banana'); + const berries = getItem('Berries'); + const blueberry = getItem('Blueberry'); + + keydown(apple!, 'ArrowDown'); + keydown(banana!, 'ArrowDown'); + keydown(berries!, 'ArrowLeft'); + keydown(blueberry!, 'ArrowRight'); + + expect(isSubmenuExpanded()).toBe(false); + expect(document.activeElement).toBe(berries); + }); }); }); @@ -441,7 +476,6 @@ describe('Menu Trigger Pattern', () => { }; function setupMenu() { - TestBed.configureTestingModule({}); fixture = TestBed.createComponent(MenuTriggerExample); fixture.detectChanges(); getItem('Apple')?.focus(); @@ -608,8 +642,10 @@ describe('Menu Bar Pattern', () => { fixture.detectChanges(); }; - function setupMenu() { - TestBed.configureTestingModule({}); + function setupMenu(opts?: {textDirection: 'ltr' | 'rtl'}) { + TestBed.configureTestingModule({ + providers: [provideFakeDirectionality(opts?.textDirection ?? 'ltr')], + }); fixture = TestBed.createComponent(MenuBarExample); fixture.detectChanges(); getMenuBarItem('File')?.focus(); @@ -641,10 +677,7 @@ describe('Menu Bar Pattern', () => { } describe('Navigation', () => { - beforeEach(() => { - setupMenu(); - getMenuBarItem('File')?.focus(); - }); + beforeEach(() => setupMenu()); it('should navigate to the first item on arrow down', () => { const file = getMenuBarItem('File'); @@ -873,16 +906,50 @@ describe('Menu Bar Pattern', () => { expect(isExpanded('Edit')).toBe(true); }); }); + + describe('RTL', () => { + beforeEach(() => setupMenu({textDirection: 'rtl'})); + + it('should focus the first item of the next menubar item on arrow left', () => { + const edit = getMenuBarItem('Edit'); + const file = getMenuBarItem('File'); + const view = getMenuBarItem('View'); + const documentation = getMenuBarItem('Documentation'); + const zoomIn = getMenuItem('Zoom In'); + + keydown(file!, 'ArrowLeft'); + keydown(edit!, 'ArrowLeft'); + keydown(view!, 'ArrowDown'); + + keydown(zoomIn!, 'ArrowLeft'); + expect(document.activeElement).toBe(documentation); + }); + + it('should focus the first item of the previous menubar item on arrow right', () => { + const edit = getMenuBarItem('Edit'); + const file = getMenuBarItem('File'); + const view = getMenuBarItem('View'); + const undo = getMenuItem('Undo'); + const zoomIn = getMenuItem('Zoom In'); + + keydown(file!, 'ArrowLeft'); + keydown(edit!, 'ArrowLeft'); + keydown(view!, 'ArrowDown'); + + keydown(zoomIn!, 'ArrowRight'); + expect(document.activeElement).toBe(undo); + }); + }); }); @Component({ template: ` -
        +
        Apple
        Banana
        -
        Berries
        +
        Berries
        -
        +
        Blueberry
        Blackberry
        Strawberry
        @@ -894,19 +961,19 @@ describe('Menu Bar Pattern', () => { imports: [Menu, MenuItem], }) class StandaloneMenuExample { - onSubmit(value: string) {} + onSelect(value: string) {} } @Component({ template: ` - + -
        +
        Apple
        Banana
        -
        Berries
        +
        Berries
        -
        +
        Blueberry
        Blackberry
        Strawberry
        @@ -923,24 +990,24 @@ class MenuTriggerExample {} template: `
        File
        -
        Edit
        +
        Edit
        -
        +
        Undo
        Redo
        -
        View
        +
        View
        -
        +
        Zoom In
        Zoom Out
        Full Screen
        -
        Help
        +
        Help
        -
        +
        Documentation
        About
        diff --git a/src/aria/menu/menu.ts b/src/aria/menu/menu.ts index 1e5b0090db60..4f1dfcb491a9 100644 --- a/src/aria/menu/menu.ts +++ b/src/aria/menu/menu.ts @@ -8,9 +8,11 @@ import { afterRenderEffect, + booleanAttribute, computed, contentChildren, Directive, + effect, ElementRef, inject, input, @@ -26,86 +28,145 @@ import { MenuItemPattern, MenuPattern, MenuTriggerPattern, -} from '@angular/aria/ui-patterns'; -import {toSignal} from '@angular/core/rxjs-interop'; + DeferredContent, + DeferredContentAware, +} from '@angular/aria/private'; +import {_IdGenerator} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; /** * A trigger for a menu. * - * The menu trigger is used to open and close menus, and can be placed on menu items to connect - * sub-menus. + * The `ngMenuTrigger` directive is used to open and close menus. It can be applied to + * any interactive element (e.g., a button) to associate it with a `ngMenu` instance. + * It also supports linking to sub-menus when applied to a `ngMenuItem`. + * + * ```html + * + * + *
        + *
        Item 1
        + *
        Item 2
        + *
        + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: 'button[ngMenuTrigger]', exportAs: 'ngMenuTrigger', host: { - 'class': 'ng-menu-trigger', - '[attr.tabindex]': 'uiPattern.tabindex()', - '[attr.aria-haspopup]': 'uiPattern.hasPopup()', - '[attr.aria-expanded]': 'uiPattern.expanded()', - '[attr.aria-controls]': 'uiPattern.submenu()?.id()', - '(click)': 'uiPattern.onClick()', - '(keydown)': 'uiPattern.onKeydown($event)', - '(focusout)': 'uiPattern.onFocusOut($event)', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.disabled]': '!softDisabled() && _pattern.disabled() ? true : null', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-haspopup]': 'hasPopup()', + '[attr.aria-expanded]': 'expanded()', + '[attr.aria-controls]': '_pattern.menu()?.id()', + '(click)': '_pattern.onClick()', + '(keydown)': '_pattern.onKeydown($event)', + '(focusout)': '_pattern.onFocusOut($event)', + '(focusin)': '_pattern.onFocusIn()', }, }) export class MenuTrigger { - /** A reference to the menu trigger element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); - /** A reference to the menu element. */ - readonly element: HTMLButtonElement = this._elementRef.nativeElement; + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; - // TODO(wagnermaciel): See we can remove the need to pass in a submenu. + /** The directionality (LTR / RTL) context for the application (or a subtree of it). */ + readonly textDirection = inject(Directionality).valueSignal; - /** The submenu associated with the menu trigger. */ - submenu = input | undefined>(undefined); + /** The menu associated with the trigger. */ + menu = input | undefined>(undefined); - /** A callback function triggered when a menu item is selected. */ - onSubmit = output(); + /** Whether the menu is expanded. */ + readonly expanded = computed(() => this._pattern.expanded()); + + /** Whether the menu trigger has a popup. */ + readonly hasPopup = computed(() => this._pattern.hasPopup()); + + /** Whether the menu trigger is disabled. */ + readonly disabled = input(false, {transform: booleanAttribute}); + + /** Whether the menu trigger is soft disabled. */ + readonly softDisabled = input(true, {transform: booleanAttribute}); /** The menu trigger ui pattern instance. */ - uiPattern: MenuTriggerPattern = new MenuTriggerPattern({ - onSubmit: (value: V) => this.onSubmit.emit(value), + _pattern: MenuTriggerPattern = new MenuTriggerPattern({ + textDirection: this.textDirection, element: computed(() => this._elementRef.nativeElement), - submenu: computed(() => this.submenu()?.uiPattern), + menu: computed(() => this.menu()?._pattern), + disabled: () => this.disabled(), }); + + constructor() { + effect(() => this.menu()?.parent.set(this)); + } + + /** Opens the menu focusing on the first menu item. */ + open() { + this._pattern.open({first: true}); + } + + /** Closes the menu. */ + close() { + this._pattern.close(); + } } /** * A list of menu items. * - * A menu is used to offer a list of menu item choices to users. Menus can be nested within other - * menus to create sub-menus. + * A `ngMenu` is used to offer a list of menu item choices to users. Menus can be nested + * within other menus to create sub-menus. It works in conjunction with `ngMenuTrigger` + * and `ngMenuItem` directives. * * ```html - * + * * - *
        + *
        *
        Star
        *
        Edit
        - *
        Delete
        + *
        More
        + *
        + * + *
        + *
        Sub Item 1
        + *
        Sub Item 2
        *
        * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngMenu]', exportAs: 'ngMenu', host: { 'role': 'menu', - 'class': 'ng-menu', - '[attr.id]': 'uiPattern.id()', - '[attr.data-visible]': 'uiPattern.isVisible()', - '(keydown)': 'uiPattern.onKeydown($event)', - '(mouseover)': 'uiPattern.onMouseOver($event)', - '(mouseout)': 'uiPattern.onMouseOut($event)', - '(focusout)': 'uiPattern.onFocusOut($event)', - '(focusin)': 'uiPattern.onFocusIn()', - '(click)': 'uiPattern.onClick($event)', + '[attr.id]': '_pattern.id()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.tabindex]': 'tabIndex()', + '[attr.data-visible]': 'visible()', + '(keydown)': '_pattern.onKeydown($event)', + '(mouseover)': '_pattern.onMouseOver($event)', + '(mouseout)': '_pattern.onMouseOut($event)', + '(focusout)': '_pattern.onFocusOut($event)', + '(focusin)': '_pattern.onFocusIn()', + '(click)': '_pattern.onClick($event)', }, + hostDirectives: [ + { + directive: DeferredContentAware, + inputs: ['preserveContent'], + }, + ], }) export class Menu { + /** The DeferredContentAware host directive. */ + private readonly _deferredContentAware = inject(DeferredContentAware, {optional: true}); + /** The menu items contained in the menu. */ readonly _allItems = contentChildren>(MenuItem, {descendants: true}); @@ -114,37 +175,32 @@ export class Menu { this._allItems().filter(i => i.parent === this), ); - /** A reference to the menu element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); - /** A reference to the menu element. */ - readonly element: HTMLElement = this._elementRef.nativeElement; + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; /** The directionality (LTR / RTL) context for the application (or a subtree of it). */ - private readonly _directionality = inject(Directionality); - - /** A signal wrapper for directionality. */ - readonly textDirection = toSignal(this._directionality.change, { - initialValue: this._directionality.value, - }); - - /** The submenu associated with the menu. */ - readonly submenu = input | undefined>(undefined); + readonly textDirection = inject(Directionality).valueSignal; /** The unique ID of the menu. */ - readonly id = input(Math.random().toString(36).substring(2, 10)); + readonly id = input(inject(_IdGenerator).getId('ng-menu-', true)); /** Whether the menu should wrap its items. */ - readonly wrap = input(true); + readonly wrap = input(true, {transform: booleanAttribute}); + + /** The delay in milliseconds before the typeahead buffer is cleared. */ + readonly typeaheadDelay = input(500); // Picked arbitrarily. - /** The delay in seconds before the typeahead buffer is cleared. */ - readonly typeaheadDelay = input(0.5); // Picked arbitrarily. + /** Whether the menu is disabled. */ + readonly disabled = input(false, {transform: booleanAttribute}); /** A reference to the parent menu item or menu trigger. */ - readonly parent = input | MenuItem>(); + readonly parent = signal | MenuItem | undefined>(undefined); /** The menu ui pattern instance. */ - readonly uiPattern: MenuPattern; + readonly _pattern: MenuPattern; /** * The menu items as a writable signal. @@ -153,26 +209,44 @@ export class Menu { * sometimes the items array is empty. The bug can be reproduced by switching this to use a * computed and then quickly opening and closing menus in the dev app. */ - readonly items = () => this._items().map(i => i.uiPattern); + private readonly _itemPatterns = () => this._items().map(i => i._pattern); /** Whether the menu is visible. */ - isVisible = computed(() => this.uiPattern.isVisible()); + readonly visible = computed(() => this._pattern.visible()); + + /** The tab index of the menu. */ + readonly tabIndex = computed(() => this._pattern.tabIndex()); /** A callback function triggered when a menu item is selected. */ - onSubmit = output(); + onSelect = output(); + + /** The delay in milliseconds before expanding sub-menus on hover. */ + readonly expansionDelay = input(100); // Arbitrarily chosen. constructor() { - this.uiPattern = new MenuPattern({ + this._pattern = new MenuPattern({ ...this, - parent: computed(() => this.parent()?.uiPattern), + parent: computed(() => this.parent()?._pattern), + items: this._itemPatterns, multi: () => false, - skipDisabled: () => false, + softDisabled: () => true, focusMode: () => 'roving', orientation: () => 'vertical', selectionMode: () => 'explicit', activeItem: signal(undefined), element: computed(() => this._elementRef.nativeElement), - onSubmit: (value: V) => this.onSubmit.emit(value), + onSelect: (value: V) => this.onSelect.emit(value), + }); + + afterRenderEffect(() => { + const parent = this.parent(); + if (parent instanceof MenuItem && parent.parent instanceof MenuBar) { + this._deferredContentAware?.contentVisible.set(true); + } else { + this._deferredContentAware?.contentVisible.set( + this._pattern.visible() || !!this.parent()?._pattern.hasBeenFocused(), + ); + } }); // TODO(wagnermaciel): This is a redundancy needed for if the user uses display: none to hide @@ -180,58 +254,64 @@ export class Menu { // update the display property. The result is focus() being called on an element that is not // focusable. This simply retries focusing the element after render. afterRenderEffect(() => { - if (this.uiPattern.isVisible()) { - const activeItem = untracked(() => this.uiPattern.inputs.activeItem()); - this.uiPattern.listBehavior.goto(activeItem!); + if (this._pattern.visible()) { + const activeItem = untracked(() => this._pattern.inputs.activeItem()); + this._pattern.listBehavior.goto(activeItem!); } }); afterRenderEffect(() => { - if (!this.uiPattern.hasBeenFocused()) { - this.uiPattern.setDefaultState(); + if (!this._pattern.hasBeenFocused() && this._items().length) { + untracked(() => this._pattern.setDefaultState()); } }); } - // TODO(wagnermaciel): Author close, closeAll, and open methods for each directive. - /** Closes the menu. */ - close(opts?: {refocus?: boolean}) { - this.uiPattern.inputs.parent()?.close(opts); - } - - /** Closes all parent menus. */ - closeAll(opts?: {refocus?: boolean}) { - const root = this.uiPattern.root(); - - if (root instanceof MenuTriggerPattern) { - root.close(opts); - } - - if (root instanceof MenuPattern || root instanceof MenuBarPattern) { - root.inputs.activeItem()?.close(opts); - } + close() { + this._pattern.close(); } } /** * A menu bar of menu items. * - * Like the menu, a menubar is used to offer a list of menu item choices to users. However, a - * menubar is used to display a persistent, top-level, - * always-visible set of menu item choices. + * Like the `ngMenu`, a `ngMenuBar` is used to offer a list of menu item choices to users. + * However, a menubar is used to display a persistent, top-level, always-visible set of + * menu item choices, typically found at the top of an application window. + * + * ```html + *
        + * + * + *
        + * + *
        + *
        New
        + *
        Open
        + *
        + * + *
        + *
        Cut
        + *
        Copy
        + *
        + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngMenuBar]', exportAs: 'ngMenuBar', host: { 'role': 'menubar', - 'class': 'ng-menu-bar', - '(keydown)': 'uiPattern.onKeydown($event)', - '(mouseover)': 'uiPattern.onMouseOver($event)', - '(click)': 'uiPattern.onClick($event)', - '(focusin)': 'uiPattern.onFocusIn()', - '(focusout)': 'uiPattern.onFocusOut($event)', + '[attr.disabled]': '!softDisabled() && _pattern.disabled() ? true : null', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.tabindex]': '_pattern.tabIndex()', + '(keydown)': '_pattern.onKeydown($event)', + '(mouseover)': '_pattern.onMouseOver($event)', + '(click)': '_pattern.onClick($event)', + '(focusin)': '_pattern.onFocusIn()', + '(focusout)': '_pattern.onFocusOut($event)', }, }) export class MenuBar { @@ -241,91 +321,107 @@ export class MenuBar { readonly _items: SignalLike[]> = () => this._allItems().filter(i => i.parent === this); - /** A reference to the menu element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); - /** A reference to the menubar element. */ - readonly element: HTMLElement = this._elementRef.nativeElement; + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; - /** The directionality (LTR / RTL) context for the application (or a subtree of it). */ - private readonly _directionality = inject(Directionality); + /** Whether the menubar is disabled. */ + readonly disabled = input(false, {transform: booleanAttribute}); - /** A signal wrapper for directionality. */ - readonly textDirection = toSignal(this._directionality.change, { - initialValue: this._directionality.value, - }); + /** Whether the menubar is soft disabled. */ + readonly softDisabled = input(true, {transform: booleanAttribute}); - /** The value of the menu. */ - readonly value = model([]); + /** The directionality (LTR / RTL) context for the application (or a subtree of it). */ + readonly textDirection = inject(Directionality).valueSignal; + + /** The values of the currently selected menu items. */ + readonly values = model([]); /** Whether the menu should wrap its items. */ - readonly wrap = input(true); + readonly wrap = input(true, {transform: booleanAttribute}); - /** The delay in seconds before the typeahead buffer is cleared. */ - readonly typeaheadDelay = input(0.5); + /** The delay in milliseconds before the typeahead buffer is cleared. */ + readonly typeaheadDelay = input(500); /** The menu ui pattern instance. */ - readonly uiPattern: MenuBarPattern; + readonly _pattern: MenuBarPattern; /** The menu items as a writable signal. */ - readonly items = signal[]>([]); + private readonly _itemPatterns = signal[]>([]); /** A callback function triggered when a menu item is selected. */ - onSubmit = output(); + onSelect = output(); constructor() { - this.uiPattern = new MenuBarPattern({ + this._pattern = new MenuBarPattern({ ...this, + items: this._itemPatterns, multi: () => false, - skipDisabled: () => false, + softDisabled: () => true, focusMode: () => 'roving', orientation: () => 'horizontal', selectionMode: () => 'explicit', - onSubmit: (value: V) => this.onSubmit.emit(value), + onSelect: (value: V) => this.onSelect.emit(value), activeItem: signal(undefined), element: computed(() => this._elementRef.nativeElement), }); afterRenderEffect(() => { - this.items.set(this._items().map(i => i.uiPattern)); + this._itemPatterns.set(this._items().map(i => i._pattern)); }); afterRenderEffect(() => { - if (!this.uiPattern.hasBeenFocused()) { - this.uiPattern.setDefaultState(); + if (!this._pattern.hasBeenFocused()) { + this._pattern.setDefaultState(); } }); } + + /** Closes the menubar. */ + close() { + this._pattern.close(); + } } /** * An item in a Menu. * - * Menu items can be used in menus and menubars to represent a choice or action a user can take. + * `ngMenuItem` directives can be used in `ngMenu` and `ngMenuBar` to represent a choice + * or action a user can take. They can also act as triggers for sub-menus. + * + * ```html + *
        Action Item
        + * + *
        Submenu Trigger
        + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngMenuItem]', exportAs: 'ngMenuItem', host: { 'role': 'menuitem', - 'class': 'ng-menu-item', - '[attr.tabindex]': 'uiPattern.tabindex()', - '[attr.data-active]': 'uiPattern.isActive()', - '[attr.aria-haspopup]': 'uiPattern.hasPopup()', - '[attr.aria-expanded]': 'uiPattern.expanded()', - '[attr.aria-disabled]': 'uiPattern.disabled()', - '[attr.aria-controls]': 'uiPattern.submenu()?.id()', + '(focusin)': '_pattern.onFocusIn()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.data-active]': 'active()', + '[attr.aria-haspopup]': 'hasPopup()', + '[attr.aria-expanded]': 'expanded()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-controls]': '_pattern.submenu()?.id()', }, }) export class MenuItem { - /** A reference to the menu item element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); - /** A reference to the menu element. */ - readonly element: HTMLElement = this._elementRef.nativeElement; + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; /** The unique ID of the menu item. */ - readonly id = input(Math.random().toString(36).substring(2, 10)); + readonly id = input(inject(_IdGenerator).getId('ng-menu-item-', true)); /** The value of the menu item. */ readonly value = input.required(); @@ -350,14 +446,61 @@ export class MenuItem { /** The submenu associated with the menu item. */ readonly submenu = input | undefined>(undefined); + /** Whether the menu item is active. */ + readonly active = computed(() => this._pattern.active()); + + /** Whether the menu is expanded. */ + readonly expanded = computed(() => this._pattern.expanded()); + + /** Whether the menu item has a popup. */ + readonly hasPopup = computed(() => this._pattern.hasPopup()); + /** The menu item ui pattern instance. */ - readonly uiPattern: MenuItemPattern = new MenuItemPattern({ + readonly _pattern: MenuItemPattern = new MenuItemPattern({ id: this.id, value: this.value, element: computed(() => this._elementRef.nativeElement), disabled: this.disabled, searchTerm: this.searchTerm, - parent: computed(() => this.parent?.uiPattern), - submenu: computed(() => this.submenu()?.uiPattern), + parent: computed(() => this.parent?._pattern), + submenu: computed(() => this.submenu()?._pattern), }); + + constructor() { + effect(() => this.submenu()?.parent.set(this)); + } + + /** Opens the submenu focusing on the first menu item. */ + open() { + this._pattern.open({first: true}); + } + + /** Closes the submenu. */ + close() { + this._pattern.close(); + } } + +/** + * Defers the rendering of the menu content. + * + * This structural directive should be applied to an `ng-template` within a `ngMenu` + * or `ngMenuBar` to lazily render its content only when the menu is opened. + * + * ```html + *
        + * + *
        Lazy Item 1
        + *
        Lazy Item 2
        + *
        + *
        + * ``` + * + * @developerPreview 21.0 + */ +@Directive({ + selector: 'ng-template[ngMenuContent]', + exportAs: 'ngMenuContent', + hostDirectives: [DeferredContent], +}) +export class MenuContent {} diff --git a/src/aria/private/BUILD.bazel b/src/aria/private/BUILD.bazel new file mode 100644 index 000000000000..f688ab1b20e1 --- /dev/null +++ b/src/aria/private/BUILD.bazel @@ -0,0 +1,24 @@ +load("//tools:defaults.bzl", "ts_project") + +package(default_visibility = ["//visibility:public"]) + +ts_project( + name = "private", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + deps = [ + "//:node_modules/@angular/core", + "//src/aria/private/accordion", + "//src/aria/private/behaviors/signal-like", + "//src/aria/private/combobox", + "//src/aria/private/deferred-content", + "//src/aria/private/grid", + "//src/aria/private/listbox", + "//src/aria/private/menu", + "//src/aria/private/tabs", + "//src/aria/private/toolbar", + "//src/aria/private/tree", + ], +) diff --git a/src/aria/ui-patterns/accordion/BUILD.bazel b/src/aria/private/accordion/BUILD.bazel similarity index 61% rename from src/aria/ui-patterns/accordion/BUILD.bazel rename to src/aria/private/accordion/BUILD.bazel index eb6bae9dcd69..373d84447d02 100644 --- a/src/aria/ui-patterns/accordion/BUILD.bazel +++ b/src/aria/private/accordion/BUILD.bazel @@ -9,12 +9,12 @@ ts_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/expansion", - "//src/aria/ui-patterns/behaviors/list-focus", - "//src/aria/ui-patterns/behaviors/list-navigation", - "//src/aria/ui-patterns/behaviors/list-selection", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/expansion", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/list-navigation", + "//src/aria/private/behaviors/list-selection", + "//src/aria/private/behaviors/signal-like", ], ) @@ -27,7 +27,7 @@ ng_project( deps = [ ":accordion", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", "//src/cdk/keycodes", "//src/cdk/testing/private", ], diff --git a/src/aria/ui-patterns/accordion/accordion.spec.ts b/src/aria/private/accordion/accordion.spec.ts similarity index 82% rename from src/aria/ui-patterns/accordion/accordion.spec.ts rename to src/aria/private/accordion/accordion.spec.ts index ccbb5181ceb0..567790ef52a2 100644 --- a/src/aria/ui-patterns/accordion/accordion.spec.ts +++ b/src/aria/private/accordion/accordion.spec.ts @@ -65,10 +65,10 @@ describe('Accordion Pattern', () => { disabled: signal(false), multiExpandable: signal(true), items: signal([]), - expandedIds: signal([]), - skipDisabled: signal(true), + softDisabled: signal(true), wrap: signal(true), element: signal(document.createElement('div')), + getItem: e => triggerPatterns.find(i => i.element() === e), }; groupPattern = new AccordionGroupPattern(groupInputs); @@ -80,7 +80,8 @@ describe('Accordion Pattern', () => { id: signal('trigger-1-id'), element: signal(createAccordionTriggerElement()), disabled: signal(false), - value: signal('panel-1'), // Value should match the panel it controls + panelId: signal('panel-1'), // Value should match the panel it controls + expanded: signal(false), }, { accordionGroup: signal(groupPattern), @@ -88,7 +89,8 @@ describe('Accordion Pattern', () => { id: signal('trigger-2-id'), element: signal(createAccordionTriggerElement()), disabled: signal(false), - value: signal('panel-2'), + panelId: signal('panel-2'), + expanded: signal(false), }, { accordionGroup: signal(groupPattern), @@ -96,7 +98,8 @@ describe('Accordion Pattern', () => { id: signal('trigger-3-id'), element: signal(createAccordionTriggerElement()), disabled: signal(false), - value: signal('panel-3'), + panelId: signal('panel-3'), + expanded: signal(false), }, ]; triggerPatterns = [ @@ -111,17 +114,17 @@ describe('Accordion Pattern', () => { panelInputs = [ { id: signal('panel-1-id'), - value: signal('panel-1'), + panelId: signal('panel-1'), accordionTrigger: signal(undefined), }, { id: signal('panel-2-id'), - value: signal('panel-2'), + panelId: signal('panel-2'), accordionTrigger: signal(undefined), }, { id: signal('panel-3-id'), - value: signal('panel-3'), + panelId: signal('panel-3'), accordionTrigger: signal(undefined), }, ]; @@ -141,12 +144,6 @@ describe('Accordion Pattern', () => { groupInputs.items.set(triggerPatterns); }); - it('expands a panel by setting `value`.', () => { - expect(triggerPatterns[0].expanded()).toBeFalse(); - groupInputs.expandedIds.set(['panel-1']); - expect(triggerPatterns[0].expanded()).toBeTrue(); - }); - it('gets a controlled panel id from a trigger.', () => { expect(panelPatterns[0].id()).toBe('panel-1-id'); expect(triggerPatterns[0].controls()).toBe('panel-1-id'); @@ -159,47 +156,49 @@ describe('Accordion Pattern', () => { describe('Keyboard Navigation', () => { it('does not handle keyboard event if an accordion group is disabled.', () => { groupInputs.disabled.set(true); - triggerPatterns[0].onKeydown(space()); + groupInputs.activeItem.set(triggerPatterns[0]); + groupPattern.onKeydown(space()); expect(panelPatterns[0].hidden()).toBeTrue(); }); it('does not handle keyboard event if an accordion trigger is disabled.', () => { triggerInputs[0].disabled.set(true); - triggerPatterns[0].onKeydown(space()); + groupInputs.activeItem.set(triggerPatterns[0]); + groupPattern.onKeydown(space()); expect(panelPatterns[0].hidden()).toBeTrue(); }); it('navigates to first accordion trigger with home key.', () => { - groupInputs.activeItem.set(groupInputs.items()[2]); + groupInputs.activeItem.set(triggerPatterns[2]); expect(triggerPatterns[2].active()).toBeTrue(); - triggerPatterns[2].onKeydown(home()); + groupPattern.onKeydown(home()); expect(triggerPatterns[2].active()).toBeFalse(); expect(triggerPatterns[0].active()).toBeTrue(); }); it('navigates to last accordion trigger with end key.', () => { - groupInputs.activeItem.set(groupInputs.items()[0]); + groupInputs.activeItem.set(triggerPatterns[0]); expect(triggerPatterns[0].active()).toBeTrue(); - triggerPatterns[0].onKeydown(end()); + groupPattern.onKeydown(end()); expect(triggerPatterns[0].active()).toBeFalse(); expect(triggerPatterns[2].active()).toBeTrue(); }); describe('Vertical Orientation (orientation=vertical)', () => { it('navigates to the next trigger with down key.', () => { - groupInputs.activeItem.set(groupInputs.items()[0]); + groupInputs.activeItem.set(triggerPatterns[0]); expect(triggerPatterns[0].active()).toBeTrue(); expect(triggerPatterns[1].active()).toBeFalse(); - triggerPatterns[0].onKeydown(down()); + groupPattern.onKeydown(down()); expect(triggerPatterns[0].active()).toBeFalse(); expect(triggerPatterns[1].active()).toBeTrue(); }); it('navigates to the previous trigger with up key.', () => { - groupInputs.activeItem.set(groupInputs.items()[1]); + groupInputs.activeItem.set(triggerPatterns[1]); expect(triggerPatterns[0].active()).toBeFalse(); expect(triggerPatterns[1].active()).toBeTrue(); - triggerPatterns[1].onKeydown(up()); + groupPattern.onKeydown(up()); expect(triggerPatterns[1].active()).toBeFalse(); expect(triggerPatterns[0].active()).toBeTrue(); }); @@ -210,19 +209,19 @@ describe('Accordion Pattern', () => { }); it('navigates to the last trigger with up key from first trigger.', () => { - groupInputs.activeItem.set(groupInputs.items()[0]); + groupInputs.activeItem.set(triggerPatterns[0]); expect(triggerPatterns[0].active()).toBeTrue(); expect(triggerPatterns[2].active()).toBeFalse(); - triggerPatterns[0].onKeydown(up()); + groupPattern.onKeydown(up()); expect(triggerPatterns[0].active()).toBeFalse(); expect(triggerPatterns[2].active()).toBeTrue(); }); it('navigates to the first trigger with down key from last trigger.', () => { - groupInputs.activeItem.set(groupInputs.items()[2]); + groupInputs.activeItem.set(triggerPatterns[2]); expect(triggerPatterns[0].active()).toBeFalse(); expect(triggerPatterns[2].active()).toBeTrue(); - triggerPatterns[2].onKeydown(down()); + groupPattern.onKeydown(down()); expect(triggerPatterns[0].active()).toBeTrue(); expect(triggerPatterns[2].active()).toBeFalse(); }); @@ -234,16 +233,16 @@ describe('Accordion Pattern', () => { }); it('stays on the first trigger with up key from first trigger.', () => { - groupInputs.activeItem.set(groupInputs.items()[0]); + groupInputs.activeItem.set(triggerPatterns[0]); expect(triggerPatterns[0].active()).toBeTrue(); - triggerPatterns[0].onKeydown(up()); + groupPattern.onKeydown(up()); expect(triggerPatterns[0].active()).toBeTrue(); }); it('stays on the last trigger with down key from last trigger.', () => { - groupInputs.activeItem.set(groupInputs.items()[2]); + groupInputs.activeItem.set(triggerPatterns[2]); expect(triggerPatterns[2].active()).toBeTrue(); - triggerPatterns[2].onKeydown(down()); + groupPattern.onKeydown(down()); expect(triggerPatterns[2].active()).toBeTrue(); }); }); @@ -255,19 +254,19 @@ describe('Accordion Pattern', () => { }); it('navigates to the next trigger with right key.', () => { - groupInputs.activeItem.set(groupInputs.items()[0]); + groupInputs.activeItem.set(triggerPatterns[0]); expect(triggerPatterns[0].active()).toBeTrue(); expect(triggerPatterns[1].active()).toBeFalse(); - triggerPatterns[0].onKeydown(right()); + groupPattern.onKeydown(right()); expect(triggerPatterns[0].active()).toBeFalse(); expect(triggerPatterns[1].active()).toBeTrue(); }); it('navigates to the previous trigger with left key.', () => { - groupInputs.activeItem.set(groupInputs.items()[1]); + groupInputs.activeItem.set(triggerPatterns[1]); expect(triggerPatterns[0].active()).toBeFalse(); expect(triggerPatterns[1].active()).toBeTrue(); - triggerPatterns[1].onKeydown(left()); + groupPattern.onKeydown(left()); expect(triggerPatterns[1].active()).toBeFalse(); expect(triggerPatterns[0].active()).toBeTrue(); }); @@ -278,19 +277,19 @@ describe('Accordion Pattern', () => { }); it('navigates to the last trigger with left key from first trigger.', () => { - groupInputs.activeItem.set(groupInputs.items()[0]); + groupInputs.activeItem.set(triggerPatterns[0]); expect(triggerPatterns[0].active()).toBeTrue(); expect(triggerPatterns[2].active()).toBeFalse(); - triggerPatterns[0].onKeydown(left()); + groupPattern.onKeydown(left()); expect(triggerPatterns[0].active()).toBeFalse(); expect(triggerPatterns[2].active()).toBeTrue(); }); it('navigates to the first trigger with right key from last trigger.', () => { - groupInputs.activeItem.set(groupInputs.items()[2]); + groupInputs.activeItem.set(triggerPatterns[2]); expect(triggerPatterns[2].active()).toBeTrue(); expect(triggerPatterns[0].active()).toBeFalse(); - triggerPatterns[2].onKeydown(right()); + groupPattern.onKeydown(right()); expect(triggerPatterns[2].active()).toBeFalse(); expect(triggerPatterns[0].active()).toBeTrue(); }); @@ -302,16 +301,16 @@ describe('Accordion Pattern', () => { }); it('stays on the first trigger with left key from first trigger.', () => { - groupInputs.activeItem.set(groupInputs.items()[0]); + groupInputs.activeItem.set(triggerPatterns[0]); expect(triggerPatterns[0].active()).toBeTrue(); - triggerPatterns[0].onKeydown(left()); + groupPattern.onKeydown(left()); expect(triggerPatterns[0].active()).toBeTrue(); }); it('stays on the last trigger with right key from last trigger.', () => { - groupInputs.activeItem.set(groupInputs.items()[2]); + groupInputs.activeItem.set(triggerPatterns[2]); expect(triggerPatterns[2].active()).toBeTrue(); - triggerPatterns[2].onKeydown(right()); + groupPattern.onKeydown(right()); expect(triggerPatterns[2].active()).toBeTrue(); }); }); @@ -323,21 +322,21 @@ describe('Accordion Pattern', () => { }); it('expands a panel and collapses others with space key.', () => { - groupInputs.expandedIds.set(['panel-2']); + triggerPatterns[1].expanded.set(true); expect(panelPatterns[0].hidden()).toBeTrue(); expect(panelPatterns[1].hidden()).toBeFalse(); - triggerPatterns[0].onKeydown(space()); + groupPattern.onKeydown(space()); expect(panelPatterns[0].hidden()).toBeFalse(); expect(panelPatterns[1].hidden()).toBeTrue(); }); it('expands a panel and collapses others with enter key.', () => { - groupInputs.expandedIds.set(['panel-2']); + triggerPatterns[1].expanded.set(true); expect(panelPatterns[0].hidden()).toBeTrue(); expect(panelPatterns[1].hidden()).toBeFalse(); - triggerPatterns[0].onKeydown(space()); + groupPattern.onKeydown(space()); expect(panelPatterns[0].hidden()).toBeFalse(); expect(panelPatterns[1].hidden()).toBeTrue(); }); @@ -349,21 +348,22 @@ describe('Accordion Pattern', () => { }); it('expands a panel without affecting other panels.', () => { - groupInputs.expandedIds.set(['panel-2']); + triggerPatterns[1].expanded.set(true); expect(panelPatterns[0].hidden()).toBeTrue(); expect(panelPatterns[1].hidden()).toBeFalse(); - triggerPatterns[0].onKeydown(space()); + groupPattern.onKeydown(space()); expect(panelPatterns[0].hidden()).toBeFalse(); expect(panelPatterns[1].hidden()).toBeFalse(); }); it('collapses a panel without affecting other panels.', () => { - groupInputs.expandedIds.set(['panel-1', 'panel-2']); + triggerPatterns[0].expanded.set(true); + triggerPatterns[1].expanded.set(true); expect(panelPatterns[0].hidden()).toBeFalse(); expect(panelPatterns[1].hidden()).toBeFalse(); - triggerPatterns[0].onKeydown(enter()); + groupPattern.onKeydown(enter()); expect(panelPatterns[0].hidden()).toBeTrue(); expect(panelPatterns[1].hidden()).toBeFalse(); }); diff --git a/src/aria/private/accordion/accordion.ts b/src/aria/private/accordion/accordion.ts new file mode 100644 index 000000000000..4cb551a85a3f --- /dev/null +++ b/src/aria/private/accordion/accordion.ts @@ -0,0 +1,225 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed} from '@angular/core'; +import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager'; +import {ExpansionItem, ListExpansion, ListExpansionInputs} from '../behaviors/expansion/expansion'; +import {ListFocus, ListFocusInputs, ListFocusItem} from '../behaviors/list-focus/list-focus'; +import { + ListNavigation, + ListNavigationInputs, + ListNavigationItem, +} from '../behaviors/list-navigation/list-navigation'; +import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; + +/** Inputs of the AccordionGroupPattern. */ +export interface AccordionGroupInputs + extends Omit< + ListNavigationInputs & + ListFocusInputs & + Omit, + 'focusMode' + > { + /** A function that returns the trigger associated with a given element. */ + getItem: (e: Element | null | undefined) => AccordionTriggerPattern | undefined; +} + +const focusMode = () => 'roving' as const; + +/** A pattern controls the nested Accordions. */ +export class AccordionGroupPattern { + /** Controls navigation for the group. */ + readonly navigationBehavior: ListNavigation; + + /** Controls focus for the group. */ + readonly focusBehavior: ListFocus; + + /** Controls expansion for the group. */ + readonly expansionBehavior: ListExpansion; + + constructor(readonly inputs: AccordionGroupInputs) { + this.focusBehavior = new ListFocus({ + ...inputs, + focusMode, + }); + this.navigationBehavior = new ListNavigation({ + ...inputs, + focusMode, + focusManager: this.focusBehavior, + }); + this.expansionBehavior = new ListExpansion({ + ...inputs, + }); + } + + /** The key used to navigate to the previous accordion trigger. */ + prevKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return 'ArrowUp'; + } + return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + }); + + /** The key used to navigate to the next accordion trigger. */ + nextKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return 'ArrowDown'; + } + return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + }); + + /** The keydown event manager for the accordion trigger. */ + keydown = computed(() => { + return new KeyboardEventManager() + .on(this.prevKey, () => this.navigationBehavior.prev()) + .on(this.nextKey, () => this.navigationBehavior.next()) + .on('Home', () => this.navigationBehavior.first()) + .on('End', () => this.navigationBehavior.last()) + .on(' ', () => this.toggle()) + .on('Enter', () => this.toggle()); + }); + + /** The pointerdown event manager for the accordion trigger. */ + pointerdown = computed(() => { + return new PointerEventManager().on(e => { + const item = this.inputs.getItem(e.target as Element); + if (!item) return; + + this.navigationBehavior.goto(item); + this.expansionBehavior.toggle(item); + }); + }); + + /** Handles keydown events on the trigger, delegating to the group if not disabled. */ + onKeydown(event: KeyboardEvent): void { + this.keydown().handle(event); + } + + /** Handles pointerdown events on the trigger, delegating to the group if not disabled. */ + onPointerdown(event: PointerEvent): void { + this.pointerdown().handle(event); + } + + /** Handles focus events on the trigger. This ensures the tabbing changes the active index. */ + onFocus(event: FocusEvent): void { + const item = this.inputs.getItem(event.target as Element); + if (!item) return; + if (!this.focusBehavior.isFocusable(item)) return; + + this.focusBehavior.focus(item); + } + + /** Toggles the expansion state of the active accordion item. */ + toggle() { + const activeItem = this.inputs.activeItem(); + if (activeItem === undefined) return; + this.expansionBehavior.toggle(activeItem); + } +} + +/** Inputs for the AccordionTriggerPattern. */ +export interface AccordionTriggerInputs + extends Omit, + Omit { + /** A local unique identifier for the trigger's corresponding panel. */ + panelId: SignalLike; + + /** The parent accordion group that controls this trigger. */ + accordionGroup: SignalLike; + + /** The accordion panel controlled by this trigger. */ + accordionPanel: SignalLike; +} + +/** A pattern controls the expansion state of an accordion. */ +export class AccordionTriggerPattern implements ListNavigationItem, ListFocusItem, ExpansionItem { + /** A unique identifier for this trigger. */ + readonly id: SignalLike = () => this.inputs.id(); + + /** A reference to the trigger element. */ + readonly element: SignalLike = () => this.inputs.element()!; + + /** Whether this trigger has expandable panel. */ + readonly expandable: SignalLike = () => true; + + /** Whether the corresponding panel is expanded. */ + readonly expanded: WritableSignalLike; + + /** Whether the trigger is active. */ + readonly active = computed(() => this.inputs.accordionGroup().inputs.activeItem() === this); + + /** Id of the accordion panel controlled by the trigger. */ + readonly controls = computed(() => this.inputs.accordionPanel()?.inputs.id()); + + /** The tabindex of the trigger. */ + readonly tabIndex = computed(() => + this.inputs.accordionGroup().focusBehavior.isFocusable(this) ? 0 : -1, + ); + + /** Whether the trigger is disabled. Disabling an accordion group disables all the triggers. */ + readonly disabled = computed( + () => this.inputs.disabled() || this.inputs.accordionGroup().inputs.disabled(), + ); + + /** Whether the trigger is hard disabled. */ + readonly hardDisabled = computed( + () => this.disabled() && !this.inputs.accordionGroup().inputs.softDisabled(), + ); + + /** The index of the trigger within its accordion group. */ + readonly index = computed(() => this.inputs.accordionGroup().inputs.items().indexOf(this)); + + constructor(readonly inputs: AccordionTriggerInputs) { + this.expanded = inputs.expanded; + } + + /** Opens the accordion panel. */ + open(): void { + this.inputs.accordionGroup().expansionBehavior.open(this); + } + + /** Closes the accordion panel. */ + close(): void { + this.inputs.accordionGroup().expansionBehavior.close(this); + } + + /** Toggles the accordion panel. */ + toggle(): void { + this.inputs.accordionGroup().expansionBehavior.toggle(this); + } +} + +/** Represents the required inputs for the AccordionPanelPattern. */ +export interface AccordionPanelInputs { + /** A global unique identifier for the panel. */ + id: SignalLike; + + /** A local unique identifier for the panel, matching its trigger's panelId. */ + panelId: SignalLike; + + /** The parent accordion trigger that controls this panel. */ + accordionTrigger: SignalLike; +} + +/** Represents an accordion panel. */ +export class AccordionPanelPattern { + /** A global unique identifier for the panel. */ + id: SignalLike; + + /** The parent accordion trigger that controls this panel. */ + accordionTrigger: SignalLike; + + /** Whether the accordion panel is hidden. True if the associated trigger is not expanded. */ + hidden: SignalLike; + + constructor(readonly inputs: AccordionPanelInputs) { + this.id = inputs.id; + this.accordionTrigger = inputs.accordionTrigger; + this.hidden = computed(() => inputs.accordionTrigger()?.expanded() === false); + } +} diff --git a/src/aria/ui-patterns/behaviors/event-manager/BUILD.bazel b/src/aria/private/behaviors/event-manager/BUILD.bazel similarity index 81% rename from src/aria/ui-patterns/behaviors/event-manager/BUILD.bazel rename to src/aria/private/behaviors/event-manager/BUILD.bazel index 623be008f858..a744602f4526 100644 --- a/src/aria/ui-patterns/behaviors/event-manager/BUILD.bazel +++ b/src/aria/private/behaviors/event-manager/BUILD.bazel @@ -9,6 +9,6 @@ ts_project( exclude = ["**/*.spec.ts"], ), deps = [ - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/ui-patterns/behaviors/event-manager/event-manager.ts b/src/aria/private/behaviors/event-manager/event-manager.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/event-manager/event-manager.ts rename to src/aria/private/behaviors/event-manager/event-manager.ts diff --git a/src/aria/ui-patterns/behaviors/event-manager/index.ts b/src/aria/private/behaviors/event-manager/index.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/event-manager/index.ts rename to src/aria/private/behaviors/event-manager/index.ts diff --git a/src/aria/ui-patterns/behaviors/event-manager/keyboard-event-manager.ts b/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts similarity index 73% rename from src/aria/ui-patterns/behaviors/event-manager/keyboard-event-manager.ts rename to src/aria/private/behaviors/event-manager/keyboard-event-manager.ts index 86cf3329ade5..a088f744dda3 100644 --- a/src/aria/ui-patterns/behaviors/event-manager/keyboard-event-manager.ts +++ b/src/aria/private/behaviors/event-manager/keyboard-event-manager.ts @@ -35,32 +35,41 @@ export class KeyboardEventManager extends EventManager< }; /** Configures this event manager to handle events with a specific key and no modifiers. */ - on(key: KeyCode, handler: EventHandler): this; + on(key: KeyCode, handler: EventHandler, options?: Partial): this; /** Configures this event manager to handle events with a specific modifer and key combination. */ - on(modifiers: ModifierInputs, key: KeyCode, handler: EventHandler): this; + on( + modifiers: ModifierInputs, + key: KeyCode, + handler: EventHandler, + options?: Partial, + ): this; on(...args: any[]) { - const {modifiers, key, handler} = this._normalizeInputs(...args); + const {modifiers, key, handler, options} = this._normalizeInputs(...args); this.configs.push({ handler: handler, matcher: event => this._isMatch(event, key, modifiers), ...this.options, + ...options, }); return this; } private _normalizeInputs(...args: any[]) { - const key = args.length === 3 ? args[1] : args[0]; - const handler = args.length === 3 ? args[2] : args[1]; - const modifiers = args.length === 3 ? args[0] : Modifier.None; + const withModifiers = Array.isArray(args[0]) || args[0] in Modifier; + const modifiers = withModifiers ? args[0] : Modifier.None; + const key = withModifiers ? args[1] : args[0]; + const handler = withModifiers ? args[2] : args[1]; + const options = withModifiers ? args[3] : args[2]; return { key: key as KeyCode, handler: handler as EventHandler, modifiers: modifiers as ModifierInputs, + options: (options ?? {}) as Partial, }; } diff --git a/src/aria/ui-patterns/behaviors/event-manager/pointer-event-manager.ts b/src/aria/private/behaviors/event-manager/pointer-event-manager.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/event-manager/pointer-event-manager.ts rename to src/aria/private/behaviors/event-manager/pointer-event-manager.ts diff --git a/src/aria/ui-patterns/behaviors/expansion/BUILD.bazel b/src/aria/private/behaviors/expansion/BUILD.bazel similarity index 75% rename from src/aria/ui-patterns/behaviors/expansion/BUILD.bazel rename to src/aria/private/behaviors/expansion/BUILD.bazel index db36a1efad80..c541df42ad9e 100644 --- a/src/aria/ui-patterns/behaviors/expansion/BUILD.bazel +++ b/src/aria/private/behaviors/expansion/BUILD.bazel @@ -9,8 +9,8 @@ ts_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/list-focus", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/signal-like", ], ) @@ -23,7 +23,7 @@ ng_project( deps = [ ":expansion", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/list-focus:unit_test_sources", + "//src/aria/private/behaviors/list-focus:unit_test_sources", ], ) diff --git a/src/aria/ui-patterns/behaviors/expansion/expansion.spec.ts b/src/aria/private/behaviors/expansion/expansion.spec.ts similarity index 79% rename from src/aria/ui-patterns/behaviors/expansion/expansion.spec.ts rename to src/aria/private/behaviors/expansion/expansion.spec.ts index f1029267fcbe..5d956a8fc230 100644 --- a/src/aria/ui-patterns/behaviors/expansion/expansion.spec.ts +++ b/src/aria/private/behaviors/expansion/expansion.spec.ts @@ -12,8 +12,8 @@ import {ListExpansion, ListExpansionInputs, ExpansionItem} from './expansion'; type TestItem = ExpansionItem & { id: WritableSignal; disabled: WritableSignal; + expanded: WritableSignal; expandable: WritableSignal; - expansionId: WritableSignal; }; type TestInputs = Partial> & { @@ -29,8 +29,8 @@ function createItems(length: number): WritableSignal { return { id: signal(itemId), disabled: signal(false), + expanded: signal(false), expandable: signal(true), - expansionId: signal(itemId), }; }), ); @@ -43,20 +43,25 @@ function getExpansion(inputs: TestInputs = {}): { const numItems = inputs.numItems ?? 3; const items = createItems(numItems); + for (const id of inputs.initialExpandedIds ?? []) { + items() + .find(i => i.id() === id) + ?.expanded.set(true); + } + const expansion = new ListExpansion({ items: items, disabled: signal(inputs.expansionDisabled ?? false), multiExpandable: signal(inputs.multiExpandable?.() ?? false), - expandedIds: signal([]), }); - if (inputs.initialExpandedIds) { - expansion.expandedIds.set(inputs.initialExpandedIds); - } - return {expansion, items: items()}; } +function getExpandedIds(items: TestItem[]): string[] { + return items.filter(i => i.expanded()).map(i => i.id()); +} + describe('Expansion', () => { describe('#open', () => { it('should open only one item at a time when multiExpandable is false', () => { @@ -65,10 +70,10 @@ describe('Expansion', () => { }); expansion.open(items[0]); - expect(expansion.expandedIds()).toEqual(['item-0']); + expect(getExpandedIds(items)).toEqual(['item-0']); expansion.open(items[1]); - expect(expansion.expandedIds()).toEqual(['item-1']); + expect(getExpandedIds(items)).toEqual(['item-1']); }); it('should open multiple items when multiExpandable is true', () => { @@ -77,24 +82,24 @@ describe('Expansion', () => { }); expansion.open(items[0]); - expect(expansion.expandedIds()).toEqual(['item-0']); + expect(getExpandedIds(items)).toEqual(['item-0']); expansion.open(items[1]); - expect(expansion.expandedIds()).toEqual(['item-0', 'item-1']); + expect(getExpandedIds(items)).toEqual(['item-0', 'item-1']); }); it('should not open an item if it is not expandable (expandable is false)', () => { const {expansion, items} = getExpansion(); items[1].expandable.set(false); expansion.open(items[1]); - expect(expansion.expandedIds()).toEqual([]); + expect(getExpandedIds(items)).toEqual([]); }); it('should not open an item if it is disabled', () => { const {expansion, items} = getExpansion(); items[1].disabled.set(true); expansion.open(items[1]); - expect(expansion.expandedIds()).toEqual([]); + expect(getExpandedIds(items)).toEqual([]); }); }); @@ -102,21 +107,21 @@ describe('Expansion', () => { it('should close the specified item', () => { const {expansion, items} = getExpansion({initialExpandedIds: ['item-0', 'item-1']}); expansion.close(items[0]); - expect(expansion.expandedIds()).toEqual(['item-1']); + expect(getExpandedIds(items)).toEqual(['item-1']); }); it('should not close an item if it is not expandable', () => { const {expansion, items} = getExpansion({initialExpandedIds: ['item-0']}); items[0].expandable.set(false); expansion.close(items[0]); - expect(expansion.expandedIds()).toEqual(['item-0']); + expect(getExpandedIds(items)).toEqual(['item-0']); }); it('should not close an item if it is disabled', () => { const {expansion, items} = getExpansion({initialExpandedIds: ['item-0']}); items[0].disabled.set(true); expansion.close(items[0]); - expect(expansion.expandedIds()).toEqual(['item-0']); + expect(getExpandedIds(items)).toEqual(['item-0']); }); }); @@ -124,7 +129,7 @@ describe('Expansion', () => { it('should open a closed item', () => { const {expansion, items} = getExpansion(); expansion.toggle(items[0]); - expect(expansion.expandedIds()).toEqual(['item-0']); + expect(getExpandedIds(items)).toEqual(['item-0']); }); it('should close an opened item', () => { @@ -132,18 +137,18 @@ describe('Expansion', () => { initialExpandedIds: ['item-0'], }); expansion.toggle(items[0]); - expect(expansion.expandedIds()).toEqual([]); + expect(getExpandedIds(items)).toEqual([]); }); }); describe('#openAll', () => { it('should open all focusable and expandable items when multiExpandable is true', () => { - const {expansion} = getExpansion({ + const {expansion, items} = getExpansion({ numItems: 3, multiExpandable: signal(true), }); expansion.openAll(); - expect(expansion.expandedIds()).toEqual(['item-0', 'item-1', 'item-2']); + expect(getExpandedIds(items)).toEqual(['item-0', 'item-1', 'item-2']); }); it('should not expand items that are not expandable', () => { @@ -153,7 +158,7 @@ describe('Expansion', () => { }); items[1].expandable.set(false); expansion.openAll(); - expect(expansion.expandedIds()).toEqual(['item-0', 'item-2']); + expect(getExpandedIds(items)).toEqual(['item-0', 'item-2']); }); it('should not expand items that are disabled', () => { @@ -163,16 +168,16 @@ describe('Expansion', () => { }); items[1].disabled.set(true); expansion.openAll(); - expect(expansion.expandedIds()).toEqual(['item-0', 'item-2']); + expect(getExpandedIds(items)).toEqual(['item-0', 'item-2']); }); it('should do nothing when multiExpandable is false', () => { - const {expansion} = getExpansion({ + const {expansion, items} = getExpansion({ numItems: 3, multiExpandable: signal(false), }); expansion.openAll(); - expect(expansion.expandedIds()).toEqual([]); + expect(getExpandedIds(items)).toEqual([]); }); }); @@ -184,7 +189,7 @@ describe('Expansion', () => { }); items[1].expandable.set(false); expansion.closeAll(); - expect(expansion.expandedIds()).toEqual([]); + expect(getExpandedIds(items)).toEqual([]); }); it('should not close items that are not expandable', () => { @@ -194,7 +199,7 @@ describe('Expansion', () => { }); items[1].expandable.set(false); expansion.closeAll(); - expect(expansion.expandedIds()).toEqual(['item-1']); + expect(getExpandedIds(items)).toEqual(['item-1']); }); it('should not close items that are disabled', () => { @@ -204,7 +209,7 @@ describe('Expansion', () => { }); items[1].disabled.set(true); expansion.closeAll(); - expect(expansion.expandedIds()).toEqual(['item-1']); + expect(getExpandedIds(items)).toEqual(['item-1']); }); }); @@ -236,13 +241,13 @@ describe('Expansion', () => { describe('#isExpanded', () => { it('should return true if item ID is in expandedIds', () => { - const {expansion, items} = getExpansion({initialExpandedIds: ['item-0']}); - expect(expansion.isExpanded(items[0])).toBeTrue(); + const {items} = getExpansion({initialExpandedIds: ['item-0']}); + expect(items[0].expanded()).toBeTrue(); }); it('should return false if item ID is not in expandedIds', () => { - const {expansion, items} = getExpansion(); - expect(expansion.isExpanded(items[0])).toBeFalse(); + const {items} = getExpansion(); + expect(items[0].expanded()).toBeFalse(); }); }); }); diff --git a/src/aria/private/behaviors/expansion/expansion.ts b/src/aria/private/behaviors/expansion/expansion.ts new file mode 100644 index 000000000000..9a9caebd61e0 --- /dev/null +++ b/src/aria/private/behaviors/expansion/expansion.ts @@ -0,0 +1,82 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ +import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; + +/** Represents an item that can be expanded or collapsed. */ +export interface ExpansionItem { + /** Whether the item is expandable. */ + expandable: SignalLike; + + /** Whether the item is expanded. */ + expanded: WritableSignalLike; + + /** Whether the expansion is disabled. */ + disabled: SignalLike; +} + +/** Represents the required inputs for an expansion behavior. */ +export interface ListExpansionInputs { + /** Whether multiple items can be expanded at once. */ + multiExpandable: SignalLike; + + /** An array of expansion items. */ + items: SignalLike; + + /** Whether all expansions are disabled. */ + disabled: SignalLike; +} + +/** Manages the expansion state of a list of items. */ +export class ListExpansion { + constructor(readonly inputs: ListExpansionInputs) {} + + /** Opens the specified item. */ + open(item: ExpansionItem): boolean { + if (!this.isExpandable(item)) return false; + if (item.expanded()) return false; + if (!this.inputs.multiExpandable()) { + this.closeAll(); + } + item.expanded.set(true); + return true; + } + + /** Closes the specified item. */ + close(item: ExpansionItem): boolean { + if (!this.isExpandable(item)) return false; + + item.expanded.set(false); + return true; + } + + /** Toggles the expansion state of the specified item. */ + toggle(item: ExpansionItem): boolean { + return item.expanded() ? this.close(item) : this.open(item); + } + + /** Opens all focusable items in the list. */ + openAll(): void { + if (this.inputs.multiExpandable()) { + for (const item of this.inputs.items()) { + this.open(item); + } + } + } + + /** Closes all focusable items in the list. */ + closeAll(): void { + for (const item of this.inputs.items()) { + this.close(item); + } + } + + /** Checks whether the specified item is expandable / collapsible. */ + isExpandable(item: ExpansionItem) { + return !this.inputs.disabled() && !item.disabled() && item.expandable(); + } +} diff --git a/src/aria/ui-patterns/behaviors/grid-focus/BUILD.bazel b/src/aria/private/behaviors/grid-focus/BUILD.bazel similarity index 90% rename from src/aria/ui-patterns/behaviors/grid-focus/BUILD.bazel rename to src/aria/private/behaviors/grid-focus/BUILD.bazel index 966ffc20f9df..fc269de90dbc 100644 --- a/src/aria/ui-patterns/behaviors/grid-focus/BUILD.bazel +++ b/src/aria/private/behaviors/grid-focus/BUILD.bazel @@ -7,7 +7,7 @@ ts_project( srcs = ["grid-focus.ts"], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/ui-patterns/behaviors/grid-focus/grid-focus.spec.ts b/src/aria/private/behaviors/grid-focus/grid-focus.spec.ts similarity index 93% rename from src/aria/ui-patterns/behaviors/grid-focus/grid-focus.spec.ts rename to src/aria/private/behaviors/grid-focus/grid-focus.spec.ts index 70f661556a21..414aa61c84b5 100644 --- a/src/aria/ui-patterns/behaviors/grid-focus/grid-focus.spec.ts +++ b/src/aria/private/behaviors/grid-focus/grid-focus.spec.ts @@ -75,7 +75,7 @@ export function setupGridFocus(inputs: TestSetupInputs = {}): { inputs.focusMode ? inputs.focusMode() : 'roving', ); const disabled = signal(inputs.disabled ? inputs.disabled() : false); - const skipDisabled = signal(inputs.skipDisabled ? inputs.skipDisabled() : true); + const softDisabled = signal(inputs.softDisabled ? inputs.softDisabled() : false); gridFocus.set( new GridFocus({ @@ -83,7 +83,7 @@ export function setupGridFocus(inputs: TestSetupInputs = {}): { activeCoords: activeCoords, focusMode: focusMode, disabled: disabled, - skipDisabled: skipDisabled, + softDisabled: softDisabled, }), ); @@ -231,17 +231,17 @@ describe('GridFocus', () => { describe('getGridTabindex', () => { it('should return 0 if grid is disabled', () => { const {gridFocus} = setupGridFocus({disabled: signal(true)}); - expect(gridFocus.getGridTabindex()).toBe(0); + expect(gridFocus.getGridTabIndex()).toBe(0); }); it('should return -1 if focusMode is "roving" and grid is not disabled', () => { const {gridFocus} = setupGridFocus({focusMode: signal('roving')}); - expect(gridFocus.getGridTabindex()).toBe(-1); + expect(gridFocus.getGridTabIndex()).toBe(-1); }); it('should return 0 if focusMode is "activedescendant" and grid is not disabled', () => { const {gridFocus} = setupGridFocus({focusMode: signal('activedescendant')}); - expect(gridFocus.getGridTabindex()).toBe(0); + expect(gridFocus.getGridTabIndex()).toBe(0); }); }); @@ -252,9 +252,9 @@ describe('GridFocus', () => { numCols: 3, disabled: signal(true), }); - expect(gridFocus.getCellTabindex(cells[0][0])).toBe(-1); - expect(gridFocus.getCellTabindex(cells[0][1])).toBe(-1); - expect(gridFocus.getCellTabindex(cells[0][2])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][0])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][1])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][2])).toBe(-1); }); it('should return -1 if focusMode is "activedescendant"', () => { @@ -263,9 +263,9 @@ describe('GridFocus', () => { numCols: 3, focusMode: signal('activedescendant'), }); - expect(gridFocus.getCellTabindex(cells[0][0])).toBe(-1); - expect(gridFocus.getCellTabindex(cells[0][1])).toBe(-1); - expect(gridFocus.getCellTabindex(cells[0][2])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][0])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][1])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][2])).toBe(-1); }); it('should return 0 if focusMode is "roving" and cell is the activeCell', () => { @@ -275,9 +275,9 @@ describe('GridFocus', () => { focusMode: signal('roving'), }); - expect(gridFocus.getCellTabindex(cells[0][0])).toBe(0); - expect(gridFocus.getCellTabindex(cells[0][1])).toBe(-1); - expect(gridFocus.getCellTabindex(cells[0][2])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][0])).toBe(0); + expect(gridFocus.getCellTabIndex(cells[0][1])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[0][2])).toBe(-1); }); }); @@ -292,11 +292,11 @@ describe('GridFocus', () => { expect(gridFocus.isFocusable(cells[0][2])).toBeTrue(); }); - it('should return false if cell is disabled and skipDisabled is true', () => { + it('should return false if cell is disabled and softDisabled is false', () => { const {gridFocus, cells} = setupGridFocus({ numRows: 1, numCols: 3, - skipDisabled: signal(true), + softDisabled: signal(false), }); cells[0][1].disabled.set(true); expect(gridFocus.isFocusable(cells[0][0])).toBeTrue(); @@ -304,11 +304,11 @@ describe('GridFocus', () => { expect(gridFocus.isFocusable(cells[0][2])).toBeTrue(); }); - it('should return true if cell is disabled but skipDisabled is false', () => { + it('should return true if cell is disabled but softDisabled is true', () => { const {gridFocus, cells} = setupGridFocus({ numRows: 1, numCols: 3, - skipDisabled: signal(false), + softDisabled: signal(true), }); cells[0][1].disabled.set(true); expect(gridFocus.isFocusable(cells[0][0])).toBeTrue(); diff --git a/src/aria/ui-patterns/behaviors/grid-focus/grid-focus.ts b/src/aria/private/behaviors/grid-focus/grid-focus.ts similarity index 94% rename from src/aria/ui-patterns/behaviors/grid-focus/grid-focus.ts rename to src/aria/private/behaviors/grid-focus/grid-focus.ts index bacb2b5d9daa..508026eb9726 100644 --- a/src/aria/ui-patterns/behaviors/grid-focus/grid-focus.ts +++ b/src/aria/private/behaviors/grid-focus/grid-focus.ts @@ -47,8 +47,8 @@ export interface GridFocusInputs { /** The coordinates (row and column) of the current active cell. */ activeCoords: WritableSignalLike; - /** Whether disabled cells in the grid should be skipped when navigating. */ - skipDisabled: SignalLike; + /** Whether disabled cells in the grid should be focusable. */ + softDisabled: SignalLike; } /** Represents coordinates in a grid. */ @@ -96,16 +96,16 @@ export class GridFocus { return gridCells.length === 0 || gridCells.every(row => row.every(cell => cell.disabled())); } - /** The tabindex for the grid container. */ - getGridTabindex(): -1 | 0 { + /** The tab index for the grid container. */ + getGridTabIndex(): -1 | 0 { if (this.isGridDisabled()) { return 0; } return this.inputs.focusMode() === 'activedescendant' ? 0 : -1; } - /** Returns the tabindex for the given grid cell cell. */ - getCellTabindex(cell: T): -1 | 0 { + /** Returns the tab index for the given grid cell cell. */ + getCellTabIndex(cell: T): -1 | 0 { if (this.isGridDisabled()) { return -1; } @@ -163,7 +163,7 @@ export class GridFocus { /** Returns true if the given cell can be navigated to. */ isFocusable(cell: T): boolean { - return !cell.disabled() || !this.inputs.skipDisabled(); + return !cell.disabled() || this.inputs.softDisabled(); } /** Finds the top-left anchor coordinates of a given cell instance in the grid. */ diff --git a/src/aria/ui-patterns/behaviors/grid-navigation/BUILD.bazel b/src/aria/private/behaviors/grid-navigation/BUILD.bazel similarity index 76% rename from src/aria/ui-patterns/behaviors/grid-navigation/BUILD.bazel rename to src/aria/private/behaviors/grid-navigation/BUILD.bazel index cc87a01c3228..5c61afecee7b 100644 --- a/src/aria/ui-patterns/behaviors/grid-navigation/BUILD.bazel +++ b/src/aria/private/behaviors/grid-navigation/BUILD.bazel @@ -7,8 +7,8 @@ ts_project( srcs = ["grid-navigation.ts"], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/grid-focus", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/grid-focus", + "//src/aria/private/behaviors/signal-like", ], ) @@ -19,7 +19,7 @@ ng_project( deps = [ ":grid-navigation", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/grid-focus", + "//src/aria/private/behaviors/grid-focus", ], ) diff --git a/src/aria/ui-patterns/behaviors/grid-navigation/grid-navigation.spec.ts b/src/aria/private/behaviors/grid-navigation/grid-navigation.spec.ts similarity index 93% rename from src/aria/ui-patterns/behaviors/grid-navigation/grid-navigation.spec.ts rename to src/aria/private/behaviors/grid-navigation/grid-navigation.spec.ts index 6d48f217d17f..d7ff9e989d12 100644 --- a/src/aria/ui-patterns/behaviors/grid-navigation/grid-navigation.spec.ts +++ b/src/aria/private/behaviors/grid-navigation/grid-navigation.spec.ts @@ -42,7 +42,7 @@ type TestGridNavInputs = Partial> & function createGridNav(config: TestGridNavInputs): {gridNav: TestGridNav; cells: TestCell[][]} { const wrap = signal(true); const disabled = signal(false); - const skipDisabled = signal(false); + const softDisabled = signal(true); const focusMode = signal('roving' as const); const activeCoords = signal({row: 0, col: 0}); const wrapBehavior = signal('continuous' as const); @@ -51,7 +51,7 @@ function createGridNav(config: TestGridNavInputs): {gridNav: TestGridNav; cells: disabled, focusMode, activeCoords, - skipDisabled, + softDisabled, ...config, }); @@ -60,7 +60,7 @@ function createGridNav(config: TestGridNavInputs): {gridNav: TestGridNav; cells: disabled, focusMode, activeCoords, - skipDisabled, + softDisabled, wrapBehavior, gridFocus, ...config, @@ -179,10 +179,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); }); - it('(skip disabled: false) should be able to navigate through disabled cells', () => { + it('(soft disabled: true) should be able to navigate through disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, - skipDisabled: signal(false), + softDisabled: signal(true), activeCoords: signal({row: 1, col: 1}), }); cells[0][1].disabled.set(true); @@ -190,10 +190,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 2, col: 1}), }); cells[1][1].disabled.set(true); @@ -201,11 +201,11 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 1}); }); - it('(wrap: false) (skip disabled: true) should not navigate through disabled cells', () => { + it('(wrap: false) (soft disabled: false) should not navigate through disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(false), - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 1, col: 1}), }); cells[0][1].disabled.set(true); @@ -241,7 +241,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('loop'), activeCoords: signal({row: 0, col: 1}), }); @@ -254,7 +254,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('loop'), activeCoords: signal({row: 0, col: 1}), }); @@ -293,7 +293,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 0, col: 1}), }); @@ -316,7 +316,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 0, col: 1}), }); @@ -340,7 +340,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 1, col: 1}), }); @@ -396,10 +396,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridB, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 2, col: 2}), }); cells[0][2].disabled.set(true); @@ -458,10 +458,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 3}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridC, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 1, col: 2}), }); cells[0][0].disabled.set(true); @@ -513,10 +513,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 3, col: 3}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridD, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 3, col: 3}), }); @@ -558,10 +558,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); }); - it('(skip disabled: false) should be able to navigate through disabled cells', () => { + it('(soft disabled: true) should be able to navigate through disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, - skipDisabled: signal(false), + softDisabled: signal(true), activeCoords: signal({row: 1, col: 1}), }); cells[2][1].disabled.set(true); @@ -569,10 +569,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 0, col: 1}), }); cells[1][1].disabled.set(true); @@ -580,11 +580,11 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 1}); }); - it('(wrap: false) (skip disabled: true) should not navigate through disabled cells', () => { + it('(wrap: false) (soft disabled: false) should not navigate through disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(false), - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 1, col: 1}), }); cells[2][1].disabled.set(true); @@ -620,7 +620,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('loop'), activeCoords: signal({row: 2, col: 1}), }); @@ -633,7 +633,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('loop'), activeCoords: signal({row: 2, col: 1}), }); @@ -661,7 +661,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 2, col: 1}), }); @@ -678,7 +678,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 1, col: 1}), }); @@ -733,10 +733,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridB, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 0, col: 0}), }); cells[1][0].disabled.set(true); @@ -789,10 +789,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); }); - it('(skip disabled: false) should be able to navigate through disabled cells', () => { + it('(soft disabled: true) should be able to navigate through disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, - skipDisabled: signal(false), + softDisabled: signal(true), activeCoords: signal({row: 1, col: 1}), }); cells[1][0].disabled.set(true); @@ -800,10 +800,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 1, col: 2}), }); cells[1][1].disabled.set(true); @@ -811,11 +811,11 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 0}); }); - it('(wrap: false) (skip disabled: true) should not navigate through disabled cells', () => { + it('(wrap: false) (soft disabled: false) should not navigate through disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(false), - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 1, col: 1}), }); cells[1][0].disabled.set(true); @@ -851,7 +851,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('loop'), activeCoords: signal({row: 1, col: 0}), }); @@ -864,7 +864,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('loop'), activeCoords: signal({row: 1, col: 0}), }); @@ -892,7 +892,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 1, col: 0}), }); @@ -909,7 +909,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 1, col: 1}), }); @@ -973,10 +973,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 2, col: 2}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridC, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 0, col: 3}), }); @@ -1031,10 +1031,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); }); - it('(skip disabled: false) should be able to navigate through disabled cells', () => { + it('(soft disabled: true) should be able to navigate through disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, - skipDisabled: signal(false), + softDisabled: signal(true), activeCoords: signal({row: 1, col: 1}), }); cells[1][2].disabled.set(true); @@ -1042,10 +1042,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 1, col: 0}), }); cells[1][1].disabled.set(true); @@ -1053,11 +1053,11 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 1, col: 2}); }); - it('(wrap: false) (skip disabled: true) should not navigate through disabled cells', () => { + it('(wrap: false) (soft disabled: false) should not navigate through disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(false), - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 1, col: 1}), }); cells[1][2].disabled.set(true); @@ -1093,7 +1093,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('loop'), activeCoords: signal({row: 1, col: 2}), }); @@ -1106,7 +1106,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('loop'), activeCoords: signal({row: 1, col: 2}), }); @@ -1134,7 +1134,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 1, col: 2}), }); @@ -1151,7 +1151,7 @@ describe('GridNavigation', () => { const {gridNav, cells} = createGridNav({ cells: gridA, wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), wrapBehavior: signal('continuous'), activeCoords: signal({row: 1, col: 1}), }); @@ -1215,10 +1215,10 @@ describe('GridNavigation', () => { expect(gridNav.inputs.activeCoords()).toEqual({row: 0, col: 2}); }); - it('(skip disabled: true) should skip disabled cells', () => { + it('(soft disabled: false) should skip disabled cells', () => { const {gridNav, cells} = createGridNav({ cells: gridC, - skipDisabled: signal(true), + softDisabled: signal(false), activeCoords: signal({row: 0, col: 0}), }); cells[0][1].disabled.set(true); diff --git a/src/aria/ui-patterns/behaviors/grid-navigation/grid-navigation.ts b/src/aria/private/behaviors/grid-navigation/grid-navigation.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/grid-navigation/grid-navigation.ts rename to src/aria/private/behaviors/grid-navigation/grid-navigation.ts diff --git a/src/aria/private/behaviors/grid/BUILD.bazel b/src/aria/private/behaviors/grid/BUILD.bazel new file mode 100644 index 000000000000..a5c7c160071c --- /dev/null +++ b/src/aria/private/behaviors/grid/BUILD.bazel @@ -0,0 +1,30 @@ +load("//tools:defaults.bzl", "ng_web_test_suite", "ts_project") + +package(default_visibility = ["//visibility:public"]) + +ts_project( + name = "grid", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + deps = [ + "//:node_modules/@angular/core", + "//src/aria/private/behaviors/signal-like", + ], +) + +ts_project( + name = "unit_test_sources", + testonly = True, + srcs = glob(["**/*.spec.ts"]), + deps = [ + ":grid", + "//:node_modules/@angular/core", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_test_sources"], +) diff --git a/src/aria/private/behaviors/grid/grid-data.spec.ts b/src/aria/private/behaviors/grid/grid-data.spec.ts new file mode 100644 index 000000000000..2702a0584139 --- /dev/null +++ b/src/aria/private/behaviors/grid/grid-data.spec.ts @@ -0,0 +1,337 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {signal, Signal, WritableSignal} from '@angular/core'; +import {BaseGridCell, GridData} from './grid-data'; + +export interface TestBaseGridCell extends BaseGridCell { + rowSpan: WritableSignal; + colSpan: WritableSignal; + id: Signal; +} + +/** + * GRID A: + * ā”Œā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā” + * │ 0,0 │ 0,1 │ 0,2 │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ 1,0 │ 1,1 │ 1,2 │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ 2,0 │ 2,1 │ 2,2 │ + * ā””ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”˜ + */ +export function createGridA(): TestBaseGridCell[][] { + return [ + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-2')}, + ], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-1-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-1-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-1-2')}, + ], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-2')}, + ], + ]; +} + +/** + * GRID B: + * ā”Œā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā” + * │ 0,0 │ 0,1 │ 0,2 │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ │ + * │ 1,0 │ 1,1 │ │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¤ ā”œā”€ā”€ā”€ā”€ā”€ā”¤ + * │ 2,0 │ │ 2,2 │ + * │ ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ │ 3,1 │ 3,2 │ + * ā””ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”˜ + */ +export function createGridB(): TestBaseGridCell[][] { + return [ + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-1')}, + {rowSpan: signal(2), colSpan: signal(1), id: signal('cell-0-2')}, + ], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-1-0')}, + {rowSpan: signal(2), colSpan: signal(1), id: signal('cell-1-1')}, + ], + [ + {rowSpan: signal(2), colSpan: signal(1), id: signal('cell-2-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-2')}, + ], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-3-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-3-2')}, + ], + ]; +} + +/** + * GRID C: + * ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā” + * │ 0,0 │ 0,2 │ 0,3 │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ 1,0 │ 1,1 │ 1,3 │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ 2,0 │ 2,1 │ 2,2 │ + * ā””ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + */ +export function createGridC(): TestBaseGridCell[][] { + return [ + [ + {rowSpan: signal(1), colSpan: signal(2), id: signal('cell-0-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-2')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-3')}, + ], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-1-0')}, + {rowSpan: signal(1), colSpan: signal(2), id: signal('cell-1-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-1-3')}, + ], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-1')}, + {rowSpan: signal(1), colSpan: signal(2), id: signal('cell-2-2')}, + ], + ]; +} + +/** + * GRID D: + * ā”Œā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā” + * │ 0,0 │ 0,1 │ 0,3 │ + * │ ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ │ 1,1 │ 1,3 │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¤ │ │ + * │ 2,0 │ │ │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ 3,0 │ 3,1 │ 3,2 │ + * ā””ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜ + */ +export function createGridD(): TestBaseGridCell[][] { + return [ + [ + {rowSpan: signal(2), colSpan: signal(1), id: signal('cell-0-0')}, + {rowSpan: signal(1), colSpan: signal(2), id: signal('cell-0-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-3')}, + ], + [ + {rowSpan: signal(2), colSpan: signal(2), id: signal('cell-1-1')}, + {rowSpan: signal(2), colSpan: signal(1), id: signal('cell-1-3')}, + ], + [{rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-0')}], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-3-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-3-1')}, + {rowSpan: signal(1), colSpan: signal(2), id: signal('cell-3-2')}, + ], + ]; +} + +/** + * GRID E: Uneven rows (jagged) + * ā”Œā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā” + * │ 0,0 │ 0,1 │ 0,2 │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¤ ā”œā”€ā”€ā”€ā”€ā”€ā”˜ + * │ 1,0 │ │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ 2,0 │ 2,1 │ + * └─────┓─────┓ + */ +export function createGridE(): TestBaseGridCell[][] { + return [ + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-0')}, + {rowSpan: signal(2), colSpan: signal(1), id: signal('cell-0-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-2')}, + ], + [{rowSpan: signal(1), colSpan: signal(1), id: signal('cell-1-0')}], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-1')}, + ], + ]; +} + +/** + * GRID F: Grid with empty rows + * ā”Œā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā” + * │ 0,0 │ 0,1 │ 0,2 │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ │ │ │ + * ā”œā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”¤ + * │ 2,0 │ 2,1 │ 2,2 │ + * ā””ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”˜ + */ +export function createGridF(): TestBaseGridCell[][] { + return [ + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-0-2')}, + ], + [], + [ + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-0')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-1')}, + {rowSpan: signal(1), colSpan: signal(1), id: signal('cell-2-2')}, + ], + ]; +} + +function createGridData(cells: TestBaseGridCell[][]): GridData { + return new GridData({cells: signal(cells)}); +} + +describe('GridData', () => { + describe('rowCount', () => { + it('should return the number of rows in the grid', () => {}); + }); + + describe('maxRowCount', () => { + it('should return the maximum number of rows, accounting for row spans', () => { + const gridA = createGridData(createGridA()); + expect(gridA.maxRowCount()).toBe(3); + + const gridB = createGridData(createGridB()); + expect(gridB.maxRowCount()).toBe(4); + + const gridC = createGridData(createGridC()); + expect(gridC.maxRowCount()).toBe(3); + + const gridD = createGridData(createGridD()); + expect(gridD.maxRowCount()).toBe(4); + + const gridF = createGridData(createGridE()); + expect(gridF.maxRowCount()).toBe(3); + + const gridG = createGridData(createGridF()); + expect(gridG.maxRowCount()).toBe(3); + }); + }); + + describe('maxColCount', () => { + it('should return the maximum number of columns, accounting for column spans', () => { + const gridA = createGridData(createGridA()); + expect(gridA.maxColCount()).toBe(3); + + const gridB = createGridData(createGridB()); + expect(gridB.maxColCount()).toBe(3); + + const gridC = createGridData(createGridC()); + expect(gridC.maxColCount()).toBe(4); + + const gridD = createGridData(createGridD()); + expect(gridD.maxColCount()).toBe(4); + + const gridE = createGridData(createGridE()); + expect(gridE.maxColCount()).toBe(3); + + const gridF = createGridData(createGridF()); + expect(gridF.maxColCount()).toBe(3); + }); + }); + + describe('getCell', () => { + it('should get the cell at the given coordinates', () => { + const cells = createGridD(); + const grid = createGridData(cells); + + expect(grid.getCell({row: 0, col: 0})).toBe(cells[0][0]); + expect(grid.getCell({row: 1, col: 0})).toBe(cells[0][0]); + expect(grid.getCell({row: 0, col: 1})).toBe(cells[0][1]); + expect(grid.getCell({row: 0, col: 2})).toBe(cells[0][1]); + expect(grid.getCell({row: 1, col: 1})).toBe(cells[1][0]); + expect(grid.getCell({row: 2, col: 2})).toBe(cells[1][0]); + }); + + it('should return undefined for out-of-bounds coordinates', () => { + const grid = createGridData(createGridA()); + expect(grid.getCell({row: 5, col: 5})).toBeUndefined(); + expect(grid.getCell({row: -1, col: 0})).toBeUndefined(); + }); + }); + + describe('getCoords', () => { + it('should get the primary coordinates of the given cell', () => { + const cells = createGridD(); + const grid = createGridData(cells); + + expect(grid.getCoords(cells[0][0])).toEqual({row: 0, col: 0}); + expect(grid.getCoords(cells[1][0])).toEqual({row: 1, col: 1}); + expect(grid.getCoords(cells[3][2])).toEqual({row: 3, col: 2}); + }); + }); + + describe('getAllCoords', () => { + it('should get all coordinates that the given cell spans', () => { + const cells = createGridD(); + const grid = createGridData(cells); + + expect(grid.getAllCoords(cells[0][0])).toEqual([ + {row: 0, col: 0}, + {row: 1, col: 0}, + ]); + expect(grid.getAllCoords(cells[1][0])).toEqual([ + {row: 1, col: 1}, + {row: 1, col: 2}, + {row: 2, col: 1}, + {row: 2, col: 2}, + ]); + expect(grid.getAllCoords(cells[3][2])).toEqual([ + {row: 3, col: 2}, + {row: 3, col: 3}, + ]); + }); + }); + + describe('getRowCount', () => { + it('should get the number of rows in the given column', () => { + const grid = createGridData(createGridD()); + expect(grid.getRowCount(0)).toBe(4); + expect(grid.getRowCount(1)).toBe(4); + expect(grid.getRowCount(2)).toBe(4); + expect(grid.getRowCount(3)).toBe(4); + }); + + it('should return undefined for an out-of-bounds column', () => { + const grid = createGridData(createGridA()); + expect(grid.getRowCount(5)).toBeUndefined(); + expect(grid.getRowCount(-1)).toBeUndefined(); + }); + }); + + describe('getColCount', () => { + it('should get the number of columns in the given row', () => { + const gridD = createGridData(createGridD()); + expect(gridD.getColCount(0)).toBe(4); + expect(gridD.getColCount(1)).toBe(4); + expect(gridD.getColCount(2)).toBe(4); + expect(gridD.getColCount(3)).toBe(4); + + const gridE = createGridData(createGridE()); + expect(gridE.getColCount(0)).toBe(3); + expect(gridE.getColCount(1)).toBe(2); + expect(gridE.getColCount(2)).toBe(2); + }); + + it('should return undefined for an out-of-bounds row', () => { + const grid = createGridData(createGridA()); + expect(grid.getColCount(5)).toBeUndefined(); + expect(grid.getColCount(-1)).toBeUndefined(); + }); + }); +}); diff --git a/src/aria/ui-patterns/behaviors/grid/grid-data.ts b/src/aria/private/behaviors/grid/grid-data.ts similarity index 97% rename from src/aria/ui-patterns/behaviors/grid/grid-data.ts rename to src/aria/private/behaviors/grid/grid-data.ts index b3065e97ac13..21380fd04660 100644 --- a/src/aria/ui-patterns/behaviors/grid/grid-data.ts +++ b/src/aria/private/behaviors/grid/grid-data.ts @@ -41,9 +41,6 @@ export class GridData { /** The two-dimensional array of cells that represents the grid. */ readonly cells: SignalLike; - /** The number of rows in the grid. */ - readonly rowCount = computed(() => this.cells().length); - /** The maximum number of rows in the grid, accounting for row spans. */ readonly maxRowCount = computed(() => Math.max(...this._rowCountByCol().values(), 0)); @@ -131,6 +128,11 @@ export class GridData { this.cells = this.inputs.cells; } + /** Whether the cell exists. */ + hasCell(cell: T): boolean { + return this._coordsMap().has(cell); + } + /** Gets the cell at the given coordinates. */ getCell(rowCol: RowCol): T | undefined { return this._cellMap().get(`${rowCol.row}:${rowCol.col}`); diff --git a/src/aria/private/behaviors/grid/grid-focus.spec.ts b/src/aria/private/behaviors/grid/grid-focus.spec.ts new file mode 100644 index 000000000000..d6faeafdbadc --- /dev/null +++ b/src/aria/private/behaviors/grid/grid-focus.spec.ts @@ -0,0 +1,380 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {signal, Signal, WritableSignal} from '@angular/core'; +import {GridData} from './grid-data'; +import {createGridA, createGridB, createGridD, TestBaseGridCell} from './grid-data.spec'; +import {GridFocus, GridFocusInputs} from './grid-focus'; + +export interface TestGridFocusCell extends TestBaseGridCell { + element: WritableSignal; + disabled: WritableSignal; +} + +function createTestCell(): Omit { + return { + element: signal(document.createElement('div')), + disabled: signal(false), + }; +} + +function createTestGrid(createGridFn: () => TestBaseGridCell[][]): TestGridFocusCell[][] { + return createGridFn().map(row => + row.map(cell => { + return {...createTestCell(), ...cell}; + }), + ); +} + +function setupGridFocus( + cells: Signal, + gridFocusInputs: Partial = {}, +): GridFocus { + const gridData = new GridData({cells}); + return new GridFocus({ + grid: gridData, + focusMode: signal('roving'), + disabled: signal(false), + softDisabled: signal(false), + ...gridFocusInputs, + }); +} + +describe('GridFocus', () => { + describe('stateEmpty', () => { + it('should be true initially', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells)); + expect(gridFocus.stateEmpty()).toBe(true); + }); + + it('should be false after focusing a cell', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells)); + + gridFocus.focusCell(cells[1][1]); + + expect(gridFocus.stateEmpty()).toBe(false); + }); + + it('should be true if activeCell is undefined', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells)); + + // Manually create a partially-empty state. + gridFocus.activeCell.set(undefined); + gridFocus.activeCoords.set({row: 1, col: 1}); + + expect(gridFocus.stateEmpty()).toBe(true); + }); + }); + + describe('stateStale', () => { + it('should be true if the active cell is no longer in the grid', () => { + const cells = createTestGrid(createGridA); + const cellsSignal = signal(cells); + const gridFocus = setupGridFocus(cellsSignal); + + gridFocus.focusCell(cells[1][1]); + + // Remove the active cell from the grid. + const newCells = createTestGrid(createGridA); + newCells[1].splice(1, 1); + cellsSignal.set(newCells); + + expect(gridFocus.stateStale()).toBe(true); + }); + + it('should be true if the active coordinates point to a different cell', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells)); + + gridFocus.focusCell(cells[1][1]); + + // Manually set the active coordinates to a different cell. + gridFocus.activeCoords.set({row: 0, col: 0}); + + expect(gridFocus.stateStale()).toBe(true); + }); + + it('should be false if the active cell and coordinates are valid and in sync', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells)); + + gridFocus.focusCell(cells[1][1]); + + expect(gridFocus.stateStale()).toBe(false); + }); + }); + + describe('activeDescendant', () => { + it('should return the ID of the active cell in activedescendant mode', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + focusMode: signal('activedescendant'), + }); + + gridFocus.focusCell(cells[1][1]); + + expect(gridFocus.activeDescendant()).toBe('cell-1-1'); + }); + + it('should be undefined in roving focus mode', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), {focusMode: signal('roving')}); + + gridFocus.focusCell(cells[1][1]); + + expect(gridFocus.activeDescendant()).toBeUndefined(); + }); + + it('should be undefined if the grid is disabled', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + focusMode: signal('activedescendant'), + disabled: signal(true), + }); + + gridFocus.activeCell.set(cells[1][1]); + + expect(gridFocus.activeDescendant()).toBeUndefined(); + }); + }); + + describe('gridDisabled', () => { + it('should be true if the grid is disabled via inputs', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + disabled: signal(true), + }); + expect(gridFocus.gridDisabled()).toBe(true); + }); + + it('should be true if all cells are disabled', () => { + const cells = createTestGrid(createGridA); + for (const row of cells) { + for (const cell of row) { + cell.disabled.set(true); + } + } + const gridFocus = setupGridFocus(signal(cells)); + expect(gridFocus.gridDisabled()).toBe(true); + }); + + it('should be true if there are no cells', () => { + const gridFocus = setupGridFocus(signal([])); + expect(gridFocus.gridDisabled()).toBe(true); + }); + + it('should be false if at least one cell is enabled', () => { + const cells = createTestGrid(createGridA); + for (const row of cells) { + for (const cell of row) { + cell.disabled.set(true); + } + } + // Enable one cell. + cells[1][1].disabled.set(false); + const gridFocus = setupGridFocus(signal(cells)); + expect(gridFocus.gridDisabled()).toBe(false); + }); + }); + + describe('gridTabIndex', () => { + it('should be 0 in activedescendant mode', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + focusMode: signal('activedescendant'), + }); + expect(gridFocus.gridTabIndex()).toBe(0); + }); + + it('should be -1 in roving focus mode', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + focusMode: signal('roving'), + }); + expect(gridFocus.gridTabIndex()).toBe(-1); + }); + + it('should be 0 if the grid is disabled', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + disabled: signal(true), + }); + expect(gridFocus.gridTabIndex()).toBe(0); + }); + }); + + describe('getCellTabindex', () => { + it('should return 0 for the active cell in roving mode', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + focusMode: signal('roving'), + }); + + gridFocus.focusCell(cells[1][1]); + + expect(gridFocus.getCellTabIndex(cells[1][1])).toBe(0); + }); + + it('should return -1 for inactive cells in roving mode', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + focusMode: signal('roving'), + }); + + gridFocus.focusCell(cells[1][1]); + + expect(gridFocus.getCellTabIndex(cells[0][0])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[2][2])).toBe(-1); + }); + + it('should return -1 for all cells in activedescendant mode', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), { + focusMode: signal('activedescendant'), + }); + + gridFocus.focusCell(cells[1][1]); + + expect(gridFocus.getCellTabIndex(cells[0][0])).toBe(-1); + expect(gridFocus.getCellTabIndex(cells[1][1])).toBe(-1); + }); + + it('should return -1 for all cells when the grid is disabled', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), {disabled: signal(true)}); + expect(gridFocus.getCellTabIndex(cells[1][1])).toBe(-1); + }); + }); + + describe('isFocusable', () => { + it('should return true for an enabled cell', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells)); + expect(gridFocus.isFocusable(cells[1][1])).toBe(true); + }); + + it('should return false for a disabled cell when softDisabled is false', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), {softDisabled: signal(false)}); + + cells[1][1].disabled.set(true); + + expect(gridFocus.isFocusable(cells[1][1])).toBe(false); + }); + + it('should return true for a disabled cell when softDisabled is true', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), {softDisabled: signal(true)}); + + cells[1][1].disabled.set(true); + + expect(gridFocus.isFocusable(cells[1][1])).toBe(true); + }); + }); + + describe('focusCell', () => { + it('should set the active cell and coordinates', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells)); + + const result = gridFocus.focusCell(cells[1][1]); + + expect(result).toBe(true); + expect(gridFocus.activeCell()).toBe(cells[1][1]); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should not focus a disabled cell if softDisabled is false', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), {softDisabled: signal(false)}); + + gridFocus.focusCell(cells[0][0]); + cells[1][1].disabled.set(true); + + const result = gridFocus.focusCell(cells[1][1]); + + expect(result).toBe(false); + expect(gridFocus.activeCell()).toBe(cells[0][0]); + expect(gridFocus.activeCoords()).toEqual({row: 0, col: 0}); + }); + + it('should focus a disabled cell if softDisabled is true', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells), {softDisabled: signal(true)}); + + cells[1][1].disabled.set(true); + const result = gridFocus.focusCell(cells[1][1]); + + expect(result).toBe(true); + expect(gridFocus.activeCell()).toBe(cells[1][1]); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should return false if the cell is not in the grid', () => { + const cells = createTestGrid(createGridA); + const gridFocus = setupGridFocus(signal(cells)); + const unrelatedCell = createTestGrid(createGridB)[0][0]; + + const result = gridFocus.focusCell(unrelatedCell); + + expect(result).toBe(false); + }); + }); + + describe('focusCoordinates', () => { + it('should set the active cell and coordinates', () => { + const cells = createTestGrid(createGridD); + const gridFocus = setupGridFocus(signal(cells)); + + const result = gridFocus.focusCoordinates({row: 1, col: 2}); + + expect(result).toBe(true); + // The cell at `[1][0]` spans `[1,1]`, `[1,2]`, `[2,1]`, and `[2,2]`. + expect(gridFocus.activeCell()).toBe(cells[1][0]); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('should not focus coordinates of a disabled cell if softDisabled is false', () => { + const cells = createTestGrid(createGridD); + const gridFocus = setupGridFocus(signal(cells), {softDisabled: signal(false)}); + + gridFocus.focusCoordinates({row: 0, col: 0}); + cells[1][0].disabled.set(true); // This cell spans {row: 1, col: 2} + + const result = gridFocus.focusCoordinates({row: 1, col: 2}); + + expect(result).toBe(false); + expect(gridFocus.activeCell()).toBe(cells[0][0]); + expect(gridFocus.activeCoords()).toEqual({row: 0, col: 0}); + }); + + it('should focus coordinates of a disabled cell if softDisabled is true', () => { + const cells = createTestGrid(createGridD); + const gridFocus = setupGridFocus(signal(cells), {softDisabled: signal(true)}); + + cells[1][0].disabled.set(true); // This cell spans {row: 1, col: 2} + const result = gridFocus.focusCoordinates({row: 1, col: 2}); + + expect(result).toBe(true); + expect(gridFocus.activeCell()).toBe(cells[1][0]); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('should return false for out-of-bounds coordinates', () => { + const cells = createTestGrid(createGridD); + const gridFocus = setupGridFocus(signal(cells)); + + const result = gridFocus.focusCoordinates({row: 10, col: 10}); + + expect(result).toBe(false); + }); + }); +}); diff --git a/src/aria/ui-patterns/behaviors/grid/grid-focus.ts b/src/aria/private/behaviors/grid/grid-focus.ts similarity index 90% rename from src/aria/ui-patterns/behaviors/grid/grid-focus.ts rename to src/aria/private/behaviors/grid/grid-focus.ts index 435a5d6abfeb..51a68410afb4 100644 --- a/src/aria/ui-patterns/behaviors/grid/grid-focus.ts +++ b/src/aria/private/behaviors/grid/grid-focus.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, signal} from '@angular/core'; +import {computed, signal, WritableSignal} from '@angular/core'; import {SignalLike} from '../signal-like/signal-like'; import type {GridData, BaseGridCell, RowCol} from './grid-data'; @@ -30,8 +30,8 @@ export interface GridFocusInputs { /** Whether the grid is disabled. */ disabled: SignalLike; - /** Whether disabled cells in the grid should be skipped when navigating. */ - skipDisabled: SignalLike; + /** Whether disabled cells in the grid should be focusable. */ + softDisabled: SignalLike; } /** Dependencies for the `GridFocus` class. */ @@ -43,7 +43,7 @@ interface GridFocusDeps { /** Controls focus for a 2D grid of cells. */ export class GridFocus { /** The current active cell. */ - readonly activeCell = signal(undefined); + readonly activeCell: WritableSignal = signal(undefined); /** The current active cell coordinates. */ readonly activeCoords = signal({row: -1, col: -1}); @@ -95,7 +95,7 @@ export class GridFocus { return gridCells.length === 0 || gridCells.every(row => row.every(cell => cell.disabled())); }); - /** The tabindex for the grid container. */ + /** The tab index for the grid container. */ readonly gridTabIndex = computed<-1 | 0>(() => { if (this.gridDisabled()) { return 0; @@ -105,8 +105,8 @@ export class GridFocus { constructor(readonly inputs: GridFocusInputs & GridFocusDeps) {} - /** Returns the tabindex for the given grid cell cell. */ - getCellTabindex(cell: T): -1 | 0 { + /** Returns the tab index for the given grid cell cell. */ + getCellTabIndex(cell: T): -1 | 0 { if (this.gridDisabled()) { return -1; } @@ -118,7 +118,7 @@ export class GridFocus { /** Returns true if the given cell can be navigated to. */ isFocusable(cell: T): boolean { - return !cell.disabled() || !this.inputs.skipDisabled(); + return this.inputs.grid.hasCell(cell) && (!cell.disabled() || this.inputs.softDisabled()); } /** Focuses the given cell. */ diff --git a/src/aria/private/behaviors/grid/grid-navigation.spec.ts b/src/aria/private/behaviors/grid/grid-navigation.spec.ts new file mode 100644 index 000000000000..e9917c895c2f --- /dev/null +++ b/src/aria/private/behaviors/grid/grid-navigation.spec.ts @@ -0,0 +1,2435 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {signal, Signal, WritableSignal} from '@angular/core'; +import {GridData} from './grid-data'; +import { + createGridA, + createGridB, + createGridC, + createGridD, + createGridE, + createGridF, + TestBaseGridCell, +} from './grid-data.spec'; +import {GridFocus, GridFocusInputs} from './grid-focus'; +import {direction, GridNavigation, GridNavigationInputs, WrapStrategy} from './grid-navigation'; + +export interface TestGridNavigationCell extends TestBaseGridCell { + element: WritableSignal; + disabled: WritableSignal; +} + +function createTestCell(): Omit { + return { + element: signal(document.createElement('div')), + disabled: signal(false), + }; +} + +function createTestGrid(createGridFn: () => TestBaseGridCell[][]): TestGridNavigationCell[][] { + return createGridFn().map((row, r) => + row.map((cell, c) => { + return {...createTestCell(), ...cell}; + }), + ); +} + +function setupGridNavigation( + cells: Signal, + inputs: Partial = {}, +): { + gridNav: GridNavigation; + gridFocus: GridFocus; +} { + const gridData = new GridData({cells}); + const gridFocusInputs: GridFocusInputs = { + focusMode: signal('roving'), + disabled: signal(false), + softDisabled: signal(true), + }; + const gridFocus = new GridFocus({ + grid: gridData, + ...gridFocusInputs, + ...inputs, + }); + + const gridNav = new GridNavigation({ + grid: gridData, + gridFocus: gridFocus, + rowWrap: signal('loop'), + colWrap: signal('loop'), + ...gridFocusInputs, + ...inputs, + }); + + return { + gridNav, + gridFocus, + }; +} + +describe('GridNavigation', () => { + describe('gotoCell', () => { + it('should focus the given cell', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells)); + + const result = gridNav.gotoCell(cells[1][1]); + + expect(result).toBe(true); + expect(gridFocus.activeCell()).toBe(cells[1][1]); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should return false if the cell cannot be focused', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + + cells[1][1].disabled.set(true); + const result = gridNav.gotoCell(cells[1][1]); + + expect(result).toBe(false); + expect(gridFocus.activeCell()).toBeUndefined(); + }); + }); + + describe('gotoCoords', () => { + it('should focus the cell at the given coordinates', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells)); + + const result = gridNav.gotoCoords({row: 1, col: 2}); + + expect(result).toBe(true); + // The cell at `[1][0]` spans `[1,1]`, `[1,2]`, `[2,1]`, and `[2,2]`. + expect(gridFocus.activeCell()).toBe(cells[1][0]); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('should return false if the coordinates cannot be focused when softDisabled is false', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + + cells[1][0].disabled.set(true); // This cell spans {row: 1, col: 2} + const result = gridNav.gotoCoords({row: 1, col: 2}); + + expect(result).toBe(false); + expect(gridFocus.activeCell()).toBeUndefined(); + }); + }); + + describe('peek', () => { + let cells: TestGridNavigationCell[][]; + let gridNav: GridNavigation; + let gridFocus: GridFocus; + + beforeEach(() => { + cells = createTestGrid(createGridB); + const setup = setupGridNavigation(signal(cells)); + gridNav = setup.gridNav; + gridFocus = setup.gridFocus; + }); + + describe('up', () => { + it('should get the next coordinates without changing focus', () => { + gridNav.gotoCoords({row: 1, col: 0}); + + const nextCoords = gridNav.peek(direction.Up, gridFocus.activeCoords()); + + expect(nextCoords).toEqual({row: 0, col: 0}); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('should respect the wrap strategy', () => { + const from = {row: 0, col: 0}; + gridNav.gotoCoords(from); + expect(gridNav.peek(direction.Up, from, 'loop')).toEqual({row: 3, col: 0}); + expect(gridNav.peek(direction.Up, from, 'nowrap')).toBeUndefined(); + expect(gridNav.peek(direction.Up, from, 'continuous')).toBeUndefined(); + }); + + it('should return undefined if all cells are disabled', () => { + cells.flat().forEach(cell => cell.disabled.set(true)); + gridNav.gotoCoords({row: 1, col: 0}); + + const nextCoords = gridNav.peek(direction.Up, gridFocus.activeCoords()); + + expect(nextCoords).toBeUndefined(); + }); + + it('should return undefined if all cells are disabled when softDisabled is false', () => { + const {gridNav} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + cells.flat().forEach(cell => cell.disabled.set(true)); + + const nextCoords = gridNav.peek(direction.Up, {row: 1, col: 0}); + + expect(nextCoords).toBeUndefined(); + }); + + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + gridNav.gotoCoords({row: 1, col: 0}); + cells[0][0].disabled.set(true); + + const nextCoords = gridNav.peek(direction.Up, gridFocus.activeCoords(), 'nowrap', true); + + expect(nextCoords).toEqual({row: 0, col: 0}); + expect(gridNav.peek(direction.Up, gridFocus.activeCoords(), 'nowrap')).toBeUndefined(); + }); + }); + + describe('down', () => { + it('should get the next coordinates without changing focus', () => { + gridNav.gotoCoords({row: 1, col: 0}); + + const nextCoords = gridNav.peek(direction.Down, gridFocus.activeCoords()); + + expect(nextCoords).toEqual({row: 2, col: 0}); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('should respect the wrap strategy', () => { + const from = {row: 3, col: 1}; + gridNav.gotoCoords(from); + expect(gridNav.peek(direction.Down, from, 'loop')).toEqual({row: 0, col: 1}); + expect(gridNav.peek(direction.Down, from, 'nowrap')).toBeUndefined(); + expect(gridNav.peek(direction.Down, from, 'continuous')).toEqual({row: 0, col: 2}); + }); + + it('should return undefined if completely disabled', () => { + cells.flat().forEach(cell => cell.disabled.set(true)); + gridNav.gotoCoords({row: 1, col: 0}); + + const nextCoords = gridNav.peek(direction.Down, gridFocus.activeCoords()); + + expect(nextCoords).toBeUndefined(); + }); + + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + gridNav.gotoCoords({row: 1, col: 0}); + cells[2][0].disabled.set(true); + + const nextCoords = gridNav.peek(direction.Down, gridFocus.activeCoords(), 'nowrap', true); + + expect(nextCoords).toEqual({row: 2, col: 0}); + expect(gridNav.peek(direction.Down, gridFocus.activeCoords(), 'nowrap')).toBeUndefined(); + }); + }); + + describe('left', () => { + it('should get the next coordinates without changing focus', () => { + gridNav.gotoCoords({row: 0, col: 1}); + + const nextCoords = gridNav.peek(direction.Left, gridFocus.activeCoords()); + + expect(nextCoords).toEqual({row: 0, col: 0}); + expect(gridFocus.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('should respect the wrap strategy', () => { + const from = {row: 0, col: 0}; + gridNav.gotoCoords(from); + expect(gridNav.peek(direction.Left, from, 'loop')).toEqual({row: 0, col: 2}); + expect(gridNav.peek(direction.Left, from, 'nowrap')).toBeUndefined(); + expect(gridNav.peek(direction.Left, from, 'continuous')).toBeUndefined(); + }); + + it('should return undefined if completely disabled', () => { + cells.flat().forEach(cell => cell.disabled.set(true)); + gridNav.gotoCoords({row: 1, col: 0}); + + const nextCoords = gridNav.peek(direction.Down, gridFocus.activeCoords()); + + expect(nextCoords).toBeUndefined(); + }); + + it('should get disabled cells when allowDisabled is true when softDisabled is false', () => { + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + gridNav.gotoCoords({row: 0, col: 1}); + cells[0][0].disabled.set(true); + + const nextCoords = gridNav.peek(direction.Left, gridFocus.activeCoords(), 'nowrap', true); + + expect(nextCoords).toEqual({row: 0, col: 0}); + expect(gridNav.peek(direction.Left, gridFocus.activeCoords(), 'nowrap')).toBeUndefined(); + }); + }); + + describe('right', () => { + it('should get the next coordinates without changing focus', () => { + gridNav.gotoCoords({row: 0, col: 1}); + + const nextCoords = gridNav.peek(direction.Right, gridFocus.activeCoords()); + + expect(nextCoords).toEqual({row: 0, col: 2}); + expect(gridFocus.activeCoords()).toEqual({row: 0, col: 1}); + }); + + it('should respect the wrap strategy', () => { + const from = {row: 0, col: 2}; + gridNav.gotoCoords(from); + expect(gridNav.peek(direction.Right, from, 'loop')).toEqual({row: 0, col: 0}); + expect(gridNav.peek(direction.Right, from, 'nowrap')).toBeUndefined(); + expect(gridNav.peek(direction.Right, from, 'continuous')).toEqual({row: 1, col: 0}); + }); + + it('should return undefined if completely disabled', () => { + cells.flat().forEach(cell => cell.disabled.set(true)); + gridNav.gotoCoords({row: 1, col: 0}); + + const nextCoords = gridNav.peek(direction.Down, gridFocus.activeCoords()); + + expect(nextCoords).toBeUndefined(); + }); + + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + gridNav.gotoCoords({row: 0, col: 1}); + cells[0][2].disabled.set(true); + + const nextCoords = gridNav.peek(direction.Right, gridFocus.activeCoords(), 'nowrap', true); + + expect(nextCoords).toEqual({row: 0, col: 2}); + expect(gridNav.peek(direction.Right, gridFocus.activeCoords(), 'nowrap')).toBeUndefined(); + }); + }); + }); + + describe('advance', () => { + describe('wrap=continuous', () => { + describe('up', () => { + describe('case 1', () => { + it('from start', () => { + const cells = createTestGrid(createGridA); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridA); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 2', () => { + it('from start', () => { + const cells = createTestGrid(createGridB); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridB); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 3, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 3', () => { + it('from start', () => { + const cells = createTestGrid(createGridC); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridC); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 3}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 4', () => { + it('from start', () => { + const cells = createTestGrid(createGridD); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridD); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 3, col: 3}); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 5', () => { + it('from start', () => { + const cells = createTestGrid(createGridE); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridE); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 6', () => { + it('from start', () => { + const cells = createTestGrid(createGridF); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + }); + + it('from end', () => { + const cells = createTestGrid(createGridF); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + }); + + describe('down', () => { + describe('case 1', () => { + it('from start', () => { + const cells = createTestGrid(createGridA); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridA); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + }); + + describe('case 2', () => { + it('from start', () => { + const cells = createTestGrid(createGridB); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + gridNav.gotoCoords({row: 3, col: 2}); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + }); + }); + + describe('case 3', () => { + it('from start', () => { + const cells = createTestGrid(createGridC); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + gridNav.gotoCoords({row: 2, col: 3}); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + }); + + describe('case 4', () => { + it('from start', () => { + const cells = createTestGrid(createGridD); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + gridNav.gotoCoords({row: 3, col: 3}); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + }); + }); + + describe('case 5', () => { + it('from start', () => { + const cells = createTestGrid(createGridE); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + gridNav.gotoCoords({row: 0, col: 2}); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + }); + }); + + describe('case 6', () => { + it('from start', () => { + const cells = createTestGrid(createGridF); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + gridNav.gotoCoords({row: 2, col: 2}); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + }); + }); + + describe('left', () => { + describe('case 1', () => { + it('from start', () => { + const cells = createTestGrid(createGridA); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + // Advancing left from the first cell should not change the active cell. + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridA); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 2', () => { + it('from start', () => { + const cells = createTestGrid(createGridB); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridB); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 3, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 3', () => { + it('from start', () => { + const cells = createTestGrid(createGridC); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridC); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 4', () => { + it('from start', () => { + const cells = createTestGrid(createGridD); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridD); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 3, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 5', () => { + it('from start', () => { + const cells = createTestGrid(createGridE); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridE); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 1}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('case 6', () => { + it('from start', () => { + const cells = createTestGrid(createGridF); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridF); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + }); + + describe('right', () => { + describe('case 1', () => { + it('from start', () => { + const cells = createTestGrid(createGridA); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridA); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + }); + + describe('case 2', () => { + it('from start', () => { + const cells = createTestGrid(createGridB); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridB); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 3, col: 2}); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + }); + }); + + describe('case 3', () => { + it('from start', () => { + const cells = createTestGrid(createGridC); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridC); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + }); + + describe('case 4', () => { + it('from start', () => { + const cells = createTestGrid(createGridD); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridD); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 3, col: 2}); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + }); + }); + + describe('case 5', () => { + it('from start', () => { + const cells = createTestGrid(createGridE); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridE); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 1}); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + }); + }); + + describe('case 6', () => { + it('from start', () => { + const cells = createTestGrid(createGridF); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + + it('from end', () => { + const cells = createTestGrid(createGridF); + const setup = setupGridNavigation(signal(cells), { + rowWrap: signal('continuous'), + colWrap: signal('continuous'), + }); + const gridNav = setup.gridNav; + const gridFocus = setup.gridFocus; + + gridNav.gotoCoords({row: 2, col: 2}); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-2'); + }); + }); + }); + }); + + describe('wrap=loop', () => { + describe('up', () => { + it('case 1', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 2', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 3', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 4', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 5', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 6', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('down', () => { + it('case 1', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 2', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 3', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 4', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 5', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 6', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('left', () => { + it('case 1', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 2', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 3', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 4', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 5', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 6', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('right', () => { + it('case 1', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 2', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 3', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 4', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 5', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 6', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('loop'), + colWrap: signal('loop'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + }); + + describe('wrap=nowrap', () => { + describe('up', () => { + it('case 1', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 2, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 2', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 3, col: 1}); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + }); + + it('case 3', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 2, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 4', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 3, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 5', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 2, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 6', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 2, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Up); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('down', () => { + it('case 1', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + }); + + it('case 2', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 1}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-1'); + }); + + it('case 3', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + }); + + it('case 4', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-0'); + }); + + it('case 5', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + }); + + it('case 6', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + gridNav.advance(direction.Down); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + }); + }); + + describe('left', () => { + it('case 1', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 2', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 3', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 3}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 4', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 3}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 5', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + + it('case 6', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 2}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Left); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + }); + }); + + describe('right', () => { + it('case 1', () => { + const cells = createTestGrid(createGridA); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + }); + + it('case 2', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + }); + + it('case 3', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + }); + + it('case 4', () => { + const cells = createTestGrid(createGridD); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-3'); + }); + + it('case 5', () => { + const cells = createTestGrid(createGridE); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + }); + + it('case 6', () => { + const cells = createTestGrid(createGridF); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + rowWrap: signal('nowrap'), + colWrap: signal('nowrap'), + }); + + gridNav.gotoCoords({row: 0, col: 0}); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-0'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-1'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + gridNav.advance(direction.Right); + expect(gridFocus.activeCell()!.id()).toBe('cell-0-2'); + }); + }); + }); + }); + + describe('first/peekFirst', () => { + it('should navigate to the first focusable cell in the grid when softDisabled is false', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + + // Disable the first few cells to make it more interesting. + cells[0][0].disabled.set(true); + cells[0][1].disabled.set(true); + + const firstCoords = gridNav.peekFirst(); + expect(firstCoords).toEqual({row: 0, col: 2}); + + // The active cell should not have changed yet. + expect(gridFocus.activeCell()).toBeUndefined(); + + const result = gridNav.first(); + expect(result).toBe(true); + expect(gridFocus.activeCell()).toBe(cells[0][2]); + expect(gridFocus.activeCoords()).toEqual({row: 0, col: 2}); + }); + + it('should navigate to the first focusable cell in the grid', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells)); + + // Disable the first few cells to make it more interesting. + cells[0][0].disabled.set(true); + cells[0][1].disabled.set(true); + + const firstCoords = gridNav.peekFirst(); + expect(firstCoords).toEqual({row: 0, col: 0}); + + // The active cell should not have changed yet. + expect(gridFocus.activeCell()).toBeUndefined(); + + const result = gridNav.first(); + expect(result).toBe(true); + expect(gridFocus.activeCell()).toBe(cells[0][0]); + expect(gridFocus.activeCoords()).toEqual({row: 0, col: 0}); + }); + + it('should navigate to the first focusable cell in a specific row when softDisabled is false', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + + // Disable the first cell in row 1. + cells[1][0].disabled.set(true); + + const firstInRowCoords = gridNav.peekFirst(1); + expect(firstInRowCoords).toEqual({row: 1, col: 1}); + + // The active cell should not have changed yet. + expect(gridFocus.activeCell()).toBeUndefined(); + + const result = gridNav.first(1); + expect(result).toBe(true); + expect(gridFocus.activeCell()).toBe(cells[1][1]); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should navigate to the first focusable cell in a specific row', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells)); + + // Disable the first cell in row 1. + cells[1][0].disabled.set(true); + + const firstInRowCoords = gridNav.peekFirst(1); + expect(firstInRowCoords).toEqual({row: 1, col: 0}); + + // The active cell should not have changed yet. + expect(gridFocus.activeCell()).toBeUndefined(); + + const result = gridNav.first(1); + expect(result).toBe(true); + expect(gridFocus.activeCell()).toBe(cells[1][0]); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 0}); + }); + + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { + const cells = createTestGrid(createGridA); + const {gridNav} = setupGridNavigation(signal(cells), {softDisabled: signal(false)}); + cells[0][0].disabled.set(true); + + const firstCoords = gridNav.peekFirst(undefined, true); + + expect(firstCoords).toEqual({row: 0, col: 0}); + expect(gridNav.peekFirst()).toEqual({row: 0, col: 1}); + }); + }); + + describe('last/peekLast', () => { + it('should navigate to the last focusable cell in the grid when softDisabled is false', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + + // Disable the last few cells to make it more interesting. + cells[3][1].disabled.set(true); // cell-3-2 + cells[3][0].disabled.set(true); // cell-3-1 + + const lastCoords = gridNav.peekLast(); + expect(lastCoords).toEqual({row: 3, col: 0}); + + // The active cell should not have changed yet. + expect(gridFocus.activeCell()).toBeUndefined(); + + const result = gridNav.last(); + expect(result).toBe(true); + expect(gridFocus.activeCell()!.id()).toBe('cell-2-0'); + expect(gridFocus.activeCoords()).toEqual({row: 3, col: 0}); + }); + + it('should navigate to the last focusable cell in the grid', () => { + const cells = createTestGrid(createGridB); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells)); + + // Disable the last few cells to make it more interesting. + cells[3][1].disabled.set(true); // cell-3-2 + cells[3][0].disabled.set(true); // cell-3-1 + + const lastCoords = gridNav.peekLast(); + expect(lastCoords).toEqual({row: 3, col: 2}); + + // The active cell should not have changed yet. + expect(gridFocus.activeCell()).toBeUndefined(); + + const result = gridNav.last(); + expect(result).toBe(true); + expect(gridFocus.activeCell()!.id()).toBe('cell-3-2'); + expect(gridFocus.activeCoords()).toEqual({row: 3, col: 2}); + }); + + it('should navigate to the last focusable cell in a specific row when softDisabled is false', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells), { + softDisabled: signal(false), + }); + + // Disable the last cell in row 1. + cells[1][2].disabled.set(true); + + const lastInRowCoords = gridNav.peekLast(1); + expect(lastInRowCoords).toEqual({row: 1, col: 2}); + + const result = gridNav.last(1); + expect(result).toBe(true); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-1'); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 2}); + }); + + it('should navigate to the last focusable cell in a specific row', () => { + const cells = createTestGrid(createGridC); + const {gridNav, gridFocus} = setupGridNavigation(signal(cells)); + + // Disable the last cell in row 1. + cells[1][2].disabled.set(true); + + const lastInRowCoords = gridNav.peekLast(1); + expect(lastInRowCoords).toEqual({row: 1, col: 3}); + + const result = gridNav.last(1); + expect(result).toBe(true); + expect(gridFocus.activeCell()!.id()).toBe('cell-1-3'); + expect(gridFocus.activeCoords()).toEqual({row: 1, col: 3}); + }); + + it('should get disabled cells when allowDisabled is true and softDisabled is false', () => { + const cells = createTestGrid(createGridA); + const {gridNav} = setupGridNavigation(signal(cells), {softDisabled: signal(false)}); + cells[2][2].disabled.set(true); + + const lastCoords = gridNav.peekLast(undefined, true); + + expect(lastCoords).toEqual({row: 2, col: 2}); + expect(gridNav.peekLast()).toEqual({row: 2, col: 1}); + }); + }); +}); diff --git a/src/aria/ui-patterns/behaviors/grid/grid-navigation.ts b/src/aria/private/behaviors/grid/grid-navigation.ts similarity index 84% rename from src/aria/ui-patterns/behaviors/grid/grid-navigation.ts rename to src/aria/private/behaviors/grid/grid-navigation.ts index 54426bf3ed7e..5c2198c221ce 100644 --- a/src/aria/ui-patterns/behaviors/grid/grid-navigation.ts +++ b/src/aria/private/behaviors/grid/grid-navigation.ts @@ -73,9 +73,14 @@ export class GridNavigation { /** * Gets the coordinates of the next focusable cell in a given direction, without changing focus. */ - peek(direction: Delta, fromCoords: RowCol, wrap?: WrapStrategy): RowCol | undefined { + peek( + direction: Delta, + fromCoords: RowCol, + wrap?: WrapStrategy, + allowDisabled?: boolean, + ): RowCol | undefined { wrap = wrap ?? (direction.row !== undefined ? this.inputs.rowWrap() : this.inputs.colWrap()); - return this._peekDirectional(direction, fromCoords, wrap); + return this._peekDirectional(direction, fromCoords, wrap, allowDisabled); } /** @@ -90,14 +95,14 @@ export class GridNavigation { * Gets the coordinates of the first focusable cell. * If a row is not provided, searches the entire grid. */ - peekFirst(row?: number): RowCol | undefined { + peekFirst(row?: number, allowDisabled?: boolean): RowCol | undefined { const fromCoords = { row: row ?? 0, col: -1, }; return row === undefined - ? this._peekDirectional(direction.Right, fromCoords, 'continuous') - : this._peekDirectional(direction.Right, fromCoords, 'nowrap'); + ? this._peekDirectional(direction.Right, fromCoords, 'continuous', allowDisabled) + : this._peekDirectional(direction.Right, fromCoords, 'nowrap', allowDisabled); } /** @@ -113,14 +118,14 @@ export class GridNavigation { * Gets the coordinates of the last focusable cell. * If a row is not provided, searches the entire grid. */ - peekLast(row?: number): RowCol | undefined { + peekLast(row?: number, allowDisabled?: boolean): RowCol | undefined { const fromCoords = { row: row ?? this.inputs.grid.maxRowCount() - 1, col: this.inputs.grid.maxColCount(), }; return row === undefined - ? this._peekDirectional(direction.Left, fromCoords, 'continuous') - : this._peekDirectional(direction.Left, fromCoords, 'nowrap'); + ? this._peekDirectional(direction.Left, fromCoords, 'continuous', allowDisabled) + : this._peekDirectional(direction.Left, fromCoords, 'nowrap', allowDisabled); } /** @@ -139,7 +144,10 @@ export class GridNavigation { delta: Delta, fromCoords: RowCol, wrap: 'continuous' | 'loop' | 'nowrap', + allowDisabled: boolean = false, ): RowCol | undefined { + if (this.inputs.gridFocus.gridDisabled()) return undefined; + const fromCell = this.inputs.grid.getCell(fromCoords); const maxRowCount = this.inputs.grid.maxRowCount(); const maxColCount = this.inputs.grid.maxColCount(); @@ -154,13 +162,19 @@ export class GridNavigation { nextCoords.row + rowDelta < 0 || nextCoords.row + rowDelta >= maxRowCount; - if (wrap === 'nowrap' && isWrapping) return; + if (wrap === 'nowrap' && isWrapping) return undefined; if (wrap === 'continuous') { const generalDelta = delta.row ?? delta.col; const rowStep = isWrapping ? generalDelta : rowDelta; const colStep = isWrapping ? generalDelta : colDelta; + // Reaching start or end. + const bothWrapping = + (nextCoords.row + rowStep >= maxRowCount && nextCoords.col + colStep >= maxColCount) || + (nextCoords.row + rowStep < 0 && nextCoords.col + colStep < 0); + if (bothWrapping) return undefined; + nextCoords = { row: (nextCoords.row + rowStep + maxRowCount) % maxRowCount, col: (nextCoords.col + colStep + maxColCount) % maxColCount, @@ -174,6 +188,13 @@ export class GridNavigation { }; } + if (wrap === 'nowrap') { + nextCoords = { + row: nextCoords.row + rowDelta, + col: nextCoords.col + colDelta, + }; + } + // Back to original coordinates. if (nextCoords.row === fromCoords.row && nextCoords.col === fromCoords.col) { return undefined; @@ -183,7 +204,7 @@ export class GridNavigation { if ( nextCell !== undefined && nextCell !== fromCell && - this.inputs.gridFocus.isFocusable(nextCell) + (allowDisabled || this.inputs.gridFocus.isFocusable(nextCell)) ) { return nextCoords; } diff --git a/src/aria/private/behaviors/grid/grid-selection.spec.ts b/src/aria/private/behaviors/grid/grid-selection.spec.ts new file mode 100644 index 000000000000..557fbe4be4ed --- /dev/null +++ b/src/aria/private/behaviors/grid/grid-selection.spec.ts @@ -0,0 +1,303 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {signal, Signal, WritableSignal} from '@angular/core'; +import {GridData} from './grid-data'; +import {createGridA, createGridB, createGridD, TestBaseGridCell} from './grid-data.spec'; +import {GridFocus, GridFocusInputs} from './grid-focus'; +import {GridSelection, GridSelectionInputs} from './grid-selection'; + +export interface TestGridSelectionCell extends TestBaseGridCell { + element: WritableSignal; + disabled: WritableSignal; + selected: WritableSignal; + selectable: WritableSignal; +} + +function createTestCell(): Omit { + return { + element: signal(document.createElement('div')), + disabled: signal(false), + selected: signal(false), + selectable: signal(true), + }; +} + +function createTestGrid(createGridFn: () => TestBaseGridCell[][]): TestGridSelectionCell[][] { + return createGridFn().map(row => + row.map(cell => { + return {...createTestCell(), ...cell}; + }), + ); +} + +function setupGridSelection( + cells: Signal, + inputs: Partial = {}, +): { + gridSelection: GridSelection; + gridFocus: GridFocus; +} { + const gridData = new GridData({cells}); + const gridFocusInputs: GridFocusInputs = { + focusMode: signal('roving'), + disabled: signal(false), + softDisabled: signal(true), + }; + const gridFocus = new GridFocus({ + grid: gridData, + ...gridFocusInputs, + ...inputs, + }); + + const gridSelection = new GridSelection({ + grid: gridData, + gridFocus: gridFocus, + ...gridFocusInputs, + ...inputs, + }); + + return {gridSelection, gridFocus}; +} + +describe('GridSelection', () => { + describe('select', () => { + it('should select a single cell', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + + gridSelection.select({row: 1, col: 1}); + + expect(cells[1][1].selected()).toBe(true); + }); + + it('should select a range of cells', () => { + const cells = createTestGrid(createGridD); + const {gridSelection} = setupGridSelection(signal(cells)); + + gridSelection.select({row: 0, col: 0}, {row: 1, col: 1}); + + expect(cells[0][0].selected()).toBe(true); // Spans {0,0}, {1,0} + expect(cells[0][1].selected()).toBe(true); // Spans {0,1}, {0,2} + expect(cells[1][0].selected()).toBe(true); // Spans {1,1}, {1,2}, {2,1}, {2,2} + }); + + it('should not select disabled or unselectable cells', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + + cells[0][1].disabled.set(true); + cells[1][0].selectable.set(false); + + gridSelection.select({row: 0, col: 0}, {row: 1, col: 1}); + + expect(cells[0][0].selected()).toBe(true); + expect(cells[0][1].selected()).toBe(false); + expect(cells[1][0].selected()).toBe(false); + expect(cells[1][1].selected()).toBe(true); + }); + }); + + describe('deselect', () => { + it('should deselect a single cell', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + cells[1][1].selected.set(true); + + gridSelection.deselect({row: 1, col: 1}); + + expect(cells[1][1].selected()).toBe(false); + }); + + it('should deselect a range of cells', () => { + const cells = createTestGrid(createGridD); + const {gridSelection} = setupGridSelection(signal(cells)); + cells[0][0].selected.set(true); + cells[0][1].selected.set(true); + cells[1][0].selected.set(true); + + gridSelection.deselect({row: 0, col: 0}, {row: 1, col: 1}); + + expect(cells[0][0].selected()).toBe(false); + expect(cells[0][1].selected()).toBe(false); + expect(cells[1][0].selected()).toBe(false); + }); + }); + + describe('toggle', () => { + it('should toggle the selection of a single cell', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + + gridSelection.toggle({row: 1, col: 1}); + expect(cells[1][1].selected()).toBe(true); + + gridSelection.toggle({row: 1, col: 1}); + expect(cells[1][1].selected()).toBe(false); + }); + + it('should toggle a range of cells', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + cells[0][0].selected.set(true); + cells[1][1].selected.set(true); + + gridSelection.toggle({row: 0, col: 0}, {row: 0, col: 1}); + + expect(cells[0][0].selected()).toBe(false); + expect(cells[0][1].selected()).toBe(true); + expect(cells[1][1].selected()).toBe(true); // Unchanged + }); + }); + + describe('selectAll', () => { + it('should select all selectable and enabled cells', () => { + const cells = createTestGrid(createGridB); + const {gridSelection} = setupGridSelection(signal(cells)); + + cells[0][1].disabled.set(true); + cells[1][1].selectable.set(false); + + gridSelection.selectAll(); + + const flatCells = cells.flat(); + expect(flatCells.filter(c => c.selected()).length).toBe(flatCells.length - 2); + expect(cells[0][1].selected()).toBe(false); + expect(cells[1][1].selected()).toBe(false); + }); + }); + + describe('deselectAll', () => { + it('should deselect all cells', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + + // Select some cells + cells[0][0].selected.set(true); + cells[1][1].selected.set(true); + cells[2][2].selected.set(true); + + gridSelection.deselectAll(); + + const flatCells = cells.flat(); + expect(flatCells.every(c => !c.selected())).toBe(true); + }); + }); + + describe('_validCells', () => { + it('should yield all selectable and enabled cells in a range', () => { + const cells = createTestGrid(createGridD); + const {gridSelection} = setupGridSelection(signal(cells)); + + cells[0][1].disabled.set(true); // cell-0-1 + cells[1][0].selectable.set(false); // cell-1-1 + + const validCells = Array.from(gridSelection._validCells({row: 0, col: 0}, {row: 3, col: 3})); + + const validCellIds = validCells.map(c => c.id()); + const allCellIds = cells.flat().map(c => c.id()); + + expect(validCellIds).not.toContain('cell-0-1'); + expect(validCellIds).not.toContain('cell-1-1'); + expect(validCellIds.length).toBe(allCellIds.length - 2); + }); + }); + + describe('undo', () => { + it('should undo a select operation', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + + gridSelection.select({row: 1, col: 1}); + expect(cells[1][1].selected()).toBe(true); + + gridSelection.undo(); + expect(cells[1][1].selected()).toBe(false); + }); + + it('should undo a deselect operation', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + cells[1][1].selected.set(true); + + gridSelection.deselect({row: 1, col: 1}); + expect(cells[1][1].selected()).toBe(false); + + gridSelection.undo(); + expect(cells[1][1].selected()).toBe(true); + }); + + it('should undo a toggle operation', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + cells[0][0].selected.set(true); + + gridSelection.toggle({row: 0, col: 0}, {row: 0, col: 1}); + expect(cells[0][0].selected()).toBe(false); + expect(cells[0][1].selected()).toBe(true); + + gridSelection.undo(); + expect(cells[0][0].selected()).toBe(true); + expect(cells[0][1].selected()).toBe(false); + }); + + it('should undo a selectAll operation', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + + gridSelection.selectAll(); + expect(cells.flat().every(c => c.selected())).toBe(true); + + gridSelection.undo(); + expect(cells.flat().every(c => !c.selected())).toBe(true); + }); + + it('should undo a deselectAll operation', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + cells.flat().forEach(c => c.selected.set(true)); + + gridSelection.deselectAll(); + expect(cells.flat().every(c => !c.selected())).toBe(true); + + gridSelection.undo(); + expect(cells.flat().every(c => c.selected())).toBe(true); + }); + + it('should do nothing if there is nothing to undo', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + cells[1][1].selected.set(true); + + gridSelection.undo(); + expect(cells[1][1].selected()).toBe(true); + }); + + it('should only undo the last operation', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + + gridSelection.select({row: 0, col: 0}); + gridSelection.select({row: 1, col: 1}); + expect(cells[1][1].selected()).toBe(true); + + gridSelection.undo(); + expect(cells[0][0].selected()).toBe(true); + expect(cells[1][1].selected()).toBe(false); + }); + + it('should do nothing after undoing once', () => { + const cells = createTestGrid(createGridA); + const {gridSelection} = setupGridSelection(signal(cells)); + gridSelection.select({row: 1, col: 1}); + gridSelection.undo(); + gridSelection.undo(); + expect(cells[1][1].selected()).toBe(false); + }); + }); +}); diff --git a/src/aria/ui-patterns/behaviors/grid/grid-selection.ts b/src/aria/private/behaviors/grid/grid-selection.ts similarity index 65% rename from src/aria/ui-patterns/behaviors/grid/grid-selection.ts rename to src/aria/private/behaviors/grid/grid-selection.ts index b7f548f91ad7..196eb611ac3f 100644 --- a/src/aria/ui-patterns/behaviors/grid/grid-selection.ts +++ b/src/aria/private/behaviors/grid/grid-selection.ts @@ -9,6 +9,7 @@ import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; import {GridFocus, GridFocusCell, GridFocusInputs} from './grid-focus'; import {GridData, RowCol} from './grid-data'; +import {signal} from '@angular/core'; /** Represents a cell in a grid that can be selected. */ export interface GridSelectionCell extends GridFocusCell { @@ -33,50 +34,58 @@ interface GridSelectionDeps { /** Controls selection for a grid of items. */ export class GridSelection { + /** The list of cells that were changed in the last selection operation. */ + private readonly _undoList: WritableSignalLike<[T, boolean][]> = signal([]); + constructor(readonly inputs: GridSelectionInputs & GridSelectionDeps) {} + /** Reverts the last selection change. */ + undo(): void { + for (const [cell, oldState] of this._undoList()) { + cell.selected.set(oldState); + } + this._undoList.set([]); + } + /** Selects one or more cells in a given range. */ select(fromCoords: RowCol, toCoords?: RowCol): void { - for (const cell of this._validCells(fromCoords, toCoords ?? fromCoords)) { - cell.selected.set(true); - } + this._updateState(fromCoords, toCoords ?? fromCoords, () => true); } /** Deselects one or more cells in a given range. */ deselect(fromCoords: RowCol, toCoords?: RowCol): void { - for (const cell of this._validCells(fromCoords, toCoords ?? fromCoords)) { - cell.selected.set(false); - } + this._updateState(fromCoords, toCoords ?? fromCoords, () => false); } /** Toggles the selection state of one or more cells in a given range. */ toggle(fromCoords: RowCol, toCoords?: RowCol): void { - for (const cell of this._validCells(fromCoords, toCoords ?? fromCoords)) { - cell.selected.update(state => !state); - } + this._updateState(fromCoords, toCoords ?? fromCoords, oldState => !oldState); } /** Selects all valid cells in the grid. */ selectAll(): void { - for (const cell of this._validCells( + this._updateState( {row: 0, col: 0}, {row: this.inputs.grid.maxRowCount(), col: this.inputs.grid.maxColCount()}, - )) { - cell.selected.set(true); - } + () => true, + ); } /** Deselects all valid cells in the grid. */ deselectAll(): void { - for (const cell of this._validCells( + this._updateState( {row: 0, col: 0}, {row: this.inputs.grid.maxRowCount(), col: this.inputs.grid.maxColCount()}, - )) { - cell.selected.set(false); - } + () => false, + ); + } + + /** Whether a cell is selctable. */ + isSelectable(cell: T) { + return cell.selectable() && !cell.disabled(); } - /** A generator that yields all valid (selectable and not disabled) cells within a given range. */ + /** A generator that yields all cells within a given range. */ *_validCells(fromCoords: RowCol, toCoords: RowCol): Generator { const startRow = Math.min(fromCoords.row, toCoords.row); const startCol = Math.min(fromCoords.col, toCoords.col); @@ -87,12 +96,29 @@ export class GridSelection { for (let col = startCol; col < endCol + 1; col++) { const cell = this.inputs.grid.getCell({row, col}); if (cell === undefined) continue; - if (!cell.selectable()) continue; - if (cell.disabled()) continue; + if (!this.isSelectable(cell)) continue; if (visited.has(cell)) continue; visited.add(cell); yield cell; } } } + + /** + * Updates the selection state of cells in a given range and preserves previous changes + * to a undo list. + */ + private _updateState( + fromCoords: RowCol, + toCoords: RowCol, + stateFn: (oldState: boolean) => boolean, + ): void { + const undoList: [T, boolean][] = []; + for (const cell of this._validCells(fromCoords, toCoords)) { + const oldState = cell.selected(); + undoList.push([cell, oldState]); + cell.selected.set(stateFn(oldState)); + } + this._undoList.set(undoList); + } } diff --git a/src/aria/private/behaviors/grid/grid.spec.ts b/src/aria/private/behaviors/grid/grid.spec.ts new file mode 100644 index 000000000000..c3728cb6edf5 --- /dev/null +++ b/src/aria/private/behaviors/grid/grid.spec.ts @@ -0,0 +1,423 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {signal, Signal, WritableSignal} from '@angular/core'; +import {Grid, GridInputs} from './grid'; +import {createGridA, createGridD, TestBaseGridCell} from './grid-data.spec'; +import {WrapStrategy} from './grid-navigation'; + +interface TestGridCell extends TestBaseGridCell { + element: WritableSignal; + disabled: WritableSignal; + selected: WritableSignal; + selectable: WritableSignal; +} + +function createTestCell(): Omit { + return { + element: signal(document.createElement('div')), + disabled: signal(false), + selected: signal(false), + selectable: signal(true), + }; +} + +function createTestGrid(createGridFn: () => TestBaseGridCell[][]): TestGridCell[][] { + return createGridFn().map(row => + row.map(cell => { + return {...createTestCell(), ...cell}; + }), + ); +} + +function setupGrid( + cells: Signal, + inputs: Partial> = {}, +): Grid { + const gridInputs: GridInputs = { + cells, + focusMode: signal('roving'), + disabled: signal(false), + softDisabled: signal(true), + rowWrap: signal('loop'), + colWrap: signal('loop'), + ...inputs, + }; + + return new Grid(gridInputs); +} + +describe('Grid', () => { + describe('indices', () => { + it('should return 1-based row and column indices', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + + expect(grid.rowIndex(cells[1][2])).toBe(2); + expect(grid.colIndex(cells[1][2])).toBe(3); + }); + + it('should return undefined for a cell not in the grid', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + const otherCell = createTestGrid(createGridA)[0][0]; + + expect(grid.rowIndex(otherCell)).toBeUndefined(); + expect(grid.colIndex(otherCell)).toBeUndefined(); + }); + }); + + describe('cellTabIndex', () => { + it('should return the tab index for a cell', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + + grid.gotoCell(cells[1][1]); + + expect(grid.cellTabIndex(cells[1][1])).toBe(0); + expect(grid.cellTabIndex(cells[0][0])).toBe(-1); + }); + }); + + describe('navigation', () => { + let cells: TestGridCell[][]; + let grid: Grid; + + beforeEach(() => { + cells = createTestGrid(createGridA); + grid = setupGrid(signal(cells)); + grid.gotoCell(cells[1][1]); + }); + + it('should navigate up/down/left/right', () => { + expect(grid.focusBehavior.activeCell()).toBe(cells[1][1]); + grid.up(); + expect(grid.focusBehavior.activeCell()).toBe(cells[0][1]); + grid.down(); + expect(grid.focusBehavior.activeCell()).toBe(cells[1][1]); + grid.left(); + expect(grid.focusBehavior.activeCell()).toBe(cells[1][0]); + grid.right(); + expect(grid.focusBehavior.activeCell()).toBe(cells[1][1]); + }); + + it('should navigate to first/last cell in grid', () => { + grid.last(); + expect(grid.focusBehavior.activeCell()).toBe(cells[2][2]); + grid.first(); + expect(grid.focusBehavior.activeCell()).toBe(cells[0][0]); + }); + + it('should navigate to first/last cell in row', () => { + grid.lastInRow(); + expect(grid.focusBehavior.activeCell()).toBe(cells[1][2]); + grid.firstInRow(); + expect(grid.focusBehavior.activeCell()).toBe(cells[1][0]); + }); + + describe('with selection', () => { + it('should select one on navigate when `selectOne` is true', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[0][0]); + grid.select(); // cell 0,0 is selected + + grid.down({selectOne: true}); + + expect(cells[0][0].selected()).toBe(false); + expect(cells[1][0].selected()).toBe(true); + expect(grid.focusBehavior.activeCell()).toBe(cells[1][0]); + }); + + it('should select on navigate when `select` is true', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[0][0]); + grid.select(); // cell 0,0 is selected + + grid.down({select: true}); + + expect(cells[0][0].selected()).toBe(true); + expect(cells[1][0].selected()).toBe(true); + expect(grid.focusBehavior.activeCell()).toBe(cells[1][0]); + }); + + it('should toggle on navigate when `toggle` is true', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[0][0]); + + grid.down({toggle: true}); // Move to 1,0 and select it + expect(cells[1][0].selected()).toBe(true); + + grid.up({toggle: true}); // Move to 0,0 and select it + expect(cells[0][0].selected()).toBe(true); + expect(cells[1][0].selected()).toBe(true); // 1,0 remains selected + + grid.down({toggle: true}); // Move to 1,0 and deselect it + expect(cells[1][0].selected()).toBe(false); + }); + + it('should toggle one on navigate when `toggleOne` is true', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[0][0]); + grid.select(); // cell 0,0 is selected + + grid.down({toggleOne: true}); // Move to 1,0 + + expect(cells[0][0].selected()).toBe(false); + expect(cells[1][0].selected()).toBe(true); + + grid.down({toggleOne: true}); // Move to 2,0 + expect(cells[1][0].selected()).toBe(false); + expect(cells[2][0].selected()).toBe(true); + }); + + it('should range select on navigate when `anchor` is true', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[1][1]); + grid.down({anchor: true}); + expect(cells.flat().filter(c => c.selected()).length).toBe(2); + expect(cells[1][1].selected()).toBe(true); + expect(cells[2][1].selected()).toBe(true); + }); + }); + }); + + describe('selection', () => { + describe('selectRow', () => { + it('should select all cells in the current row', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[0][0]); + grid.select(); + expect(cells[0][0].selected()).toBe(true); + + grid.gotoCell(cells[1][1]); + grid.selectRow(); + + expect(cells[0][0].selected()).toBe(false); + expect(cells[1][0].selected()).toBe(true); + expect(cells[1][1].selected()).toBe(true); + expect(cells[1][2].selected()).toBe(true); + expect(cells[2][0].selected()).toBe(false); + }); + }); + + describe('selectCol', () => { + it('should select all cells in the current column', () => { + const cells = createTestGrid(createGridD); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[0][0]); + grid.selectCol(); + + expect(cells[0][0].selected()).toBe(true); // spans row 0 and 1 in col 0 + expect(cells[2][0].selected()).toBe(true); + expect(cells[3][0].selected()).toBe(true); + expect(cells[0][1].selected()).toBe(false); + }); + }); + + describe('select', () => { + it('should select the active cell', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[1][1]); + grid.select(); + + expect(cells[1][1].selected()).toBe(true); + expect(cells[0][0].selected()).toBe(false); + }); + }); + + describe('deselect', () => { + it('should deselect the active cell', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[1][1]); + grid.select(); + expect(cells[1][1].selected()).toBe(true); + + grid.deselect(); + expect(cells[1][1].selected()).toBe(false); + }); + }); + + describe('toggle', () => { + it('should toggle the selection of the active cell', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[1][1]); + + grid.toggle(); + expect(cells[1][1].selected()).toBe(true); + + grid.toggle(); + expect(cells[1][1].selected()).toBe(false); + }); + }); + + describe('toggleOne', () => { + it('should toggle the selection of the active cell and deselect others', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + grid.gotoCell(cells[0][0]); + grid.select(); + + grid.gotoCell(cells[1][1]); + grid.toggleOne(); + + expect(cells[0][0].selected()).toBe(false); + expect(cells[1][1].selected()).toBe(true); + + grid.toggleOne(); + expect(cells[1][1].selected()).toBe(false); + }); + }); + + describe('selectAll', () => { + it('should select all selectable cells', () => { + const cells = createTestGrid(createGridA); + cells[1][1].selectable.set(false); + const grid = setupGrid(signal(cells)); + grid.selectAll(); + + expect(cells.flat().filter(c => c.selected()).length).toBe(8); + expect(cells[1][1].selected()).toBe(false); + }); + }); + }); + + describe('setDefaultState', () => { + it('should focus the first focusable selected cell if one exists', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells), {softDisabled: signal(false)}); + + // This one is selected but not focusable. + cells[1][1].selected.set(true); + cells[1][1].disabled.set(true); + + // This is the first focusable selected cell. + cells[2][0].selected.set(true); + + // This one is also focusable and selected, but comes after. + cells[2][2].selected.set(true); + + const result = grid.setDefaultState(); + + expect(result).toBe(true); + expect(grid.focusBehavior.activeCell()).toBe(cells[2][0]); + }); + + it('should focus the first focusable cell if no selected cell exists', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells), {softDisabled: signal(false)}); + + cells[0][0].disabled.set(true); + + const result = grid.setDefaultState(); + + expect(result).toBe(true); + expect(grid.focusBehavior.activeCell()).toBe(cells[0][1]); + }); + + it('should return false if no focusable cell is found', () => { + const cells = createTestGrid(createGridA); + cells.flat().forEach(c => c.disabled.set(true)); + const grid = setupGrid(signal(cells), {softDisabled: signal(false)}); + + const result = grid.setDefaultState(); + + expect(result).toBe(false); + expect(grid.focusBehavior.activeCell()).toBeUndefined(); + }); + }); + + describe('resetState', () => { + it('should focus the first focusable cell if state is empty', () => { + const cells = createTestGrid(createGridA); + const grid = setupGrid(signal(cells)); + + expect(grid.focusBehavior.stateEmpty()).toBe(true); + const result = grid.resetState(); + expect(result).toBe(true); + expect(grid.focusBehavior.activeCell()).toBe(cells[0][0]); + }); + + it('should return false if no focusable cell is found when state is empty', () => { + const cells = createTestGrid(createGridA); + cells.flat().forEach(c => c.disabled.set(true)); + const grid = setupGrid(signal(cells)); + + const result = grid.resetState(); + expect(result).toBe(false); + expect(grid.focusBehavior.activeCell()).toBeUndefined(); + }); + + it('should re-focus the active cell if it is stale but still exists', () => { + const cellsSignal = signal(createTestGrid(createGridA)); + const grid = setupGrid(cellsSignal); + const originalCell = cellsSignal()[1][1]; + grid.gotoCell(originalCell); + + // Simulate reordering by creating a new grid but keeping the original cell instance + const newCells = createTestGrid(createGridA); + newCells[2][2] = originalCell; + cellsSignal.set(newCells); + + expect(grid.focusBehavior.stateStale()).toBe(true); + const result = grid.resetState(); + expect(result).toBe(true); + expect(grid.focusBehavior.activeCell()).toBe(originalCell); + expect(grid.focusBehavior.activeCoords()).toEqual({row: 2, col: 2}); + }); + + it('should focus the original coordinates if the active cell is gone', () => { + const cellsSignal = signal(createTestGrid(createGridA)); + const grid = setupGrid(cellsSignal); + grid.gotoCell(cellsSignal()[1][1]); + + // Replace the cell at {1,1} + const newCells = createTestGrid(createGridA); + cellsSignal.set(newCells); + + expect(grid.focusBehavior.stateStale()).toBe(true); + const result = grid.resetState(); + expect(result).toBe(true); + expect(grid.focusBehavior.activeCell()).toBe(newCells[1][1]); + expect(grid.focusBehavior.activeCoords()).toEqual({row: 1, col: 1}); + }); + + it('should focus the first cell if active cell and coords are no longer valid', () => { + const cellsSignal = signal(createTestGrid(createGridA)); + const grid = setupGrid(cellsSignal); + grid.gotoCell(cellsSignal()[2][2]); + + // Make grid smaller + const newCells: TestGridCell[][] = [ + [ + {...createTestCell(), id: signal('cell-0-0'), rowSpan: signal(1), colSpan: signal(1)}, + {...createTestCell(), id: signal('cell-0-1'), rowSpan: signal(1), colSpan: signal(1)}, + ], + [ + {...createTestCell(), id: signal('cell-1-0'), rowSpan: signal(1), colSpan: signal(1)}, + {...createTestCell(), id: signal('cell-1-1'), rowSpan: signal(1), colSpan: signal(1)}, + ], + ]; + cellsSignal.set(newCells); + + expect(grid.focusBehavior.stateStale()).toBe(true); + const result = grid.resetState(); + expect(result).toBe(true); + expect(grid.focusBehavior.activeCell()).toBe(newCells[0][0]); + expect(grid.focusBehavior.activeCoords()).toEqual({row: 0, col: 0}); + }); + }); +}); diff --git a/src/aria/private/behaviors/grid/grid.ts b/src/aria/private/behaviors/grid/grid.ts new file mode 100644 index 000000000000..889bd00fd46c --- /dev/null +++ b/src/aria/private/behaviors/grid/grid.ts @@ -0,0 +1,420 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed, linkedSignal, signal} from '@angular/core'; +import {GridData, BaseGridCell, GridDataInputs, RowCol} from './grid-data'; +import {GridFocus, GridFocusCell, GridFocusInputs} from './grid-focus'; +import { + direction, + GridNavigation, + GridNavigationCell, + GridNavigationInputs, +} from './grid-navigation'; +import {GridSelectionCell, GridSelectionInputs, GridSelection} from './grid-selection'; +import {SignalLike} from '../signal-like/signal-like'; + +/** The selection operations that can be performed after a navigation operation. */ +export interface NavOptions { + /** Toggles the selection state of the active cell. */ + toggle?: boolean; + + /** + * Toggles the selection state of the active cell, and deselects all other cells if the + * active cell is selected. If the active cell is the only selected cell, it will be deselected. + */ + toggleOne?: boolean; + + /** Selects the active cell, preserving the selection state of other cells. */ + select?: boolean; + + /** Deselects all other cells and selects the active cell. */ + selectOne?: boolean; + + /** + * Moves the selection anchor to the active cell and updates the selection to include all + * cells between the anchor and the active cell. + */ + anchor?: boolean; +} + +/** A type that represents a cell in a grid, combining all cell-related interfaces. */ +export type GridCell = BaseGridCell & GridFocusCell & GridNavigationCell & GridSelectionCell; + +/** Represents the required inputs for a grid. */ +export type GridInputs = GridDataInputs & + GridFocusInputs & + GridNavigationInputs & + GridSelectionInputs; + +/** The main class that orchestrates the grid behaviors. */ +export class Grid { + /** The underlying data structure for the grid. */ + readonly data: GridData; + + /** Controls focus for the grid. */ + readonly focusBehavior: GridFocus; + + /** Controls navigation for the grid. */ + readonly navigationBehavior: GridNavigation; + + /** Controls selection for the grid. */ + readonly selectionBehavior: GridSelection; + + /** The anchor point for range selection, linked to the active coordinates. */ + readonly selectionAnchor = linkedSignal(() => this.focusBehavior.activeCoords()); + + /** The cell at the selection anchor. */ + readonly selectionAnchorCell = computed(() => this.data.getCell(this.selectionAnchor())); + + /** Whether a range selection has settled. */ + readonly selectionStabled = signal(true); + + /** Whether all selectable cells are selected. */ + readonly allSelected: SignalLike = computed(() => + this.data + .cells() + .flat() + .filter(c => this.selectionBehavior.isSelectable(c)) + .every(c => c.selected()), + ); + + /** The tab index for the grid container. */ + readonly gridTabIndex: SignalLike<-1 | 0> = () => this.focusBehavior.gridTabIndex(); + + /** Whether the grid is in a disabled state. */ + readonly gridDisabled: SignalLike = () => this.focusBehavior.gridDisabled(); + + /** The ID of the active descendant for ARIA `activedescendant` focus management. */ + readonly activeDescendant: SignalLike = () => + this.focusBehavior.activeDescendant(); + + constructor(readonly inputs: GridInputs) { + this.data = new GridData(inputs); + this.focusBehavior = new GridFocus({...inputs, grid: this.data}); + this.navigationBehavior = new GridNavigation({ + ...inputs, + grid: this.data, + gridFocus: this.focusBehavior, + }); + this.selectionBehavior = new GridSelection({ + ...inputs, + grid: this.data, + gridFocus: this.focusBehavior, + }); + } + + /** Gets the 1-based row index of a cell. */ + rowIndex(cell: T): number | undefined { + const index = this.data.getCoords(cell)?.row; + return index !== undefined ? index + 1 : undefined; + } + + /** Gets the 1-based column index of a cell. */ + colIndex(cell: T): number | undefined { + const index = this.data.getCoords(cell)?.col; + return index !== undefined ? index + 1 : undefined; + } + + /** Gets the tab index for a given cell. */ + cellTabIndex(cell: T): -1 | 0 { + return this.focusBehavior.getCellTabIndex(cell); + } + + /** Navigates to the cell above the currently active cell. */ + up(opts: NavOptions = {}): boolean { + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => + this.navigationBehavior.peek(direction.Up, this.selectionAnchor(), 'nowrap', true), + ) + : this.navigationBehavior.advance(direction.Up), + opts, + ); + } + + /** Navigates to the cell below the currently active cell. */ + down(opts: NavOptions = {}): boolean { + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => + this.navigationBehavior.peek(direction.Down, this.selectionAnchor(), 'nowrap', true), + ) + : this.navigationBehavior.advance(direction.Down), + opts, + ); + } + + /** Navigates to the cell to the left of the currently active cell. */ + left(opts: NavOptions = {}): boolean { + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => + this.navigationBehavior.peek(direction.Left, this.selectionAnchor(), 'nowrap', true), + ) + : this.navigationBehavior.advance(direction.Left), + opts, + ); + } + + /** Navigates to the cell to the right of the currently active cell. */ + right(opts: NavOptions = {}): boolean { + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => + this.navigationBehavior.peek(direction.Right, this.selectionAnchor(), 'nowrap', true), + ) + : this.navigationBehavior.advance(direction.Right), + opts, + ); + } + + /** Navigates to the first focusable cell in the grid. */ + first(opts: NavOptions = {}): boolean { + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => this.navigationBehavior.peekFirst(undefined, true)) + : this.navigationBehavior.first(), + opts, + ); + } + + /** Navigates to the first focusable cell in the current row. */ + firstInRow(opts: NavOptions = {}): boolean { + const row = this.focusBehavior.activeCoords().row; + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => this.navigationBehavior.peekFirst(row, true)) + : this.navigationBehavior.first(row), + opts, + ); + } + + /** Navigates to the last focusable cell in the grid. */ + last(opts: NavOptions = {}): boolean { + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => this.navigationBehavior.peekLast(undefined, true)) + : this.navigationBehavior.last(), + opts, + ); + } + + /** Navigates to the last focusable cell in the current row. */ + lastInRow(opts: NavOptions = {}): boolean { + const row = this.focusBehavior.activeCoords().row; + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => this.navigationBehavior.peekLast(row, true)) + : this.navigationBehavior.last(row), + opts, + ); + } + + /** Selects all cells in the current row. */ + selectRow(): void { + const row = this.focusBehavior.activeCoords().row; + this.selectionBehavior.deselectAll(); + this.selectionBehavior.select({row, col: 0}, {row, col: this.data.maxColCount()}); + } + + /** Selects all cells in the current column. */ + selectCol(): void { + const col = this.focusBehavior.activeCoords().col; + this.selectionBehavior.deselectAll(); + this.selectionBehavior.select({row: 0, col}, {row: this.data.maxRowCount(), col}); + } + + /** Selects the active cell. */ + select(): void { + this.selectionBehavior.select(this.focusBehavior.activeCoords()); + } + + /** Deselects the active cell. */ + deselect(): void { + this.selectionBehavior.deselect(this.focusBehavior.activeCoords()); + } + + /** + * Toggles the selection state of the coordinates of the given cell + * or the current active coordinates. + */ + toggle(): void { + this.selectionBehavior.toggle(this.focusBehavior.activeCoords()); + } + + /** Toggles the selection state of the active cell, and deselects all other cells. */ + toggleOne(): void { + const selected = !!this.focusBehavior.activeCell()?.selected(); + if (selected) { + this.deselect(); + return; + } + + this.deselectAll(); + this.select(); + } + + /** Selects all selectable cells in the grid. */ + selectAll(): void { + this.selectionBehavior.selectAll(); + } + + /** Deselects all cells in the grid. */ + deselectAll(): void { + this.selectionBehavior.deselectAll(); + } + + /** Navigates to and focuses the given cell. */ + gotoCell(cell: T, opts: NavOptions = {}): boolean { + return this._navigateWithSelection( + () => + opts.anchor + ? this._updateSelectionAnchor(() => this.data.getCoords(cell)) + : this.navigationBehavior.gotoCell(cell), + opts, + ); + } + + /** Sets the default active state of the grid. */ + setDefaultState(): boolean { + // Try to find a selected cell that's focusable. + const focusableSelectedCell: T | undefined = this.data + .cells() + .flat() + .filter(c => this.focusBehavior.isFocusable(c)) + .find(c => c.selected()); + + if (focusableSelectedCell !== undefined) { + this.focusBehavior.focusCell(focusableSelectedCell); + return true; + } + + // Otherwise find the first focusable cell. + const firstFocusableCoords = this.navigationBehavior.peekFirst(); + + if (firstFocusableCoords !== undefined) { + return this.focusBehavior.focusCoordinates(firstFocusableCoords); + } + + return false; + } + + /** Resets the active state of the grid if it is empty or stale. */ + resetState(): boolean { + if (this.focusBehavior.stateEmpty()) { + return this.setDefaultState(); + } + + if (this.focusBehavior.stateStale()) { + // Try focus on the same active cell after if a reordering happened. + if (this.focusBehavior.focusCell(this.focusBehavior.activeCell()!)) { + return true; + } + + // If the active cell is no longer exist, focus on the coordinates instead. + if (this.focusBehavior.focusCoordinates(this.focusBehavior.activeCoords())) { + return true; + } + + // If the coordinates no longer valid, go back to the first available cell. + if (this.focusBehavior.focusCoordinates(this.navigationBehavior.peekFirst()!)) { + return true; + } + } + + return false; + } + + /** Updates the selection anchor to the given coordinates. */ + private _updateSelectionAnchor(peekFn: () => RowCol | undefined): boolean { + const coords = peekFn(); + const success = coords !== undefined; + if (!success) return false; + this.selectionAnchor.set(coords); + return success; + } + + /** Updates the selection to include all cells between the anchor and the active cell. */ + private _updateRangeSelection(): void { + if (!this.selectionStabled()) { + this.selectionBehavior.undo(); + } + this.selectionBehavior.select( + ...this._getSelectionCoords(this.focusBehavior.activeCoords(), this.selectionAnchor()), + ); + } + + /** Gets the start and end coordinates for a selection range. */ + private _getSelectionCoords(startCoords: RowCol, endCoords: RowCol): [RowCol, RowCol] { + const startCell = this.data.getCell(startCoords)!; + const endCell = this.data.getCell(endCoords)!; + const allCoords = [...this.data.getAllCoords(startCell)!, ...this.data.getAllCoords(endCell)!]; + const allRows = allCoords.map(c => c.row); + const allCols = allCoords.map(c => c.col); + const fromCoords = { + row: Math.min(...allRows), + col: Math.min(...allCols), + }; + const toCoords = { + row: Math.max(...allRows), + col: Math.max(...allCols), + }; + + return [fromCoords, toCoords]; + } + + /** Executes a navigation operation and applies selection options. */ + private _navigateWithSelection(op: () => boolean, opts: NavOptions = {}): boolean { + const success = op(); + if (!success) return false; + + if (opts.anchor) { + this._updateRangeSelection(); + this.selectionStabled.set(false); + return success; + } + + // Selection becomes stable after the active cell/coords moved. + this.selectionStabled.set(true); + + if (opts.select) { + this.select(); + return success; + } + + if (opts.selectOne) { + this.deselectAll(); + this.select(); + return success; + } + + if (opts.toggle) { + this.toggle(); + return success; + } + + if (opts.toggleOne) { + const selected = !!this.focusBehavior.activeCell()?.selected(); + this.deselectAll(); + if (!selected) { + this.select(); + } + return success; + } + + return success; + } +} diff --git a/src/aria/ui-patterns/behaviors/grid/index.ts b/src/aria/private/behaviors/grid/index.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/grid/index.ts rename to src/aria/private/behaviors/grid/index.ts diff --git a/src/aria/ui-patterns/behaviors/label/BUILD b/src/aria/private/behaviors/label/BUILD similarity index 90% rename from src/aria/ui-patterns/behaviors/label/BUILD rename to src/aria/private/behaviors/label/BUILD index 4e53d5d033e1..0467a50a4a8e 100644 --- a/src/aria/ui-patterns/behaviors/label/BUILD +++ b/src/aria/private/behaviors/label/BUILD @@ -9,7 +9,7 @@ ts_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/ui-patterns/behaviors/label/label.spec.ts b/src/aria/private/behaviors/label/label.spec.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/label/label.spec.ts rename to src/aria/private/behaviors/label/label.spec.ts diff --git a/src/aria/ui-patterns/behaviors/label/label.ts b/src/aria/private/behaviors/label/label.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/label/label.ts rename to src/aria/private/behaviors/label/label.ts diff --git a/src/aria/ui-patterns/behaviors/list-focus/BUILD.bazel b/src/aria/private/behaviors/list-focus/BUILD.bazel similarity index 91% rename from src/aria/ui-patterns/behaviors/list-focus/BUILD.bazel rename to src/aria/private/behaviors/list-focus/BUILD.bazel index 8efad0179f46..b9f7f6fb60b4 100644 --- a/src/aria/ui-patterns/behaviors/list-focus/BUILD.bazel +++ b/src/aria/private/behaviors/list-focus/BUILD.bazel @@ -10,7 +10,7 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/ui-patterns/behaviors/list-focus/list-focus.spec.ts b/src/aria/private/behaviors/list-focus/list-focus.spec.ts similarity index 73% rename from src/aria/ui-patterns/behaviors/list-focus/list-focus.spec.ts rename to src/aria/private/behaviors/list-focus/list-focus.spec.ts index cb9cce52279f..b6fa3c3c881b 100644 --- a/src/aria/ui-patterns/behaviors/list-focus/list-focus.spec.ts +++ b/src/aria/private/behaviors/list-focus/list-focus.spec.ts @@ -22,7 +22,7 @@ export function getListFocus(inputs: TestInputs = {}): ListFocus return new ListFocus({ activeItem: signal(items()[0]), disabled: signal(false), - skipDisabled: signal(false), + softDisabled: signal(true), focusMode: signal('roving'), element: signal({focus: () => {}} as HTMLElement), items: items, @@ -51,22 +51,22 @@ describe('List Focus', () => { focusManager = getListFocus({focusMode: signal('roving')}); }); - it('should set the list tabindex to -1', () => { - expect(focusManager.getListTabindex()).toBe(-1); + it('should set the list to -1', () => { + expect(focusManager.getListTabIndex()).toBe(-1); }); - it('should set the activedescendant to undefined', () => { + it('should set the active descendant to undefined', () => { expect(focusManager.getActiveDescendant()).toBeUndefined(); }); - it('should set the tabindex based on the active index', () => { + it('should set the tab index based on the active index', () => { const items = focusManager.inputs.items() as TestItem[]; focusManager.inputs.activeItem.set(focusManager.inputs.items()[2]); - expect(focusManager.getItemTabindex(items[0])).toBe(-1); - expect(focusManager.getItemTabindex(items[1])).toBe(-1); - expect(focusManager.getItemTabindex(items[2])).toBe(0); - expect(focusManager.getItemTabindex(items[3])).toBe(-1); - expect(focusManager.getItemTabindex(items[4])).toBe(-1); + expect(focusManager.getItemTabIndex(items[0])).toBe(-1); + expect(focusManager.getItemTabIndex(items[1])).toBe(-1); + expect(focusManager.getItemTabIndex(items[2])).toBe(0); + expect(focusManager.getItemTabIndex(items[3])).toBe(-1); + expect(focusManager.getItemTabIndex(items[4])).toBe(-1); }); }); @@ -77,22 +77,22 @@ describe('List Focus', () => { focusManager = getListFocus({focusMode: signal('activedescendant')}); }); - it('should set the list tabindex to 0', () => { - expect(focusManager.getListTabindex()).toBe(0); + it('should set the list tab index to 0', () => { + expect(focusManager.getListTabIndex()).toBe(0); }); it('should set the activedescendant to the active items id', () => { expect(focusManager.getActiveDescendant()).toBe(focusManager.inputs.items()[0].id()); }); - it('should set the tabindex of all items to -1', () => { + it('should set the tab index of all items to -1', () => { const items = focusManager.inputs.items() as TestItem[]; focusManager.inputs.activeItem.set(focusManager.inputs.items()[0]); - expect(focusManager.getItemTabindex(items[0])).toBe(-1); - expect(focusManager.getItemTabindex(items[1])).toBe(-1); - expect(focusManager.getItemTabindex(items[2])).toBe(-1); - expect(focusManager.getItemTabindex(items[3])).toBe(-1); - expect(focusManager.getItemTabindex(items[4])).toBe(-1); + expect(focusManager.getItemTabIndex(items[0])).toBe(-1); + expect(focusManager.getItemTabIndex(items[1])).toBe(-1); + expect(focusManager.getItemTabIndex(items[2])).toBe(-1); + expect(focusManager.getItemTabIndex(items[3])).toBe(-1); + expect(focusManager.getItemTabIndex(items[4])).toBe(-1); }); it('should update the activedescendant of the list when navigating', () => { @@ -109,7 +109,7 @@ describe('List Focus', () => { describe('#isFocusable', () => { it('should return true for enabled items', () => { - const focusManager = getListFocus({skipDisabled: signal(true)}); + const focusManager = getListFocus({softDisabled: signal(false)}); const items = focusManager.inputs.items() as TestItem[]; expect(focusManager.isFocusable(items[0])).toBeTrue(); expect(focusManager.isFocusable(items[1])).toBeTrue(); @@ -117,7 +117,7 @@ describe('List Focus', () => { }); it('should return false for disabled items', () => { - const focusManager = getListFocus({skipDisabled: signal(true)}); + const focusManager = getListFocus({softDisabled: signal(false)}); const items = focusManager.inputs.items() as TestItem[]; items[1].disabled.set(true); @@ -126,8 +126,8 @@ describe('List Focus', () => { expect(focusManager.isFocusable(items[2])).toBeTrue(); }); - it('should return true for disabled items if skip disabled is false', () => { - const focusManager = getListFocus({skipDisabled: signal(false)}); + it('should return true for disabled items if soft disabled is true', () => { + const focusManager = getListFocus({softDisabled: signal(true)}); const items = focusManager.inputs.items() as TestItem[]; items[1].disabled.set(true); diff --git a/src/aria/ui-patterns/behaviors/list-focus/list-focus.ts b/src/aria/private/behaviors/list-focus/list-focus.ts similarity index 88% rename from src/aria/ui-patterns/behaviors/list-focus/list-focus.ts rename to src/aria/private/behaviors/list-focus/list-focus.ts index 53e1c47fd174..ac0f43ce952b 100644 --- a/src/aria/ui-patterns/behaviors/list-focus/list-focus.ts +++ b/src/aria/private/behaviors/list-focus/list-focus.ts @@ -15,7 +15,7 @@ export interface ListFocusItem { id: SignalLike; /** The html element that should receive focus. */ - element: SignalLike; + element: SignalLike; /** Whether an item is disabled. */ disabled: SignalLike; @@ -38,8 +38,8 @@ export interface ListFocusInputs { /** The active item. */ activeItem: WritableSignalLike; - /** Whether disabled items in the list should be skipped when navigating. */ - skipDisabled: SignalLike; + /** Whether disabled items in the list should be focusable. */ + softDisabled: SignalLike; element: SignalLike; } @@ -77,16 +77,16 @@ export class ListFocus { return this.inputs.activeItem()?.id() ?? undefined; } - /** The tabindex for the list. */ - getListTabindex(): -1 | 0 { + /** The tab index for the list. */ + getListTabIndex(): -1 | 0 { if (this.isListDisabled()) { return 0; } return this.inputs.focusMode() === 'activedescendant' ? 0 : -1; } - /** Returns the tabindex for the given item. */ - getItemTabindex(item: T): -1 | 0 { + /** Returns the tab index for the given item. */ + getItemTabIndex(item: T): -1 | 0 { if (this.isListDisabled()) { return -1; } @@ -107,7 +107,7 @@ export class ListFocus { if (opts?.focusElement || opts?.focusElement === undefined) { this.inputs.focusMode() === 'roving' - ? item.element().focus() + ? item.element()?.focus() : this.inputs.element()?.focus(); } @@ -116,6 +116,6 @@ export class ListFocus { /** Returns true if the given item can be navigated to. */ isFocusable(item: T): boolean { - return !item.disabled() || !this.inputs.skipDisabled(); + return !item.disabled() || this.inputs.softDisabled(); } } diff --git a/src/aria/ui-patterns/behaviors/list-navigation/BUILD.bazel b/src/aria/private/behaviors/list-navigation/BUILD.bazel similarity index 76% rename from src/aria/ui-patterns/behaviors/list-navigation/BUILD.bazel rename to src/aria/private/behaviors/list-navigation/BUILD.bazel index 22e9433bad1a..3ab4de5f18fd 100644 --- a/src/aria/ui-patterns/behaviors/list-navigation/BUILD.bazel +++ b/src/aria/private/behaviors/list-navigation/BUILD.bazel @@ -10,8 +10,8 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/list-focus", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/signal-like", ], ) @@ -22,7 +22,7 @@ ng_project( deps = [ ":list-navigation", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/list-focus:unit_test_sources", + "//src/aria/private/behaviors/list-focus:unit_test_sources", ], ) diff --git a/src/aria/ui-patterns/behaviors/list-navigation/list-navigation.spec.ts b/src/aria/private/behaviors/list-navigation/list-navigation.spec.ts similarity index 92% rename from src/aria/ui-patterns/behaviors/list-navigation/list-navigation.spec.ts rename to src/aria/private/behaviors/list-navigation/list-navigation.spec.ts index 04bc68f605af..a3f3e600c15d 100644 --- a/src/aria/ui-patterns/behaviors/list-navigation/list-navigation.spec.ts +++ b/src/aria/private/behaviors/list-navigation/list-navigation.spec.ts @@ -73,7 +73,7 @@ describe('List Navigation', () => { }); it('should skip disabled items', () => { - const nav = getNavigation({skipDisabled: signal(true)}); + const nav = getNavigation({softDisabled: signal(false)}); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); nav.next(); // 0 -> 2 @@ -81,7 +81,7 @@ describe('List Navigation', () => { }); it('should not skip disabled items', () => { - const nav = getNavigation({skipDisabled: signal(false)}); + const nav = getNavigation({softDisabled: signal(true)}); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); nav.next(); // 0 -> 1 @@ -91,7 +91,7 @@ describe('List Navigation', () => { it('should wrap and skip disabled items', () => { const nav = getNavigation({ wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), }); const items = nav.inputs.items() as TestItem[]; items[2].disabled.set(true); @@ -105,7 +105,7 @@ describe('List Navigation', () => { }); it('should do nothing if other items are disabled', () => { - const nav = getNavigation({skipDisabled: signal(true)}); + const nav = getNavigation({softDisabled: signal(false)}); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); items[2].disabled.set(true); @@ -150,7 +150,7 @@ describe('List Navigation', () => { }); it('should skip disabled items', () => { - const nav = getNavigation({skipDisabled: signal(true)}); + const nav = getNavigation({softDisabled: signal(false)}); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); @@ -159,7 +159,7 @@ describe('List Navigation', () => { }); it('should not skip disabled items', () => { - const nav = getNavigation({skipDisabled: signal(false)}); + const nav = getNavigation({softDisabled: signal(true)}); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); @@ -170,7 +170,7 @@ describe('List Navigation', () => { it('should wrap and skip disabled items', () => { const nav = getNavigation({ wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), }); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; @@ -182,7 +182,7 @@ describe('List Navigation', () => { it('should do nothing if other items are disabled', () => { const nav = getNavigation({ - skipDisabled: signal(true), + softDisabled: signal(false), }); const items = nav.inputs.items() as TestItem[]; items[1].disabled.set(true); @@ -209,7 +209,7 @@ describe('List Navigation', () => { }); it('should skip disabled items', () => { - const nav = getNavigation({skipDisabled: signal(true)}); + const nav = getNavigation({softDisabled: signal(false)}); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; items[0].disabled.set(true); @@ -218,7 +218,7 @@ describe('List Navigation', () => { }); it('should not skip disabled items', () => { - const nav = getNavigation({skipDisabled: signal(false)}); + const nav = getNavigation({softDisabled: signal(true)}); nav.goto(nav.inputs.items()[2]); const items = nav.inputs.items() as TestItem[]; items[0].disabled.set(true); @@ -236,7 +236,7 @@ describe('List Navigation', () => { it('should skip disabled items', () => { const nav = getNavigation({ - skipDisabled: signal(true), + softDisabled: signal(false), }); const items = nav.inputs.items() as TestItem[]; items[4].disabled.set(true); @@ -246,7 +246,7 @@ describe('List Navigation', () => { it('should not skip disabled items', () => { const nav = getNavigation({ - skipDisabled: signal(false), + softDisabled: signal(true), }); const items = nav.inputs.items() as TestItem[]; items[4].disabled.set(true); diff --git a/src/aria/ui-patterns/behaviors/list-navigation/list-navigation.ts b/src/aria/private/behaviors/list-navigation/list-navigation.ts similarity index 87% rename from src/aria/ui-patterns/behaviors/list-navigation/list-navigation.ts rename to src/aria/private/behaviors/list-navigation/list-navigation.ts index 7899eec6e180..fb63c70386ac 100644 --- a/src/aria/ui-patterns/behaviors/list-navigation/list-navigation.ts +++ b/src/aria/private/behaviors/list-navigation/list-navigation.ts @@ -55,19 +55,29 @@ export class ListNavigation { /** Navigates to the first item in the list. */ first(opts?: {focusElement?: boolean}): boolean { - const item = this.inputs.items().find(i => this.inputs.focusManager.isFocusable(i)); + const item = this.peekFirst(); return item ? this.goto(item, opts) : false; } /** Navigates to the last item in the list. */ last(opts?: {focusElement?: boolean}): boolean { - const items = this.inputs.items(); + const item = this.peekLast(); + return item ? this.goto(item, opts) : false; + } + + /** Gets the first focusable item from the given list of items. */ + peekFirst(items: T[] = this.inputs.items()): T | undefined { + return items.find(i => this.inputs.focusManager.isFocusable(i)); + } + + /** Gets the last focusable item from the given list of items. */ + peekLast(items: T[] = this.inputs.items()): T | undefined { for (let i = items.length - 1; i >= 0; i--) { if (this.inputs.focusManager.isFocusable(items[i])) { - return this.goto(items[i], opts); + return items[i]; } } - return false; + return; } /** Advances to the next or previous focusable item in the list based on the given delta. */ diff --git a/src/aria/ui-patterns/behaviors/list-selection/BUILD.bazel b/src/aria/private/behaviors/list-selection/BUILD.bazel similarity index 71% rename from src/aria/ui-patterns/behaviors/list-selection/BUILD.bazel rename to src/aria/private/behaviors/list-selection/BUILD.bazel index 9d9c1d33441b..962dac60935e 100644 --- a/src/aria/ui-patterns/behaviors/list-selection/BUILD.bazel +++ b/src/aria/private/behaviors/list-selection/BUILD.bazel @@ -10,8 +10,8 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/list-focus", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/signal-like", ], ) @@ -22,8 +22,8 @@ ng_project( deps = [ ":list-selection", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/list-focus", - "//src/aria/ui-patterns/behaviors/list-focus:unit_test_sources", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/list-focus:unit_test_sources", ], ) diff --git a/src/aria/ui-patterns/behaviors/list-selection/list-selection.spec.ts b/src/aria/private/behaviors/list-selection/list-selection.spec.ts similarity index 80% rename from src/aria/ui-patterns/behaviors/list-selection/list-selection.spec.ts rename to src/aria/private/behaviors/list-selection/list-selection.spec.ts index ef54329c7131..735b984105ee 100644 --- a/src/aria/ui-patterns/behaviors/list-selection/list-selection.spec.ts +++ b/src/aria/private/behaviors/list-selection/list-selection.spec.ts @@ -27,7 +27,7 @@ function getSelection(inputs: TestInputs = {}): ListSelection { it('should select an item', () => { const selection = getSelection(); selection.select(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); it('should select multiple options', () => { @@ -65,7 +65,7 @@ describe('List Selection', () => { selection.inputs.focusManager.focus(items[1]); selection.select(); // [0, 1] - expect(selection.inputs.value()).toEqual([0, 1]); + expect(selection.inputs.values()).toEqual([0, 1]); }); it('should not select multiple options', () => { @@ -74,7 +74,7 @@ describe('List Selection', () => { selection.select(); // [0] selection.inputs.focusManager.focus(items[1]); selection.select(); // [1] - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); it('should not select disabled items', () => { @@ -82,7 +82,7 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].disabled.set(true); selection.select(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should not select non-selectable items', () => { @@ -90,14 +90,14 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.select(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should do nothing to already selected items', () => { const selection = getSelection(); selection.select(); // [0] selection.select(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); }); @@ -105,7 +105,7 @@ describe('List Selection', () => { it('should deselect an item', () => { const selection = getSelection(); selection.deselect(); // [] - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); it('should not deselect disabled items', () => { @@ -114,7 +114,7 @@ describe('List Selection', () => { selection.select(); // [0] items[0].disabled.set(true); selection.deselect(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); it('should not deselect non-selectable items', () => { @@ -123,7 +123,7 @@ describe('List Selection', () => { selection.select(); // [0] items[0].selectable.set(false); selection.deselect(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); }); @@ -131,14 +131,14 @@ describe('List Selection', () => { it('should select an unselected item', () => { const selection = getSelection(); selection.toggle(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); it('should deselect a selected item', () => { const selection = getSelection(); selection.select(); // [0] selection.toggle(); // [] - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); it('should not toggle non-selectable items', () => { @@ -146,7 +146,7 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.toggle(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); }); @@ -154,14 +154,14 @@ describe('List Selection', () => { it('should select an unselected item', () => { const selection = getSelection({multi: signal(true)}); selection.toggleOne(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); it('should deselect a selected item', () => { const selection = getSelection({multi: signal(true)}); selection.select(); // [0] selection.toggleOne(); // [] - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); it('should only leave one item selected', () => { @@ -170,7 +170,7 @@ describe('List Selection', () => { selection.select(); // [0] selection.inputs.focusManager.focus(items[1]); selection.toggleOne(); // [1] - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); it('should not toggle non-selectable items', () => { @@ -178,7 +178,7 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.toggleOne(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); }); @@ -186,13 +186,13 @@ describe('List Selection', () => { it('should select all items', () => { const selection = getSelection({multi: signal(true)}); selection.selectAll(); - expect(selection.inputs.value()).toEqual([0, 1, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([0, 1, 2, 3, 4]); }); it('should do nothing if a list is not multiselectable', () => { const selection = getSelection({multi: signal(false)}); selection.selectAll(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should not select non-selectable items', () => { @@ -200,7 +200,7 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[1].selectable.set(false); selection.selectAll(); - expect(selection.inputs.value()).toEqual([0, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([0, 2, 3, 4]); }); }); @@ -209,14 +209,14 @@ describe('List Selection', () => { const selection = getSelection({multi: signal(true)}); selection.selectAll(); // [0, 1, 2, 3, 4] selection.deselectAll(); // [] - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); it('should deselect items that are not in the list', () => { const selection = getSelection({multi: signal(true)}); - selection.inputs.value.update(() => [5]); + selection.inputs.values.update(() => [5]); selection.deselectAll(); - expect(selection.inputs.value().length).toBe(0); + expect(selection.inputs.values().length).toBe(0); }); it('should not deselect non-selectable items', () => { @@ -225,7 +225,7 @@ describe('List Selection', () => { selection.selectAll(); // [0, 1, 2, 3, 4] items[1].selectable.set(false); selection.deselectAll(); // [1] - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); }); @@ -233,14 +233,14 @@ describe('List Selection', () => { it('should select all items', () => { const selection = getSelection({multi: signal(true)}); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([0, 1, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([0, 1, 2, 3, 4]); }); it('should deselect all if all items are selected', () => { const selection = getSelection({multi: signal(true)}); selection.selectAll(); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should ignore disabled items when determining if all items are selected', () => { @@ -248,9 +248,9 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].disabled.set(true); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([1, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([1, 2, 3, 4]); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should ignore non-selectable items when determining if all items are selected', () => { @@ -258,9 +258,9 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([1, 2, 3, 4]); + expect(selection.inputs.values()).toEqual([1, 2, 3, 4]); selection.toggleAll(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); }); @@ -271,7 +271,7 @@ describe('List Selection', () => { selection.selectOne(); // [0] selection.inputs.focusManager.focus(items[1]); selection.selectOne(); // [1] - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); it('should not select disabled items', () => { @@ -280,7 +280,7 @@ describe('List Selection', () => { items[0].disabled.set(true); selection.select(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should not select non-selectable items', () => { @@ -288,14 +288,14 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; items[0].selectable.set(false); selection.selectOne(); // [] - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); }); it('should do nothing to already selected items', () => { const selection = getSelection({multi: signal(true)}); selection.selectOne(); // [0] selection.selectOne(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); }); it('should do nothing if the current active item is disabled', () => { @@ -304,12 +304,12 @@ describe('List Selection', () => { selection.inputs.focusManager.focus(items[1]); selection.select(); - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); selection.inputs.focusManager.focus(items[0]); items[0].disabled.set(true); selection.selectOne(); - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); it('should not select an item if the list is not multiselectable and not all items are deselected', () => { @@ -318,12 +318,12 @@ describe('List Selection', () => { selection.inputs.focusManager.focus(items[1]); selection.select(); - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); items[1].disabled.set(true); selection.inputs.focusManager.focus(items[2]); selection.selectOne(); - expect(selection.inputs.value()).toEqual([1]); + expect(selection.inputs.values()).toEqual([1]); }); }); @@ -334,7 +334,7 @@ describe('List Selection', () => { selection.select(); // [0] selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 1, 2] - expect(selection.inputs.value()).toEqual([0, 1, 2]); + expect(selection.inputs.values()).toEqual([0, 1, 2]); }); it('should select all items from an anchor at a higher index', () => { @@ -346,7 +346,7 @@ describe('List Selection', () => { selection.inputs.focusManager.focus(items[1]); selection.selectRange(); // [3, 2, 1] - expect(selection.inputs.value()).toEqual([3, 2, 1]); + expect(selection.inputs.values()).toEqual([3, 2, 1]); }); it('should deselect items within the range when the range is changed', () => { @@ -355,15 +355,15 @@ describe('List Selection', () => { selection.inputs.activeItem.set(items[2]); selection.select(); // [2] - expect(selection.inputs.value()).toEqual([2]); + expect(selection.inputs.values()).toEqual([2]); selection.inputs.focusManager.focus(items[4]); selection.selectRange(); // [2, 3, 4] - expect(selection.inputs.value()).toEqual([2, 3, 4]); + expect(selection.inputs.values()).toEqual([2, 3, 4]); selection.inputs.focusManager.focus(items[0]); selection.selectRange(); // [2, 1, 0] - expect(selection.inputs.value()).toEqual([2, 1, 0]); + expect(selection.inputs.values()).toEqual([2, 1, 0]); }); it('should not select a disabled item', () => { @@ -372,15 +372,15 @@ describe('List Selection', () => { items[1].disabled.set(true); selection.select(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); selection.inputs.focusManager.focus(items[1]); selection.selectRange(); // [0] - expect(selection.inputs.value()).toEqual([0]); + expect(selection.inputs.values()).toEqual([0]); selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 2] - expect(selection.inputs.value()).toEqual([0, 2]); + expect(selection.inputs.values()).toEqual([0, 2]); }); it('should not select a non-selectable item', () => { @@ -390,7 +390,7 @@ describe('List Selection', () => { selection.select(); // [0] selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 2] - expect(selection.inputs.value()).toEqual([0, 2]); + expect(selection.inputs.values()).toEqual([0, 2]); }); it('should not deselect a disabled item', () => { @@ -401,15 +401,15 @@ describe('List Selection', () => { items[1].disabled.set(true); selection.select(); // [0, 1] - expect(selection.inputs.value()).toEqual([1, 0]); + expect(selection.inputs.values()).toEqual([1, 0]); selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 1, 2] - expect(selection.inputs.value()).toEqual([1, 0, 2]); + expect(selection.inputs.values()).toEqual([1, 0, 2]); selection.inputs.focusManager.focus(items[0]); selection.selectRange(); // [0, 1] - expect(selection.inputs.value()).toEqual([1, 0]); + expect(selection.inputs.values()).toEqual([1, 0]); }); it('should not deselect a non-selectable item', () => { @@ -418,13 +418,13 @@ describe('List Selection', () => { selection.select(items[1]); // [1] items[1].selectable.set(false); selection.select(); // [0, 1] - expect(selection.inputs.value()).toEqual([1, 0]); + expect(selection.inputs.values()).toEqual([1, 0]); selection.inputs.focusManager.focus(items[2]); selection.selectRange(); // [0, 1, 2] - expect(selection.inputs.value()).toEqual([1, 0, 2]); + expect(selection.inputs.values()).toEqual([1, 0, 2]); selection.inputs.focusManager.focus(items[0]); selection.selectRange(); // [0, 1] - expect(selection.inputs.value()).toEqual([1, 0]); + expect(selection.inputs.values()).toEqual([1, 0]); }); }); @@ -434,10 +434,10 @@ describe('List Selection', () => { const items = selection.inputs.items() as TestItem[]; selection.inputs.focusManager.focus(items[2]); selection.beginRangeSelection(); - expect(selection.inputs.value()).toEqual([]); + expect(selection.inputs.values()).toEqual([]); selection.inputs.focusManager.focus(items[4]); selection.selectRange(); // [2, 3, 4] - expect(selection.inputs.value()).toEqual([2, 3, 4]); + expect(selection.inputs.values()).toEqual([2, 3, 4]); }); it('should be able to select a range starting on a disabled item', () => { @@ -447,7 +447,7 @@ describe('List Selection', () => { selection.beginRangeSelection(0); selection.inputs.focusManager.focus(items[2]); selection.selectRange(); - expect(selection.inputs.value()).toEqual([1, 2]); + expect(selection.inputs.values()).toEqual([1, 2]); }); }); }); diff --git a/src/aria/ui-patterns/behaviors/list-selection/list-selection.ts b/src/aria/private/behaviors/list-selection/list-selection.ts similarity index 86% rename from src/aria/ui-patterns/behaviors/list-selection/list-selection.ts rename to src/aria/private/behaviors/list-selection/list-selection.ts index d687b9939e3d..6f7c191defec 100644 --- a/src/aria/ui-patterns/behaviors/list-selection/list-selection.ts +++ b/src/aria/private/behaviors/list-selection/list-selection.ts @@ -10,7 +10,7 @@ import {computed, signal} from '@angular/core'; import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; import {ListFocus, ListFocusInputs, ListFocusItem} from '../list-focus/list-focus'; -/** Represents an item in a collection, such as a listbox option, than can be selected. */ +/** Represents an item in a collection, such as a listbox option, that can be selected. */ export interface ListSelectionItem extends ListFocusItem { /** The value of the item. */ value: SignalLike; @@ -25,7 +25,7 @@ export interface ListSelectionInputs, V> extends multi: SignalLike; /** The current value of the list selection. */ - value: WritableSignalLike; + values: WritableSignalLike; /** The selection strategy used by the list. */ selectionMode: SignalLike<'follow' | 'explicit'>; @@ -41,7 +41,7 @@ export class ListSelection, V> { /** The currently selected items. */ selectedItems = computed(() => - this.inputs.items().filter(item => this.inputs.value().includes(item.value())), + this.inputs.items().filter(item => this.inputs.values().includes(item.value())), ); constructor(readonly inputs: ListSelectionInputs & {focusManager: ListFocus}) {} @@ -54,7 +54,7 @@ export class ListSelection, V> { !item || item.disabled() || !item.selectable() || - this.inputs.value().includes(item.value()) + this.inputs.values().includes(item.value()) ) { return; } @@ -67,23 +67,23 @@ export class ListSelection, V> { if (opts.anchor) { this.beginRangeSelection(index); } - this.inputs.value.update(values => values.concat(item.value())); + this.inputs.values.update(values => values.concat(item.value())); } /** Deselects the item at the current active index. */ - deselect(item?: T | null) { + deselect(item?: ListSelectionItem) { item = item ?? this.inputs.focusManager.inputs.activeItem(); if (item && !item.disabled() && item.selectable()) { - this.inputs.value.update(values => values.filter(value => value !== item.value())); + this.inputs.values.update(values => values.filter(value => value !== item.value())); } } /** Toggles the item at the current active index. */ - toggle() { - const item = this.inputs.focusManager.inputs.activeItem(); + toggle(item?: ListSelectionItem) { + item = item ?? this.inputs.focusManager.inputs.activeItem(); if (item) { - this.inputs.value().includes(item.value()) ? this.deselect() : this.select(); + this.inputs.values().includes(item.value()) ? this.deselect(item) : this.select(item); } } @@ -91,7 +91,7 @@ export class ListSelection, V> { toggleOne() { const item = this.inputs.focusManager.inputs.activeItem(); if (item) { - this.inputs.value().includes(item.value()) ? this.deselect() : this.selectOne(); + this.inputs.values().includes(item.value()) ? this.deselect() : this.selectOne(); } } @@ -123,12 +123,12 @@ export class ListSelection, V> { // inverse (and more common) effect of keeping enabled items selected when they aren't in the // list. - for (const value of this.inputs.value()) { + for (const value of this.inputs.values()) { const item = this.inputs.items().find(i => i.value() === value); item ? this.deselect(item) - : this.inputs.value.update(values => values.filter(v => v !== value)); + : this.inputs.values.update(values => values.filter(v => v !== value)); } } @@ -142,7 +142,7 @@ export class ListSelection, V> { .filter(i => !i.disabled() && i.selectable()) .map(i => i.value()); - selectableValues.every(i => this.inputs.value().includes(i)) + selectableValues.every(i => this.inputs.values().includes(i)) ? this.deselectAll() : this.selectAll(); } @@ -156,7 +156,7 @@ export class ListSelection, V> { this.deselectAll(); - if (this.inputs.value().length > 0 && !this.inputs.multi()) { + if (this.inputs.values().length > 0 && !this.inputs.multi()) { return; } diff --git a/src/aria/ui-patterns/behaviors/list-typeahead/BUILD.bazel b/src/aria/private/behaviors/list-typeahead/BUILD.bazel similarity index 71% rename from src/aria/ui-patterns/behaviors/list-typeahead/BUILD.bazel rename to src/aria/private/behaviors/list-typeahead/BUILD.bazel index 4a4c998e0386..ed246f3620fc 100644 --- a/src/aria/ui-patterns/behaviors/list-typeahead/BUILD.bazel +++ b/src/aria/private/behaviors/list-typeahead/BUILD.bazel @@ -10,8 +10,8 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/list-focus", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/signal-like", ], ) @@ -22,8 +22,8 @@ ng_project( deps = [ ":list-typeahead", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/list-focus", - "//src/aria/ui-patterns/behaviors/list-focus:unit_test_sources", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/list-focus:unit_test_sources", ], ) diff --git a/src/aria/ui-patterns/behaviors/list-typeahead/list-typeahead.spec.ts b/src/aria/private/behaviors/list-typeahead/list-typeahead.spec.ts similarity index 91% rename from src/aria/ui-patterns/behaviors/list-typeahead/list-typeahead.spec.ts rename to src/aria/private/behaviors/list-typeahead/list-typeahead.spec.ts index 34c57899e4ca..efc0da99eb64 100644 --- a/src/aria/ui-patterns/behaviors/list-typeahead/list-typeahead.spec.ts +++ b/src/aria/private/behaviors/list-typeahead/list-typeahead.spec.ts @@ -8,7 +8,6 @@ import {Signal, signal, WritableSignal} from '@angular/core'; import {ListTypeaheadItem, ListTypeahead, ListTypeaheadInputs} from './list-typeahead'; -import {fakeAsync, tick} from '@angular/core/testing'; import {getListFocus} from '../list-focus/list-focus.spec'; import {ListFocus} from '../list-focus/list-focus'; @@ -27,7 +26,7 @@ function getTypeahead(inputs: TestInputs = {}): ListTypeahead { focusManager, ...focusManager.inputs, items, - typeaheadDelay: signal(0.5), + typeaheadDelay: signal(500), ...inputs, }); } @@ -68,26 +67,26 @@ describe('List Typeahead', () => { expect(typeahead.inputs.focusManager.activeIndex()).toBe(3); }); - it('should reset after a delay', fakeAsync(() => { + it('should reset after a delay', async () => { typeahead.search('i'); expect(typeahead.inputs.focusManager.activeIndex()).toBe(1); - tick(500); + await new Promise(resolve => setTimeout(resolve, 500)); typeahead.search('i'); expect(typeahead.inputs.focusManager.activeIndex()).toBe(2); - })); + }); it('should skip disabled items', () => { items[1].disabled.set(true); - (typeahead.inputs.skipDisabled as WritableSignal).set(true); + (typeahead.inputs.softDisabled as WritableSignal).set(false); typeahead.search('i'); expect(typeahead.inputs.focusManager.activeIndex()).toBe(2); }); it('should not skip disabled items', () => { items[1].disabled.set(true); - (typeahead.inputs.skipDisabled as WritableSignal).set(false); + (typeahead.inputs.softDisabled as WritableSignal).set(true); typeahead.search('i'); expect(typeahead.inputs.focusManager.activeIndex()).toBe(1); }); diff --git a/src/aria/ui-patterns/behaviors/list-typeahead/list-typeahead.ts b/src/aria/private/behaviors/list-typeahead/list-typeahead.ts similarity index 98% rename from src/aria/ui-patterns/behaviors/list-typeahead/list-typeahead.ts rename to src/aria/private/behaviors/list-typeahead/list-typeahead.ts index 3ee7bfe3a7a0..2c77756b70e4 100644 --- a/src/aria/ui-patterns/behaviors/list-typeahead/list-typeahead.ts +++ b/src/aria/private/behaviors/list-typeahead/list-typeahead.ts @@ -74,7 +74,7 @@ export class ListTypeahead { this.timeout = setTimeout(() => { this._query.set(''); this._startIndex.set(undefined); - }, this.inputs.typeaheadDelay() * 1000); + }, this.inputs.typeaheadDelay()); return true; } diff --git a/src/aria/ui-patterns/behaviors/list/BUILD.bazel b/src/aria/private/behaviors/list/BUILD.bazel similarity index 64% rename from src/aria/ui-patterns/behaviors/list/BUILD.bazel rename to src/aria/private/behaviors/list/BUILD.bazel index 6e3522120137..0020d4fe5ef1 100644 --- a/src/aria/ui-patterns/behaviors/list/BUILD.bazel +++ b/src/aria/private/behaviors/list/BUILD.bazel @@ -10,12 +10,12 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/list-focus", - "//src/aria/ui-patterns/behaviors/list-navigation", - "//src/aria/ui-patterns/behaviors/list-selection", - "//src/aria/ui-patterns/behaviors/list-typeahead", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/list-navigation", + "//src/aria/private/behaviors/list-selection", + "//src/aria/private/behaviors/list-typeahead", + "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/ui-patterns/behaviors/list/list.spec.ts b/src/aria/private/behaviors/list/list.spec.ts similarity index 76% rename from src/aria/ui-patterns/behaviors/list/list.spec.ts rename to src/aria/private/behaviors/list/list.spec.ts index 82e7c45c8ba7..4d916d8ebc4e 100644 --- a/src/aria/ui-patterns/behaviors/list/list.spec.ts +++ b/src/aria/private/behaviors/list/list.spec.ts @@ -8,7 +8,6 @@ import {signal, WritableSignal} from '@angular/core'; import {List, ListItem, ListInputs} from './list'; -import {fakeAsync, tick} from '@angular/core/testing'; type TestItem = ListItem & { disabled: WritableSignal; @@ -23,9 +22,9 @@ type TestList = List, V>; describe('List Behavior', () => { function getList(inputs: Partial> & Pick, 'items'>): TestList { return new List({ - value: inputs.value ?? signal([]), + values: inputs.values ?? signal([]), activeItem: signal(undefined), - typeaheadDelay: inputs.typeaheadDelay ?? signal(0.5), + typeaheadDelay: inputs.typeaheadDelay ?? signal(500), wrap: inputs.wrap ?? signal(true), disabled: inputs.disabled ?? signal(false), multi: inputs.multi ?? signal(false), @@ -33,7 +32,7 @@ describe('List Behavior', () => { orientation: inputs.orientation ?? signal('vertical'), element: signal({focus: () => {}} as HTMLElement), focusMode: inputs.focusMode ?? signal('roving'), - skipDisabled: inputs.skipDisabled ?? signal(true), + softDisabled: inputs.softDisabled ?? signal(true), selectionMode: signal('explicit'), ...inputs, }); @@ -77,36 +76,36 @@ describe('List Behavior', () => { } describe('with focusMode: "activedescendant"', () => { - it('should set the list tabindex to 0', () => { + it('should set the list tab index to 0', () => { const {list} = getDefaultPatterns({focusMode: signal('activedescendant')}); - expect(list.tabindex()).toBe(0); + expect(list.tabIndex()).toBe(0); }); it('should set the active descendant to the active item id', () => { const {list} = getDefaultPatterns({focusMode: signal('activedescendant')}); - expect(list.activedescendant()).toBe('item-0'); + expect(list.activeDescendant()).toBe('item-0'); list.next(); - expect(list.activedescendant()).toBe('item-1'); + expect(list.activeDescendant()).toBe('item-1'); }); - it('should set item tabindex to -1', () => { + it('should set item tab index to -1', () => { const {list, items} = getDefaultPatterns({focusMode: signal('activedescendant')}); expect(list.getItemTabindex(items[0])).toBe(-1); }); }); describe('with focusMode: "roving"', () => { - it('should set the list tabindex to -1', () => { + it('should set the list tab index to -1', () => { const {list} = getDefaultPatterns({focusMode: signal('roving')}); - expect(list.tabindex()).toBe(-1); + expect(list.tabIndex()).toBe(-1); }); it('should not set the active descendant', () => { const {list} = getDefaultPatterns({focusMode: signal('roving')}); - expect(list.activedescendant()).toBeUndefined(); + expect(list.activeDescendant()).toBeUndefined(); }); - it('should set the active item tabindex to 0 and others to -1', () => { + it('should set the active item tab index to 0 and others to -1', () => { const {list, items} = getDefaultPatterns({focusMode: signal('roving')}); expect(list.getItemTabindex(items[0])).toBe(0); expect(list.getItemTabindex(items[1])).toBe(-1); @@ -116,6 +115,36 @@ describe('List Behavior', () => { }); }); + describe('with disabled: true and softDisabled is false', () => { + let list: TestList; + + beforeEach(() => { + const patterns = getDefaultPatterns({disabled: signal(true), softDisabled: signal(false)}); + list = patterns.list; + }); + + it('should report disabled state', () => { + expect(list.disabled()).toBe(true); + }); + + it('should not change active index on navigation', () => { + expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); + list.next(); + expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); + list.last(); + expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); + }); + + it('should not select items', () => { + list.next({selectOne: true}); + expect(list.inputs.values()).toEqual([]); + }); + + it('should have a tab index of 0', () => { + expect(list.tabIndex()).toBe(0); + }); + }); + describe('with disabled: true', () => { let list: TestList; @@ -138,11 +167,11 @@ describe('List Behavior', () => { it('should not select items', () => { list.next({selectOne: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); - it('should have a tabindex of 0', () => { - expect(list.tabindex()).toBe(0); + it('should have a tab index of 0', () => { + expect(list.tabIndex()).toBe(0); }); }); @@ -177,8 +206,8 @@ describe('List Behavior', () => { expect(list.inputs.activeItem()).toBe(list.inputs.items()[8]); }); - it('should skip disabled items when navigating', () => { - const {list, items} = getDefaultPatterns(); + it('should skip disabled items when softDisabled is false', () => { + const {list, items} = getDefaultPatterns({softDisabled: signal(false)}); items[1].disabled.set(true); // Disable second item expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); list.next(); @@ -187,8 +216,8 @@ describe('List Behavior', () => { expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); // Should skip back to 'Apple' }); - it('should not skip disabled items when skipDisabled is false', () => { - const {list, items} = getDefaultPatterns({skipDisabled: signal(false)}); + it('should not skip disabled items when navigating', () => { + const {list, items} = getDefaultPatterns(); items[1].disabled.set(true); // Disable second item expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); list.next(); @@ -225,7 +254,7 @@ describe('List Behavior', () => { beforeEach(() => { const patterns = getDefaultPatterns({ - value: signal([]), + values: signal([]), multi: signal(false), }); list = patterns.list; @@ -234,39 +263,39 @@ describe('List Behavior', () => { it('should not select when navigating', () => { list.next(); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should select an item when navigating with selectOne:true', () => { list.next({selectOne: true}); - expect(list.inputs.value()).toEqual(['Apricot']); + expect(list.inputs.values()).toEqual(['Apricot']); }); it('should not select a non-selectable item when navigating with selectOne:true', () => { items[1].selectable.set(false); list.next({selectOne: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should toggle an item when navigating with toggle:true', () => { list.goto(items[1], {selectOne: true}); - expect(list.inputs.value()).toEqual(['Apricot']); + expect(list.inputs.values()).toEqual(['Apricot']); list.goto(items[1], {toggle: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should not toggle a non-selectable item when navigating with toggle:true', () => { items[1].selectable.set(false); list.goto(items[1], {toggle: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should only allow one selected item', () => { list.next({selectOne: true}); - expect(list.inputs.value()).toEqual(['Apricot']); + expect(list.inputs.values()).toEqual(['Apricot']); list.next({selectOne: true}); - expect(list.inputs.value()).toEqual(['Banana']); + expect(list.inputs.values()).toEqual(['Banana']); }); }); @@ -276,7 +305,7 @@ describe('List Behavior', () => { beforeEach(() => { const patterns = getDefaultPatterns({ - value: signal([]), + values: signal([]), multi: signal(true), }); list = patterns.list; @@ -285,63 +314,67 @@ describe('List Behavior', () => { it('should not select when navigating', () => { list.next(); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should select an item with toggle:true', () => { list.next({toggle: true}); - expect(list.inputs.value()).toEqual(['Apricot']); + expect(list.inputs.values()).toEqual(['Apricot']); }); it('should not select a non-selectable item with toggle:true', () => { items[1].selectable.set(false); list.next({toggle: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should allow multiple selected items', () => { list.next({toggle: true}); list.next({toggle: true}); - expect(list.inputs.value()).toEqual(['Apricot', 'Banana']); + expect(list.inputs.values()).toEqual(['Apricot', 'Banana']); }); it('should select a range of items with selectRange:true', () => { list.anchor(0); list.next({selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(list.inputs.values()).toEqual(['Apple', 'Apricot']); list.next({selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Apricot', 'Banana']); + expect(list.inputs.values()).toEqual(['Apple', 'Apricot', 'Banana']); list.prev({selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(list.inputs.values()).toEqual(['Apple', 'Apricot']); list.prev({selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple']); + expect(list.inputs.values()).toEqual(['Apple']); }); it('should not wrap when range selecting', () => { list.anchor(0); list.prev({selectRange: true}); expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); it('should not select disabled items in a range', () => { items[1].disabled.set(true); list.anchor(0); list.goto(items[3], {selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Banana', 'Blackberry']); + expect(list.inputs.values()).toEqual(['Apple', 'Banana', 'Blackberry']); }); it('should not select non-selectable items in a range', () => { items[1].selectable.set(false); list.anchor(0); list.goto(items[3], {selectRange: true}); - expect(list.inputs.value()).toEqual(['Apple', 'Banana', 'Blackberry']); + expect(list.inputs.values()).toEqual(['Apple', 'Banana', 'Blackberry']); }); }); }); describe('Typeahead', () => { - it('should navigate to an item via typeahead', fakeAsync(() => { + function delay(amount: number) { + return new Promise(resolve => setTimeout(resolve, amount)); + } + + it('should navigate to an item via typeahead', async () => { const {list} = getDefaultPatterns(); expect(list.inputs.activeItem()).toBe(list.inputs.items()[0]); list.search('b'); @@ -350,36 +383,35 @@ describe('List Behavior', () => { expect(list.inputs.activeItem()).toBe(list.inputs.items()[3]); // Blackberry list.search('u'); expect(list.inputs.activeItem()).toBe(list.inputs.items()[4]); // Blueberry - - tick(500); // Default delay + await delay(500); list.search('c'); expect(list.inputs.activeItem()).toBe(list.inputs.items()[5]); // Cantaloupe - })); + }); - it('should respect typeaheadDelay', fakeAsync(() => { - const {list} = getDefaultPatterns({typeaheadDelay: signal(0.1)}); + it('should respect typeaheadDelay', async () => { + const {list} = getDefaultPatterns({typeaheadDelay: signal(100)}); list.search('b'); expect(list.inputs.activeItem()).toBe(list.inputs.items()[2]); // Banana - tick(50); // Less than delay + await delay(50); // Less than delay list.search('l'); expect(list.inputs.activeItem()).toBe(list.inputs.items()[3]); // Blackberry - tick(101); // More than delay + await delay(101); // More than delay list.search('c'); expect(list.inputs.activeItem()).toBe(list.inputs.items()[5]); // Cantaloupe - })); + }); it('should select an item via typeahead', () => { const {list} = getDefaultPatterns({multi: signal(false)}); list.search('b', {selectOne: true}); - expect(list.inputs.value()).toEqual(['Banana']); + expect(list.inputs.values()).toEqual(['Banana']); }); it('should not select a non-selectable item via typeahead', () => { const {list, items} = getDefaultPatterns({multi: signal(false)}); items[2].selectable.set(false); // 'Banana' list.search('b', {selectOne: true}); - expect(list.inputs.value()).toEqual([]); + expect(list.inputs.values()).toEqual([]); }); }); }); diff --git a/src/aria/ui-patterns/behaviors/list/list.ts b/src/aria/private/behaviors/list/list.ts similarity index 94% rename from src/aria/ui-patterns/behaviors/list/list.ts rename to src/aria/private/behaviors/list/list.ts index 49576d5902ac..55b48d34f4fc 100644 --- a/src/aria/ui-patterns/behaviors/list/list.ts +++ b/src/aria/private/behaviors/list/list.ts @@ -64,10 +64,10 @@ export class List, V> { disabled = computed(() => this.focusBehavior.isListDisabled()); /** The id of the current active item. */ - activedescendant = computed(() => this.focusBehavior.getActiveDescendant()); + activeDescendant = computed(() => this.focusBehavior.getActiveDescendant()); - /** The tabindex of the list. */ - tabindex = computed(() => this.focusBehavior.getListTabindex()); + /** The tab index of the list. */ + tabIndex = computed(() => this.focusBehavior.getListTabIndex()); /** The index of the currently active item in the list. */ activeIndex = computed(() => this.focusBehavior.activeIndex()); @@ -100,9 +100,9 @@ export class List, V> { }); } - /** Returns the tabindex for the given item. */ + /** Returns the tab index for the given item. */ getItemTabindex(item: T) { - return this.focusBehavior.getItemTabindex(item); + return this.focusBehavior.getItemTabIndex(item); } /** Navigates to the first option in the list. */ @@ -161,8 +161,8 @@ export class List, V> { } /** Deselects the currently active item in the list. */ - deselect() { - this.selectionBehavior.deselect(); + deselect(item?: T) { + this.selectionBehavior.deselect(item); } /** Deselects all items in the list. */ @@ -171,8 +171,8 @@ export class List, V> { } /** Toggles the currently active item in the list. */ - toggle() { - this.selectionBehavior.toggle(); + toggle(item?: T) { + this.selectionBehavior.toggle(item); } /** Toggles the currently active item in the list, deselecting all other items. */ diff --git a/src/aria/ui-patterns/behaviors/popup/BUILD.bazel b/src/aria/private/behaviors/popup/BUILD.bazel similarity index 91% rename from src/aria/ui-patterns/behaviors/popup/BUILD.bazel rename to src/aria/private/behaviors/popup/BUILD.bazel index 3702057adb4a..29d865ed40a3 100644 --- a/src/aria/ui-patterns/behaviors/popup/BUILD.bazel +++ b/src/aria/private/behaviors/popup/BUILD.bazel @@ -10,7 +10,7 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/ui-patterns/behaviors/popup/popup.spec.ts b/src/aria/private/behaviors/popup/popup.spec.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/popup/popup.spec.ts rename to src/aria/private/behaviors/popup/popup.spec.ts diff --git a/src/aria/ui-patterns/behaviors/popup/popup.ts b/src/aria/private/behaviors/popup/popup.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/popup/popup.ts rename to src/aria/private/behaviors/popup/popup.ts diff --git a/src/aria/ui-patterns/behaviors/signal-like/BUILD.bazel b/src/aria/private/behaviors/signal-like/BUILD.bazel similarity index 100% rename from src/aria/ui-patterns/behaviors/signal-like/BUILD.bazel rename to src/aria/private/behaviors/signal-like/BUILD.bazel diff --git a/src/aria/ui-patterns/behaviors/signal-like/signal-like.ts b/src/aria/private/behaviors/signal-like/signal-like.ts similarity index 100% rename from src/aria/ui-patterns/behaviors/signal-like/signal-like.ts rename to src/aria/private/behaviors/signal-like/signal-like.ts diff --git a/src/aria/ui-patterns/combobox/BUILD.bazel b/src/aria/private/combobox/BUILD.bazel similarity index 67% rename from src/aria/ui-patterns/combobox/BUILD.bazel rename to src/aria/private/combobox/BUILD.bazel index 4dda9b51a441..61c73f8a68a8 100644 --- a/src/aria/ui-patterns/combobox/BUILD.bazel +++ b/src/aria/private/combobox/BUILD.bazel @@ -10,9 +10,9 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/list", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/signal-like", ], ) @@ -23,9 +23,9 @@ ts_project( deps = [ ":combobox", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", - "//src/aria/ui-patterns/listbox", - "//src/aria/ui-patterns/tree", + "//src/aria/private/behaviors/signal-like", + "//src/aria/private/listbox", + "//src/aria/private/tree", "//src/cdk/keycodes", "//src/cdk/testing/private", ], diff --git a/src/aria/ui-patterns/combobox/combobox.spec.ts b/src/aria/private/combobox/combobox.spec.ts similarity index 76% rename from src/aria/ui-patterns/combobox/combobox.spec.ts rename to src/aria/private/combobox/combobox.spec.ts index 9d18703e841e..2922a50e61fc 100644 --- a/src/aria/ui-patterns/combobox/combobox.spec.ts +++ b/src/aria/private/combobox/combobox.spec.ts @@ -106,6 +106,7 @@ function getComboboxPattern( filterMode: signal(inputs.filterMode ?? 'manual'), firstMatch, inputValue, + alwaysExpanded: signal(false), }); return {combobox, inputEl, containerEl, firstMatch, inputValue}; @@ -121,14 +122,14 @@ function getListboxPattern( const listbox = new ComboboxListboxPattern({ id: signal('listbox-1'), items: options, - value: signal(initialValue ? [initialValue] : []), + values: signal(initialValue ? [initialValue] : []), combobox: signal(combobox) as any, activeItem: signal(undefined), - typeaheadDelay: signal(0.5), + typeaheadDelay: signal(500), wrap: signal(true), readonly: signal(false), disabled: signal(false), - skipDisabled: signal(true), + softDisabled: signal(true), multi: signal(false), focusMode: signal('activedescendant'), textDirection: signal('ltr'), @@ -165,13 +166,13 @@ function getTreePattern( const tree = new ComboboxTreePattern({ id: signal('tree-1'), allItems: items, - value: signal(initialValue ? [initialValue] : []), + values: signal(initialValue ? [initialValue] : []), combobox: signal(combobox) as any, activeItem: signal(undefined), - typeaheadDelay: signal(0.5), + typeaheadDelay: signal(500), wrap: signal(true), disabled: signal(false), - skipDisabled: signal(true), + softDisabled: signal(true), multi: signal(false), focusMode: signal('activedescendant'), textDirection: signal('ltr'), @@ -192,9 +193,10 @@ function getTreePattern( element.role = 'treeitem'; const treeItem = new TreeItemPattern({ value: signal(node.value), - id: signal('tree-item-' + tree.allItems().length), + id: signal('tree-item-' + tree.inputs.allItems().length), disabled: signal(false), selectable: signal(true), + expanded: signal(false), searchTerm: signal(node.value), tree: signal(tree), parent: signal(parent), @@ -203,13 +205,13 @@ function getTreePattern( children: signal([]), }); - (tree.allItems as WritableSignal[]>).update(items => + (tree.inputs.allItems as WritableSignal[]>).update(items => items.concat(treeItem), ); if (node.children) { const children = createTreeItems(node.children, treeItem); - (treeItem.children as WritableSignal[]>).set(children); + (treeItem.inputs.children as WritableSignal[]>).set(children); } return treeItem; @@ -295,13 +297,6 @@ describe('Combobox with Listbox Pattern', () => { }); describe('Expansion', () => { - it('should open on click', () => { - const {combobox, inputEl} = getPatterns(); - expect(combobox.expanded()).toBe(false); - combobox.onPointerup(clickInput(inputEl)); - expect(combobox.expanded()).toBe(true); - }); - it('should open on ArrowDown', () => { const {combobox} = getPatterns(); expect(combobox.expanded()).toBe(false); @@ -357,7 +352,7 @@ describe('Combobox with Listbox Pattern', () => { it('should not expand when disabled', () => { const {combobox, inputEl} = getPatterns({disabled: true}); expect(combobox.expanded()).toBe(false); - combobox.onPointerup(clickInput(inputEl)); + combobox.onClick(clickInput(inputEl)); expect(combobox.expanded()).toBe(false); }); }); @@ -381,55 +376,55 @@ describe('Combobox with Listbox Pattern', () => { }); it('should select and commit on click', () => { - combobox.onPointerup(clickOption(listbox.inputs.items(), 0)); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + combobox.onClick(clickOption(listbox.inputs.items(), 0)); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); expect(inputEl.value).toBe('Apple'); }); it('should select and commit to input on Enter', () => { combobox.onKeydown(down()); combobox.onKeydown(enter()); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); expect(inputEl.value).toBe('Apple'); }); it('should select on focusout if the input text exactly matches an item', () => { type('Apple'); combobox.onFocusOut(new FocusEvent('focusout')); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); }); - it('should deselect on backspace', () => { + it('should deselect on close if the input text does not match any options', () => { combobox.onKeydown(down()); combobox.onKeydown(enter()); + expect(listbox.inputs.values()).toEqual(['Apple']); type('Appl', {backspace: true}); - combobox.onInput(new InputEvent('input', {inputType: 'deleteContentBackward'})); - - expect(listbox.getSelectedItem()).toBe(undefined); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual(['Apple']); + combobox.onKeydown(escape()); + expect(listbox.inputs.values()).toEqual([]); }); it('should not select on navigation', () => { combobox.onKeydown(down()); - expect(listbox.getSelectedItem()).toBe(undefined); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.getSelectedItems().length).toBe(0); + expect(listbox.inputs.values()).toEqual([]); }); it('should not select on input', () => { type('A'); - expect(listbox.getSelectedItem()).toBe(undefined); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.getSelectedItems().length).toBe(0); + expect(listbox.inputs.values()).toEqual([]); }); it('should not select on focusout if the input text does not match an item', () => { type('Appl'); combobox.onFocusOut(new FocusEvent('focusout')); - expect(listbox.getSelectedItem()).toBe(undefined); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.getSelectedItems().length).toBe(0); + expect(listbox.inputs.values()).toEqual([]); expect(inputEl.value).toBe('Appl'); }); }); @@ -442,9 +437,9 @@ describe('Combobox with Listbox Pattern', () => { }); it('should select and commit on click', () => { - combobox.onPointerup(clickOption(listbox.inputs.items(), 3)); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[3]); - expect(listbox.inputs.value()).toEqual(['Blackberry']); + combobox.onClick(clickOption(listbox.inputs.items(), 3)); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[3]); + expect(listbox.inputs.values()).toEqual(['Blackberry']); expect(inputEl.value).toBe('Blackberry'); }); @@ -453,38 +448,38 @@ describe('Combobox with Listbox Pattern', () => { combobox.onKeydown(down()); combobox.onKeydown(down()); combobox.onKeydown(enter()); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[2]); - expect(listbox.inputs.value()).toEqual(['Banana']); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[2]); + expect(listbox.inputs.values()).toEqual(['Banana']); expect(inputEl.value).toBe('Banana'); }); it('should select the first item on arrow down when collapsed', () => { combobox.onKeydown(down()); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should select the last item on arrow up when collapsed', () => { combobox.onKeydown(up()); - expect(listbox.getSelectedItem()).toBe( + expect(listbox.getSelectedItems()[0]).toBe( listbox.inputs.items()[listbox.inputs.items().length - 1], ); - expect(listbox.inputs.value()).toEqual(['Cranberry']); + expect(listbox.inputs.values()).toEqual(['Cranberry']); }); it('should select on navigation', () => { combobox.onKeydown(down()); combobox.onKeydown(down()); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[1]); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[1]); + expect(listbox.inputs.values()).toEqual(['Apricot']); }); it('should select the first option on input', () => { type('A'); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); type('Apr'); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); }); it('should commit the selected option on focusout', () => { @@ -503,9 +498,9 @@ describe('Combobox with Listbox Pattern', () => { }); it('should select and commit on click', () => { - combobox.onPointerup(clickOption(listbox.inputs.items(), 3)); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[3]); - expect(listbox.inputs.value()).toEqual(['Blackberry']); + combobox.onClick(clickOption(listbox.inputs.items(), 3)); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[3]); + expect(listbox.inputs.values()).toEqual(['Blackberry']); expect(inputEl.value).toBe('Blackberry'); }); @@ -514,38 +509,38 @@ describe('Combobox with Listbox Pattern', () => { combobox.onKeydown(down()); combobox.onKeydown(down()); combobox.onKeydown(enter()); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[2]); - expect(listbox.inputs.value()).toEqual(['Banana']); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[2]); + expect(listbox.inputs.values()).toEqual(['Banana']); expect(inputEl.value).toBe('Banana'); }); it('should select the first item on arrow down when collapsed', () => { combobox.onKeydown(down()); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[0]); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should select the last item on arrow up when collapsed', () => { combobox.onKeydown(up()); - expect(listbox.getSelectedItem()).toBe( + expect(listbox.getSelectedItems()[0]).toBe( listbox.inputs.items()[listbox.inputs.items().length - 1], ); - expect(listbox.inputs.value()).toEqual(['Cranberry']); + expect(listbox.inputs.values()).toEqual(['Cranberry']); }); it('should select on navigation', () => { combobox.onKeydown(down()); combobox.onKeydown(down()); - expect(listbox.getSelectedItem()).toBe(listbox.inputs.items()[1]); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[1]); + expect(listbox.inputs.values()).toEqual(['Apricot']); }); it('should select the first option on input', () => { type('A'); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); type('Apr'); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); }); it('should commit the selected option on navigation', () => { @@ -585,6 +580,40 @@ describe('Combobox with Listbox Pattern', () => { }); }); }); + + describe('Readonly mode', () => { + describe('with single-select', () => { + it('should select and close on selection', () => { + const {combobox, listbox, inputEl} = getPatterns({readonly: true}); + combobox.onClick(clickOption(listbox.inputs.items(), 2)); + expect(listbox.getSelectedItems()[0]).toBe(listbox.inputs.items()[2]); + expect(listbox.inputs.values()).toEqual(['Banana']); + expect(inputEl.value).toBe('Banana'); + expect(combobox.expanded()).toBe(false); + }); + + it('should close on escape', () => { + const {combobox} = getPatterns({readonly: true}); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); + combobox.onKeydown(escape()); + expect(combobox.expanded()).toBe(false); + }); + }); + + describe('with multi-select', () => { + it('should allow users to select multiple options', () => { + const {combobox, listbox, inputEl} = getPatterns({readonly: true}); + (listbox.inputs.multi as WritableSignal).set(true); + + combobox.onClick(clickOption(listbox.inputs.items(), 1)); + combobox.onClick(clickOption(listbox.inputs.items(), 2)); + + expect(listbox.inputs.values()).toEqual(['Apricot', 'Banana']); + expect(inputEl.value).toBe('Apricot, Banana'); + }); + }); + }); }); describe('Combobox with Tree Pattern', () => { @@ -712,53 +741,54 @@ describe('Combobox with Tree Pattern', () => { }); it('should select and commit on click', () => { - combobox.onPointerup(clickTreeItem(tree.inputs.allItems(), 0)); - expect(tree.inputs.value()).toEqual(['Fruit']); + combobox.onClick(clickTreeItem(tree.inputs.allItems(), 0)); + expect(tree.inputs.values()).toEqual(['Fruit']); expect(inputEl.value).toBe('Fruit'); }); it('should select and commit to input on Enter', () => { combobox.onKeydown(down()); combobox.onKeydown(enter()); - expect(tree.getSelectedItem()).toBe(tree.inputs.allItems()[0]); - expect(tree.inputs.value()).toEqual(['Fruit']); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.allItems()[0]); + expect(tree.inputs.values()).toEqual(['Fruit']); expect(inputEl.value).toBe('Fruit'); }); it('should select on focusout if the input text exactly matches an item', () => { - combobox.onPointerup(clickInput(inputEl)); + combobox.onClick(clickInput(inputEl)); type('Apple'); combobox.onFocusOut(new FocusEvent('focusout')); - expect(tree.inputs.value()).toEqual(['Apple']); + expect(tree.inputs.values()).toEqual(['Apple']); }); - it('should deselect on backspace', () => { + it('should deselect on close if the input text does not match any options', () => { combobox.onKeydown(down()); combobox.onKeydown(enter()); - type('Appl', {backspace: true}); - - expect(tree.getSelectedItem()).toBe(undefined); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual(['Fruit']); + type('Frui', {backspace: true}); + expect(tree.inputs.values()).toEqual(['Fruit']); + combobox.onKeydown(escape()); + expect(tree.inputs.values()).toEqual([]); }); it('should not select on navigation', () => { combobox.onKeydown(down()); - expect(tree.getSelectedItem()).toBe(undefined); - expect(tree.inputs.value()).toEqual([]); + expect(tree.getSelectedItems().length).toBe(0); + expect(tree.inputs.values()).toEqual([]); }); it('should not select on input', () => { type('A'); - expect(tree.getSelectedItem()).toBe(undefined); - expect(tree.inputs.value()).toEqual([]); + expect(tree.getSelectedItems().length).toBe(0); + expect(tree.inputs.values()).toEqual([]); }); it('should not select on focusout if the input text does not match an item', () => { type('Appl'); combobox.onFocusOut(new FocusEvent('focusout')); - expect(tree.getSelectedItem()).toBe(undefined); - expect(tree.inputs.value()).toEqual([]); + expect(tree.getSelectedItems().length).toBe(0); + expect(tree.inputs.values()).toEqual([]); expect(inputEl.value).toBe('Appl'); }); }); @@ -771,9 +801,9 @@ describe('Combobox with Tree Pattern', () => { }); it('should select and commit on click', () => { - combobox.onPointerup(clickTreeItem(tree.inputs.allItems(), 2)); - expect(tree.getSelectedItem()).toBe(tree.inputs.allItems()[2]); - expect(tree.inputs.value()).toEqual(['Banana']); + combobox.onClick(clickTreeItem(tree.inputs.allItems(), 2)); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.allItems()[2]); + expect(tree.inputs.values()).toEqual(['Banana']); expect(inputEl.value).toBe('Banana'); }); @@ -782,34 +812,34 @@ describe('Combobox with Tree Pattern', () => { combobox.onKeydown(down()); combobox.onKeydown(down()); combobox.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Grains']); + expect(tree.inputs.values()).toEqual(['Grains']); expect(inputEl.value).toBe('Grains'); }); it('should select the first item on arrow down when collapsed', () => { combobox.onKeydown(down()); - expect(tree.getSelectedItem()).toBe(tree.inputs.allItems()[0]); - expect(tree.inputs.value()).toEqual(['Fruit']); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.allItems()[0]); + expect(tree.inputs.values()).toEqual(['Fruit']); }); it('should select the last focusable item on arrow up when collapsed', () => { combobox.onKeydown(up()); - expect(tree.inputs.value()).toEqual(['Grains']); + expect(tree.inputs.values()).toEqual(['Grains']); }); it('should select on navigation', () => { combobox.onKeydown(down()); combobox.onKeydown(right()); combobox.onKeydown(right()); - expect(tree.inputs.value()).toEqual(['Apple']); + expect(tree.inputs.values()).toEqual(['Apple']); }); it('should select the first option on input', () => { type('B'); - expect(tree.inputs.value()).toEqual(['Banana']); + expect(tree.inputs.values()).toEqual(['Banana']); type('Bro'); - expect(tree.inputs.value()).toEqual(['Broccoli']); + expect(tree.inputs.values()).toEqual(['Broccoli']); }); it('should commit the selected option on focusout', () => { @@ -828,9 +858,9 @@ describe('Combobox with Tree Pattern', () => { }); it('should select and commit on click', () => { - combobox.onPointerup(clickTreeItem(tree.inputs.allItems(), 2)); - expect(tree.getSelectedItem()).toBe(tree.inputs.allItems()[2]); - expect(tree.inputs.value()).toEqual(['Banana']); + combobox.onClick(clickTreeItem(tree.inputs.allItems(), 2)); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.allItems()[2]); + expect(tree.inputs.values()).toEqual(['Banana']); expect(inputEl.value).toBe('Banana'); }); @@ -839,34 +869,34 @@ describe('Combobox with Tree Pattern', () => { combobox.onKeydown(down()); combobox.onKeydown(down()); combobox.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Grains']); + expect(tree.inputs.values()).toEqual(['Grains']); expect(inputEl.value).toBe('Grains'); }); it('should select the first item on arrow down when collapsed', () => { combobox.onKeydown(down()); - expect(tree.getSelectedItem()).toBe(tree.inputs.allItems()[0]); - expect(tree.inputs.value()).toEqual(['Fruit']); + expect(tree.getSelectedItems()[0]).toBe(tree.inputs.allItems()[0]); + expect(tree.inputs.values()).toEqual(['Fruit']); }); it('should select the last focusable item on arrow up when collapsed', () => { combobox.onKeydown(up()); - expect(tree.inputs.value()).toEqual(['Grains']); + expect(tree.inputs.values()).toEqual(['Grains']); }); it('should select on navigation', () => { combobox.onKeydown(down()); combobox.onKeydown(right()); combobox.onKeydown(right()); - expect(tree.inputs.value()).toEqual(['Apple']); + expect(tree.inputs.values()).toEqual(['Apple']); }); it('should select the first option on input', () => { type('B'); - expect(tree.inputs.value()).toEqual(['Banana']); + expect(tree.inputs.values()).toEqual(['Banana']); type('Bro'); - expect(tree.inputs.value()).toEqual(['Broccoli']); + expect(tree.inputs.values()).toEqual(['Broccoli']); }); it('should commit the selected option on navigation', () => { @@ -876,7 +906,7 @@ describe('Combobox with Tree Pattern', () => { combobox.onKeydown(right()); expect(inputEl.value).toBe('Apple'); combobox.onKeydown(down()); - expect(tree.inputs.value()).toEqual(['Banana']); + expect(tree.inputs.values()).toEqual(['Banana']); }); it('should commit the selected option on focusout', () => { @@ -894,4 +924,24 @@ describe('Combobox with Tree Pattern', () => { }); }); }); + + describe('Readonly mode', () => { + it('should select and close on selection', () => { + const {combobox, tree, inputEl} = getPatterns({readonly: true}); + combobox.onClick(clickInput(inputEl)); + expect(combobox.expanded()).toBe(true); + combobox.onClick(clickTreeItem(tree.inputs.allItems(), 0)); + expect(tree.inputs.values()).toEqual(['Fruit']); + expect(inputEl.value).toBe('Fruit'); + expect(combobox.expanded()).toBe(false); + }); + + it('should close on escape', () => { + const {combobox} = getPatterns({readonly: true}); + combobox.onKeydown(down()); + expect(combobox.expanded()).toBe(true); + combobox.onKeydown(escape()); + expect(combobox.expanded()).toBe(false); + }); + }); }); diff --git a/src/aria/ui-patterns/combobox/combobox.ts b/src/aria/private/combobox/combobox.ts similarity index 52% rename from src/aria/ui-patterns/combobox/combobox.ts rename to src/aria/private/combobox/combobox.ts index 9d8163d68db5..ada494f4d471 100644 --- a/src/aria/ui-patterns/combobox/combobox.ts +++ b/src/aria/private/combobox/combobox.ts @@ -14,7 +14,9 @@ import {ListItem} from '../behaviors/list/list'; /** Represents the required inputs for a combobox. */ export interface ComboboxInputs, V> { /** The controls for the popup associated with the combobox. */ - popupControls: SignalLike | ComboboxTreeControls | undefined>; + popupControls: SignalLike< + ComboboxListboxControls | ComboboxTreeControls | ComboboxDialogPattern | undefined + >; /** The HTML input element that serves as the combobox input. */ inputEl: SignalLike; @@ -39,6 +41,9 @@ export interface ComboboxInputs, V> { /** Whether the combobox is in a right-to-left context. */ textDirection: SignalLike<'rtl' | 'ltr'>; + + /** Whether the combobox is always expanded. */ + alwaysExpanded: SignalLike; } /** An interface that allows combobox popups to expose the necessary controls for the combobox. */ @@ -49,6 +54,11 @@ export interface ComboboxListboxControls, V> { /** The ARIA role for the popup. */ role: SignalLike<'listbox' | 'tree' | 'grid'>; + // TODO(wagnermaciel): Add validation that ensures only readonly comboboxes can have multi-select popups. + + /** Whether multiple items in the popup can be selected at once. */ + multi: SignalLike; + /** The ID of the active item in the popup. */ activeId: SignalLike; @@ -56,7 +66,7 @@ export interface ComboboxListboxControls, V> { items: SignalLike; /** Navigates to the given item in the popup. */ - focus: (item: T) => void; + focus: (item: T, opts?: {focusElement?: boolean}) => void; /** Navigates to the next item in the popup. */ next: () => void; @@ -73,6 +83,9 @@ export interface ComboboxListboxControls, V> { /** Selects the current item in the popup. */ select: (item?: T) => void; + /** Toggles the selection state of the given item in the popup. */ + toggle: (item?: T) => void; + /** Clears the selection state of the popup. */ clearSelection: () => void; @@ -82,8 +95,11 @@ export interface ComboboxListboxControls, V> { /** Returns the item corresponding to the given event. */ getItem: (e: PointerEvent) => T | undefined; - /** Returns the currently selected item in the popup. */ - getSelectedItem: () => T | undefined; + /** Returns the currently active (focused) item in the popup. */ + getActiveItem: () => T | undefined; + + /** Returns the currently selected items in the popup. */ + getSelectedItems: () => T[]; /** Sets the value of the combobox based on the selected item. */ setValue: (value: V | undefined) => void; // For re-setting the value if the popup was destroyed. @@ -101,13 +117,19 @@ export interface ComboboxTreeControls, V> collapseItem: () => void; /** Checks if the currently active item in the popup is expandable. */ - isItemExpandable: () => boolean; + isItemExpandable: (item?: T) => boolean; /** Expands all nodes in the tree. */ expandAll: () => void; /** Collapses all nodes in the tree. */ collapseAll: () => void; + + /** Toggles the expansion state of the currently active item in the popup. */ + toggleExpansion: (item?: T) => void; + + /** Whether the current active item is selectable. */ + isItemSelectable: (item?: T) => boolean; } /** Controls the state of a combobox. */ @@ -115,8 +137,18 @@ export class ComboboxPattern, V> { /** Whether the combobox is expanded. */ expanded = signal(false); + /** Whether the combobox is disabled. */ + disabled = () => this.inputs.disabled(); + /** The ID of the active item in the combobox. */ - activedescendant = computed(() => this.inputs.popupControls()?.activeId() ?? null); + activeDescendant = computed(() => { + const popupControls = this.inputs.popupControls(); + if (popupControls instanceof ComboboxDialogPattern) { + return null; + } + + return popupControls?.activeId() ?? null; + }); /** The currently highlighted item in the combobox. */ highlightedItem = signal(undefined); @@ -127,6 +159,9 @@ export class ComboboxPattern, V> { /** Whether the combobox is focused. */ isFocused = signal(false); + /** Whether the combobox has ever been focused. */ + hasBeenFocused = signal(false); + /** The key used to navigate to the previous item in the list. */ expandKey = computed(() => (this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight')); @@ -144,72 +179,139 @@ export class ComboboxPattern, V> { /** The ARIA role of the popup associated with the combobox. */ hasPopup = computed(() => this.inputs.popupControls()?.role() || null); - /** Whether the combobox is interactive. */ - isInteractive = computed(() => !this.inputs.disabled() && !this.inputs.readonly()); + /** Whether the combobox is read-only. */ + readonly = computed(() => this.inputs.readonly() || this.inputs.disabled() || null); + + /** Returns the listbox controls for the combobox. */ + listControls = () => { + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return null; + } + + return popupControls; + }; + + /** Returns the tree controls for the combobox. */ + treeControls = () => { + const popupControls = this.inputs.popupControls(); + + if (popupControls?.role() === 'tree') { + return popupControls as ComboboxTreeControls; + } + + return null; + }; /** The keydown event manager for the combobox. */ keydown = computed(() => { + const manager = new KeyboardEventManager(); + const popupControls = this.inputs.popupControls(); + + if (!popupControls) { + return manager; + } + + if (popupControls instanceof ComboboxDialogPattern) { + if (!this.expanded()) { + manager.on('ArrowUp', () => this.open()).on('ArrowDown', () => this.open()); + + if (this.readonly()) { + manager.on('Enter', () => this.open()).on(' ', () => this.open()); + } + } + + return manager; + } + + if (!this.inputs.alwaysExpanded()) { + manager.on('Escape', () => this.close({reset: !this.readonly()})); + } + if (!this.expanded()) { - return new KeyboardEventManager() + manager .on('ArrowDown', () => this.open({first: true})) .on('ArrowUp', () => this.open({last: true})); - } - const popupControls = this.inputs.popupControls(); + if (this.readonly()) { + manager + .on('Enter', () => this.open({selected: true})) + .on(' ', () => this.open({selected: true})); + } - if (!popupControls) { - return new KeyboardEventManager(); + return manager; } - const manager = new KeyboardEventManager() + manager .on('ArrowDown', () => this.next()) .on('ArrowUp', () => this.prev()) .on('Home', () => this.first()) - .on('End', () => this.last()) - .on('Escape', () => { - // TODO(wagnermaciel): We may want to fold this logic into the close() method. - if (this.inputs.filterMode() === 'highlight' && popupControls.activeId()) { - popupControls.unfocus(); - popupControls.clearSelection(); - - const inputEl = this.inputs.inputEl(); - if (inputEl) { - inputEl.value = this.inputs.inputValue!(); - } - } else { - this.close(); - this.inputs.popupControls()?.clearSelection(); - } - }) // TODO: When filter mode is 'highlight', escape should revert to the last committed value. - .on('Enter', () => this.select({commit: true, close: true})); + .on('End', () => this.last()); - if (popupControls.role() === 'tree') { - const treeControls = popupControls as ComboboxTreeControls; + if (this.readonly()) { + manager.on(' ', () => this.select({commit: true, close: !popupControls.multi()})); + } - if (treeControls.isItemExpandable() || treeControls.isItemCollapsible()) { - manager.on(this.collapseKey(), () => this.collapseItem()); - } + if (popupControls.role() === 'listbox') { + manager.on('Enter', () => { + this.select({commit: true, close: !popupControls.multi()}); + }); + } + + const treeControls = this.treeControls(); + + if (treeControls?.isItemSelectable()) { + manager.on('Enter', () => this.select({commit: true, close: true})); + } + + if (treeControls?.isItemExpandable()) { + manager + .on(this.expandKey(), () => this.expandItem()) + .on(this.collapseKey(), () => this.collapseItem()); - if (treeControls.isItemExpandable()) { - manager.on(this.expandKey(), () => this.expandItem()); + if (!treeControls.isItemSelectable()) { + manager.on('Enter', () => this.expandItem()); } } + if (treeControls?.isItemCollapsible()) { + manager.on(this.collapseKey(), () => this.collapseItem()); + } + return manager; }); - /** The pointerup event manager for the combobox. */ - pointerup = computed(() => + /** The click event manager for the combobox. */ + click = computed(() => new PointerEventManager().on(e => { - const item = this.inputs.popupControls()?.getItem(e); + if (e.target === this.inputs.inputEl()) { + if (this.readonly()) { + this.expanded() ? this.close() : this.open({selected: true}); + } + } - if (item) { - this.select({item, commit: true, close: true}); - this.inputs.inputEl()?.focus(); // Return focus to the input after selecting. + const controls = this.inputs.popupControls(); + + if (controls instanceof ComboboxDialogPattern) { + return; } - if (e.target === this.inputs.inputEl()) { - this.open(); + const item = controls?.getItem(e); + + if (item) { + if (controls?.role() === 'tree') { + const treeControls = controls as ComboboxTreeControls; + + if (treeControls.isItemExpandable(item) && !treeControls.isItemSelectable(item)) { + treeControls.toggleExpansion(item); + this.inputs.inputEl()?.focus(); + return; + } + } + + this.select({item, commit: true, close: !controls?.multi()}); + this.inputs.inputEl()?.focus(); // Return focus to the input after selecting. } }), ); @@ -218,21 +320,21 @@ export class ComboboxPattern, V> { /** Handles keydown events for the combobox. */ onKeydown(event: KeyboardEvent) { - if (this.isInteractive()) { + if (!this.inputs.disabled()) { this.keydown().handle(event); } } - /** Handles pointerup events for the combobox. */ - onPointerup(event: PointerEvent) { - if (this.isInteractive()) { - this.pointerup().handle(event); + /** Handles click events for the combobox. */ + onClick(event: MouseEvent) { + if (!this.inputs.disabled()) { + this.click().handle(event as PointerEvent); } } /** Handles input events for the combobox. */ onInput(event: Event) { - if (!this.isInteractive()) { + if (this.inputs.disabled() || this.inputs.readonly()) { return; } @@ -242,27 +344,41 @@ export class ComboboxPattern, V> { return; } + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return; + } + this.open(); this.inputs.inputValue?.set(inputEl.value); this.isDeleting = event instanceof InputEvent && !!event.inputType.match(/^delete/); - if (this.inputs.filterMode() === 'manual') { - const searchTerm = this.inputs.popupControls()?.getSelectedItem()?.searchTerm(); - - if (searchTerm && this.inputs.inputValue!() !== searchTerm) { - this.inputs.popupControls()?.clearSelection(); - } + if (this.inputs.filterMode() === 'highlight' && !this.isDeleting) { + this.highlight(); } } /** Handles focus in events for the combobox. */ onFocusIn() { + if (this.inputs.alwaysExpanded() && !this.hasBeenFocused()) { + const firstSelectedItem = this.listControls()?.getSelectedItems()[0]; + firstSelectedItem ? this.listControls()?.focus(firstSelectedItem) : this.first(); + } + this.isFocused.set(true); + this.hasBeenFocused.set(true); } /** Handles focus out events for the combobox. */ onFocusOut(event: FocusEvent) { - if (this.inputs.disabled() || this.inputs.readonly()) { + if (this.inputs.disabled()) { + return; + } + + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { return; } @@ -271,11 +387,16 @@ export class ComboboxPattern, V> { !this.inputs.containerEl()?.contains(event.relatedTarget) ) { this.isFocused.set(false); + + if (this.readonly()) { + this.close(); + return; + } + if (this.inputs.filterMode() !== 'manual') { this.commit(); } else { - const item = this.inputs - .popupControls() + const item = popupControls ?.items() .find(i => i.searchTerm() === this.inputs.inputEl()?.value); @@ -293,18 +414,27 @@ export class ComboboxPattern, V> { // TODO(wagnermaciel): Consider whether we should not provide this default behavior for the // listbox. Instead, we may want to allow users to have no match so that typing does not focus // any option. - if (this.inputs.popupControls()?.role() === 'listbox') { - return this.inputs.popupControls()?.items()[0]; + if (this.listControls()?.role() === 'listbox') { + return this.listControls()?.items()[0]; } - return this.inputs - .popupControls() + return this.listControls() ?.items() .find(i => i.value() === this.inputs.firstMatch()); }); /** Handles filtering logic for the combobox. */ onFilter() { + if (this.readonly()) { + return; + } + + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return; + } + // TODO(wagnermaciel) // When the user first interacts with the combobox, the popup will lazily render for the first // time. This is a simple way to detect this and avoid auto-focus & selection logic, but this @@ -328,12 +458,12 @@ export class ComboboxPattern, V> { const item = this.firstMatch(); if (!item) { - this.inputs.popupControls()?.clearSelection(); - this.inputs.popupControls()?.unfocus(); + popupControls?.clearSelection(); + popupControls?.unfocus(); return; } - this.inputs.popupControls()?.focus(item); + popupControls?.focus(item); if (this.inputs.filterMode() !== 'manual') { this.select({item}); @@ -347,7 +477,8 @@ export class ComboboxPattern, V> { /** Highlights the currently selected item in the combobox. */ highlight() { const inputEl = this.inputs.inputEl(); - const item = this.inputs.popupControls()?.getSelectedItem(); + const selectedItems = this.listControls()?.getSelectedItems(); + const item = selectedItems?.[0]; if (!inputEl || !item) { return; @@ -367,14 +498,85 @@ export class ComboboxPattern, V> { } /** Closes the combobox. */ - close() { - this.expanded.set(false); - this.inputs.popupControls()?.unfocus(); + close(opts?: {reset: boolean}) { + const popupControls = this.inputs.popupControls(); + + if (this.inputs.alwaysExpanded()) { + return; + } + + if (popupControls instanceof ComboboxDialogPattern) { + this.expanded.set(false); + return; + } + + if (this.readonly()) { + this.expanded.set(false); + popupControls?.unfocus(); + return; + } + + if (!opts?.reset) { + if (this.inputs.filterMode() === 'manual') { + if ( + !this.listControls() + ?.items() + .some(i => i.searchTerm() === this.inputs.inputEl()?.value) + ) { + this.listControls()?.clearSelection(); + } + } + + this.expanded.set(false); + popupControls?.unfocus(); + return; + } + + if (!this.expanded()) { + this.inputs.inputValue?.set(''); + popupControls?.clearSelection(); + + const inputEl = this.inputs.inputEl(); + + if (inputEl) { + inputEl.value = ''; + } + } else if (this.expanded()) { + this.expanded.set(false); + const selectedItem = popupControls?.getSelectedItems()?.[0]; + + if (selectedItem?.searchTerm() !== this.inputs.inputValue!()) { + popupControls?.clearSelection(); + } + + return; + } + + this.close(); + + if (!this.readonly()) { + popupControls?.clearSelection(); + } } /** Opens the combobox. */ - open(nav?: {first?: boolean; last?: boolean}) { + open(nav?: {first?: boolean; last?: boolean; selected?: boolean}) { this.expanded.set(true); + const popupControls = this.inputs.popupControls(); + + if (popupControls instanceof ComboboxDialogPattern) { + return; + } + + const inputEl = this.inputs.inputEl(); + + if (inputEl && this.inputs.filterMode() === 'highlight') { + const isHighlighting = inputEl.selectionStart !== inputEl.value.length; + this.inputs.inputValue?.set(inputEl.value.slice(0, inputEl.selectionStart || 0)); + if (!isHighlighting) { + this.highlightedItem.set(undefined); + } + } if (nav?.first) { this.first(); @@ -382,26 +584,35 @@ export class ComboboxPattern, V> { if (nav?.last) { this.last(); } + if (nav?.selected) { + const selectedItem = popupControls + ?.items() + .find(i => popupControls?.getSelectedItems().includes(i)); + + if (selectedItem) { + popupControls?.focus(selectedItem); + } + } } /** Navigates to the next focusable item in the combobox popup. */ next() { - this._navigate(() => this.inputs.popupControls()?.next()); + this._navigate(() => this.listControls()?.next()); } /** Navigates to the previous focusable item in the combobox popup. */ prev() { - this._navigate(() => this.inputs.popupControls()?.prev()); + this._navigate(() => this.listControls()?.prev()); } /** Navigates to the first focusable item in the combobox popup. */ first() { - this._navigate(() => this.inputs.popupControls()?.first()); + this._navigate(() => this.listControls()?.first()); } /** Navigates to the last focusable item in the combobox popup. */ last() { - this._navigate(() => this.inputs.popupControls()?.last()); + this._navigate(() => this.listControls()?.last()); } /** Collapses the currently focused item in the combobox. */ @@ -418,7 +629,13 @@ export class ComboboxPattern, V> { /** Selects an item in the combobox popup. */ select(opts: {item?: T; commit?: boolean; close?: boolean} = {}) { - this.inputs.popupControls()?.select(opts.item); + const controls = this.listControls(); + + if (opts.item) { + controls?.focus(opts.item, {focusElement: false}); + } + + controls?.multi() ? controls.toggle(opts.item) : controls?.select(opts.item); if (opts.commit) { this.commit(); @@ -431,16 +648,18 @@ export class ComboboxPattern, V> { /** Updates the value of the input based on the currently selected item. */ commit() { const inputEl = this.inputs.inputEl(); - const item = this.inputs.popupControls()?.getSelectedItem(); + const selectedItems = this.listControls()?.getSelectedItems(); - if (inputEl && item) { - inputEl.value = item.searchTerm(); - this.inputs.inputValue?.set(item.searchTerm()); + if (!inputEl) { + return; + } - if (this.inputs.filterMode() === 'highlight') { - const length = inputEl.value.length; - inputEl.setSelectionRange(length, length); - } + inputEl.value = selectedItems?.map(i => i.searchTerm()).join(', ') || ''; + this.inputs.inputValue?.set(inputEl.value); + + if (this.inputs.filterMode() === 'highlight' && !this.readonly()) { + const length = inputEl.value.length; + inputEl.setSelectionRange(length, length); } } @@ -455,7 +674,7 @@ export class ComboboxPattern, V> { if (this.inputs.filterMode() === 'highlight') { // This is to handle when the user navigates back to the originally highlighted item. // E.g. User types "Al", highlights "Alice", then navigates down and back up to "Alice". - const selectedItem = this.inputs.popupControls()?.getSelectedItem(); + const selectedItem = this.listControls()?.getSelectedItems()[0]; if (!selectedItem) { return; @@ -470,3 +689,32 @@ export class ComboboxPattern, V> { } } } + +export class ComboboxDialogPattern { + id = () => this.inputs.id(); + + role = () => 'dialog' as const; + + keydown = computed(() => { + return new KeyboardEventManager().on('Escape', () => this.inputs.combobox.close()); + }); + + constructor( + readonly inputs: { + combobox: ComboboxPattern; + element: SignalLike; + id: SignalLike; + }, + ) {} + + onKeydown(event: KeyboardEvent) { + this.keydown().handle(event); + } + + onClick(event: MouseEvent) { + // The "click" event fires on the dialog when the user clicks outside of the dialog content. + if (event.target === this.inputs.element()) { + this.inputs.combobox.close(); + } + } +} diff --git a/src/aria/deferred-content/BUILD.bazel b/src/aria/private/deferred-content/BUILD.bazel similarity index 100% rename from src/aria/deferred-content/BUILD.bazel rename to src/aria/private/deferred-content/BUILD.bazel diff --git a/src/aria/deferred-content/deferred-content.spec.ts b/src/aria/private/deferred-content/deferred-content.spec.ts similarity index 100% rename from src/aria/deferred-content/deferred-content.spec.ts rename to src/aria/private/deferred-content/deferred-content.spec.ts diff --git a/src/aria/deferred-content/deferred-content.ts b/src/aria/private/deferred-content/deferred-content.ts similarity index 70% rename from src/aria/deferred-content/deferred-content.ts rename to src/aria/private/deferred-content/deferred-content.ts index 6a42d10ebb70..d6ee311fa768 100644 --- a/src/aria/deferred-content/deferred-content.ts +++ b/src/aria/private/deferred-content/deferred-content.ts @@ -14,6 +14,8 @@ import { signal, ViewContainerRef, model, + EmbeddedViewRef, + OnDestroy, } from '@angular/core'; /** @@ -41,10 +43,11 @@ export class DeferredContentAware { * ``` */ @Directive() -export class DeferredContent { +export class DeferredContent implements OnDestroy { private readonly _deferredContentAware = inject(DeferredContentAware, {optional: true}); private readonly _templateRef = inject(TemplateRef); private readonly _viewContainerRef = inject(ViewContainerRef); + private _currentViewRef: EmbeddedViewRef | null = null; private _isRendered = false; readonly deferredContentAware = signal(this._deferredContentAware); @@ -52,14 +55,28 @@ export class DeferredContent { constructor() { afterRenderEffect(() => { if (this.deferredContentAware()?.contentVisible()) { - if (this._isRendered) return; - this._viewContainerRef.clear(); - this._viewContainerRef.createEmbeddedView(this._templateRef); - this._isRendered = true; + if (!this._isRendered) { + this._destroyContent(); + this._currentViewRef = this._viewContainerRef.createEmbeddedView(this._templateRef); + this._isRendered = true; + } } else if (!this.deferredContentAware()?.preserveContent()) { - this._viewContainerRef.clear(); + this._destroyContent(); this._isRendered = false; } }); } + + ngOnDestroy(): void { + this._destroyContent(); + } + + private _destroyContent() { + const ref = this._currentViewRef; + + if (ref && !ref.destroyed) { + ref.destroy(); + this._currentViewRef = null; + } + } } diff --git a/src/aria/deferred-content/index.ts b/src/aria/private/deferred-content/index.ts similarity index 100% rename from src/aria/deferred-content/index.ts rename to src/aria/private/deferred-content/index.ts diff --git a/src/aria/ui-patterns/grid/BUILD.bazel b/src/aria/private/grid/BUILD.bazel similarity index 52% rename from src/aria/ui-patterns/grid/BUILD.bazel rename to src/aria/private/grid/BUILD.bazel index 7f3cf1c3a4cf..2546eeb29fbb 100644 --- a/src/aria/ui-patterns/grid/BUILD.bazel +++ b/src/aria/private/grid/BUILD.bazel @@ -12,9 +12,10 @@ ts_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/grid", - "//src/aria/ui-patterns/behaviors/list", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/grid", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/list-navigation", + "//src/aria/private/behaviors/signal-like", ], ) diff --git a/src/aria/private/grid/cell.ts b/src/aria/private/grid/cell.ts new file mode 100644 index 000000000000..d240afc0e74b --- /dev/null +++ b/src/aria/private/grid/cell.ts @@ -0,0 +1,316 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed, signal, linkedSignal, WritableSignal} from '@angular/core'; +import {KeyboardEventManager} from '../behaviors/event-manager'; +import {ListFocus} from '../behaviors/list-focus/list-focus'; +import {ListNavigation, ListNavigationInputs} from '../behaviors/list-navigation/list-navigation'; +import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; +import {GridCell} from '../behaviors/grid'; +import type {GridPattern} from './grid'; +import type {GridRowPattern} from './row'; +import {GridCellWidgetPattern} from './widget'; + +/** The inputs for the `GridCellPattern`. */ +export interface GridCellInputs + extends GridCell, + Omit< + ListNavigationInputs, + 'focusMode' | 'items' | 'activeItem' | 'softDisabled' | 'element' + > { + /** The `GridPattern` that this cell belongs to. */ + grid: SignalLike; + + /** The `GridRowPattern` that this cell belongs to. */ + row: SignalLike; + + /** The widget patterns contained within this cell, if any. */ + widgets: SignalLike; + + /** The index of this cell's row within the grid. */ + rowIndex: SignalLike; + + /** The index of this cell's column within the grid. */ + colIndex: SignalLike; + + /** A function that returns the cell widget associated with a given element. */ + getWidget: (e: Element | null) => GridCellWidgetPattern | undefined; +} + +/** The UI pattern for a grid cell. */ +export class GridCellPattern implements GridCell { + /** A unique identifier for the cell. */ + readonly id: SignalLike = () => this.inputs.id(); + + /** The html element that should receive focus. */ + readonly element: SignalLike = () => this.inputs.element(); + + /** Whether the cell has focus. */ + readonly isFocused: WritableSignal = signal(false); + + /** Whether the cell is selected. */ + readonly selected: WritableSignalLike; + + /** Whether the cell is selectable. */ + readonly selectable: SignalLike = () => this.inputs.selectable(); + + /** Whether a cell is disabled. */ + readonly disabled: SignalLike = () => this.inputs.disabled(); + + /** The number of rows the cell should span. */ + readonly rowSpan: SignalLike = () => this.inputs.rowSpan(); + + /** The number of columns the cell should span. */ + readonly colSpan: SignalLike = () => this.inputs.colSpan(); + + /** Whether the cell is active. */ + readonly active: SignalLike = computed(() => this.inputs.grid().activeCell() === this); + + /** Whether the cell is a selection anchor. */ + readonly anchor: SignalLike = computed(() => + this.inputs.grid().anchorCell() === this ? true : undefined, + ); + + /** The `aria-selected` attribute for the cell. */ + readonly ariaSelected: SignalLike = computed(() => + this.inputs.grid().inputs.enableSelection() && this.selectable() ? this.selected() : undefined, + ); + + /** The `aria-rowindex` attribute for the cell. */ + readonly ariaRowIndex: SignalLike = computed( + () => + this.inputs.row().rowIndex() ?? + this.inputs.rowIndex() ?? + this.inputs.grid().gridBehavior.rowIndex(this), + ); + + /** The `aria-colindex` attribute for the cell. */ + readonly ariaColIndex: SignalLike = computed( + () => this.inputs.colIndex() ?? this.inputs.grid().gridBehavior.colIndex(this), + ); + + /** The internal tab index calculation for the cell. */ + private readonly _tabIndex: SignalLike<-1 | 0> = computed(() => + this.inputs.grid().gridBehavior.cellTabIndex(this), + ); + + /** The tab index for the cell. If the cell contains a widget, the cell's tab index is -1. */ + readonly tabIndex: SignalLike<-1 | 0> = computed(() => { + if (this.singleWidgetMode() || this.navigationActivated()) { + return -1; + } + return this._tabIndex(); + }); + + // Single/Multi Widget Navigation Setup + + /** Whether the cell contains a single widget. */ + readonly singleWidgetMode: SignalLike = computed( + () => this.inputs.widgets().length === 1, + ); + + /** Whether the cell contains multiple widgets. */ + readonly multiWidgetMode: SignalLike = computed(() => this.inputs.widgets().length > 1); + + /** Whether navigation between widgets is disabled. */ + readonly navigationDisabled: SignalLike = computed( + () => !this.multiWidgetMode() || !this.active() || this.inputs.disabled(), + ); + + /** The focus behavior for the widgets in the cell. */ + readonly focusBehavior: ListFocus; + + /** The navigation behavior for the widgets in the cell. */ + readonly navigationBehavior: ListNavigation; + + /** The currently active widget in the cell. */ + readonly activeWidget: WritableSignalLike = linkedSignal(() => + this.inputs.widgets().length > 0 ? this.inputs.widgets()[0] : undefined, + ); + + /** Whether navigation between widgets is activated. */ + readonly navigationActivated: WritableSignalLike = signal(false); + + /** Whether any widget within the cell is activated. */ + readonly widgetActivated: SignalLike = computed(() => + this.inputs.widgets().some(w => w.isActivated()), + ); + + /** Whether the cell or widget inside the cell is activated. */ + readonly isActivated: SignalLike = computed( + () => this.navigationActivated() || this.widgetActivated(), + ); + + /** The key used to navigate to the previous widget. */ + readonly prevKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return 'ArrowUp'; + } + return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + }); + + /** The key used to navigate to the next widget. */ + readonly nextKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return 'ArrowDown'; + } + return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + }); + + /** The keyboard event manager for the cell. */ + readonly keydown = computed(() => { + const manager = new KeyboardEventManager(); + + // Before start list navigation. + if (!this.navigationActivated()) { + manager.on('Enter', () => this.startNavigation()); + return manager; + } + + // Start list navigation. + manager + .on('Escape', () => this.stopNavigation()) + .on(this.prevKey(), () => + this._advance(() => this.navigationBehavior.prev({focusElement: false})), + ) + .on(this.nextKey(), () => + this._advance(() => this.navigationBehavior.next({focusElement: false})), + ) + .on('Home', () => this._advance(() => this.navigationBehavior.next({focusElement: false}))) + .on('End', () => this._advance(() => this.navigationBehavior.next({focusElement: false}))); + + return manager; + }); + + constructor(readonly inputs: GridCellInputs) { + this.selected = inputs.selected; + + const listNavigationInputs: ListNavigationInputs = { + ...inputs, + items: inputs.widgets, + activeItem: this.activeWidget, + disabled: this.navigationDisabled, + focusMode: () => 'roving', + softDisabled: () => true, + }; + + this.focusBehavior = new ListFocus(listNavigationInputs); + this.navigationBehavior = new ListNavigation({ + ...listNavigationInputs, + focusManager: this.focusBehavior, + }); + } + + /** Handles keydown events for the cell. */ + onKeydown(event: KeyboardEvent): void { + if (this.disabled() || this.inputs.widgets().length === 0) return; + + // No navigation needed if single widget. + if (this.singleWidgetMode()) { + this.activeWidget()!.onKeydown(event); + return; + } + + // Focus is on the cell before the navigation starts. + if (!this.navigationActivated()) { + this.keydown().handle(event); + return; + } + + // Widget activate state can be changed during the widget keydown handling. + const widgetActivated = this.widgetActivated(); + + this.activeWidget()!.onKeydown(event); + + if (!widgetActivated) { + this.keydown().handle(event); + } + } + + /** Handles focusin events for the cell. */ + onFocusIn(event: FocusEvent): void { + this.isFocused.set(true); + + const focusTarget = event.target as Element | null; + const widget = this.inputs.getWidget(focusTarget); + if (!widget) return; + + // Pass down focusin event to the widget. + widget.onFocusIn(event); + + // Update internal states if the widget(or anything within the widget) is + // receiving focus by tabbing, pointer, or any programmatic control. + + // Update current active widget. + if (widget !== this.activeWidget()) { + this.navigationBehavior.goto(widget, {focusElement: false}); + } + + // Start widget navigation if multi widget. + if (this.multiWidgetMode()) { + this.navigationActivated.set(true); + } + } + + /** Handles focusout events for the cell. */ + onFocusOut(event: FocusEvent): void { + const blurTarget = event.target as Element | null; + const widget = this.inputs.getWidget(blurTarget); + + // Pass down focusout event to the widget. + widget?.onFocusOut(event); + + const focusTarget = event.relatedTarget as Element | null; + if (this.element().contains(focusTarget)) return; + + this.isFocused.set(false); + // Reset navigation state when focus leaving cell. + this.navigationActivated.set(false); + } + + /** Focuses the cell or the active widget. */ + focus(): void { + if (this.singleWidgetMode()) { + this.activeWidget()?.focus(); + } else { + this.element().focus(); + } + } + + /** Gets the tab index for the widget within the cell. */ + widgetTabIndex(): -1 | 0 { + if (this.singleWidgetMode()) { + return this._tabIndex(); + } + return this.navigationActivated() ? 0 : -1; + } + + /** Starts navigation between widgets. */ + startNavigation(): void { + if (this.navigationActivated()) return; + + this.navigationActivated.set(true); + this.navigationBehavior.first(); + } + + /** Stops navigation between widgets and restores focus to the cell. */ + stopNavigation(): void { + if (!this.navigationActivated()) return; + + this.navigationActivated.set(false); + this.element().focus(); + } + + /** Executes a navigation operation and focuses the new active widget. */ + private _advance(op: () => boolean): void { + const success = op(); + if (success) { + this.activeWidget()?.focus(); + } + } +} diff --git a/src/aria/private/grid/grid.ts b/src/aria/private/grid/grid.ts new file mode 100644 index 000000000000..69ffce05ad9f --- /dev/null +++ b/src/aria/private/grid/grid.ts @@ -0,0 +1,397 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed, signal, untracked} from '@angular/core'; +import {SignalLike} from '../behaviors/signal-like/signal-like'; +import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/event-manager'; +import {NavOptions, Grid, GridInputs as GridBehaviorInputs} from '../behaviors/grid'; +import type {GridRowPattern} from './row'; +import type {GridCellPattern} from './cell'; + +/** Represents the required inputs for the grid pattern. */ +export interface GridInputs extends Omit, 'cells'> { + /** The html element of the grid. */ + element: SignalLike; + + /** The rows that make up the grid. */ + rows: SignalLike; + + /** The direction that text is read based on the users locale. */ + textDirection: SignalLike<'rtl' | 'ltr'>; + + /** Whether selection is enabled for the grid. */ + enableSelection: SignalLike; + + /** Whether multiple cell in the grid can be selected. */ + multi: SignalLike; + + /** The selection strategy used by the grid. */ + selectionMode: SignalLike<'follow' | 'explicit'>; + + /** Whether enable range selection. */ + enableRangeSelection: SignalLike; + + /** A function that returns the grid cell associated with a given element. */ + getCell: (e: Element | null) => GridCellPattern | undefined; +} + +/** The UI pattern for a grid, handling keyboard navigation, focus, and selection. */ +export class GridPattern { + /** The underlying grid behavior that this pattern is built on. */ + readonly gridBehavior: Grid; + + /** The cells in the grid. */ + readonly cells = computed(() => this.gridBehavior.data.cells()); + + /** The tab index for the grid. */ + readonly tabIndex = computed(() => this.gridBehavior.gridTabIndex()); + + /** Whether the grid is disabled. */ + readonly disabled = computed(() => this.gridBehavior.gridDisabled()); + + /** The ID of the currently active descendant cell. */ + readonly activeDescendant = computed(() => this.gridBehavior.activeDescendant()); + + /** The currently active cell. */ + readonly activeCell = computed(() => this.gridBehavior.focusBehavior.activeCell()); + + /** The current selection anchor cell. */ + readonly anchorCell: SignalLike = computed(() => + this.inputs.enableSelection() && this.inputs.multi() + ? this.gridBehavior.selectionAnchorCell() + : undefined, + ); + + /** Whether to pause grid navigation and give the keyboard control to cell or widget. */ + readonly pauseNavigation: SignalLike = computed(() => + this.gridBehavior.data + .cells() + .flat() + .reduce((res, c) => res || c.isActivated(), false), + ); + + /** Whether the focus is in the grid. */ + readonly isFocused = signal(false); + + /** Whether the grid has been focused once. */ + readonly hasBeenFocused = signal(false); + + /** Whether the user is currently dragging to select a range of cells. */ + readonly dragging = signal(false); + + /** The key for navigating to the previous column. */ + readonly prevColKey = computed(() => + this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft', + ); + + /** The key for navigating to the next column. */ + readonly nextColKey = computed(() => + this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight', + ); + + /** The keydown event manager for the grid. */ + readonly keydown = computed(() => { + const manager = new KeyboardEventManager(); + + if (this.pauseNavigation()) { + return manager; + } + + // Navigation handlers. + const opts: NavOptions = { + selectOne: this.inputs.enableSelection() && this.inputs.selectionMode() === 'follow', + }; + manager + .on('ArrowUp', () => this.gridBehavior.up(opts)) + .on('ArrowDown', () => this.gridBehavior.down(opts)) + .on(this.prevColKey(), () => this.gridBehavior.left(opts)) + .on(this.nextColKey(), () => this.gridBehavior.right(opts)) + .on('Home', () => this.gridBehavior.firstInRow(opts)) + .on('End', () => this.gridBehavior.lastInRow(opts)) + .on([Modifier.Ctrl], 'Home', () => this.gridBehavior.first(opts)) + .on([Modifier.Ctrl], 'End', () => this.gridBehavior.last(opts)); + + // Basic explicit selection handlers. + if (this.inputs.enableSelection() && this.inputs.selectionMode() === 'explicit') { + manager.on(/Enter| /, () => + this.inputs.multi() ? this.gridBehavior.toggle() : this.gridBehavior.toggleOne(), + ); + } + + // Range selection handlers. + if (this.inputs.enableSelection() && this.inputs.enableRangeSelection()) { + manager + .on(Modifier.Shift, 'ArrowUp', () => this.gridBehavior.up({anchor: true})) + .on(Modifier.Shift, 'ArrowDown', () => this.gridBehavior.down({anchor: true})) + .on(Modifier.Shift, this.prevColKey(), () => this.gridBehavior.left({anchor: true})) + .on(Modifier.Shift, this.nextColKey(), () => this.gridBehavior.right({anchor: true})) + .on(Modifier.Shift, 'Home', () => this.gridBehavior.firstInRow({anchor: true})) + .on(Modifier.Shift, 'End', () => this.gridBehavior.lastInRow({anchor: true})) + .on([Modifier.Ctrl | Modifier.Shift], 'Home', () => this.gridBehavior.first({anchor: true})) + .on([Modifier.Ctrl | Modifier.Shift], 'End', () => this.gridBehavior.last({anchor: true})) + .on([Modifier.Ctrl, Modifier.Meta], 'A', () => { + if (this.gridBehavior.allSelected()) { + this.gridBehavior.deselectAll(); + } else { + this.gridBehavior.selectAll(); + } + }) + .on([Modifier.Shift], ' ', () => this.gridBehavior.selectRow()) + .on([Modifier.Ctrl, Modifier.Meta], ' ', () => this.gridBehavior.selectCol()); + } + + return manager; + }); + + /** The pointerdown event manager for the grid. */ + readonly pointerdown = computed(() => { + const manager = new PointerEventManager(); + + // Navigation without selection. + if (!this.inputs.enableSelection()) { + manager.on(e => { + const cell = this.inputs.getCell(e.target as Element); + if (!cell || !this.gridBehavior.focusBehavior.isFocusable(cell)) return; + + this.gridBehavior.gotoCell(cell); + }); + } + + // Navigation with selection. + if (this.inputs.enableSelection()) { + manager.on(e => { + const cell = this.inputs.getCell(e.target as Element); + if (!cell || !this.gridBehavior.focusBehavior.isFocusable(cell)) return; + + this.gridBehavior.gotoCell(cell, { + selectOne: this.inputs.selectionMode() === 'follow', + toggleOne: this.inputs.selectionMode() === 'explicit' && !this.inputs.multi(), + toggle: this.inputs.selectionMode() === 'explicit' && this.inputs.multi(), + }); + + if (this.inputs.multi() && this.inputs.enableRangeSelection()) { + this.dragging.set(true); + } + }); + + // Selection with modifier keys. + if (this.inputs.multi()) { + manager.on([Modifier.Ctrl, Modifier.Meta], e => { + const cell = this.inputs.getCell(e.target as Element); + if (!cell || !this.gridBehavior.focusBehavior.isFocusable(cell)) return; + + this.gridBehavior.gotoCell(cell, {toggle: true}); + + if (this.inputs.enableRangeSelection()) { + this.dragging.set(true); + } + }); + + if (this.inputs.enableRangeSelection()) { + manager.on(Modifier.Shift, e => { + const cell = this.inputs.getCell(e.target as Element); + if (!cell) return; + + this.gridBehavior.gotoCell(cell, {anchor: true}); + this.dragging.set(true); + }); + } + } + } + + return manager; + }); + + /** The pointerup event manager for the grid. */ + readonly pointerup = computed(() => { + const manager = new PointerEventManager(); + + if (this.inputs.enableSelection() && this.inputs.enableRangeSelection()) { + manager.on([Modifier.Shift, Modifier.Ctrl, Modifier.Meta, Modifier.None], () => { + this.dragging.set(false); + }); + } + + return manager; + }); + + /** Indicates maybe the losing focus is caused by row/cell deletion. */ + private readonly _maybeDeletion = signal(false); + + /** Indicates the losing focus is certainly caused by row/cell deletion. */ + private readonly _deletion = signal(false); + + /** Whether the grid state is stale and needs to be reconciled. */ + private readonly _stateStale = signal(false); + + constructor(readonly inputs: GridInputs) { + this.gridBehavior = new Grid({ + ...inputs, + cells: computed(() => this.inputs.rows().map(row => row.inputs.cells())), + }); + } + + /** Handles keydown events on the grid. */ + onKeydown(event: KeyboardEvent) { + if (this.disabled()) return; + + this.activeCell()?.onKeydown(event); + this.keydown().handle(event); + } + + /** Handles pointerdown events on the grid. */ + onPointerdown(event: PointerEvent) { + if (this.disabled()) return; + + this.pointerdown().handle(event); + } + + /** Handles pointermove events on the grid. */ + onPointermove(event: PointerEvent) { + if ( + this.disabled() || + !this.inputs.enableSelection() || + !this.inputs.enableRangeSelection() || + !this.dragging() + ) { + return; + } + + const cell = this.inputs.getCell(event.target as Element); + + // Dragging anchor. + if (cell !== undefined) { + this.gridBehavior.gotoCell(cell, {anchor: true}); + } + } + + /** Handles pointerup events on the grid. */ + onPointerup(event: PointerEvent) { + if (this.disabled()) return; + + this.pointerup().handle(event); + } + + /** Handles focusin events on the grid. */ + onFocusIn(event: FocusEvent) { + this.isFocused.set(true); + this.hasBeenFocused.set(true); + + // Skip if in the middle of range selection. + if (this.dragging()) return; + + // Cell that receives focus. + const cell = this.inputs.getCell(event.target as Element | null); + if (!cell || !this.gridBehavior.focusBehavior.isFocusable(cell)) return; + + // Pass down the focusin event to the cell. + cell.onFocusIn(event); + + // Update active cell state if the cell is receiving focus by + // tabbing, pointer, or any programmatic control into a widget inside the cell. + if (cell !== this.activeCell()) { + this.gridBehavior.gotoCell(cell); + } + } + + /** Handles focusout events on the grid. */ + onFocusOut(event: FocusEvent) { + // Pass down focusout event to the cell that loses focus. + const blurTarget = event.target as Element | null; + const cell = this.inputs.getCell(blurTarget); + + // Pass down the focusout event to the cell. + cell?.onFocusOut(event); + + const focusTarget = event.relatedTarget as Element | null; + if (this.inputs.element().contains(focusTarget)) return; + + // If a `relatedTarget`(focusing target) is null, then it can be caused by either + // - Clicking on a non-focusable element, or + // - The focused element is removed from the page. + if (focusTarget === null) { + this._maybeDeletion.set(true); + } + + this.isFocused.set(false); + } + + /** Sets the default active state of the grid before receiving focus the first time. */ + setDefaultStateEffect(): void { + if (this.hasBeenFocused()) return; + + this.gridBehavior.setDefaultState(); + } + + /** Resets the active state of the grid if it is empty or stale. */ + resetStateEffect(): void { + const hasReset = this.gridBehavior.resetState(); + + if (hasReset) { + // If the active state has been reset right after a focusout event, then + // we know it's caused by a row/cell deletion. + if (this._maybeDeletion()) { + this._deletion.set(true); + } else { + this._stateStale.set(true); + } + } + // Reset maybe deletion state. + this._maybeDeletion.set(false); + } + + /** Resets the focus to the active cell element or grid element. */ + resetFocusEffect(): void { + const stateStale = this._stateStale(); + if (!stateStale) return; + + const isFocused = untracked(() => this.isFocused()); + const isRoving = untracked(() => this.inputs.focusMode() === 'roving'); + const activeCell = untracked(() => this.activeCell()); + + if (isRoving && activeCell !== undefined && isFocused) { + if (!activeCell.isFocused()) { + activeCell.focus(); + } + } + + this._stateStale.set(false); + } + + /** Restore focus when a deletion happened. */ + restoreFocusEffect(): void { + const deletion = this._deletion(); + if (!deletion) return; + + const isRoving = untracked(() => this.inputs.focusMode() === 'roving'); + const activeCell = untracked(() => this.activeCell()); + + if (isRoving && activeCell !== undefined) { + if (!activeCell.isFocused()) { + activeCell.focus(); + } + } + + this._deletion.set(false); + } + + /** Sets focus when active cell changed. */ + focusEffect(): void { + const activeCell = this.activeCell(); + const gridFocused = untracked(() => this.isFocused()); + + if (activeCell === undefined || !gridFocused) return; + + const isRoving = untracked(() => this.inputs.focusMode() === 'roving'); + const cellFocused = untracked(() => activeCell.isFocused()); + + if (isRoving && !cellFocused) { + activeCell.focus(); + } + } +} diff --git a/src/aria/ui-patterns/grid/row.ts b/src/aria/private/grid/row.ts similarity index 100% rename from src/aria/ui-patterns/grid/row.ts rename to src/aria/private/grid/row.ts diff --git a/src/aria/private/grid/widget.ts b/src/aria/private/grid/widget.ts new file mode 100644 index 000000000000..b008003d3ac8 --- /dev/null +++ b/src/aria/private/grid/widget.ts @@ -0,0 +1,159 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed, signal, Signal, WritableSignal} from '@angular/core'; +import {KeyboardEventManager, Modifier} from '../behaviors/event-manager'; +import {ListNavigationItem} from '../behaviors/list-navigation/list-navigation'; +import {SignalLike} from '../behaviors/signal-like/signal-like'; +import type {GridCellPattern} from './cell'; + +/** The inputs for the `GridCellWidgetPattern`. */ +export interface GridCellWidgetInputs extends Omit { + /** The `GridCellPattern` that this widget belongs to. */ + cell: SignalLike; + + /** The html element that should receive focus. */ + element: SignalLike; + + /** The type of widget, which determines how it is activated. */ + widgetType: SignalLike<'simple' | 'complex' | 'editable'>; + + /** The element that will receive focus when the widget is activated. */ + focusTarget: SignalLike; +} + +/** The UI pattern for a widget inside a grid cell. */ +export class GridCellWidgetPattern implements ListNavigationItem { + /** A unique identifier for the widget. */ + readonly id: SignalLike = () => this.inputs.id(); + + /** The html element that should receive focus. */ + readonly element: SignalLike = () => this.inputs.element(); + + /** The element that should receive focus. */ + readonly widgetHost: Signal = computed( + () => this.inputs.focusTarget() ?? this.element(), + ); + + /** The index of the widget within the cell. */ + readonly index: Signal = computed(() => + this.inputs.cell().inputs.widgets().indexOf(this), + ); + + /** Whether the widget is disabled. */ + readonly disabled: Signal = computed( + () => this.inputs.disabled() || this.inputs.cell().disabled(), + ); + + /** The tab index for the widget. */ + readonly tabIndex: Signal<-1 | 0> = computed(() => this.inputs.cell().widgetTabIndex()); + + /** Whether the widget is the active item in the widget list. */ + readonly active: Signal = computed(() => this.inputs.cell().activeWidget() === this); + + /** Whether the widget is currently activated. */ + readonly isActivated: WritableSignal = signal(false); + + /** The last event that caused the widget to be activated. */ + readonly lastActivateEvent: WritableSignal = + signal(undefined); + + /** The last event that caused the widget to be deactivated. */ + readonly lastDeactivateEvent: WritableSignal = + signal(undefined); + + /** The keyboard event manager for the widget. */ + readonly keydown = computed(() => { + const manager = new KeyboardEventManager(); + + // Simple widget does not need to pause default grid behaviors. + if (this.inputs.widgetType() === 'simple') { + return manager; + } + + // If a widget is activated, only listen to events that exits activate state. + if (this.isActivated()) { + manager.on('Escape', e => { + this.deactivate(e); + this.focus(); + }); + + if (this.inputs.widgetType() === 'editable') { + manager.on('Enter', e => { + this.deactivate(e); + this.focus(); + }); + } + + return manager; + } + + // Enter key is used to activate widget for both complex and editable type. + manager.on('Enter', e => this.activate(e)); + + if (this.inputs.widgetType() === 'editable') { + manager.on([Modifier.Shift, Modifier.None], /^[a-zA-Z0-9]$/, e => this.activate(e), { + preventDefault: false, + }); + } + + return manager; + }); + + constructor(readonly inputs: GridCellWidgetInputs) {} + + /** Handles keydown events for the widget. */ + onKeydown(event: KeyboardEvent): void { + if (this.disabled()) return; + + this.keydown().handle(event); + } + + /** Handles focusin events for the widget. */ + onFocusIn(event: FocusEvent): void { + // Simple widget does not have activate state. + if (this.inputs.widgetType() === 'simple') return; + + // Set activate state if the focus is inside of widget. + const focusTarget = event.target as Element; + if (this.widgetHost().contains(focusTarget) && this.widgetHost() !== focusTarget) { + this.activate(event); + } + } + + /** Handles focusout events for the widget. */ + onFocusOut(event: FocusEvent): void { + const focusTarget = event.relatedTarget as Element; + if (this.widgetHost().contains(focusTarget)) return; + + // Reset states when focus leaving widget. + this.deactivate(event); + } + + /** Focuses the widget's host element. */ + focus(): void { + this.widgetHost().focus(); + } + + /** Activates the widget. */ + activate(event?: KeyboardEvent | FocusEvent): void { + if (this.isActivated()) return; + if (this.inputs.widgetType() === 'simple') return; + + this.isActivated.set(true); + this.lastActivateEvent.set(event); + } + + /** Deactivates the widget and restores focus to the widget's host element. */ + deactivate(event?: KeyboardEvent | FocusEvent): void { + if (!this.isActivated()) return; + + this.isActivated.set(false); + this.lastDeactivateEvent.set(event); + } +} diff --git a/src/aria/ui-patterns/index.ts b/src/aria/private/index.ts similarity index 100% rename from src/aria/ui-patterns/index.ts rename to src/aria/private/index.ts diff --git a/src/aria/ui-patterns/listbox/BUILD.bazel b/src/aria/private/listbox/BUILD.bazel similarity index 75% rename from src/aria/ui-patterns/listbox/BUILD.bazel rename to src/aria/private/listbox/BUILD.bazel index d30344d420aa..f38e71709afa 100644 --- a/src/aria/ui-patterns/listbox/BUILD.bazel +++ b/src/aria/private/listbox/BUILD.bazel @@ -10,10 +10,10 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/list", - "//src/aria/ui-patterns/behaviors/signal-like", - "//src/aria/ui-patterns/combobox", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/signal-like", + "//src/aria/private/combobox", ], ) diff --git a/src/aria/ui-patterns/listbox/combobox-listbox.ts b/src/aria/private/listbox/combobox-listbox.ts similarity index 68% rename from src/aria/ui-patterns/listbox/combobox-listbox.ts rename to src/aria/private/listbox/combobox-listbox.ts index b331de3c358e..96cf94eb4655 100644 --- a/src/aria/ui-patterns/listbox/combobox-listbox.ts +++ b/src/aria/private/listbox/combobox-listbox.ts @@ -28,17 +28,21 @@ export class ComboboxListboxPattern role = computed(() => 'listbox' as const); /** The id of the active (focused) item in the listbox. */ - activeId = computed(() => this.listBehavior.activedescendant()); + activeId = computed(() => this.listBehavior.activeDescendant()); /** The list of options in the listbox. */ items: SignalLike[]> = computed(() => this.inputs.items()); - /** The tabindex for the listbox. Always -1 because the combobox handles focus. */ - override tabindex: SignalLike<-1 | 0> = () => -1; + /** The tab index for the listbox. Always -1 because the combobox handles focus. */ + override tabIndex: SignalLike<-1 | 0> = () => -1; + + /** Whether multiple items in the list can be selected at once. */ + override multi = computed(() => { + return this.inputs.combobox()?.readonly() ? this.inputs.multi() : false; + }); constructor(override readonly inputs: ComboboxListboxInputs) { if (inputs.combobox()) { - inputs.multi = () => false; inputs.focusMode = () => 'activedescendant'; inputs.element = inputs.combobox()!.inputs.inputEl; } @@ -56,7 +60,12 @@ export class ComboboxListboxPattern override setDefaultState(): void {} /** Navigates to the specified item in the listbox. */ - focus = (item: OptionPattern) => this.listBehavior.goto(item); + focus = (item: OptionPattern, opts?: {focusElement?: boolean}) => { + this.listBehavior.goto(item, opts); + }; + + /** Navigates to the previous focusable item in the listbox. */ + getActiveItem = () => this.inputs.activeItem(); /** Navigates to the next focusable item in the listbox. */ next = () => this.listBehavior.next(); @@ -76,15 +85,28 @@ export class ComboboxListboxPattern /** Selects the specified item in the listbox. */ select = (item?: OptionPattern) => this.listBehavior.select(item); + /** Toggles the selection state of the given item in the listbox. */ + toggle = (item?: OptionPattern) => this.listBehavior.toggle(item); + /** Clears the selection in the listbox. */ clearSelection = () => this.listBehavior.deselectAll(); /** Retrieves the OptionPattern associated with a pointer event. */ getItem = (e: PointerEvent) => this._getItem(e); - /** Retrieves the currently selected item in the listbox. */ - getSelectedItem = () => this.inputs.items().find(i => i.selected()); + /** Retrieves the currently selected items in the listbox. */ + getSelectedItems = () => { + // NOTE: We need to do this funky for loop to preserve the order of the selected values. + const items = []; + for (const value of this.inputs.values()) { + const item = this.items().find(i => i.value() === value); + if (item) { + items.push(item); + } + } + return items; + }; /** Sets the value of the combobox listbox. */ - setValue = (value: V | undefined) => this.inputs.value.set(value ? [value] : []); + setValue = (value: V | undefined) => this.inputs.values.set(value ? [value] : []); } diff --git a/src/aria/ui-patterns/listbox/listbox.spec.ts b/src/aria/private/listbox/listbox.spec.ts similarity index 76% rename from src/aria/ui-patterns/listbox/listbox.spec.ts rename to src/aria/private/listbox/listbox.spec.ts index da3244c187b4..ec2b867a33b9 100644 --- a/src/aria/ui-patterns/listbox/listbox.spec.ts +++ b/src/aria/private/listbox/listbox.spec.ts @@ -34,13 +34,13 @@ describe('Listbox Pattern', () => { return new ListboxPattern({ id: signal('listbox-1'), items: inputs.items, - value: inputs.value ?? signal([]), + values: inputs.values ?? signal([]), activeItem: signal(undefined), - typeaheadDelay: inputs.typeaheadDelay ?? signal(0.5), + typeaheadDelay: inputs.typeaheadDelay ?? signal(500), wrap: inputs.wrap ?? signal(true), readonly: inputs.readonly ?? signal(false), disabled: inputs.disabled ?? signal(false), - skipDisabled: inputs.skipDisabled ?? signal(true), + softDisabled: inputs.softDisabled ?? signal(true), multi: inputs.multi ?? signal(false), focusMode: inputs.focusMode ?? signal('roving'), textDirection: inputs.textDirection ?? signal('ltr'), @@ -174,45 +174,45 @@ describe('Listbox Pattern', () => { describe('follows focus & single select', () => { it('should select an option on navigation', () => { const {listbox} = getDefaultPatterns({ - value: signal(['Apple']), + values: signal(['Apple']), multi: signal(false), selectionMode: signal('follow'), }); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[1]); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onKeydown(up()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(end()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[8]); - expect(listbox.inputs.value()).toEqual(['Cranberry']); + expect(listbox.inputs.values()).toEqual(['Cranberry']); listbox.onKeydown(home()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should not be able to change selection when in readonly mode', () => { const {listbox} = getDefaultPatterns({ - value: signal(['Apple']), + values: signal(['Apple']), readonly: signal(true), multi: signal(false), selectionMode: signal('follow'), }); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[0]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down()); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[1]); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); }); @@ -221,7 +221,7 @@ describe('Listbox Pattern', () => { beforeEach(() => { listbox = getDefaultPatterns({ - value: signal([]), + values: signal([]), selectionMode: signal('explicit'), multi: signal(false), }).listbox; @@ -229,30 +229,30 @@ describe('Listbox Pattern', () => { it('should select an option on Space', () => { listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should select an option on Enter', () => { listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should only allow one selected option', () => { listbox.onKeydown(enter()); listbox.onKeydown(down()); listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); }); it('should not be able to change selection when in readonly mode', () => { const readonly = listbox.inputs.readonly as WritableSignal; readonly.set(true); listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(down()); listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); }); @@ -262,7 +262,7 @@ describe('Listbox Pattern', () => { beforeEach(() => { const patterns = getDefaultPatterns({ - value: signal([]), + values: signal([]), selectionMode: signal('explicit'), multi: signal(true), }); @@ -272,37 +272,37 @@ describe('Listbox Pattern', () => { it('should select an option on Space', () => { listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should select an option on Enter', () => { listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should allow multiple selected options', () => { listbox.onKeydown(enter()); listbox.onKeydown(down()); listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); }); it('should select a range of options on Shift + ArrowDown/ArrowUp', () => { listbox.onKeydown(shift()); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot', 'Banana']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should not allow wrapping while Shift is held down', () => { listbox.onKeydown(shift()); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); it('should select a range of options on Shift + Space (or Enter)', () => { @@ -312,20 +312,20 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apricot', 'Banana', 'Blackberry']); + expect(listbox.inputs.values()).toEqual(['Apricot', 'Banana', 'Blackberry']); }); it('should deselect options outside the range on subsequent on Shift + Space (or Enter)', () => { listbox.onKeydown(down()); listbox.onKeydown(down()); listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual(['Banana']); + expect(listbox.inputs.values()).toEqual(['Banana']); listbox.onKeydown(down()); listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Blackberry', 'Blueberry']); listbox.onKeydown(up()); listbox.onKeydown(up()); @@ -333,7 +333,7 @@ describe('Listbox Pattern', () => { listbox.onKeydown(up()); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); it('should select the focused option and all options up to the first option on Ctrl + Shift + Home', () => { @@ -342,7 +342,7 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(home({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual(['Blackberry', 'Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Blackberry', 'Banana', 'Apricot', 'Apple']); }); it('should select the focused option and all options down to the last option on Ctrl + Shift + End', () => { @@ -353,51 +353,56 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(end({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual(['Cantaloupe', 'Cherry', 'Clementine', 'Cranberry']); + expect(listbox.inputs.values()).toEqual([ + 'Cantaloupe', + 'Cherry', + 'Clementine', + 'Cranberry', + ]); }); it('should not be able to change selection when in readonly mode', () => { const readonly = listbox.inputs.readonly as WritableSignal; readonly.set(true); listbox.onKeydown(space()); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(down()); listbox.onKeydown(enter()); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(shift()); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(end({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(home({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); it('should not change the selected state of disabled options on Shift + ArrowUp / ArrowDown', () => { - (listbox.inputs.skipDisabled as WritableSignal).set(false); + (listbox.inputs.softDisabled as WritableSignal).set(true); options[1].disabled.set(true); listbox.onKeydown(shift()); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Banana']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should select all options on Ctrl + A', () => { - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(a({control: true})); - expect(listbox.inputs.value()).toEqual([ + expect(listbox.inputs.values()).toEqual([ 'Apple', 'Apricot', 'Banana', @@ -411,10 +416,10 @@ describe('Listbox Pattern', () => { }); it('should deselect all options on Ctrl + A if all options are selected', () => { - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onKeydown(a({control: true})); listbox.onKeydown(a({control: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); }); @@ -424,7 +429,7 @@ describe('Listbox Pattern', () => { beforeEach(() => { const patterns = getDefaultPatterns({ - value: signal(['Apple']), + values: signal(['Apple']), multi: signal(true), selectionMode: signal('follow'), }); @@ -433,25 +438,25 @@ describe('Listbox Pattern', () => { }); it('should select an option on navigation', () => { - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onKeydown(up()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(end()); - expect(listbox.inputs.value()).toEqual(['Cranberry']); + expect(listbox.inputs.values()).toEqual(['Cranberry']); listbox.onKeydown(home()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should navigate without selecting an option if the Ctrl key is pressed', () => { - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(up({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(end({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(home({control: true})); }); @@ -459,26 +464,26 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(space({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Banana']); }); it('should select a range of options on Shift + ArrowDown/ArrowUp', () => { listbox.onKeydown(shift()); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onKeydown(down({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot', 'Banana']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should not allow wrapping while Shift is held down', () => { listbox.listBehavior.deselectAll(); listbox.onKeydown(shift()); listbox.onKeydown(up({shift: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); it('should select a range of options on Shift + Space (or Enter)', () => { @@ -487,19 +492,19 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down({control: true})); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Apricot', 'Banana', 'Blackberry']); + expect(listbox.inputs.values()).toEqual(['Apricot', 'Banana', 'Blackberry']); }); it('should deselect options outside the range on subsequent on Shift + Space (or Enter)', () => { listbox.onKeydown(down()); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Banana']); + expect(listbox.inputs.values()).toEqual(['Banana']); listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Blackberry', 'Blueberry']); listbox.onKeydown(up({control: true})); listbox.onKeydown(up({control: true})); @@ -507,7 +512,7 @@ describe('Listbox Pattern', () => { listbox.onKeydown(up({control: true})); listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); it('should select the focused option and all options up to the first option on Ctrl + Shift + Home', () => { @@ -516,7 +521,7 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(home({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual(['Blackberry', 'Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Blackberry', 'Banana', 'Apricot', 'Apple']); }); it('should select the focused option and all options down to the last option on Ctrl + Shift + End', () => { @@ -527,38 +532,43 @@ describe('Listbox Pattern', () => { listbox.onKeydown(down()); listbox.onKeydown(shift()); listbox.onKeydown(end({control: true, shift: true})); - expect(listbox.inputs.value()).toEqual(['Cantaloupe', 'Cherry', 'Clementine', 'Cranberry']); + expect(listbox.inputs.values()).toEqual([ + 'Cantaloupe', + 'Cherry', + 'Clementine', + 'Cranberry', + ]); }); it('should not be able to change selection when in readonly mode', () => { const readonly = listbox.inputs.readonly as WritableSignal; readonly.set(true); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(up()); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(space({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should not select disabled options', () => { options[2].disabled.set(true); - (listbox.inputs.skipDisabled as WritableSignal).set(false); - expect(listbox.inputs.value()).toEqual(['Apple']); + (listbox.inputs.softDisabled as WritableSignal).set(true); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onKeydown(down()); - expect(listbox.inputs.value()).toEqual(['Blackberry']); + expect(listbox.inputs.values()).toEqual(['Blackberry']); }); it('should deselect all except one option on Ctrl + A if all options are selected', () => { listbox.onKeydown(a({control: true})); listbox.onKeydown(a({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); }); }); @@ -579,7 +589,7 @@ describe('Listbox Pattern', () => { selectionMode: signal('follow'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); }); @@ -590,17 +600,17 @@ describe('Listbox Pattern', () => { selectionMode: signal('explicit'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should deselect a selected option on click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(false), - value: signal(['Apple']), + values: signal(['Apple']), selectionMode: signal('explicit'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); }); @@ -611,17 +621,17 @@ describe('Listbox Pattern', () => { selectionMode: signal('explicit'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); it('should deselect a selected option on click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(true), - value: signal(['Apple']), + values: signal(['Apple']), selectionMode: signal('explicit'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); it('should select options from anchor on shift + click', () => { @@ -632,7 +642,12 @@ describe('Listbox Pattern', () => { listbox.onPointerdown(click(options, 2)); listbox.onKeydown(shift()); listbox.onPointerdown(click(options, 5, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry', 'Cantaloupe']); + expect(listbox.inputs.values()).toEqual([ + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + ]); }); it('should deselect options outside the range on subsequent shift + clicks', () => { @@ -643,9 +658,14 @@ describe('Listbox Pattern', () => { listbox.onPointerdown(click(options, 2)); listbox.onKeydown(shift()); listbox.onPointerdown(click(options, 5, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry', 'Cantaloupe']); + expect(listbox.inputs.values()).toEqual([ + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + ]); listbox.onPointerdown(click(options, 0, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); }); @@ -656,11 +676,11 @@ describe('Listbox Pattern', () => { selectionMode: signal('follow'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onPointerdown(click(options, 1)); - expect(listbox.inputs.value()).toEqual(['Apricot']); + expect(listbox.inputs.values()).toEqual(['Apricot']); listbox.onPointerdown(click(options, 2)); - expect(listbox.inputs.value()).toEqual(['Banana']); + expect(listbox.inputs.values()).toEqual(['Banana']); }); it('should select an unselected option on ctrl + click', () => { @@ -669,11 +689,11 @@ describe('Listbox Pattern', () => { selectionMode: signal('follow'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onPointerdown(click(options, 1, {control: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); listbox.onPointerdown(click(options, 2, {control: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot', 'Banana']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot', 'Banana']); }); it('should deselect a selected option on ctrl + click', () => { @@ -682,9 +702,9 @@ describe('Listbox Pattern', () => { selectionMode: signal('follow'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onPointerdown(click(options, 0, {control: true})); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); it('should select a range of options on shift + click', () => { @@ -695,7 +715,12 @@ describe('Listbox Pattern', () => { listbox.onPointerdown(click(options, 2)); listbox.onKeydown(shift()); listbox.onPointerdown(click(options, 5, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry', 'Cantaloupe']); + expect(listbox.inputs.values()).toEqual([ + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + ]); }); it('should deselect options outside the range on subsequent shift + clicks', () => { @@ -706,40 +731,45 @@ describe('Listbox Pattern', () => { listbox.onPointerdown(click(options, 2)); listbox.onKeydown(shift()); listbox.onPointerdown(click(options, 5, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry', 'Cantaloupe']); + expect(listbox.inputs.values()).toEqual([ + 'Banana', + 'Blackberry', + 'Blueberry', + 'Cantaloupe', + ]); listbox.onPointerdown(click(options, 0, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); it('should select a range up to but not including a disabled option on shift + click', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(true), - skipDisabled: signal(false), + softDisabled: signal(true), selectionMode: signal('follow'), }); options[2].disabled.set(true); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(shift()); listbox.onPointerdown(click(options, 2, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Apricot']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Apricot']); expect(listbox.inputs.activeItem()).toEqual(options[2]); }); it('should do nothing on click if the option is disabled', () => { const {listbox, options} = getDefaultPatterns({ multi: signal(true), - skipDisabled: signal(true), + softDisabled: signal(false), selectionMode: signal('follow'), }); options[2].disabled.set(true); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down({control: true})); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onPointerdown(click(options, 2)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); }); }); @@ -749,11 +779,11 @@ describe('Listbox Pattern', () => { selectionMode: signal('follow'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onPointerdown(click(options, 1)); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); listbox.onPointerdown(click(options, 2)); - expect(listbox.inputs.value()).toEqual([]); + expect(listbox.inputs.values()).toEqual([]); }); it('should maintain the range selection between pointer and keyboard', () => { @@ -767,9 +797,9 @@ describe('Listbox Pattern', () => { listbox.onKeydown(shift()); listbox.onKeydown(space({shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Blackberry', 'Blueberry']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Blackberry', 'Blueberry']); listbox.onPointerdown(click(options, 0, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Banana', 'Apricot', 'Apple']); + expect(listbox.inputs.values()).toEqual(['Banana', 'Apricot', 'Apple']); }); it('should select a range from the currently focused option', () => { @@ -778,12 +808,12 @@ describe('Listbox Pattern', () => { selectionMode: signal('follow'), }); listbox.onPointerdown(click(options, 0)); - expect(listbox.inputs.value()).toEqual(['Apple']); + expect(listbox.inputs.values()).toEqual(['Apple']); listbox.onKeydown(down({control: true})); listbox.onKeydown(down({control: true})); listbox.onKeydown(shift()); listbox.onPointerdown(click(options, 4, {shift: true})); - expect(listbox.inputs.value()).toEqual(['Apple', 'Banana', 'Blackberry', 'Blueberry']); + expect(listbox.inputs.values()).toEqual(['Apple', 'Banana', 'Blackberry', 'Blueberry']); }); }); @@ -796,7 +826,7 @@ describe('Listbox Pattern', () => { it('should set the active index to the first focusable option', () => { const {listbox, options} = getDefaultPatterns({ - skipDisabled: signal(true), + softDisabled: signal(false), }); options[0].disabled.set(true); listbox.setDefaultState(); @@ -805,8 +835,8 @@ describe('Listbox Pattern', () => { it('should set the active index to the first selected option', () => { const {listbox} = getDefaultPatterns({ - value: signal(['Banana']), - skipDisabled: signal(true), + values: signal(['Banana']), + softDisabled: signal(false), }); listbox.setDefaultState(); expect(listbox.inputs.activeItem()).toBe(listbox.inputs.items()[2]); @@ -814,8 +844,8 @@ describe('Listbox Pattern', () => { it('should set the active index to the first focusable selected option', () => { const {listbox, options} = getDefaultPatterns({ - value: signal(['Banana', 'Blackberry']), - skipDisabled: signal(true), + values: signal(['Banana', 'Blackberry']), + softDisabled: signal(false), }); options[2].disabled.set(true); listbox.setDefaultState(); @@ -824,8 +854,8 @@ describe('Listbox Pattern', () => { it('should set the active index to the first option if no selected option is focusable', () => { const {listbox, options} = getDefaultPatterns({ - value: signal(['Banana']), - skipDisabled: signal(true), + values: signal(['Banana']), + softDisabled: signal(false), }); options[2].disabled.set(true); listbox.setDefaultState(); diff --git a/src/aria/ui-patterns/listbox/listbox.ts b/src/aria/private/listbox/listbox.ts similarity index 96% rename from src/aria/ui-patterns/listbox/listbox.ts rename to src/aria/private/listbox/listbox.ts index 5f5c034e4866..e65fffb1e05f 100644 --- a/src/aria/ui-patterns/listbox/listbox.ts +++ b/src/aria/private/listbox/listbox.ts @@ -34,11 +34,11 @@ export class ListboxPattern { /** Whether the listbox is readonly. */ readonly: SignalLike; - /** The tabindex of the listbox. */ - tabindex: SignalLike<-1 | 0> = computed(() => this.listBehavior.tabindex()); + /** The tab index of the listbox. */ + tabIndex: SignalLike<-1 | 0> = computed(() => this.listBehavior.tabIndex()); /** The id of the current active item. */ - activedescendant = computed(() => this.listBehavior.activedescendant()); + activeDescendant = computed(() => this.listBehavior.activeDescendant()); /** Whether multiple items in the list can be selected at once. */ multi: SignalLike; @@ -199,9 +199,9 @@ export class ListboxPattern { validate(): string[] { const violations: string[] = []; - if (!this.inputs.multi() && this.inputs.value().length > 1) { + if (!this.inputs.multi() && this.inputs.values().length > 1) { violations.push( - `A single-select listbox should not have multiple selected options. Selected options: ${this.inputs.value().join(', ')}`, + `A single-select listbox should not have multiple selected options. Selected options: ${this.inputs.values().join(', ')}`, ); } diff --git a/src/aria/ui-patterns/listbox/option.ts b/src/aria/private/listbox/option.ts similarity index 89% rename from src/aria/ui-patterns/listbox/option.ts rename to src/aria/private/listbox/option.ts index 8ea16149d004..718774382f40 100644 --- a/src/aria/ui-patterns/listbox/option.ts +++ b/src/aria/private/listbox/option.ts @@ -39,7 +39,7 @@ export class OptionPattern { active = computed(() => this.listbox()?.inputs.activeItem() === this); /** Whether the option is selected. */ - selected = computed(() => this.listbox()?.inputs.value().includes(this.value())); + selected = computed(() => this.listbox()?.inputs.values().includes(this.value())); /** Whether the option is selectable. */ selectable = () => true; @@ -53,11 +53,11 @@ export class OptionPattern { /** A reference to the parent listbox. */ listbox: SignalLike | undefined>; - /** The tabindex of the option. */ - tabindex = computed(() => this.listbox()?.listBehavior.getItemTabindex(this)); + /** The tab index of the option. */ + tabIndex = computed(() => this.listbox()?.listBehavior.getItemTabindex(this)); /** The html element that should receive focus. */ - element: SignalLike; + element: SignalLike; constructor(args: OptionInputs) { this.id = args.id; diff --git a/src/aria/ui-patterns/menu/BUILD.bazel b/src/aria/private/menu/BUILD.bazel similarity index 68% rename from src/aria/ui-patterns/menu/BUILD.bazel rename to src/aria/private/menu/BUILD.bazel index 6609d161f54d..dc1c20d548aa 100644 --- a/src/aria/ui-patterns/menu/BUILD.bazel +++ b/src/aria/private/menu/BUILD.bazel @@ -9,10 +9,10 @@ ts_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/expansion", - "//src/aria/ui-patterns/behaviors/list", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/expansion", + "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/signal-like", ], ) @@ -25,7 +25,7 @@ ng_project( deps = [ ":menu", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", "//src/cdk/keycodes", "//src/cdk/testing/private", ], diff --git a/src/aria/ui-patterns/menu/menu.spec.ts b/src/aria/private/menu/menu.spec.ts similarity index 68% rename from src/aria/ui-patterns/menu/menu.spec.ts rename to src/aria/private/menu/menu.spec.ts index 689af41216f0..bccb19d74cb3 100644 --- a/src/aria/ui-patterns/menu/menu.spec.ts +++ b/src/aria/private/menu/menu.spec.ts @@ -10,12 +10,12 @@ import {signal, WritableSignal} from '@angular/core'; import {MenuPattern, MenuBarPattern, MenuItemPattern, MenuTriggerPattern} from './menu'; import {createKeyboardEvent} from '@angular/cdk/testing/private'; import {ModifierKeys} from '@angular/cdk/testing'; -import {fakeAsync, tick} from '@angular/core/testing'; // Test types type TestMenuItem = MenuItemPattern & { - disabled: WritableSignal; - submenu: WritableSignal | undefined>; + inputs: { + disabled: WritableSignal; + }; }; // Keyboard event helpers @@ -37,32 +37,35 @@ function clickMenuItem(items: MenuItemPattern[], index: number, mods?: Modi } as unknown as PointerEvent; } -function getMenuTriggerPattern() { +function getMenuTriggerPattern(opts?: {textDirection: 'ltr' | 'rtl'}) { const element = signal(document.createElement('button')); const submenu = signal | undefined>(undefined); const trigger = new MenuTriggerPattern({ + textDirection: signal(opts?.textDirection || 'ltr'), element, - submenu, + menu: submenu, + disabled: signal(false), }); return trigger; } -function getMenuBarPattern(values: string[]) { +function getMenuBarPattern(values: string[], opts?: {textDirection: 'ltr' | 'rtl'}) { const items = signal([]); const menubar = new MenuBarPattern({ items: items, activeItem: signal(undefined), orientation: signal('horizontal'), - textDirection: signal('ltr'), + textDirection: signal(opts?.textDirection || 'ltr'), multi: signal(false), selectionMode: signal('explicit'), - value: signal([]), + values: signal([]), wrap: signal(true), - typeaheadDelay: signal(0.5), - skipDisabled: signal(true), + typeaheadDelay: signal(500), + softDisabled: signal(true), focusMode: signal('activedescendant'), element: signal(document.createElement('div')), + disabled: signal(false), }); items.set( @@ -87,6 +90,7 @@ function getMenuBarPattern(values: string[]) { function getMenuPattern( parent: undefined | MenuItemPattern | MenuTriggerPattern, values: string[], + opts?: {textDirection: 'ltr' | 'rtl'}, ) { const items = signal([]); @@ -95,15 +99,17 @@ function getMenuPattern( items: items, parent: signal(parent) as any, activeItem: signal(undefined), - typeaheadDelay: signal(0.5), + typeaheadDelay: signal(500), wrap: signal(true), - skipDisabled: signal(true), + softDisabled: signal(true), multi: signal(false), focusMode: signal('activedescendant'), - textDirection: signal('ltr'), + textDirection: signal(opts?.textDirection || 'ltr'), orientation: signal('vertical'), selectionMode: signal('explicit'), element: signal(document.createElement('div')), + expansionDelay: signal(0), + disabled: signal(false), }); items.set( @@ -123,10 +129,14 @@ function getMenuPattern( }), ); - if (parent) { + if (parent instanceof MenuTriggerPattern) { + (parent.menu as WritableSignal>).set(menu); + parent.inputs.element()?.appendChild(menu.inputs.element()!); + } else if (parent instanceof MenuItemPattern) { (parent.submenu as WritableSignal>).set(menu); parent.inputs.element()?.appendChild(menu.inputs.element()!); } + menu.inputs.activeItem.set(items()[0]); return menu; } @@ -173,22 +183,26 @@ describe('Standalone Menu Pattern', () => { }); describe('Typeahead', () => { - it('should move the active item to the next item that starts with the typed character', fakeAsync(() => { + function delay(amount: number) { + return new Promise(resolve => setTimeout(resolve, amount)); + } + + it('should move the active item to the next item that starts with the typed character', async () => { const menu = getMenuPattern(undefined, ['Apple', 'Banana', 'Cherry']); const items = menu.inputs.items(); const b = createKeyboardEvent('keydown', 66, 'b'); menu.onKeydown(b); - tick(500); + await delay(500); expect(menu.inputs.activeItem()).toBe(items[1]); const c = createKeyboardEvent('keydown', 67, 'c'); menu.onKeydown(c); - tick(500); + await delay(500); expect(menu.inputs.activeItem()).toBe(items[2]); - })); + }); - it('should support multi-character typeahead', fakeAsync(() => { + it('should support multi-character typeahead', async () => { const menu = getMenuPattern(undefined, ['Cabbage', 'Chard', 'Cherry', 'Cilantro']); const c = createKeyboardEvent('keydown', 67, 'c'); @@ -204,202 +218,229 @@ describe('Standalone Menu Pattern', () => { menu.onKeydown(e); expect(menu.inputs.activeItem()?.value()).toBe('Cherry'); - tick(500); + await delay(500); menu.onKeydown(c); expect(menu.inputs.activeItem()?.value()).toBe('Cilantro'); - })); + }); - it('should wrap when reaching the end of the list during typeahead', fakeAsync(() => { + it('should wrap when reaching the end of the list during typeahead', async () => { const menu = getMenuPattern(undefined, ['Apple', 'Banana', 'Avocado']); const items = menu.inputs.items(); menu.inputs.activeItem.set(items[1]); const a = createKeyboardEvent('keydown', 65, 'a'); menu.onKeydown(a); - tick(500); + await delay(500); expect(menu.inputs.activeItem()).toBe(items[2]); menu.onKeydown(a); - tick(500); + await delay(500); expect(menu.inputs.activeItem()).toBe(items[0]); - })); + }); - it('should not move the active item if no item matches the typed character', fakeAsync(() => { + it('should not move the active item if no item matches the typed character', async () => { const menu = getMenuPattern(undefined, ['Apple', 'Banana', 'Cherry']); const items = menu.inputs.items(); menu.inputs.activeItem.set(items[0]); const z = createKeyboardEvent('keydown', 90, 'z'); menu.onKeydown(z); - tick(500); + await delay(500); expect(menu.inputs.activeItem()).toBe(items[0]); - })); + }); }); }); describe('Selection', () => { it('should select an item on click', () => { const items = menu.inputs.items(); - menu.inputs.onSubmit = jasmine.createSpy('onSubmit'); + menu.inputs.onSelect = jasmine.createSpy('onSelect'); menu.onClick(clickMenuItem(items, 1)); - expect(menu.inputs.onSubmit).toHaveBeenCalledWith('b'); + expect(menu.inputs.onSelect).toHaveBeenCalledWith('b'); }); it('should select an item on enter', () => { const items = menu.inputs.items(); menu.inputs.activeItem.set(items[1]); - menu.inputs.onSubmit = jasmine.createSpy('onSubmit'); + menu.inputs.onSelect = jasmine.createSpy('onSelect'); menu.onKeydown(enter()); - expect(menu.inputs.onSubmit).toHaveBeenCalledWith('b'); + expect(menu.inputs.onSelect).toHaveBeenCalledWith('b'); }); it('should select an item on space', () => { const items = menu.inputs.items(); menu.inputs.activeItem.set(items[1]); - menu.inputs.onSubmit = jasmine.createSpy('onSubmit'); + menu.inputs.onSelect = jasmine.createSpy('onSelect'); menu.onKeydown(space()); - expect(menu.inputs.onSubmit).toHaveBeenCalledWith('b'); + expect(menu.inputs.onSelect).toHaveBeenCalledWith('b'); }); it('should not select a disabled item', () => { const items = menu.inputs.items() as TestMenuItem[]; - items[1].disabled.set(true); + items[1].inputs.disabled.set(true); menu.inputs.activeItem.set(items[1]); - menu.inputs.onSubmit = jasmine.createSpy('onSubmit'); + menu.inputs.onSelect = jasmine.createSpy('onSelect'); menu.onClick(clickMenuItem(items, 1)); - expect(menu.inputs.onSubmit).not.toHaveBeenCalled(); + expect(menu.inputs.onSelect).not.toHaveBeenCalled(); menu.onKeydown(enter()); - expect(menu.inputs.onSubmit).not.toHaveBeenCalled(); + expect(menu.inputs.onSelect).not.toHaveBeenCalled(); menu.onKeydown(space()); - expect(menu.inputs.onSubmit).not.toHaveBeenCalled(); + expect(menu.inputs.onSelect).not.toHaveBeenCalled(); }); }); describe('Expansion and Collapse', () => { it('should be expanded by default', () => { - expect(menu.isVisible()).toBe(true); - expect(submenu.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu.visible()).toBe(false); }); it('should expand submenu on click', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); }); it('should open submenu on arrow right', () => { menu.onKeydown(right()); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); }); it('should close submenu on arrow left', () => { menu.onKeydown(right()); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); submenu.onKeydown(left()); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); it('should close submenu on click outside', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); submenu.onFocusOut(new FocusEvent('focusout', {relatedTarget: null})); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); it('should expand submenu on enter', () => { menu.onKeydown(enter()); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); }); it('should expand submenu on space', () => { menu.onKeydown(space()); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); }); it('should close submenu on escape', () => { menu.onKeydown(right()); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); submenu.onKeydown(escape()); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); it('should close submenu on arrow left', () => { menu.onKeydown(right()); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); submenu.onKeydown(left()); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); - it('should open submenu on mouseover', () => { + it('should open submenu on mouseover', async () => { const menuItem = menu.inputs.items()[0]; menu.onMouseOver({target: menuItem.element()} as unknown as MouseEvent); - expect(submenu.isVisible()).toBe(true); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(submenu.visible()).toBe(true); }); it('should close on selecting an item on click', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); submenu.onClick(clickMenuItem(submenu.inputs.items(), 0)); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); it('should close on selecting an item on enter', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); submenu.onKeydown(enter()); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); it('should close on selecting an item on space', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); submenu.onKeydown(space()); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); it('should close on focus out from the menu', () => { menu.onClick(clickMenuItem(menu.inputs.items(), 0)); - expect(submenu.isVisible()).toBe(true); + expect(submenu.visible()).toBe(true); submenu.onFocusOut(new FocusEvent('focusout', {relatedTarget: null})); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); - it('should close a submenu on focus out', () => { + it('should close a submenu on focus out', async () => { const parentMenuItem = menu.inputs.items()[0]; menu.onMouseOver({target: parentMenuItem.element()} as unknown as MouseEvent); - expect(submenu.isVisible()).toBe(true); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(submenu.visible()).toBe(true); expect(submenu.isFocused()).toBe(false); submenu.onFocusOut(new FocusEvent('focusout', {relatedTarget: document.body})); - expect(submenu.isVisible()).toBe(false); + expect(submenu.visible()).toBe(false); }); - it('should close an unfocused submenu on mouse out', () => { + it('should close an unfocused submenu on mouse out', async () => { menu.onMouseOver({target: menu.inputs.items()[0].element()} as unknown as MouseEvent); - expect(submenu.isVisible()).toBe(true); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(submenu.visible()).toBe(true); submenu.onMouseOut({relatedTarget: document.body} as unknown as MouseEvent); - expect(submenu.isVisible()).toBe(false); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(submenu.visible()).toBe(false); }); - it('should not close an unfocused submenu on mouse out if the parent menu is hovered', () => { + it('should not close an unfocused submenu on mouse out if the parent menu is hovered', async () => { const parentMenuItem = menu.inputs.items()[0]; menu.onMouseOver({target: parentMenuItem.element()} as unknown as MouseEvent); - expect(submenu.isVisible()).toBe(true); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(submenu.visible()).toBe(true); submenu.onMouseOut({relatedTarget: parentMenuItem.element()} as unknown as MouseEvent); - expect(submenu.isVisible()).toBe(true); + await new Promise(resolve => setTimeout(resolve, 0)); + expect(submenu.visible()).toBe(true); + }); + }); + + describe('RTL', () => { + beforeEach(() => { + const opts = {textDirection: 'rtl' as const}; + menu = getMenuPattern(undefined, ['a', 'b', 'c'], opts); + submenu = getMenuPattern(menu.inputs.items()[0], ['d', 'e'], opts); + }); + + it('should open submenu on arrow left', () => { + menu.onKeydown(left()); + expect(submenu.visible()).toBe(true); + }); + + it('should close submenu on arrow right', () => { + menu.onKeydown(left()); + expect(submenu.visible()).toBe(true); + + submenu.onKeydown(right()); + expect(submenu.visible()).toBe(false); }); }); }); @@ -445,118 +486,118 @@ describe('Menu Trigger Pattern', () => { describe('Expansion and Collapse', () => { it('should be closed by default', () => { expect(trigger.expanded()).toBe(false); - expect(menu.isVisible()).toBe(false); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(false); + expect(submenu?.visible()).toBe(false); }); it('should open on click', () => { trigger.onClick(); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(false); }); it('should close on second click', () => { trigger.onClick(); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); + expect(menu.visible()).toBe(true); trigger.onClick(); expect(trigger.expanded()).toBe(false); - expect(menu.isVisible()).toBe(false); + expect(menu.visible()).toBe(false); }); it('should open on arrow down', () => { trigger.onKeydown(down()); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(false); }); it('should open on arrow up', () => { trigger.onKeydown(up()); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(false); }); it('should open on space', () => { trigger.onKeydown(space()); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(false); }); it('should open on enter', () => { trigger.onKeydown(enter()); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(false); }); it('should close on escape', () => { trigger.onKeydown(down()); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); + expect(menu.visible()).toBe(true); menu.onKeydown(escape()); expect(trigger.expanded()).toBe(false); - expect(menu.isVisible()).toBe(false); + expect(menu.visible()).toBe(false); }); it('should close on selecting an item on click', () => { trigger.onClick(); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(false); menu.onClick(clickMenuItem(menu.inputs.items(), 0)); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(true); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(true); submenu?.onClick(clickMenuItem(submenu.inputs.items(), 0)); expect(trigger.expanded()).toBe(false); - expect(menu.isVisible()).toBe(false); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(false); + expect(submenu?.visible()).toBe(false); }); it('should close on selecting an item on enter', () => { trigger.onKeydown(down()); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(false); menu.onKeydown(right()); - expect(submenu?.isVisible()).toBe(true); + expect(submenu?.visible()).toBe(true); submenu?.onKeydown(enter()); expect(trigger.expanded()).toBe(false); - expect(menu.isVisible()).toBe(false); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(false); + expect(submenu?.visible()).toBe(false); }); it('should close on selecting an item on space', () => { trigger.onKeydown(down()); expect(trigger.expanded()).toBe(true); - expect(menu.isVisible()).toBe(true); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(true); + expect(submenu?.visible()).toBe(false); menu.onKeydown(right()); - expect(submenu?.isVisible()).toBe(true); + expect(submenu?.visible()).toBe(true); submenu?.onKeydown(space()); expect(trigger.expanded()).toBe(false); - expect(menu.isVisible()).toBe(false); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(false); + expect(submenu?.visible()).toBe(false); }); it('should close the trigger on focus out from the menu', () => { trigger.onKeydown(down()); menu.onFocusOut(new FocusEvent('focusout', {relatedTarget: null})); expect(trigger.expanded()).toBe(false); - expect(menu.isVisible()).toBe(false); - expect(submenu?.isVisible()).toBe(false); + expect(menu.visible()).toBe(false); + expect(submenu?.visible()).toBe(false); }); }); }); @@ -580,7 +621,7 @@ describe('Menu Bar Pattern', () => { menubar.inputs.activeItem.set(menubarItems[0]); menubar.onKeydown(down()); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[0]); }); @@ -589,7 +630,7 @@ describe('Menu Bar Pattern', () => { menubar.inputs.activeItem.set(menubarItems[0]); menubar.onKeydown(up()); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[1]); }); @@ -598,7 +639,7 @@ describe('Menu Bar Pattern', () => { menubar.inputs.activeItem.set(menubarItems[0]); menubar.onKeydown(enter()); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[0]); }); @@ -607,45 +648,45 @@ describe('Menu Bar Pattern', () => { menubar.inputs.activeItem.set(menubarItems[0]); menubar.onKeydown(space()); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[0]); }); it('should navigate to a menubar item on mouse over', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); - expect(menuA.isVisible()).toBe(true); - expect(menuB.isVisible()).toBe(false); + expect(menuA.visible()).toBe(true); + expect(menuB.visible()).toBe(false); const mouseOverEvent = {target: menubarItems[1].element()} as unknown as MouseEvent; menubar.onMouseOver(mouseOverEvent); - expect(menuA.isVisible()).toBe(false); - expect(menuB.isVisible()).toBe(true); + expect(menuA.visible()).toBe(false); + expect(menuB.visible()).toBe(true); expect(menubar.inputs.activeItem()).toBe(menubarItems[1]); }); it('should focus the first item of the next menubar item on arrow right', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); // open menuA - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); menuA.onKeydown(right()); - expect(menuA.isVisible()).toBe(false); - expect(menuB.isVisible()).toBe(true); + expect(menuA.visible()).toBe(false); + expect(menuB.visible()).toBe(true); expect(menuB.inputs.activeItem()).toBe(menuB.inputs.items()[0]); }); it('should focus the first item of the previous menubar item on arrow left', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 1)); // open menuB - expect(menuB.isVisible()).toBe(true); + expect(menuB.visible()).toBe(true); menuB.onKeydown(left()); - expect(menuB.isVisible()).toBe(false); - expect(menuA.isVisible()).toBe(true); + expect(menuB.visible()).toBe(false); + expect(menuA.visible()).toBe(true); expect(menuA.inputs.activeItem()).toBe(menuA.inputs.items()[0]); }); }); @@ -657,9 +698,9 @@ describe('Menu Bar Pattern', () => { expect(menubarItems[1].expanded()).toBe(false); expect(menubarItems[2].expanded()).toBe(false); - expect(menuA.isVisible()).toBe(false); - expect(menuB.isVisible()).toBe(false); - expect(menuC.isVisible()).toBe(false); + expect(menuA.visible()).toBe(false); + expect(menuB.visible()).toBe(false); + expect(menuC.visible()).toBe(false); }); it('should expand on click', () => { @@ -670,9 +711,9 @@ describe('Menu Bar Pattern', () => { expect(menubarItems[1].expanded()).toBe(false); expect(menubarItems[2].expanded()).toBe(false); - expect(menuA.isVisible()).toBe(true); - expect(menuB.isVisible()).toBe(false); - expect(menuC.isVisible()).toBe(false); + expect(menuA.visible()).toBe(true); + expect(menuB.visible()).toBe(false); + expect(menuC.visible()).toBe(false); }); it('should collapse on second click', () => { @@ -680,12 +721,12 @@ describe('Menu Bar Pattern', () => { menubar.onClick(clickMenuItem(menubarItems, 0)); expect(menubarItems[0].expanded()).toBe(true); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); menubar.onClick(clickMenuItem(menubarItems, 0)); expect(menubarItems[0].expanded()).toBe(false); - expect(menuA.isVisible()).toBe(false); + expect(menuA.visible()).toBe(false); }); it('should expand on arrow down', () => { @@ -694,7 +735,7 @@ describe('Menu Bar Pattern', () => { menubar.onKeydown(down()); expect(menubarItems[0].expanded()).toBe(true); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); }); it('should expand on arrow up', () => { @@ -703,7 +744,7 @@ describe('Menu Bar Pattern', () => { menubar.onKeydown(up()); expect(menubarItems[0].expanded()).toBe(true); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); }); it('should expand on space', () => { @@ -712,7 +753,7 @@ describe('Menu Bar Pattern', () => { menubar.onKeydown(space()); expect(menubarItems[0].expanded()).toBe(true); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); }); it('should expand on enter', () => { @@ -721,84 +762,84 @@ describe('Menu Bar Pattern', () => { menubar.onKeydown(enter()); expect(menubarItems[0].expanded()).toBe(true); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); }); it('should close on escape', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); expect(menubarItems[0].expanded()).toBe(true); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); menuA.onKeydown(escape()); expect(menubarItems[0].expanded()).toBe(false); - expect(menuA.isVisible()).toBe(false); + expect(menuA.visible()).toBe(false); }); it('should close on selecting an item on click', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); menuA.onClick(clickMenuItem(menuA.inputs.items(), 0)); expect(menubarItems[0].expanded()).toBe(false); - expect(menuA.isVisible()).toBe(false); + expect(menuA.visible()).toBe(false); }); it('should close on selecting an item on enter', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); menuA.onKeydown(enter()); expect(menubarItems[0].expanded()).toBe(false); - expect(menuA.isVisible()).toBe(false); + expect(menuA.visible()).toBe(false); }); it('should close on selecting an item on space', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); menuA.onKeydown(space()); expect(menubarItems[0].expanded()).toBe(false); - expect(menuA.isVisible()).toBe(false); + expect(menuA.visible()).toBe(false); }); it('should close on focus out from the menu', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); menuA.onFocusOut(new FocusEvent('focusout', {relatedTarget: null})); expect(menubarItems[0].expanded()).toBe(false); - expect(menuA.isVisible()).toBe(false); + expect(menuA.visible()).toBe(false); }); it('should close on arrow right on a leaf menu item', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 0)); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); menuA.onKeydown(right()); - expect(menuA.isVisible()).toBe(false); + expect(menuA.visible()).toBe(false); expect(menubarItems[0].expanded()).toBe(false); }); it('should close on arrow left on a root menu item', () => { const menubarItems = menubar.inputs.items(); menubar.onClick(clickMenuItem(menubarItems, 1)); - expect(menuB.isVisible()).toBe(true); + expect(menuB.visible()).toBe(true); menuB.onKeydown(left()); - expect(menuB.isVisible()).toBe(false); + expect(menuB.visible()).toBe(false); expect(menubarItems[1].expanded()).toBe(false); }); @@ -808,7 +849,7 @@ describe('Menu Bar Pattern', () => { menuA.onKeydown(right()); - expect(menuB.isVisible()).toBe(true); + expect(menuB.visible()).toBe(true); expect(menubarItems[1].expanded()).toBe(true); expect(menubar.inputs.activeItem()).toBe(menubarItems[1]); }); @@ -819,9 +860,63 @@ describe('Menu Bar Pattern', () => { menuB.onKeydown(left()); - expect(menuA.isVisible()).toBe(true); + expect(menuA.visible()).toBe(true); expect(menubarItems[0].expanded()).toBe(true); expect(menubar.inputs.activeItem()).toBe(menubarItems[0]); }); + + describe('RTL', () => { + beforeEach(() => { + const opts = {textDirection: 'rtl' as const}; + menubar = getMenuBarPattern(['a', 'b', 'c'], opts); + menuA = getMenuPattern(menubar.inputs.items()[0], ['apple', 'avocado'], opts); + menuB = getMenuPattern(menubar.inputs.items()[1], ['banana', 'blueberry'], opts); + menuC = getMenuPattern(menubar.inputs.items()[2], ['cherry', 'cranberry'], opts); + }); + + it('should close on arrow left on a leaf menu item', () => { + const menubarItems = menubar.inputs.items(); + menubar.onClick(clickMenuItem(menubarItems, 0)); + expect(menuA.visible()).toBe(true); + + menuA.onKeydown(left()); + + expect(menuA.visible()).toBe(false); + expect(menubarItems[0].expanded()).toBe(false); + }); + + it('should close on arrow right on a root menu item', () => { + const menubarItems = menubar.inputs.items(); + menubar.onClick(clickMenuItem(menubarItems, 1)); + expect(menuB.visible()).toBe(true); + + menuB.onKeydown(right()); + + expect(menuB.visible()).toBe(false); + expect(menubarItems[1].expanded()).toBe(false); + }); + + it('should expand the next menu bar item on arrow left on a leaf menu item', () => { + const menubarItems = menubar.inputs.items(); + menubar.onClick(clickMenuItem(menubarItems, 0)); + + menuA.onKeydown(left()); + + expect(menuB.visible()).toBe(true); + expect(menubarItems[1].expanded()).toBe(true); + expect(menubar.inputs.activeItem()).toBe(menubarItems[1]); + }); + + it('should expand the previous menu bar item on arrow right on a root menu item', () => { + const menubarItems = menubar.inputs.items(); + menubar.onClick(clickMenuItem(menubarItems, 1)); + + menuB.onKeydown(right()); + + expect(menuA.visible()).toBe(true); + expect(menubarItems[0].expanded()).toBe(true); + expect(menubar.inputs.activeItem()).toBe(menubarItems[0]); + }); + }); }); }); diff --git a/src/aria/ui-patterns/menu/menu.ts b/src/aria/private/menu/menu.ts similarity index 77% rename from src/aria/ui-patterns/menu/menu.ts rename to src/aria/private/menu/menu.ts index 08f2e96167de..9be9bec6fdb8 100644 --- a/src/aria/ui-patterns/menu/menu.ts +++ b/src/aria/private/menu/menu.ts @@ -12,17 +12,19 @@ import {SignalLike} from '../behaviors/signal-like/signal-like'; import {List, ListInputs, ListItem} from '../behaviors/list/list'; /** The inputs for the MenuBarPattern class. */ -export interface MenuBarInputs extends Omit, V>, 'disabled'> { +export interface MenuBarInputs extends ListInputs, V> { /** The menu items contained in the menu. */ items: SignalLike[]>; /** Callback function triggered when a menu item is selected. */ - onSubmit?: (value: V) => void; + onSelect?: (value: V) => void; + + /** The text direction of the menu bar. */ + textDirection: SignalLike<'ltr' | 'rtl'>; } /** The inputs for the MenuPattern class. */ -export interface MenuInputs - extends Omit, V>, 'value' | 'disabled'> { +export interface MenuInputs extends Omit, V>, 'values'> { /** The unique ID of the menu. */ id: SignalLike; @@ -33,7 +35,13 @@ export interface MenuInputs parent: SignalLike | MenuItemPattern | undefined>; /** Callback function triggered when a menu item is selected. */ - onSubmit?: (value: V) => void; + onSelect?: (value: V) => void; + + /** The text direction of the menu bar. */ + textDirection: SignalLike<'ltr' | 'rtl'>; + + /** The delay in milliseconds before expanding sub-menus on hover. */ + expansionDelay: SignalLike; } /** The inputs for the MenuTriggerPattern class. */ @@ -41,11 +49,14 @@ export interface MenuTriggerInputs { /** A reference to the menu trigger element. */ element: SignalLike; - /** A reference to the submenu associated with the menu trigger. */ - submenu: SignalLike | undefined>; + /** A reference to the menu associated with the trigger. */ + menu: SignalLike | undefined>; - /** Callback function triggered when a menu item is selected. */ - onSubmit?: (value: V) => void; + /** The text direction of the menu bar. */ + textDirection: SignalLike<'ltr' | 'rtl'>; + + /** Whether the menu trigger is disabled. */ + disabled: SignalLike; } /** The inputs for the MenuItemPattern class. */ @@ -65,8 +76,11 @@ export class MenuPattern { /** The role of the menu. */ role = () => 'menu'; + /** Whether the menu is disabled. */ + disabled = () => this.inputs.disabled(); + /** Whether the menu is visible. */ - isVisible = computed(() => (this.inputs.parent() ? !!this.inputs.parent()?.expanded() : true)); + visible = computed(() => (this.inputs.parent() ? !!this.inputs.parent()?.expanded() : true)); /** Controls list behavior for the menu items. */ listBehavior: List, V>; @@ -77,6 +91,15 @@ export class MenuPattern { /** Whether the menu has received focus. */ hasBeenFocused = signal(false); + /** Timeout used to open sub-menus on hover. */ + _openTimeout: any; + + /** Timeout used to close sub-menus on hover out. */ + _closeTimeout: any; + + /** The tab index of the menu. */ + tabIndex = () => this.listBehavior.tabIndex(); + /** Whether the menu should be focused on mouse over. */ shouldFocus = computed(() => { const root = this.root(); @@ -150,15 +173,14 @@ export class MenuPattern { this.id = inputs.id; this.listBehavior = new List, V>({ ...inputs, - value: signal([]), - disabled: () => false, + values: signal([]), }); } /** Sets the default state for the menu. */ setDefaultState() { if (!this.inputs.parent()) { - this.inputs.activeItem.set(this.inputs.items()[0]); + this.listBehavior.goto(this.inputs.items()[0], {focusElement: false}); } } @@ -169,7 +191,7 @@ export class MenuPattern { /** Handles mouseover events for the menu. */ onMouseOver(event: MouseEvent) { - if (!this.isVisible()) { + if (!this.visible()) { return; } @@ -179,23 +201,55 @@ export class MenuPattern { return; } + const parent = this.inputs.parent(); const activeItem = this?.inputs.activeItem(); + if (parent instanceof MenuItemPattern) { + const grandparent = parent.inputs.parent(); + if (grandparent instanceof MenuPattern) { + grandparent._clearTimeouts(); + grandparent.listBehavior.goto(parent, {focusElement: false}); + } + } + if (activeItem && activeItem !== item) { - activeItem.close(); + this._closeItem(activeItem); } - if (item.expanded() && item.submenu()?.inputs.activeItem()) { - item.submenu()?.inputs.activeItem()?.close(); - item.submenu()?.listBehavior.unfocus(); + if (item.expanded()) { + this._clearCloseTimeout(); } - item.open(); + this._openItem(item); this.listBehavior.goto(item, {focusElement: this.shouldFocus()}); } + /** Closes the specified menu item after a delay. */ + private _closeItem(item: MenuItemPattern) { + this._clearOpenTimeout(); + + if (!this._closeTimeout) { + this._closeTimeout = setTimeout(() => { + item.close(); + this._closeTimeout = undefined; + }, this.inputs.expansionDelay()); + } + } + + /** Opens the specified menu item after a delay. */ + private _openItem(item: MenuItemPattern) { + this._clearOpenTimeout(); + + this._openTimeout = setTimeout(() => { + item.open(); + this._openTimeout = undefined; + }, this.inputs.expansionDelay()); + } + /** Handles mouseout events for the menu. */ onMouseOut(event: MouseEvent) { + this._clearOpenTimeout(); + if (this.isFocused()) { return; } @@ -251,7 +305,7 @@ export class MenuPattern { if (parent instanceof MenuItemPattern) { const grandparent = parent.inputs.parent(); const siblings = grandparent?.inputs.items().filter(i => i !== parent); - const item = siblings?.find(i => i.element().contains(relatedTarget)); + const item = siblings?.find(i => i.element()?.contains(relatedTarget)); if (item) { return; @@ -259,7 +313,7 @@ export class MenuPattern { } if ( - this.isVisible() && + this.visible() && !parentEl?.contains(relatedTarget) && !this.inputs.element()?.contains(relatedTarget) ) { @@ -308,14 +362,18 @@ export class MenuPattern { const isMenuBar = root instanceof MenuBarPattern; const isMenuTrigger = root instanceof MenuTriggerPattern; - if (!item.submenu() && (isMenuTrigger || isMenuBar)) { + if (!item.submenu() && isMenuTrigger) { root.close({refocus: true}); - root?.inputs.onSubmit?.(item.value()); + } + + if (!item.submenu() && isMenuBar) { + root.close(); + root?.inputs.onSelect?.(item.value()); } if (!item.submenu() && isMenu) { root.inputs.activeItem()?.close({refocus: true}); - root?.inputs.onSubmit?.(item.value()); + root?.inputs.onSelect?.(item.value()); } } } @@ -344,6 +402,11 @@ export class MenuPattern { } } + /** Closes the menu. */ + close() { + this.inputs.parent()?.close(); + } + /** Closes the menu and all parent menus. */ closeAll() { const root = this.root(); @@ -360,6 +423,28 @@ export class MenuPattern { root.inputs.activeItem()?.close({refocus: true}); } } + + /** Clears any open or close timeouts for sub-menus. */ + _clearTimeouts() { + this._clearOpenTimeout(); + this._clearCloseTimeout(); + } + + /** Clears the open timeout. */ + _clearOpenTimeout() { + if (this._openTimeout) { + clearTimeout(this._openTimeout); + this._openTimeout = undefined; + } + } + + /** Clears the close timeout. */ + _clearCloseTimeout() { + if (this._closeTimeout) { + clearTimeout(this._closeTimeout); + this._closeTimeout = undefined; + } + } } /** The menubar ui pattern class. */ @@ -367,6 +452,9 @@ export class MenuBarPattern { /** Controls list behavior for the menu items. */ listBehavior: List, V>; + /** The tab index of the menu. */ + tabIndex = () => this.listBehavior.tabIndex(); + /** The key used to navigate to the next item. */ private _nextKey = computed(() => { return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; @@ -389,6 +477,9 @@ export class MenuBarPattern { /** Whether the menubar has been focused. */ hasBeenFocused = signal(false); + /** Whether the menubar is disabled. */ + disabled = () => this.inputs.disabled(); + /** Handles keyboard events for the menu. */ keydownManager = computed(() => { return new KeyboardEventManager() @@ -404,7 +495,7 @@ export class MenuBarPattern { }); constructor(readonly inputs: MenuBarInputs) { - this.listBehavior = new List, V>({...inputs, disabled: () => false}); + this.listBehavior = new List, V>(inputs); } /** Sets the default state for the menubar. */ @@ -505,17 +596,23 @@ export class MenuTriggerPattern { /** Whether the menu is expanded. */ expanded = signal(false); + /** Whether the menu trigger has received focus. */ + hasBeenFocused = signal(false); + /** The role of the menu trigger. */ role = () => 'button'; /** Whether the menu trigger has a popup. */ hasPopup = () => true; - /** The submenu associated with the trigger. */ - submenu: SignalLike | undefined>; + /** The menu associated with the trigger. */ + menu: SignalLike | undefined>; + + /** The tab index of the menu trigger. */ + tabIndex = computed(() => (this.expanded() && this.menu()?.inputs.activeItem() ? -1 : 0)); - /** The tabindex of the menu trigger. */ - tabindex = computed(() => (this.expanded() && this.submenu()?.inputs.activeItem() ? -1 : 0)); + /** Whether the menu trigger is disabled. */ + disabled = () => this.inputs.disabled(); /** Handles keyboard events for the menu trigger. */ keydownManager = computed(() => { @@ -528,17 +625,26 @@ export class MenuTriggerPattern { }); constructor(readonly inputs: MenuTriggerInputs) { - this.submenu = this.inputs.submenu; + this.menu = this.inputs.menu; } /** Handles keyboard events for the menu trigger. */ onKeydown(event: KeyboardEvent) { - this.keydownManager().handle(event); + if (!this.inputs.disabled()) { + this.keydownManager().handle(event); + } } /** Handles click events for the menu trigger. */ onClick() { - this.expanded() ? this.close() : this.open({first: true}); + if (!this.inputs.disabled()) { + this.expanded() ? this.close() : this.open({first: true}); + } + } + + /** Handles focusin events for the menu trigger. */ + onFocusIn() { + this.hasBeenFocused.set(true); } /** Handles focusout events for the menu trigger. */ @@ -549,7 +655,7 @@ export class MenuTriggerPattern { if ( this.expanded() && !element?.contains(relatedTarget) && - !this.inputs.submenu()?.inputs.element()?.contains(relatedTarget) + !this.inputs.menu()?.inputs.element()?.contains(relatedTarget) ) { this.close(); } @@ -560,22 +666,22 @@ export class MenuTriggerPattern { this.expanded.set(true); if (opts?.first) { - this.inputs.submenu()?.first(); + this.inputs.menu()?.first(); } else if (opts?.last) { - this.inputs.submenu()?.last(); + this.inputs.menu()?.last(); } } /** Closes the menu. */ close(opts: {refocus?: boolean} = {}) { this.expanded.set(false); - this.submenu()?.listBehavior.unfocus(); + this.menu()?.listBehavior.unfocus(); if (opts.refocus) { this.inputs.element()?.focus(); } - let menuitems = this.inputs.submenu()?.inputs.items() ?? []; + let menuitems = this.inputs.menu()?.inputs.items() ?? []; while (menuitems.length) { const menuitem = menuitems.pop(); @@ -595,19 +701,22 @@ export class MenuItemPattern implements ListItem { id: SignalLike; /** Whether the menu item is disabled. */ - disabled: SignalLike; + disabled = () => this.inputs.parent()?.disabled() || this.inputs.disabled(); /** The search term for the menu item. */ searchTerm: SignalLike; /** The element of the menu item. */ - element: SignalLike; + element: SignalLike; /** Whether the menu item is active. */ - isActive = computed(() => this.inputs.parent()?.inputs.activeItem() === this); + active = computed(() => this.inputs.parent()?.inputs.activeItem() === this); - /** The tabindex of the menu item. */ - tabindex = computed(() => { + /** Whether the menu item has received focus. */ + hasBeenFocused = signal(false); + + /** The tab index of the menu item. */ + tabIndex = computed(() => { if (this.submenu() && this.submenu()?.inputs.activeItem()) { return -1; } @@ -642,7 +751,6 @@ export class MenuItemPattern implements ListItem { this.id = inputs.id; this.value = inputs.value; this.element = inputs.element; - this.disabled = inputs.disabled; this.submenu = this.inputs.submenu; this.searchTerm = inputs.searchTerm; this.selectable = computed(() => !this.submenu()); @@ -650,6 +758,10 @@ export class MenuItemPattern implements ListItem { /** Opens the submenu. */ open(opts?: {first?: boolean; last?: boolean}) { + if (this.disabled()) { + return; + } + this._expanded.set(true); if (opts?.first) { @@ -675,6 +787,17 @@ export class MenuItemPattern implements ListItem { menuitem?._expanded.set(false); menuitem?.inputs.parent()?.listBehavior.unfocus(); menuitems = menuitems.concat(menuitem?.submenu()?.inputs.items() ?? []); + + const parent = menuitem?.inputs.parent(); + + if (parent instanceof MenuPattern) { + parent._clearTimeouts(); + } } } + + /** Handles focusin events for the menu item. */ + onFocusIn() { + this.hasBeenFocused.set(true); + } } diff --git a/src/aria/ui-patterns/public-api.ts b/src/aria/private/public-api.ts similarity index 86% rename from src/aria/ui-patterns/public-api.ts rename to src/aria/private/public-api.ts index 90367fef69f0..ed8716c7b67b 100644 --- a/src/aria/ui-patterns/public-api.ts +++ b/src/aria/private/public-api.ts @@ -11,9 +11,6 @@ export * from './listbox/listbox'; export * from './listbox/option'; export * from './listbox/combobox-listbox'; export * from './menu/menu'; -export * from './radio-group/radio-group'; -export * from './radio-group/radio-button'; -export * from './radio-group/toolbar-radio-group'; export * from './behaviors/signal-like/signal-like'; export * from './tabs/tabs'; export * from './toolbar/toolbar'; @@ -27,3 +24,4 @@ export * from './grid/grid'; export * from './grid/row'; export * from './grid/cell'; export * from './grid/widget'; +export * from './deferred-content'; diff --git a/src/aria/ui-patterns/tabs/BUILD.bazel b/src/aria/private/tabs/BUILD.bazel similarity index 62% rename from src/aria/ui-patterns/tabs/BUILD.bazel rename to src/aria/private/tabs/BUILD.bazel index fbd6d5598c61..891074f610e6 100644 --- a/src/aria/ui-patterns/tabs/BUILD.bazel +++ b/src/aria/private/tabs/BUILD.bazel @@ -9,11 +9,12 @@ ts_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/expansion", - "//src/aria/ui-patterns/behaviors/label", - "//src/aria/ui-patterns/behaviors/list", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/expansion", + "//src/aria/private/behaviors/label", + "//src/aria/private/behaviors/list-focus", + "//src/aria/private/behaviors/list-navigation", + "//src/aria/private/behaviors/signal-like", ], ) @@ -26,7 +27,7 @@ ng_project( deps = [ ":tabs", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", "//src/cdk/keycodes", "//src/cdk/testing/private", ], diff --git a/src/aria/private/tabs/tabs.spec.ts b/src/aria/private/tabs/tabs.spec.ts new file mode 100644 index 000000000000..72e3829e7197 --- /dev/null +++ b/src/aria/private/tabs/tabs.spec.ts @@ -0,0 +1,395 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {signal} from '@angular/core'; +import { + TabInputs, + TabPattern, + TabListInputs, + TabListPattern, + TabPanelInputs, + TabPanelPattern, +} from './tabs'; +import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; +import {createKeyboardEvent} from '@angular/cdk/testing/private'; +import {ModifierKeys} from '@angular/cdk/testing'; + +// Converts the SignalLike type to WritableSignalLike type for controlling test inputs. +type WritableSignalOverrides = { + [K in keyof O as O[K] extends SignalLike ? K : never]: O[K] extends SignalLike + ? WritableSignalLike + : never; +}; + +type TestTabListInputs = TabListInputs & WritableSignalOverrides; +type TestTabInputs = TabInputs & WritableSignalOverrides; +type TestTabPanelInputs = TabPanelInputs & WritableSignalOverrides; + +const up = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 38, 'ArrowUp', mods); +const down = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 40, 'ArrowDown', mods); +const left = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 37, 'ArrowLeft', mods); +const right = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 39, 'ArrowRight', mods); +const home = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 36, 'Home', mods); +const end = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 35, 'End', mods); +const space = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 32, ' ', mods); +const enter = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 13, 'Enter', mods); + +function createTabElement(): HTMLElement { + const element = document.createElement('div'); + element.role = 'tab'; + return element; +} + +describe('Tabs Pattern', () => { + let tabListInputs: TestTabListInputs; + let tabListPattern: TabListPattern; + let tabInputs: TestTabInputs[]; + let tabPatterns: TabPattern[]; + let tabPanelInputs: TestTabPanelInputs[]; + let tabPanelPatterns: TabPanelPattern[]; + + beforeEach(() => { + // Initiate TabListPattern. + tabListInputs = { + orientation: signal('horizontal'), + wrap: signal(true), + textDirection: signal('ltr'), + selectionMode: signal('follow'), + focusMode: signal('roving'), + disabled: signal(false), + activeItem: signal(undefined), + softDisabled: signal(true), + items: signal([]), + element: signal(document.createElement('div')), + }; + tabListPattern = new TabListPattern(tabListInputs); + + // Initiate a list of TabPatterns. + tabInputs = [ + { + tablist: signal(tabListPattern), + tabpanel: signal(undefined), + id: signal('tab-1-id'), + element: signal(createTabElement()), + disabled: signal(false), + value: signal('tab-1'), + expanded: signal(false), + }, + { + tablist: signal(tabListPattern), + tabpanel: signal(undefined), + id: signal('tab-2-id'), + element: signal(createTabElement()), + disabled: signal(false), + value: signal('tab-2'), + expanded: signal(false), + }, + { + tablist: signal(tabListPattern), + tabpanel: signal(undefined), + id: signal('tab-3-id'), + element: signal(createTabElement()), + disabled: signal(false), + value: signal('tab-3'), + expanded: signal(false), + }, + ]; + tabPatterns = [ + new TabPattern(tabInputs[0]), + new TabPattern(tabInputs[1]), + new TabPattern(tabInputs[2]), + ]; + + // Initiate a list of TabPanelPatterns. + tabPanelInputs = [ + { + id: signal('tabpanel-1-id'), + tab: signal(undefined), + value: signal('tab-1'), + }, + { + id: signal('tabpanel-2-id'), + tab: signal(undefined), + value: signal('tab-2'), + }, + { + id: signal('tabpanel-3-id'), + tab: signal(undefined), + value: signal('tab-3'), + }, + ]; + tabPanelPatterns = [ + new TabPanelPattern(tabPanelInputs[0]), + new TabPanelPattern(tabPanelInputs[1]), + new TabPanelPattern(tabPanelInputs[2]), + ]; + + // Binding between tabs and tabpanels. + tabInputs[0].tabpanel.set(tabPanelPatterns[0]); + tabInputs[1].tabpanel.set(tabPanelPatterns[1]); + tabInputs[2].tabpanel.set(tabPanelPatterns[2]); + tabPanelInputs[0].tab.set(tabPatterns[0]); + tabPanelInputs[1].tab.set(tabPatterns[1]); + tabPanelInputs[2].tab.set(tabPatterns[2]); + tabListInputs.items.set(tabPatterns); + tabListInputs.activeItem.set(tabPatterns[0]); + }); + + describe('TabListPattern', () => { + describe('#open', () => { + it('should open a tab with value', () => { + expect(tabListPattern.selectedTab()).toBeUndefined(); + tabListPattern.open('tab-1'); + expect(tabListPattern.selectedTab()!.value()).toBe('tab-1'); + }); + + it('should open a tab with tab pattern instance', () => { + expect(tabListPattern.selectedTab()).toBeUndefined(); + tabListPattern.open(tabPatterns[0]); + expect(tabListPattern.selectedTab()).toBe(tabPatterns[0]); + }); + + it('should open the active tab', () => { + expect(tabListPattern.selectedTab()).toBeUndefined(); + expect(tabListPattern.activeTab()).toBe(tabPatterns[0]); + tabListPattern.open(); + expect(tabListPattern.selectedTab()).toBe(tabPatterns[0]); + }); + }); + + describe('#setDefaultState', () => { + it('should not set activeIndex if there are no tabs', () => { + tabListInputs.items.set([]); + tabListInputs.activeItem.set(tabPatterns[10]); + tabListPattern.setDefaultState(); + expect(tabListInputs.activeItem()).toBe(tabPatterns[10]); + }); + + it('should not set activeIndex if no tabs are focusable', () => { + tabListInputs.softDisabled.set(false); + tabInputs.forEach(input => input.disabled.set(true)); + tabListInputs.activeItem.set(tabPatterns[10]); + tabListPattern.setDefaultState(); + expect(tabListInputs.activeItem()).toBe(tabPatterns[10]); + }); + + it('should set activeIndex to the first focusable tab if no tabs are selected', () => { + tabListInputs.softDisabled.set(false); + tabListInputs.activeItem.set(tabPatterns[2]); + tabListPattern.selectedTab.set(undefined); + tabInputs[0].disabled.set(true); + tabListPattern.setDefaultState(); + expect(tabListInputs.activeItem()).toBe(tabPatterns[1]); + }); + + it('should set activeIndex to the first focusable and selected tab', () => { + tabListInputs.activeItem.set(tabPatterns[0]); + tabListPattern.selectedTab.set(tabPatterns[2]); + tabListPattern.setDefaultState(); + expect(tabListInputs.activeItem()).toBe(tabPatterns[2]); + }); + + it('should set activeIndex to the first focusable tab when the selected tab is not focusable', () => { + tabListInputs.softDisabled.set(false); + tabListPattern.selectedTab.set(tabPatterns[1]); + tabInputs[1].disabled.set(true); + tabListPattern.setDefaultState(); + expect(tabListInputs.activeItem()).toBe(tabPatterns[0]); + }); + }); + + describe('Keyboard Navigation', () => { + it('does not handle keyboard event if a tablist is disabled.', () => { + expect(tabPatterns[1].active()).toBeFalse(); + tabListInputs.disabled.set(true); + tabListPattern.onKeydown(right()); + expect(tabPatterns[1].active()).toBeFalse(); + }); + + it('skips the disabled tab when `softDisabled` is set to false.', () => { + tabListInputs.softDisabled.set(false); + tabInputs[1].disabled.set(true); + tabListPattern.onKeydown(right()); + expect(tabPatterns[0].active()).toBeFalse(); + expect(tabPatterns[1].active()).toBeFalse(); + expect(tabPatterns[2].active()).toBeTrue(); + }); + + it('does not skip the disabled tab when `softDisabled` is set to true.', () => { + tabInputs[1].disabled.set(true); + tabListPattern.onKeydown(right()); + expect(tabPatterns[0].active()).toBeFalse(); + expect(tabPatterns[1].active()).toBeTrue(); + expect(tabPatterns[2].active()).toBeFalse(); + }); + + it('selects a tab by focus if `selectionMode` is "follow".', () => { + tabListPattern.onKeydown(space()); + expect(tabPatterns[0].selected()).toBeTrue(); + expect(tabPatterns[1].selected()).toBeFalse(); + tabListPattern.onKeydown(right()); + expect(tabPatterns[0].selected()).toBeFalse(); + expect(tabPatterns[1].selected()).toBeTrue(); + }); + + it('selects a tab by enter key if `selectionMode` is "explicit".', () => { + tabListInputs.selectionMode.set('explicit'); + tabListPattern.onKeydown(space()); + expect(tabPatterns[0].selected()).toBeTrue(); + expect(tabPatterns[1].selected()).toBeFalse(); + tabListPattern.onKeydown(right()); + expect(tabPatterns[0].selected()).toBeTrue(); + expect(tabPatterns[1].selected()).toBeFalse(); + tabListPattern.onKeydown(enter()); + expect(tabPatterns[0].selected()).toBeFalse(); + expect(tabPatterns[1].selected()).toBeTrue(); + }); + + it('selects a tab by space key if `selectionMode` is "explicit".', () => { + tabListInputs.selectionMode.set('explicit'); + tabListPattern.onKeydown(space()); + expect(tabPatterns[0].selected()).toBeTrue(); + expect(tabPatterns[1].selected()).toBeFalse(); + tabListPattern.onKeydown(right()); + expect(tabPatterns[0].selected()).toBeTrue(); + expect(tabPatterns[1].selected()).toBeFalse(); + tabListPattern.onKeydown(space()); + expect(tabPatterns[0].selected()).toBeFalse(); + expect(tabPatterns[1].selected()).toBeTrue(); + }); + + it('uses left key to navigate to the previous tab when `orientation` is set to "horizontal".', () => { + tabListInputs.activeItem.set(tabPatterns[1]); + expect(tabPatterns[1].active()).toBeTrue(); + tabListPattern.onKeydown(left()); + expect(tabPatterns[0].active()).toBeTrue(); + }); + + it('uses right key to navigate to the next tab when `orientation` is set to "horizontal".', () => { + tabListInputs.activeItem.set(tabPatterns[1]); + expect(tabPatterns[1].active()).toBeTrue(); + tabListPattern.onKeydown(right()); + expect(tabPatterns[2].active()).toBeTrue(); + }); + + it('uses up key to navigate to the previous tab when `orientation` is set to "vertical".', () => { + tabListInputs.orientation.set('vertical'); + tabListInputs.activeItem.set(tabPatterns[1]); + expect(tabPatterns[1].active()).toBeTrue(); + tabListPattern.onKeydown(up()); + expect(tabPatterns[0].active()).toBeTrue(); + }); + + it('uses down key to navigate to the next tab when `orientation` is set to "vertical".', () => { + tabListInputs.orientation.set('vertical'); + tabListInputs.activeItem.set(tabPatterns[1]); + expect(tabPatterns[1].active()).toBeTrue(); + tabListPattern.onKeydown(down()); + expect(tabPatterns[2].active()).toBeTrue(); + }); + + it('uses home key to navigate to the first tab.', () => { + tabListInputs.activeItem.set(tabPatterns[1]); + expect(tabPatterns[1].active()).toBeTrue(); + tabListPattern.onKeydown(home()); + expect(tabPatterns[0].active()).toBeTrue(); + }); + + it('uses end key to navigate to the last tab.', () => { + tabListInputs.activeItem.set(tabPatterns[1]); + expect(tabPatterns[1].active()).toBeTrue(); + tabListPattern.onKeydown(end()); + expect(tabPatterns[2].active()).toBeTrue(); + }); + + it('moves to the last tab from first tab when navigating to the previous tab if `wrap` is set to true', () => { + expect(tabPatterns[0].active()).toBeTrue(); + tabListPattern.onKeydown(left()); + expect(tabPatterns[2].active()).toBeTrue(); + }); + + it('moves to the first tab from last tab when navigating to the next tab if `wrap` is set to true', () => { + tabListPattern.onKeydown(end()); + expect(tabPatterns[2].active()).toBeTrue(); + tabListPattern.onKeydown(right()); + expect(tabPatterns[0].active()).toBeTrue(); + }); + + it('stays on the first tab when navigating to the previous tab if `wrap` is set to false', () => { + tabListInputs.wrap.set(false); + expect(tabPatterns[0].active()).toBeTrue(); + tabListPattern.onKeydown(left()); + expect(tabPatterns[0].active()).toBeTrue(); + }); + + it('stays on the last tab when navigating to the next tab if `wrap` is set to false', () => { + tabListInputs.wrap.set(false); + tabListPattern.onKeydown(end()); + expect(tabPatterns[2].active()).toBeTrue(); + tabListPattern.onKeydown(right()); + expect(tabPatterns[2].active()).toBeTrue(); + }); + + it('changes the navigation direction with `rtl` mode.', () => { + tabListInputs.textDirection.set('rtl'); + tabListInputs.activeItem.set(tabPatterns[1]); + tabListPattern.onKeydown(left()); + expect(tabPatterns[2].active()).toBeTrue(); + }); + }); + }); + + describe('TabPattern', () => { + it('gets a controlled tabpanel id from a tab', () => { + expect(tabPanelPatterns[0].id()).toBe('tabpanel-1-id'); + expect(tabPatterns[0].controls()).toBe('tabpanel-1-id'); + expect(tabPanelPatterns[1].id()).toBe('tabpanel-2-id'); + expect(tabPatterns[1].controls()).toBe('tabpanel-2-id'); + expect(tabPanelPatterns[2].id()).toBe('tabpanel-3-id'); + expect(tabPatterns[2].controls()).toBe('tabpanel-3-id'); + }); + + describe('#open', () => { + it('should open the current tab', () => { + expect(tabListPattern.selectedTab()).toBeUndefined(); + tabPatterns[0].open(); + expect(tabListPattern.selectedTab()).toBe(tabPatterns[0]); + }); + }); + }); + + describe('TabPanelPattern', () => { + it('should set a tabpanel to be not hidden if a tab is opened', () => { + tabPatterns[0].open(); + expect(tabPatterns[0].selected()).toBeTrue(); + expect(tabPanelPatterns[0].hidden()).toBeFalse(); + }); + + it('sets a tabpanel to be hidden if a tab is not opened', () => { + expect(tabPatterns[1].selected()).toBeFalse(); + expect(tabPanelPatterns[1].hidden()).toBeTrue(); + }); + + it('should set a tabpanel tab index to 0 if the tab is opened', () => { + tabPatterns[0].open(); + expect(tabPatterns[0].tabIndex()).toBe(0); + }); + + it('should set a tabpanel tab index to -1 if the tab is not opened', () => { + tabPatterns[0].open(); + expect(tabPatterns[1].tabIndex()).toBe(-1); + expect(tabPatterns[2].tabIndex()).toBe(-1); + }); + + it('should set a tabpanel aria-labelledby pointing to its tab id', () => { + expect(tabPanelPatterns[0].labelledBy()).toBe('tab-1-id'); + expect(tabPanelPatterns[1].labelledBy()).toBe('tab-2-id'); + expect(tabPanelPatterns[2].labelledBy()).toBe('tab-3-id'); + }); + }); +}); diff --git a/src/aria/ui-patterns/tabs/tabs.ts b/src/aria/private/tabs/tabs.ts similarity index 52% rename from src/aria/ui-patterns/tabs/tabs.ts rename to src/aria/private/tabs/tabs.ts index 95f593009343..07b4231f6327 100644 --- a/src/aria/ui-patterns/tabs/tabs.ts +++ b/src/aria/private/tabs/tabs.ts @@ -6,104 +6,96 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed} from '@angular/core'; +import {computed, signal, WritableSignal} from '@angular/core'; import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager'; -import { - ExpansionItem, - ExpansionControl, - ListExpansionInputs, - ListExpansion, -} from '../behaviors/expansion/expansion'; -import {SignalLike} from '../behaviors/signal-like/signal-like'; +import {ExpansionItem, ListExpansionInputs, ListExpansion} from '../behaviors/expansion/expansion'; +import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; import {LabelControl, LabelControlOptionalInputs} from '../behaviors/label/label'; -import {List, ListInputs, ListItem} from '../behaviors/list/list'; +import {ListFocus} from '../behaviors/list-focus/list-focus'; +import { + ListNavigationItem, + ListNavigation, + ListNavigationInputs, +} from '../behaviors/list-navigation/list-navigation'; /** The required inputs to tabs. */ export interface TabInputs - extends Omit, 'searchTerm' | 'index' | 'selectable'>, - Omit { + extends Omit, + Omit { /** The parent tablist that controls the tab. */ tablist: SignalLike; /** The remote tabpanel controlled by the tab. */ tabpanel: SignalLike; + + /** The remote tabpanel unique identifier. */ + value: SignalLike; } /** A tab in a tablist. */ export class TabPattern { - /** Controls expansion for this tab. */ - readonly expansion: ExpansionControl; - /** A global unique identifier for the tab. */ - readonly id: SignalLike; + readonly id: SignalLike = () => this.inputs.id(); /** The index of the tab. */ readonly index = computed(() => this.inputs.tablist().inputs.items().indexOf(this)); - /** A local unique identifier for the tab. */ - readonly value: SignalLike; + /** The remote tabpanel unique identifier. */ + readonly value: SignalLike = () => this.inputs.value(); /** Whether the tab is disabled. */ - readonly disabled: SignalLike; + readonly disabled: SignalLike = () => this.inputs.disabled(); /** The html element that should receive focus. */ - readonly element: SignalLike; + readonly element: SignalLike = () => this.inputs.element()!; - /** Whether the tab is selectable. */ - readonly selectable = () => true; + /** Whether this tab has expandable panel. */ + readonly expandable: SignalLike = () => true; - /** The text used by the typeahead search. */ - readonly searchTerm = () => ''; // Unused because tabs do not support typeahead. - - /** Whether this tab has expandable content. */ - readonly expandable = computed(() => this.expansion.expandable()); - - /** The unique identifier used by the expansion behavior. */ - readonly expansionId = computed(() => this.expansion.expansionId()); - - /** Whether the tab is expanded. */ - readonly expanded = computed(() => this.expansion.isExpanded()); + /** Whether the tab panel is expanded. */ + readonly expanded: WritableSignalLike; /** Whether the tab is active. */ readonly active = computed(() => this.inputs.tablist().inputs.activeItem() === this); /** Whether the tab is selected. */ - readonly selected = computed(() => !!this.inputs.tablist().inputs.value().includes(this.value())); + readonly selected = computed(() => this.inputs.tablist().selectedTab() === this); - /** The tabindex of the tab. */ - readonly tabindex = computed(() => this.inputs.tablist().listBehavior.getItemTabindex(this)); + /** The tab index of the tab. */ + readonly tabIndex = computed(() => this.inputs.tablist().focusBehavior.getItemTabIndex(this)); /** The id of the tabpanel associated with the tab. */ readonly controls = computed(() => this.inputs.tabpanel()?.id()); constructor(readonly inputs: TabInputs) { - this.id = inputs.id; - this.value = inputs.value; - this.disabled = inputs.disabled; - this.element = inputs.element; - this.expansion = new ExpansionControl({ - ...inputs, - expansionId: inputs.value, - expandable: () => true, - expansionManager: inputs.tablist().expansionManager, - }); + this.expanded = inputs.expanded; + } + + /** Opens the tab. */ + open(): boolean { + return this.inputs.tablist().open(this); } } /** The required inputs for the tabpanel. */ export interface TabPanelInputs extends LabelControlOptionalInputs { + /** A global unique identifier for the tabpanel. */ id: SignalLike; + + /** The tab that controls this tabpanel. */ tab: SignalLike; + + /** A local unique identifier for the tabpanel. */ value: SignalLike; } /** A tabpanel associated with a tab. */ export class TabPanelPattern { /** A global unique identifier for the tabpanel. */ - readonly id: SignalLike; + readonly id: SignalLike = () => this.inputs.id(); /** A local unique identifier for the tabpanel. */ - readonly value: SignalLike; + readonly value: SignalLike = () => this.inputs.value(); /** Controls label for this tabpanel. */ readonly labelManager: LabelControl; @@ -111,8 +103,8 @@ export class TabPanelPattern { /** Whether the tabpanel is hidden. */ readonly hidden = computed(() => this.inputs.tab()?.expanded() === false); - /** The tabindex of this tabpanel. */ - readonly tabindex = computed(() => (this.hidden() ? -1 : 0)); + /** The tab index of this tabpanel. */ + readonly tabIndex = computed(() => (this.hidden() ? -1 : 0)); /** The aria-labelledby value for this tabpanel. */ readonly labelledBy = computed(() => @@ -122,8 +114,6 @@ export class TabPanelPattern { ); constructor(readonly inputs: TabPanelInputs) { - this.id = inputs.id; - this.value = inputs.value; this.labelManager = new LabelControl({ ...inputs, defaultLabelledBy: computed(() => (this.inputs.tab() ? [this.inputs.tab()!.id()] : [])), @@ -132,28 +122,41 @@ export class TabPanelPattern { } /** The required inputs for the tablist. */ -export type TabListInputs = Omit, 'multi' | 'typeaheadDelay'> & - Omit; +export interface TabListInputs + extends Omit, 'multi'>, + Omit { + /** The selection strategy used by the tablist. */ + selectionMode: SignalLike<'follow' | 'explicit'>; +} /** Controls the state of a tablist. */ export class TabListPattern { - /** The list behavior for the tablist. */ - readonly listBehavior: List; + /** The list focus behavior for the tablist. */ + readonly focusBehavior: ListFocus; + + /** The list navigation behavior for the tablist. */ + readonly navigationBehavior: ListNavigation; /** Controls expansion for the tablist. */ - readonly expansionManager: ListExpansion; + readonly expansionBehavior: ListExpansion; + + /** The currently active tab. */ + readonly activeTab: SignalLike = () => this.inputs.activeItem(); + + /** The currently selected tab. */ + readonly selectedTab: WritableSignal = signal(undefined); /** Whether the tablist is vertically or horizontally oriented. */ - readonly orientation: SignalLike<'vertical' | 'horizontal'>; + readonly orientation: SignalLike<'vertical' | 'horizontal'> = () => this.inputs.orientation(); /** Whether the tablist is disabled. */ - readonly disabled: SignalLike; + readonly disabled: SignalLike = () => this.inputs.disabled(); - /** The tabindex of the tablist. */ - readonly tabindex = computed(() => this.listBehavior.tabindex()); + /** The tab index of the tablist. */ + readonly tabIndex = computed(() => this.focusBehavior.getListTabIndex()); /** The id of the current active tab. */ - readonly activedescendant = computed(() => this.listBehavior.activedescendant()); + readonly activeDescendant = computed(() => this.focusBehavior.getActiveDescendant()); /** Whether selection should follow focus. */ readonly followFocus = computed(() => this.inputs.selectionMode() === 'follow'); @@ -177,35 +180,36 @@ export class TabListPattern { /** The keydown event manager for the tablist. */ readonly keydown = computed(() => { return new KeyboardEventManager() - .on(this.prevKey, () => this.listBehavior.prev({select: this.followFocus()})) - .on(this.nextKey, () => this.listBehavior.next({select: this.followFocus()})) - .on('Home', () => this.listBehavior.first({select: this.followFocus()})) - .on('End', () => this.listBehavior.last({select: this.followFocus()})) - .on(' ', () => this.listBehavior.select()) - .on('Enter', () => this.listBehavior.select()); + .on(this.prevKey, () => + this._navigate(() => this.navigationBehavior.prev(), this.followFocus()), + ) + .on(this.nextKey, () => + this._navigate(() => this.navigationBehavior.next(), this.followFocus()), + ) + .on('Home', () => this._navigate(() => this.navigationBehavior.first(), this.followFocus())) + .on('End', () => this._navigate(() => this.navigationBehavior.last(), this.followFocus())) + .on(' ', () => this.open()) + .on('Enter', () => this.open()); }); /** The pointerdown event manager for the tablist. */ readonly pointerdown = computed(() => { return new PointerEventManager().on(e => - this.listBehavior.goto(this._getItem(e)!, {select: true}), + this._navigate(() => this.navigationBehavior.goto(this._getItem(e)!), true), ); }); constructor(readonly inputs: TabListInputs) { - this.disabled = inputs.disabled; - this.orientation = inputs.orientation; + this.focusBehavior = new ListFocus(inputs); - this.listBehavior = new List({ + this.navigationBehavior = new ListNavigation({ ...inputs, - multi: () => false, - typeaheadDelay: () => 0, // Tabs do not support typeahead. + focusManager: this.focusBehavior, }); - this.expansionManager = new ListExpansion({ + this.expansionBehavior = new ListExpansion({ ...inputs, multiExpandable: () => false, - expandedIds: this.inputs.value, }); } @@ -221,7 +225,7 @@ export class TabListPattern { let firstItem: TabPattern | undefined; for (const item of this.inputs.items()) { - if (!this.listBehavior.isFocusable(item)) continue; + if (!this.focusBehavior.isFocusable(item)) continue; if (firstItem === undefined) { firstItem = item; @@ -251,6 +255,37 @@ export class TabListPattern { } } + /** Opens the tab by given value. */ + open(value: string): boolean; + + /** Opens the given tab or the current active tab. */ + open(tab?: TabPattern): boolean; + + open(tab: TabPattern | string | undefined): boolean { + tab ??= this.activeTab(); + + if (typeof tab === 'string') { + tab = this.inputs.items().find(t => t.value() === tab); + } + + if (tab === undefined) return false; + + const success = this.expansionBehavior.open(tab); + if (success) { + this.selectedTab.set(tab); + } + + return success; + } + + /** Executes a navigation operation and expand the active tab if needed. */ + private _navigate(op: () => boolean, shouldExpand: boolean = false): void { + const success = op(); + if (success && shouldExpand) { + this.open(); + } + } + /** Returns the tab item associated with the given pointer event. */ private _getItem(e: PointerEvent) { if (!(e.target instanceof HTMLElement)) { diff --git a/src/aria/ui-patterns/toolbar/BUILD.bazel b/src/aria/private/toolbar/BUILD.bazel similarity index 71% rename from src/aria/ui-patterns/toolbar/BUILD.bazel rename to src/aria/private/toolbar/BUILD.bazel index 1c3d9d978ebe..6c2dd3d6221b 100644 --- a/src/aria/ui-patterns/toolbar/BUILD.bazel +++ b/src/aria/private/toolbar/BUILD.bazel @@ -11,9 +11,9 @@ ts_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/list", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/signal-like", ], ) @@ -24,8 +24,7 @@ ng_project( deps = [ ":toolbar", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", - "//src/aria/ui-patterns/radio-group", + "//src/aria/private/behaviors/signal-like", "//src/cdk/keycodes", "//src/cdk/testing/private", ], diff --git a/src/aria/private/toolbar/toolbar-widget-group.ts b/src/aria/private/toolbar/toolbar-widget-group.ts new file mode 100644 index 000000000000..c59eeb231204 --- /dev/null +++ b/src/aria/private/toolbar/toolbar-widget-group.ts @@ -0,0 +1,45 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ListItem} from '../behaviors/list/list'; +import {SignalLike} from '../behaviors/signal-like/signal-like'; +import type {ToolbarPattern} from './toolbar'; + +/** Represents the required inputs for a toolbar widget group. */ +export interface ToolbarWidgetGroupInputs, V> { + /** A reference to the parent toolbar. */ + toolbar: SignalLike | undefined>; + + /** Whether the widget group is disabled. */ + disabled: SignalLike; + + /** The list of items within the widget group. */ + items: SignalLike; + + /** Whether the group allows multiple widgets to be selected. */ + multi: SignalLike; +} + +/** A group of widgets within a toolbar that provides nested navigation. */ +export class ToolbarWidgetGroupPattern, V> { + /** Whether the widget is disabled. */ + readonly disabled = () => this.inputs.disabled(); + + /** A reference to the parent toolbar. */ + readonly toolbar = () => this.inputs.toolbar(); + + /** Whether the group allows multiple widgets to be selected. */ + readonly multi = () => this.inputs.multi(); + + readonly searchTerm = () => ''; // Unused because toolbar does not support typeahead. + readonly value = () => '' as V; // Unused because toolbar does not support selection. + readonly selectable = () => true; // Unused because toolbar does not support selection. + readonly element = () => undefined; // Unused because toolbar does not focus the group element. + + constructor(readonly inputs: ToolbarWidgetGroupInputs) {} +} diff --git a/src/aria/ui-patterns/toolbar/toolbar-widget.ts b/src/aria/private/toolbar/toolbar-widget.ts similarity index 56% rename from src/aria/ui-patterns/toolbar/toolbar-widget.ts rename to src/aria/private/toolbar/toolbar-widget.ts index e86247481968..242e02f71d8a 100644 --- a/src/aria/ui-patterns/toolbar/toolbar-widget.ts +++ b/src/aria/private/toolbar/toolbar-widget.ts @@ -10,35 +10,42 @@ import {computed} from '@angular/core'; import {SignalLike} from '../behaviors/signal-like/signal-like'; import {ListItem} from '../behaviors/list/list'; import type {ToolbarPattern} from './toolbar'; +import {ToolbarWidgetGroupPattern} from './toolbar-widget-group'; /** Represents the required inputs for a toolbar widget in a toolbar. */ export interface ToolbarWidgetInputs - extends Omit, 'searchTerm' | 'value' | 'index' | 'selectable'> { + extends Omit, 'searchTerm' | 'index' | 'selectable'> { /** A reference to the parent toolbar. */ toolbar: SignalLike>; + + /** A reference to the parent widget group. */ + group: SignalLike, V> | undefined>; } export class ToolbarWidgetPattern implements ListItem { /** A unique identifier for the widget. */ - readonly id: SignalLike; + readonly id = () => this.inputs.id(); /** The html element that should receive focus. */ - readonly element: SignalLike; + readonly element = () => this.inputs.element(); /** Whether the widget is disabled. */ - readonly disabled: SignalLike; + readonly disabled = () => this.inputs.disabled() || this.group()?.disabled() || false; /** A reference to the parent toolbar. */ - readonly toolbar: SignalLike>; + readonly group = () => this.inputs.group(); + + /** A reference to the toolbar containing the widget. */ + readonly toolbar = () => this.inputs.toolbar(); - /** The tabindex of the widgdet. */ - readonly tabindex = computed(() => this.toolbar().listBehavior.getItemTabindex(this)); + /** The tabindex of the widget. */ + readonly tabIndex = computed(() => this.toolbar().listBehavior.getItemTabindex(this)); /** The text used by the typeahead search. */ readonly searchTerm = () => ''; // Unused because toolbar does not support typeahead. /** The value associated with the widget. */ - readonly value = () => '' as V; // Unused because toolbar does not support selection. + readonly value = () => this.inputs.value(); /** Whether the widget is selectable. */ readonly selectable = () => true; // Unused because toolbar does not support selection. @@ -46,13 +53,13 @@ export class ToolbarWidgetPattern implements ListItem { /** The position of the widget within the toolbar. */ readonly index = computed(() => this.toolbar().inputs.items().indexOf(this) ?? -1); + /** Whether the widget is selected (only relevant in a selection group). */ + readonly selected = computed(() => + this.toolbar().listBehavior.inputs.values().includes(this.value()), + ); + /** Whether the widget is currently the active one (focused). */ - readonly active = computed(() => this.toolbar().inputs.activeItem() === this); - - constructor(readonly inputs: ToolbarWidgetInputs) { - this.id = inputs.id; - this.element = inputs.element; - this.disabled = inputs.disabled; - this.toolbar = inputs.toolbar; - } + readonly active: SignalLike = computed(() => this.toolbar().activeItem() === this); + + constructor(readonly inputs: ToolbarWidgetInputs) {} } diff --git a/src/aria/private/toolbar/toolbar.spec.ts b/src/aria/private/toolbar/toolbar.spec.ts new file mode 100644 index 000000000000..23b77d4360ee --- /dev/null +++ b/src/aria/private/toolbar/toolbar.spec.ts @@ -0,0 +1,1370 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed, signal, WritableSignal} from '@angular/core'; +import {ToolbarInputs, ToolbarPattern} from './toolbar'; +import {ToolbarWidgetPattern} from './toolbar-widget'; +import {ToolbarWidgetGroupPattern} from './toolbar-widget-group'; +import {createKeyboardEvent} from '@angular/cdk/testing/private'; +import {SignalLike} from '../behaviors/signal-like/signal-like'; +import {ModifierKeys} from '@angular/cdk/testing'; + +// Test types +type TestWidget = ToolbarWidgetPattern & { + inputs: {disabled: WritableSignal}; +}; + +type TestWidgetGroup = ToolbarWidgetGroupPattern, string> & { + disabled: WritableSignal; + items: WritableSignal; +}; + +type TestItem = TestWidget; + +type TestInputs = { + readonly [K in keyof ToolbarInputs]: WritableSignal< + ToolbarInputs[K] extends SignalLike ? T : never + >; +}; + +// Keyboard event helpers +const up = () => createKeyboardEvent('keydown', 38, 'ArrowUp'); +const down = () => createKeyboardEvent('keydown', 40, 'ArrowDown'); +const home = () => createKeyboardEvent('keydown', 36, 'Home'); +const end = () => createKeyboardEvent('keydown', 35, 'End'); +const enter = () => createKeyboardEvent('keydown', 13, 'Enter'); +const right = () => createKeyboardEvent('keydown', 39, 'ArrowRight'); +const left = () => createKeyboardEvent('keydown', 37, 'ArrowLeft'); +const space = () => createKeyboardEvent('keydown', 32, ' '); + +function clickItem(item: ToolbarWidgetPattern, mods?: ModifierKeys) { + return { + target: item.element(), + shiftKey: mods?.shift, + ctrlKey: mods?.control, + } as unknown as PointerEvent; +} + +function getToolbarPattern( + inputs: Partial<{ + [K in keyof TestInputs]: TestInputs[K] extends WritableSignal ? T : never; + }>, + items: WritableSignal, +) { + const element = signal(document.createElement('div')); + const activeItem = signal(undefined); + + const allItems = computed(() => { + const flatItems: ToolbarWidgetPattern[] = []; + for (const item of items()) { + if (item instanceof ToolbarWidgetGroupPattern) { + flatItems.push(...item.inputs.items()); + } else { + flatItems.push(item); + } + } + return flatItems; + }); + + const toolbar = new ToolbarPattern({ + element, + items, + activeItem, + values: signal([]), + wrap: signal(inputs.wrap ?? true), + disabled: signal(inputs.disabled ?? false), + softDisabled: signal(inputs.softDisabled ?? true), + textDirection: signal(inputs.textDirection ?? 'ltr'), + orientation: signal(inputs.orientation ?? 'horizontal'), + getItem: (e: Element) => allItems().find(i => i.element() === e), + }); + + return {toolbar, element, activeItem}; +} + +function getWidgetPattern( + value: string, + toolbar: ToolbarPattern, + group?: ToolbarWidgetGroupPattern, string>, +): TestWidget { + const element = signal(document.createElement('button')); + const widget = new ToolbarWidgetPattern({ + id: signal(`widget-${value}`), + element, + disabled: signal(false), + value: signal(value), + group: signal(group), + toolbar: signal(toolbar), + }); + return widget as TestWidget; +} + +function getWidgetGroupPattern(id: string, toolbar: ToolbarPattern): TestWidgetGroup { + const disabled = signal(false); + const items = signal([]); + + const group = new ToolbarWidgetGroupPattern, string>({ + disabled, + toolbar: signal(toolbar), + items, + multi: signal(false), + }); + + (group as TestWidgetGroup).disabled = disabled; + (group as TestWidgetGroup).items = items; + return group as TestWidgetGroup; +} + +function getPatterns( + inputs: Partial<{ + [K in keyof TestInputs]: TestInputs[K] extends WritableSignal ? T : never; + }> = {}, +) { + const items = signal([]); + const {toolbar} = getToolbarPattern(inputs, items); + + const group0 = getWidgetGroupPattern('group 0', toolbar); + const group1 = getWidgetGroupPattern('group 1', toolbar); + + items.set([ + getWidgetPattern('item 0', toolbar), + getWidgetPattern('item 1', toolbar), + getWidgetPattern('item 2', toolbar, group0), + getWidgetPattern('item 3', toolbar, group0), + getWidgetPattern('item 4', toolbar, group0), + getWidgetPattern('item 5', toolbar), + getWidgetPattern('item 6', toolbar, group1), + getWidgetPattern('item 7', toolbar, group1), + getWidgetPattern('item 8', toolbar, group1), + ]); + + // [ [ group 0 ] [ group 1 ]] + // [item 0, item 1, [item 2, item 3, item 4], item 5, [item 6, item 7, item 8]] + + (group0.inputs.items as WritableSignal).set(items().slice(2, 5) as TestWidget[]); + (group1.inputs.items as WritableSignal).set(items().slice(6, 9) as TestWidget[]); + + toolbar.setDefaultState(); + return {toolbar, items: items(), group0, group1}; +} + +describe('Toolbar Pattern', () => { + function getItem(toolbar: ToolbarPattern, value: string) { + return toolbar.inputs.items().find(item => item.value() === value)!; + } + + describe('Navigation', () => { + describe('with horizontal orientation', () => { + it('should navigate on click', () => { + const {toolbar} = getPatterns(); + const item5 = getItem(toolbar, 'item 5'); + toolbar.onClick(clickItem(item5)); + expect(toolbar.activeItem()?.value()).toBe('item 5'); + }); + + describe('with ltr text direction', () => { + it('should navigate next on ArrowRight', () => { + const {toolbar} = getPatterns(); + toolbar.onKeydown(right()); // Item 0 -> Item 1 + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate prev on ArrowLeft', () => { + const {toolbar} = getPatterns(); + toolbar.onKeydown(right()); // Item 0 -> Item 1 + toolbar.onKeydown(left()); // Item 1 -> Item 0 + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should not navigate next on ArrowDown when not in a widget group', () => { + const {toolbar} = getPatterns(); + toolbar.onKeydown(down()); // Item 0 -> Item 0 + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should not navigate prev on ArrowUp when not in a widget group', () => { + const {toolbar} = getPatterns(); + toolbar.onKeydown(right()); // Item 0 -> Item 1 + toolbar.onKeydown(up()); // Item 1 -> Item 1 + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate next in a widget group on ArrowDown', () => { + const {toolbar} = getPatterns(); + + toolbar.onKeydown(right()); // Item 0 -> Item 1 + toolbar.onKeydown(right()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(down()); // Item 2 -> Item 3 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 3'); + }); + + it('should navigate prev in a widget group on ArrowUp', () => { + const {toolbar} = getPatterns(); + + toolbar.onKeydown(right()); // Item 0 -> Item 1 + toolbar.onKeydown(right()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(down()); // Item 2 -> Item 3 (Group 0) + toolbar.onKeydown(up()); // Item 3 -> Item 2 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should navigate last to first in a widget group on ArrowDown', () => { + const {toolbar} = getPatterns(); + + toolbar.onKeydown(right()); // Item 0 -> Item 1 + toolbar.onKeydown(right()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(down()); // Item 2 -> Item 3 (Group 0) + toolbar.onKeydown(down()); // Item 3 -> Item 4 (Group 0) + toolbar.onKeydown(down()); // Item 4 -> Item 2 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should navigate first to last in a widget group on ArrowUp', () => { + const {toolbar} = getPatterns(); + + toolbar.onKeydown(right()); // Item 0 -> Item 1 + toolbar.onKeydown(right()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(up()); // Item 2 -> Item 4 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 4'); + }); + + describe('with wrap false', () => { + it('should not wrap from last to first', () => { + const {toolbar} = getPatterns({wrap: false}); + toolbar.onKeydown(end()); + toolbar.onKeydown(right()); + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should not wrap from first to last', () => { + const {toolbar} = getPatterns({wrap: false}); + toolbar.onKeydown(left()); + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + }); + + describe('with softDisabled true', () => { + it('should not skip disabled items when navigating next', () => { + const {toolbar, items} = getPatterns({softDisabled: true}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(right()); // Item 0 -> Item 1 (disabled) + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should not skip disabled items when navigating prev', () => { + const {toolbar, items} = getPatterns({softDisabled: true}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(right()); // Item 0 -> Item 1 (disabled) + toolbar.onKeydown(right()); // Item 1 -> Item 2 + toolbar.onKeydown(left()); // Item 2 -> Item 1 (disabled) + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should not skip disabled groups when navigating next', () => { + const {toolbar, group0} = getPatterns({softDisabled: true}); + group0.disabled.set(true); + + toolbar.onKeydown(right()); // Item 0 -> Item 1 + toolbar.onKeydown(right()); // Item 1 -> Item 2 + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should not skip disabled groups when navigating prev', () => { + const {toolbar, group0} = getPatterns({softDisabled: true}); + group0.disabled.set(true); + toolbar.onKeydown(left()); // Item 0 -> Item 8 + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should navigate to the last item on End', () => { + const {toolbar, items} = getPatterns({softDisabled: true}); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should navigate to the first item on Home', () => { + const {toolbar, items} = getPatterns({softDisabled: true}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); // Item 0 -> Item 8 + toolbar.onKeydown(home()); // Item 8 -> Item 0 + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + describe('with wrap true', () => { + it('should wrap from last to first', () => { + const {toolbar, items} = getPatterns({softDisabled: true, wrap: true}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(right()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should wrap from first to last', () => { + const {toolbar, items} = getPatterns({softDisabled: true, wrap: true}); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(left()); + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + }); + }); + + describe('with softDisabled false', () => { + it('should not navigate to disabled items on click', () => { + const {toolbar, items} = getPatterns({softDisabled: false}); + items[1].inputs.disabled.set(true); + + toolbar.onClick(clickItem(items[1])); + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should skip disabled items when navigating next', () => { + const {toolbar, items} = getPatterns({softDisabled: false}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(right()); // Item 0 -> Item 2 (skips Item 1) + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should skip disabled items when navigating prev', () => { + const {toolbar, items} = getPatterns({softDisabled: false}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(right()); // Item 0 -> Item 2 + toolbar.onKeydown(left()); // Item 2 -> Item 0 (skips Item 1) + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should not navigate to items in disabled groups on click', () => { + const {toolbar, group0} = getPatterns({softDisabled: false}); + group0.disabled.set(true); + const item2 = getItem(toolbar, 'item 2'); + toolbar.onClick(clickItem(item2)); + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should skip disabled groups when navigating next', () => { + const {toolbar, group0} = getPatterns({softDisabled: false}); + group0.disabled.set(true); + + toolbar.onKeydown(right()); // Item 0 -> Item 1 + toolbar.onKeydown(right()); // Item 1 -> Item 5 (skips Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 5'); + }); + + it('should skip disabled groups when navigating prev', () => { + const {toolbar, group0, group1} = getPatterns({softDisabled: false}); + group0.disabled.set(true); + group1.disabled.set(true); + + toolbar.onKeydown(left()); // Item 0 -> Item 5 (skips Group 1) + toolbar.onKeydown(left()); // Item 5 -> Item 1 (skips Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate to the last focusable item on End', () => { + const {toolbar, items} = getPatterns({softDisabled: false}); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + expect(toolbar.activeItem()?.value()).toBe('item 7'); + }); + + it('should navigate to the first focusable item on Home', () => { + const {toolbar, items} = getPatterns({softDisabled: false}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(home()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + describe('with wrap true', () => { + it('should wrap from last to first focusable item', () => { + const {toolbar, items} = getPatterns({softDisabled: false, wrap: true}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(right()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should wrap from first to last focusable item', () => { + const {toolbar, items} = getPatterns({softDisabled: false, wrap: true}); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(left()); + + expect(toolbar.activeItem()?.value()).toBe('item 7'); + }); + }); + + describe('with wrap false', () => { + it('should not wrap from last to first focusable item', () => { + const {toolbar, items} = getPatterns({softDisabled: false, wrap: false}); + items[items.length - 1].inputs.disabled.set(true); + toolbar.onKeydown(end()); + toolbar.onKeydown(right()); + + expect(toolbar.activeItem()?.value()).toBe('item 7'); + }); + + it('should not wrap from first to last focusable item', () => { + const {toolbar, items} = getPatterns({softDisabled: false, wrap: false}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(home()); + toolbar.onKeydown(left()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + }); + }); + }); + + describe('with rtl text direction', () => { + it('should navigate on click', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + const item5 = getItem(toolbar, 'item 5'); + toolbar.onClick(clickItem(item5)); + expect(toolbar.activeItem()?.value()).toBe('item 5'); + }); + + it('should navigate next on ArrowLeft', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + toolbar.onKeydown(left()); // Item 0 -> Item 1 + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate prev on ArrowRight', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + toolbar.onKeydown(left()); // Item 0 -> Item 1 + toolbar.onKeydown(right()); // Item 1 -> Item 0 + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should not navigate next on ArrowDown when not in a widget group', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + toolbar.onKeydown(up()); // Item 0 -> Item 0 + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should not navigate prev on ArrowUp when not in a widget group', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + toolbar.onKeydown(left()); // Item 0 -> Item 1 + toolbar.onKeydown(down()); // Item 1 -> Item 1 + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate next in a widget group on ArrowDown', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + + toolbar.onKeydown(left()); // Item 0 -> Item 1 + toolbar.onKeydown(left()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(down()); // Item 2 -> Item 3 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 3'); + }); + + it('should navigate prev in a widget group on ArrowUp', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + + toolbar.onKeydown(left()); // Item 0 -> Item 1 + toolbar.onKeydown(left()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(down()); // Item 2 -> Item 3 (Group 0) + toolbar.onKeydown(up()); // Item 3 -> Item 2 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should navigate first to last in a widget group on ArrowUp', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + + toolbar.onKeydown(left()); // Item 0 -> Item 1 + toolbar.onKeydown(left()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(up()); // Item 2 -> Item 4 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 4'); + }); + + it('should navigate last to first in a widget group on ArrowDown', () => { + const {toolbar} = getPatterns({textDirection: 'rtl'}); + + toolbar.onKeydown(left()); // Item 0 -> Item 1 + toolbar.onKeydown(left()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(down()); // Item 2 -> Item 3 (Group 0) + toolbar.onKeydown(down()); // Item 3 -> Item 4 (Group 0) + toolbar.onKeydown(down()); // Item 4 -> Item 2 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + describe('with softDisabled true', () => { + it('should not skip disabled items when navigating next', () => { + const {toolbar, items} = getPatterns({softDisabled: true, textDirection: 'rtl'}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(left()); // Item 0 -> Item 1 (disabled) + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should not skip disabled items when navigating prev', () => { + const {toolbar, items} = getPatterns({softDisabled: true, textDirection: 'rtl'}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(left()); // Item 0 -> Item 1 (disabled) + toolbar.onKeydown(left()); // Item 1 -> Item 2 + toolbar.onKeydown(right()); // Item 2 -> Item 1 (disabled) + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate to the last item on End', () => { + const {toolbar, items} = getPatterns({softDisabled: true, textDirection: 'rtl'}); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should navigate to the first item on Home', () => { + const {toolbar, items} = getPatterns({softDisabled: true, textDirection: 'rtl'}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(left()); // Item 0 -> Item 1 + toolbar.onKeydown(home()); // Item 1 -> Item 0 + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + describe('with wrap true', () => { + it('should wrap from last to first', () => { + const {toolbar, items} = getPatterns({ + wrap: true, + softDisabled: true, + textDirection: 'rtl', + }); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(left()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should wrap from first to last', () => { + const {toolbar, items} = getPatterns({ + wrap: true, + softDisabled: true, + textDirection: 'rtl', + }); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(right()); + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + }); + + describe('with wrap false', () => { + it('should not wrap from last to first', () => { + const {toolbar} = getPatterns({ + wrap: false, + softDisabled: true, + textDirection: 'rtl', + }); + toolbar.onKeydown(end()); + toolbar.onKeydown(left()); + + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should not wrap from first to last', () => { + const {toolbar} = getPatterns({ + wrap: false, + softDisabled: true, + textDirection: 'rtl', + }); + toolbar.onKeydown(home()); + toolbar.onKeydown(right()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + }); + }); + + describe('with softDisabled false', () => { + it('should skip disabled items when navigating next', () => { + const {toolbar, items} = getPatterns({softDisabled: false, textDirection: 'rtl'}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(left()); // Item 0 -> Item 2 (skips Item 1) + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should skip disabled items when navigating prev', () => { + const {toolbar, items} = getPatterns({softDisabled: false, textDirection: 'rtl'}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(left()); // Item 0 -> Item 2 + toolbar.onKeydown(right()); // Item 2 -> Item 0 (skips Item 1) + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should navigate to the last focusable item on End', () => { + const {toolbar, items} = getPatterns({softDisabled: false, textDirection: 'rtl'}); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + expect(toolbar.activeItem()?.value()).toBe('item 7'); + }); + + it('should navigate to the first focusable item on Home', () => { + const {toolbar, items} = getPatterns({softDisabled: false, textDirection: 'rtl'}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(home()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + describe('with wrap true', () => { + it('should wrap from last to first focusable item', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + wrap: true, + textDirection: 'rtl', + }); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(left()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should wrap from first to last focusable item', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + wrap: true, + textDirection: 'rtl', + }); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(right()); + + expect(toolbar.activeItem()?.value()).toBe('item 7'); + }); + }); + + describe('with wrap false', () => { + it('should not wrap from last to first focusable item', () => { + const {toolbar} = getPatterns({ + softDisabled: false, + wrap: false, + textDirection: 'rtl', + }); + toolbar.onKeydown(end()); + toolbar.onKeydown(left()); + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should not wrap from first to last focusable item', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + wrap: false, + textDirection: 'rtl', + }); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(right()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + }); + }); + }); + }); + + describe('with vertical orientation', () => { + describe('with ltr text direction', () => { + it('should navigate next on ArrowDown', () => { + const {toolbar} = getPatterns({orientation: 'vertical'}); + toolbar.onKeydown(down()); // Item 0 -> Item 1 + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate prev on ArrowUp', () => { + const {toolbar} = getPatterns({orientation: 'vertical'}); + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(up()); // Item 1 -> Item 0 + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should not navigate next on ArrowRight when not in a widget group', () => { + const {toolbar} = getPatterns({orientation: 'vertical'}); + toolbar.onKeydown(right()); // Item 0 -> Item 0 + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should not navigate prev on ArrowLeft when not in a widget group', () => { + const {toolbar} = getPatterns({orientation: 'vertical'}); + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(left()); // Item 1 -> Item 1 + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate next in a widget group on ArrowRight', () => { + const {toolbar} = getPatterns({orientation: 'vertical'}); + + toolbar.onKeydown(down()); + toolbar.onKeydown(down()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(right()); // Item 2 -> Item 3 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 3'); + }); + + it('should navigate prev in a widget group on ArrowLeft', () => { + const {toolbar} = getPatterns({orientation: 'vertical'}); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(down()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(right()); // Item 2 -> Item 3 (Group 0) + toolbar.onKeydown(left()); // Item 3 -> Item 2 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should navigate last to first in a widget group on ArrowRight', () => { + const {toolbar} = getPatterns({orientation: 'vertical'}); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(down()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(right()); // Item 2 -> Item 3 (Group 0) + toolbar.onKeydown(right()); // Item 3 -> Item 4 (Group 0) + toolbar.onKeydown(right()); // Item 4 -> Item 2 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should navigate first to last in a widget group on ArrowLeft', () => { + const {toolbar} = getPatterns({orientation: 'vertical'}); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(down()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(left()); // Item 2 -> Item 4 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 4'); + }); + + describe('with softDisabled true', () => { + it('should not skip disabled items when navigating next', () => { + const {toolbar, items} = getPatterns({softDisabled: true, orientation: 'vertical'}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 (disabled) + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should not skip disabled items when navigating prev', () => { + const {toolbar, items} = getPatterns({softDisabled: true, orientation: 'vertical'}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 (disabled) + toolbar.onKeydown(down()); // Item 1 -> Item 2 + toolbar.onKeydown(up()); // Item 2 -> Item 1 (disabled) + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate to the last item on End', () => { + const {toolbar, items} = getPatterns({softDisabled: true, orientation: 'vertical'}); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should navigate to the first item on Home', () => { + const {toolbar, items} = getPatterns({softDisabled: true, orientation: 'vertical'}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(home()); // Item 1 -> Item 0 + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + describe('with wrap true', () => { + it('should wrap from last to first', () => { + const {toolbar, items} = getPatterns({ + softDisabled: true, + wrap: true, + orientation: 'vertical', + }); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(down()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should wrap from first to last', () => { + const {toolbar, items} = getPatterns({ + softDisabled: true, + wrap: true, + orientation: 'vertical', + }); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(up()); + + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + }); + + describe('with wrap false', () => { + it('should not wrap from last to first', () => { + const {toolbar} = getPatterns({ + softDisabled: true, + wrap: false, + orientation: 'vertical', + }); + toolbar.onKeydown(end()); + toolbar.onKeydown(down()); + + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should not wrap from first to last', () => { + const {toolbar} = getPatterns({ + softDisabled: true, + wrap: false, + orientation: 'vertical', + }); + toolbar.onKeydown(home()); + toolbar.onKeydown(up()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + }); + }); + + describe('with softDisabled false', () => { + it('should skip disabled items when navigating next', () => { + const {toolbar, items} = getPatterns({softDisabled: false, orientation: 'vertical'}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 2 (skips Item 1) + + expect(toolbar.activeItem()).toBe(items[2]); + }); + + it('should skip disabled items when navigating prev', () => { + const {toolbar, items} = getPatterns({softDisabled: false, orientation: 'vertical'}); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 2 + toolbar.onKeydown(up()); // Item 2 -> Item 0 (skips Item 1) + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should navigate to the last focusable item on End', () => { + const {toolbar, items} = getPatterns({softDisabled: false, orientation: 'vertical'}); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + expect(toolbar.activeItem()?.value()).toBe('item 7'); + }); + + it('should navigate to the first focusable item on Home', () => { + const {toolbar, items} = getPatterns({softDisabled: false, orientation: 'vertical'}); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(home()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + describe('with wrap true', () => { + it('should wrap from last to first focusable item', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + wrap: true, + orientation: 'vertical', + }); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(down()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should wrap from first to last focusable item', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + wrap: true, + orientation: 'vertical', + }); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(up()); + + expect(toolbar.activeItem()?.value()).toBe('item 7'); + }); + }); + + describe('with wrap false', () => { + it('should not wrap from last to first focusable item', () => { + const {toolbar} = getPatterns({ + softDisabled: false, + wrap: false, + orientation: 'vertical', + }); + toolbar.onKeydown(end()); + toolbar.onKeydown(down()); + + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should not wrap from first to last focusable item', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + wrap: false, + orientation: 'vertical', + }); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(up()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + }); + }); + }); + + describe('with rtl text direction', () => { + it('should navigate next on ArrowDown', () => { + const {toolbar} = getPatterns({orientation: 'vertical', textDirection: 'rtl'}); + toolbar.onKeydown(down()); // Item 0 -> Item 1 + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate prev on ArrowUp', () => { + const {toolbar} = getPatterns({orientation: 'vertical', textDirection: 'rtl'}); + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(up()); // Item 1 -> Item 0 + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should navigate last to first in a widget group on ArrowLeft', () => { + const {toolbar} = getPatterns({orientation: 'vertical', textDirection: 'rtl'}); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(down()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(left()); // Item 2 -> Item 3 (Group 0) + toolbar.onKeydown(left()); // Item 3 -> Item 4 (Group 0) + toolbar.onKeydown(left()); // Item 4 -> Item 2 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 2'); + }); + + it('should navigate first to last in a widget group on ArrowRight', () => { + const {toolbar} = getPatterns({orientation: 'vertical', textDirection: 'rtl'}); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(down()); // Item 1 -> Item 2 (Group 0) + toolbar.onKeydown(right()); // Item 2 -> Item 4 (Group 0) + + expect(toolbar.activeItem()?.value()).toBe('item 4'); + }); + + describe('with softDisabled true', () => { + it('should not skip disabled items when navigating next', () => { + const {toolbar, items} = getPatterns({ + softDisabled: true, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 (disabled) + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should not skip disabled items when navigating prev', () => { + const {toolbar, items} = getPatterns({ + softDisabled: true, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 (disabled) + toolbar.onKeydown(down()); // Item 1 -> Item 2 + toolbar.onKeydown(up()); // Item 2 -> Item 1 (disabled) + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should navigate to the last item on End', () => { + const {toolbar, items} = getPatterns({ + softDisabled: true, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should navigate to the first item on Home', () => { + const {toolbar, items} = getPatterns({ + softDisabled: true, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 1 + toolbar.onKeydown(home()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + describe('with wrap true', () => { + it('should wrap from last to first', () => { + const {toolbar, items} = getPatterns({ + softDisabled: true, + wrap: true, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(down()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should wrap from first to last', () => { + const {toolbar, items} = getPatterns({ + softDisabled: true, + wrap: true, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(up()); + + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + }); + + describe('with wrap false', () => { + it('should not wrap from last to first', () => { + const {toolbar} = getPatterns({ + softDisabled: true, + wrap: false, + orientation: 'vertical', + textDirection: 'rtl', + }); + toolbar.onKeydown(end()); + toolbar.onKeydown(down()); + + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should not wrap from first to last', () => { + const {toolbar} = getPatterns({ + softDisabled: true, + wrap: false, + orientation: 'vertical', + textDirection: 'rtl', + }); + toolbar.onKeydown(home()); + toolbar.onKeydown(up()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + }); + }); + + describe('with softDisabled false', () => { + it('should skip disabled items when navigating next', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 2 (skips Item 1) + + expect(toolbar.activeItem()).toBe(items[2]); + }); + + it('should skip disabled items when navigating prev', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(down()); // Item 0 -> Item 2 + toolbar.onKeydown(up()); // Item 2 -> Item 0 (skips Item 1) + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + + it('should navigate to the last focusable item on End', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + orientation: 'vertical', + textDirection: 'rtl', + }); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + expect(toolbar.activeItem()).toBe(items[items.length - 2]); + }); + + it('should navigate to the first focusable item on Home', () => { + const {toolbar, items} = getPatterns({ + softDisabled: false, + textDirection: 'rtl', + orientation: 'vertical', + }); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(home()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + describe('with wrap true', () => { + it('should wrap from last to first focusable item', () => { + const {toolbar, items} = getPatterns({ + wrap: true, + softDisabled: false, + textDirection: 'rtl', + orientation: 'vertical', + }); + items[0].inputs.disabled.set(true); + + toolbar.onKeydown(end()); + toolbar.onKeydown(down()); + + expect(toolbar.activeItem()?.value()).toBe('item 1'); + }); + + it('should wrap from first to last focusable item', () => { + const {toolbar, items} = getPatterns({ + wrap: true, + softDisabled: false, + textDirection: 'rtl', + orientation: 'vertical', + }); + items[items.length - 1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(up()); + + expect(toolbar.activeItem()).toBe(items[items.length - 2]); + }); + }); + + describe('with wrap false', () => { + it('should not wrap from last to first focusable item', () => { + const {toolbar} = getPatterns({ + wrap: false, + softDisabled: false, + textDirection: 'rtl', + orientation: 'vertical', + }); + toolbar.onKeydown(end()); + toolbar.onKeydown(down()); + + expect(toolbar.activeItem()?.value()).toBe('item 8'); + }); + + it('should not wrap from first to last focusable item', () => { + const {toolbar, items} = getPatterns({ + wrap: false, + softDisabled: false, + textDirection: 'rtl', + orientation: 'vertical', + }); + items[1].inputs.disabled.set(true); + + toolbar.onKeydown(home()); + toolbar.onKeydown(up()); + + expect(toolbar.activeItem()?.value()).toBe('item 0'); + }); + }); + }); + }); + }); + + describe('with disabled toolbar', () => { + it('should not navigate on any key press', () => { + const {toolbar} = getPatterns({disabled: true}); + const initialActiveItem = toolbar.activeItem(); + + toolbar.onKeydown(right()); + expect(toolbar.activeItem()).toBe(initialActiveItem); + + toolbar.onKeydown(left()); + expect(toolbar.activeItem()).toBe(initialActiveItem); + + toolbar.onKeydown(up()); + expect(toolbar.activeItem()).toBe(initialActiveItem); + + toolbar.onKeydown(down()); + expect(toolbar.activeItem()).toBe(initialActiveItem); + + toolbar.onKeydown(home()); + expect(toolbar.activeItem()).toBe(initialActiveItem); + + toolbar.onKeydown(end()); + expect(toolbar.activeItem()).toBe(initialActiveItem); + }); + }); + }); + + describe('Selection', () => { + it('should toggle the active item on Enter', () => { + const {toolbar} = getPatterns(); + expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + toolbar.onKeydown(enter()); + expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); + toolbar.onKeydown(enter()); + expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + }); + + it('should toggle the active item on Space', () => { + const {toolbar} = getPatterns(); + expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + toolbar.onKeydown(space()); + expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); + toolbar.onKeydown(space()); + expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + }); + + it('should toggle the active item on click', () => { + const {toolbar, items} = getPatterns(); + expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + toolbar.onClick(clickItem(items[0])); + expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); + toolbar.onClick(clickItem(items[0])); + expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + }); + + it('should be able to select multiple items in the toolbar', () => { + const {toolbar} = getPatterns(); + expect(getItem(toolbar, 'item 0').selected()).toBeFalse(); + expect(getItem(toolbar, 'item 1').selected()).toBeFalse(); + + // Select first item + toolbar.onKeydown(enter()); + expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); + expect(getItem(toolbar, 'item 1').selected()).toBeFalse(); + + // Navigate to and select second item + toolbar.onKeydown(right()); + toolbar.onKeydown(space()); + expect(getItem(toolbar, 'item 0').selected()).toBeTrue(); + expect(getItem(toolbar, 'item 1').selected()).toBeTrue(); + }); + + it('should not be able to select multiple items in a group', () => { + const {toolbar} = getPatterns(); + expect(getItem(toolbar, 'item 2').selected()).toBeFalse(); + expect(getItem(toolbar, 'item 3').selected()).toBeFalse(); + + // Navigate to and select first item in group + toolbar.onKeydown(right()); + toolbar.onKeydown(right()); + toolbar.onKeydown(enter()); + expect(getItem(toolbar, 'item 2').selected()).toBeTrue(); + expect(getItem(toolbar, 'item 3').selected()).toBeFalse(); + + // Navigate to and select second item in group + toolbar.onKeydown(right()); + toolbar.onKeydown(enter()); + expect(getItem(toolbar, 'item 2').selected()).toBeFalse(); + expect(getItem(toolbar, 'item 3').selected()).toBeTrue(); + }); + + it('should not select disabled items', () => { + const {toolbar, items} = getPatterns(); + items[1].inputs.disabled.set(true); + + // Navigate to disabled item + toolbar.onKeydown(right()); + expect(toolbar.activeItem()?.value()).toBe('item 1'); + + // Try to select disabled item + toolbar.onKeydown(enter()); + expect(getItem(toolbar, 'item 1').selected()).toBeFalse(); + }); + + it('should not select items in a disabled group', () => { + const {toolbar, items, group0} = getPatterns(); + group0.disabled.set(true); + + toolbar.onClick(clickItem(items[2])); + expect(toolbar.activeItem()?.value()).toBe('item 2'); + expect(getItem(toolbar, 'item 2').selected()).toBeFalse(); + + toolbar.onKeydown(right()); + toolbar.onKeydown(enter()); + expect(toolbar.activeItem()?.value()).toBe('item 3'); + expect(getItem(toolbar, 'item 3').selected()).toBeFalse(); + }); + }); +}); diff --git a/src/aria/private/toolbar/toolbar.ts b/src/aria/private/toolbar/toolbar.ts new file mode 100644 index 000000000000..2886d37bfd1d --- /dev/null +++ b/src/aria/private/toolbar/toolbar.ts @@ -0,0 +1,202 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {computed} from '@angular/core'; +import {SignalLike} from '../behaviors/signal-like/signal-like'; +import {KeyboardEventManager} from '../behaviors/event-manager'; +import {List, ListInputs} from '../behaviors/list/list'; +import {ToolbarWidgetPattern} from './toolbar-widget'; + +/** Represents the required inputs for a toolbar. */ +export type ToolbarInputs = Omit< + ListInputs, V>, + 'multi' | 'typeaheadDelay' | 'selectionMode' | 'focusMode' +> & { + /** A function that returns the toolbar item associated with a given element. */ + getItem: (e: Element) => ToolbarWidgetPattern | undefined; +}; + +/** Controls the state of a toolbar. */ +export class ToolbarPattern { + /** The list behavior for the toolbar. */ + readonly listBehavior: List, V>; + + /** Whether the tablist is vertically or horizontally oriented. */ + readonly orientation: SignalLike<'vertical' | 'horizontal'>; + + /** Whether disabled items in the group should be focusable. */ + readonly softDisabled: SignalLike; + + /** Whether the toolbar is disabled. */ + readonly disabled = computed(() => this.listBehavior.disabled()); + + /** The tab index of the toolbar (if using activedescendant). */ + readonly tabIndex = computed(() => this.listBehavior.tabIndex()); + + /** The id of the current active widget (if using activedescendant). */ + readonly activeDescendant = computed(() => this.listBehavior.activeDescendant()); + + /** The currently active item in the toolbar. */ + readonly activeItem = () => this.listBehavior.inputs.activeItem(); + + /** The key used to navigate to the previous widget. */ + private readonly _prevKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return 'ArrowUp'; + } + return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + }); + + /** The key used to navigate to the next widget. */ + private readonly _nextKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return 'ArrowDown'; + } + return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + }); + + /** The alternate key used to navigate to the previous widget. */ + private readonly _altPrevKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + } + return 'ArrowUp'; + }); + + /** The alternate key used to navigate to the next widget. */ + private readonly _altNextKey = computed(() => { + if (this.inputs.orientation() === 'vertical') { + return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + } + return 'ArrowDown'; + }); + + /** The keydown event manager for the toolbar. */ + private readonly _keydown = computed(() => { + const manager = new KeyboardEventManager(); + + return manager + .on(this._nextKey, () => this.listBehavior.next()) + .on(this._prevKey, () => this.listBehavior.prev()) + .on(this._altNextKey, () => this._groupNext()) + .on(this._altPrevKey, () => this._groupPrev()) + .on(' ', () => this.select()) + .on('Enter', () => this.select()) + .on('Home', () => this.listBehavior.first()) + .on('End', () => this.listBehavior.last()); + }); + + /** Navigates to the next widget in a widget group. */ + private _groupNext() { + const currGroup = this.inputs.activeItem()?.group(); + const nextGroup = this.listBehavior.navigationBehavior.peekNext()?.group(); + + if (!currGroup) { + return; + } + + if (currGroup !== nextGroup) { + this.listBehavior.goto( + this.listBehavior.navigationBehavior.peekFirst(currGroup.inputs.items())!, + ); + + return; + } + + this.listBehavior.next(); + } + + /** Navigates to the previous widget in a widget group. */ + private _groupPrev() { + const currGroup = this.inputs.activeItem()?.group(); + const nextGroup = this.listBehavior.navigationBehavior.peekPrev()?.group(); + + if (!currGroup) { + return; + } + + if (currGroup !== nextGroup) { + this.listBehavior.goto( + this.listBehavior.navigationBehavior.peekLast(currGroup.inputs.items())!, + ); + + return; + } + + this.listBehavior.prev(); + } + + /** Navigates to the widget targeted by a pointer event. */ + private _goto(e: MouseEvent) { + const item = this.inputs.getItem(e.target as Element); + + if (item) { + this.listBehavior.goto(item); + this.select(); + } + } + + select() { + const group = this.inputs.activeItem()?.group(); + + if (!group?.multi()) { + group?.inputs.items().forEach(i => this.listBehavior.deselect(i)); + } + + this.listBehavior.toggle(); + } + + constructor(readonly inputs: ToolbarInputs) { + this.orientation = inputs.orientation; + this.softDisabled = inputs.softDisabled; + + this.listBehavior = new List({ + ...inputs, + multi: () => true, + focusMode: () => 'roving', + selectionMode: () => 'explicit', + typeaheadDelay: () => 0, // Toolbar widgets do not support typeahead. + }); + } + + /** Handles keydown events for the toolbar. */ + onKeydown(event: KeyboardEvent) { + if (this.disabled()) return; + this._keydown().handle(event); + } + + onPointerdown(event: PointerEvent) { + event.preventDefault(); + } + + /** Handles click events for the toolbar. */ + onClick(event: MouseEvent) { + if (this.disabled()) return; + this._goto(event); + } + + /** + * Sets the toolbar to its default initial state. + * + * Sets the active index to the selected widget if one exists and is focusable. + * Otherwise, sets the active index to the first focusable widget. + */ + setDefaultState() { + const firstItem = this.listBehavior.navigationBehavior.peekFirst(this.inputs.items()); + + if (firstItem) { + this.inputs.activeItem.set(firstItem); + } + } + + /** Validates the state of the toolbar and returns a list of accessibility violations. */ + validate(): string[] { + const violations: string[] = []; + return violations; + } +} diff --git a/src/aria/ui-patterns/tree/BUILD.bazel b/src/aria/private/tree/BUILD.bazel similarity index 66% rename from src/aria/ui-patterns/tree/BUILD.bazel rename to src/aria/private/tree/BUILD.bazel index 4d82175fc3bf..6f1779bf56d2 100644 --- a/src/aria/ui-patterns/tree/BUILD.bazel +++ b/src/aria/private/tree/BUILD.bazel @@ -10,11 +10,11 @@ ts_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/expansion", - "//src/aria/ui-patterns/behaviors/list", - "//src/aria/ui-patterns/behaviors/signal-like", - "//src/aria/ui-patterns/combobox", + "//src/aria/private/behaviors/event-manager", + "//src/aria/private/behaviors/expansion", + "//src/aria/private/behaviors/list", + "//src/aria/private/behaviors/signal-like", + "//src/aria/private/combobox", ], ) @@ -27,7 +27,7 @@ ng_project( deps = [ ":tree", "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", + "//src/aria/private/behaviors/signal-like", "//src/cdk/keycodes", "//src/cdk/testing/private", ], diff --git a/src/aria/ui-patterns/tree/combobox-tree.ts b/src/aria/private/tree/combobox-tree.ts similarity index 76% rename from src/aria/ui-patterns/tree/combobox-tree.ts rename to src/aria/private/tree/combobox-tree.ts index 70fed29206c3..46ab5c5c633c 100644 --- a/src/aria/ui-patterns/tree/combobox-tree.ts +++ b/src/aria/private/tree/combobox-tree.ts @@ -27,13 +27,16 @@ export class ComboboxTreePattern role = () => 'tree' as const; /* The id of the active (focused) item in the tree. */ - activeId = computed(() => this.listBehavior.activedescendant()); + activeId = computed(() => this.listBehavior.activeDescendant()); + + /** Returns the currently active (focused) item in the tree. */ + getActiveItem = () => this.inputs.activeItem(); /** The list of items in the tree. */ items = computed(() => this.inputs.allItems()); - /** The tabindex for the tree. Always -1 because the combobox handles focus. */ - override tabindex: SignalLike<-1 | 0> = () => -1; + /** The tab index for the tree. Always -1 because the combobox handles focus. */ + override tabIndex: SignalLike<-1 | 0> = () => -1; constructor(override readonly inputs: ComboboxTreeInputs) { if (inputs.combobox()) { @@ -76,17 +79,20 @@ export class ComboboxTreePattern /** Selects the specified item in the tree or the current active item if not provided. */ select = (item?: TreeItemPattern) => this.listBehavior.select(item); + /** Toggles the selection state of the given item in the tree or the current active item if not provided. */ + toggle = (item?: TreeItemPattern) => this.listBehavior.toggle(item); + /** Clears the selection in the tree. */ clearSelection = () => this.listBehavior.deselectAll(); /** Retrieves the TreeItemPattern associated with a pointer event. */ getItem = (e: PointerEvent) => this._getItem(e); - /** Retrieves the currently selected item in the tree */ - getSelectedItem = () => this.inputs.allItems().find(i => i.selected()); + /** Retrieves the currently selected items in the tree */ + getSelectedItems = () => this.inputs.allItems().filter(item => item.selected()); /** Sets the value of the combobox tree. */ - setValue = (value: V | undefined) => this.inputs.value.set(value ? [value] : []); + setValue = (value: V | undefined) => this.inputs.values.set(value ? [value] : []); /** Expands the currently focused item if it is expandable. */ expandItem = () => this.expand(); @@ -100,8 +106,13 @@ export class ComboboxTreePattern } /** Expands all of the tree items. */ - expandAll = () => this.items().forEach(item => item.expansion.open()); + expandAll = () => this.items().forEach(item => this.expansionBehavior.open(item)); /** Collapses all of the tree items. */ - collapseAll = () => this.items().forEach(item => item.expansion.close()); + collapseAll = () => this.items().forEach(item => item.expansionBehavior.close(item)); + + /** Whether the currently active item is selectable. */ + isItemSelectable = (item: TreeItemPattern | undefined = this.inputs.activeItem()) => { + return item ? item.selectable() : false; + }; } diff --git a/src/aria/ui-patterns/tree/tree.spec.ts b/src/aria/private/tree/tree.spec.ts similarity index 76% rename from src/aria/ui-patterns/tree/tree.spec.ts rename to src/aria/private/tree/tree.spec.ts index 844881aa6645..94972c167595 100644 --- a/src/aria/ui-patterns/tree/tree.spec.ts +++ b/src/aria/private/tree/tree.spec.ts @@ -58,6 +58,7 @@ interface TestTreeItem { children?: TestTreeItem[]; disabled: boolean; selectable: boolean; + expanded: boolean; } describe('Tree Pattern', () => { @@ -86,6 +87,7 @@ describe('Tree Pattern', () => { element: signal(element), disabled: signal(node.disabled), selectable: signal(node.selectable), + expanded: signal(node.expanded), searchTerm: signal(String(node.value)), parent: signal(parent), hasChildren: signal((node.children ?? []).length > 0), @@ -120,18 +122,20 @@ describe('Tree Pattern', () => { { value: 'Item 0', children: [ - {value: 'Item 0-0', disabled: false, selectable: true}, - {value: 'Item 0-1', disabled: false, selectable: true}, + {value: 'Item 0-0', disabled: false, selectable: true, expanded: false}, + {value: 'Item 0-1', disabled: false, selectable: true, expanded: false}, ], disabled: false, selectable: true, + expanded: false, }, - {value: 'Item 1', disabled: false, selectable: true}, + {value: 'Item 1', disabled: false, selectable: true, expanded: false}, { value: 'Item 2', - children: [{value: 'Item 2-0', disabled: false, selectable: true}], + children: [{value: 'Item 2-0', disabled: false, selectable: true, expanded: false}], disabled: false, selectable: true, + expanded: false, }, ]; @@ -147,10 +151,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('follow'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -201,10 +205,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('follow'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(true), currentType: signal('page'), @@ -215,7 +219,7 @@ describe('Tree Pattern', () => { it('should have undefined selected state', () => { const {allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); expect(item0.selected()).toBeUndefined(); }); @@ -224,11 +228,11 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); expect(item0.current()).toBe('page'); expect(item1.current()).toBeUndefined(); - treeInputs.value.set(['Item 1']); + treeInputs.values.set(['Item 1']); treeInputs.currentType.set('step'); expect(item0.current()).toBeUndefined(); expect(item1.current()).toBe('step'); @@ -237,7 +241,7 @@ describe('Tree Pattern', () => { it('should have undefined current state when non-selectable', () => { const {allItems, itemPatternInputsMap} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); expect(item0.current()).toBe('page'); itemPatternInputsMap.get(item0.id())!.selectable.set(false); expect(item0.current()).toBeUndefined(); @@ -257,10 +261,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('explicit'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -282,10 +286,10 @@ describe('Tree Pattern', () => { expect(item1.active()).toBe(true); }); - it('should correctly compute tabindex state', () => { + it('should correctly compute tab index state', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - expect(item0.tabindex()).toBe(tree.listBehavior.getItemTabindex(item0)); + expect(item0.tabIndex()).toBe(tree.listBehavior.getItemTabindex(item0)); }); it('should navigate next on ArrowDown (vertical)', () => { @@ -384,12 +388,12 @@ describe('Tree Pattern', () => { expect(tree.activeItem()).toBe(item2); }); - it('should skip disabled items when skipDisabled is true', () => { - treeInputs.skipDisabled.set(true); + it('should skip disabled items when softDisabled is false', () => { + treeInputs.softDisabled.set(false); const localTreeExample: TestTreeItem[] = [ - {value: 'Item A', disabled: false, selectable: true}, - {value: 'Item B', disabled: true, selectable: true}, - {value: 'Item C', disabled: false, selectable: true}, + {value: 'Item A', disabled: false, selectable: true, expanded: false}, + {value: 'Item B', disabled: true, selectable: true, expanded: false}, + {value: 'Item C', disabled: false, selectable: true, expanded: false}, ]; const {tree, allItems} = createTree(localTreeExample, treeInputs); const itemA = getItemByValue(allItems(), 'Item A'); @@ -401,12 +405,12 @@ describe('Tree Pattern', () => { expect(tree.activeItem()).toBe(itemC); }); - it('should not skip disabled items when skipDisabled is false', () => { - treeInputs.skipDisabled.set(false); + it('should not skip disabled items when softDisabled is true', () => { + treeInputs.softDisabled.set(true); const localTreeExample: TestTreeItem[] = [ - {value: 'Item A', disabled: false, selectable: true}, - {value: 'Item B', disabled: true, selectable: true}, - {value: 'Item C', disabled: false, selectable: true}, + {value: 'Item A', disabled: false, selectable: true, expanded: false}, + {value: 'Item B', disabled: true, selectable: true, expanded: false}, + {value: 'Item C', disabled: false, selectable: true, expanded: false}, ]; const {tree, allItems} = createTree(localTreeExample, treeInputs); const itemA = getItemByValue(allItems(), 'Item A'); @@ -443,10 +447,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('follow'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -459,11 +463,11 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); expect(item0.selected()).toBe(true); expect(item1.selected()).toBe(false); - treeInputs.value.set(['Item 1']); + treeInputs.values.set(['Item 1']); expect(item0.selected()).toBe(false); expect(item1.selected()).toBe(true); }); @@ -471,7 +475,7 @@ describe('Tree Pattern', () => { it('should have undefined selected state when non-selectable', () => { const {allItems, itemPatternInputsMap} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); itemPatternInputsMap.get(item0.id())!.selectable.set(false); expect(item0.selected()).toBeUndefined(); }); @@ -483,11 +487,11 @@ describe('Tree Pattern', () => { tree.onKeydown(down()); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); tree.onKeydown(up()); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should not change selection when the tree is disabled', () => { @@ -495,7 +499,7 @@ describe('Tree Pattern', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -511,10 +515,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('explicit'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -526,45 +530,45 @@ describe('Tree Pattern', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should not deselect an item on Space', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should select an item on Enter', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should not deselect an item on Enter', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should only allow one selected item', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); tree.onKeydown(down()); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should not change selection when the tree is disabled', () => { @@ -572,10 +576,10 @@ describe('Tree Pattern', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -591,10 +595,10 @@ describe('Tree Pattern', () => { multi: signal(true), orientation: signal('vertical'), selectionMode: signal('explicit'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -606,14 +610,14 @@ describe('Tree Pattern', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should select an item on Enter', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should allow multiple selected items', () => { @@ -622,23 +626,23 @@ describe('Tree Pattern', () => { tree.onKeydown(enter()); tree.onKeydown(down()); tree.onKeydown(enter()); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 1']); }); it('should select a range of visible items on Shift + ArrowDown/ArrowUp', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(shift()); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0']); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); tree.onKeydown(up({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0']); }); it('should not allow wrapping while Shift is held down', () => { @@ -648,47 +652,47 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(up({shift: true})); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should select a range of visible items on Shift + Space (or Enter)', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(down()); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual(['Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0-0']); tree.onKeydown(down()); tree.onKeydown(down()); tree.onKeydown(shift()); tree.onKeydown(space({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1']); + expect(tree.inputs.values()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1']); }); it('should select the focused item and all visible items up to the first on Ctrl + Shift + Home', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item1); tree.onKeydown(shift()); tree.onKeydown(home({control: true, shift: true})); - expect(tree.inputs.value()).toEqual(['Item 1', 'Item 0-1', 'Item 0-0', 'Item 0']); + expect(tree.inputs.values()).toEqual(['Item 1', 'Item 0-1', 'Item 0-0', 'Item 0']); }); it('should select the focused item and all visible items down to the last on Ctrl + Shift + End', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item0_0); tree.onKeydown(shift()); tree.onKeydown(end({control: true, shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + expect(tree.inputs.values()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); }); it('should not change selection when the tree is disabled', () => { @@ -696,19 +700,19 @@ describe('Tree Pattern', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(space()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should not select disabled items on Shift + ArrowUp / ArrowDown', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: true, selectable: true}, - {value: 'C', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: true, selectable: true, expanded: false}, + {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.skipDisabled.set(false); + treeInputs.softDisabled.set(true); const {tree, allItems} = createTree(localTreeData, treeInputs); const itemA = getItemByValue(allItems(), 'A'); @@ -716,14 +720,14 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(down({shift: true})); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['A', 'C']); + expect(tree.inputs.values()).toEqual(['A', 'C']); }); it('should not select non-selectable items on Shift + ArrowUp / ArrowDown', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: false, selectable: false}, - {value: 'C', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: false, selectable: false, expanded: false}, + {value: 'C', disabled: false, selectable: true, expanded: false}, ]; const {tree, allItems} = createTree(localTreeData, treeInputs); const itemA = getItemByValue(allItems(), 'A'); @@ -732,26 +736,32 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(down({shift: true})); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['A', 'C']); + expect(tree.inputs.values()).toEqual(['A', 'C']); }); it('should select all visible items on Ctrl + A', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + expect(tree.inputs.values()).toEqual([ + 'Item 0', + 'Item 0-0', + 'Item 0-1', + 'Item 1', + 'Item 2', + ]); }); it('should deselect all visible items on Ctrl + A if all are selected', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(a({control: true})); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -767,10 +777,10 @@ describe('Tree Pattern', () => { multi: signal(true), orientation: signal('vertical'), selectionMode: signal('follow'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -782,42 +792,42 @@ describe('Tree Pattern', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should navigate without selecting if the Ctrl key is pressed', () => { - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); const {tree, allItems} = createTree(treeExample, treeInputs); const item1 = getItemByValue(allItems(), 'Item 1'); tree.onKeydown(down({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); expect(tree.activeItem()).toBe(item1); }); it('should toggle an item selection state on Ctrl + Space', () => { - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(down({control: true})); tree.onKeydown(space({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 1']); tree.onKeydown(space({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); it('should select a range of visible items on Shift + ArrowDown/ArrowUp', () => { - treeInputs.value.set(['Item 0']); + treeInputs.values.set(['Item 0']); const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(shift()); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0']); tree.onKeydown(down({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); }); it('should not allow wrapping while Shift is held down', () => { @@ -828,44 +838,44 @@ describe('Tree Pattern', () => { tree.onKeydown(shift()); tree.onKeydown(up({shift: true})); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should select a range of visible items on Shift + Space (or Enter)', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item0); tree.onKeydown(down({control: true})); tree.onKeydown(down({control: true})); tree.onKeydown(shift()); tree.onKeydown(space({shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1']); }); it('should select the focused item and all visible items up to the first on Ctrl + Shift + Home', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item1); tree.onKeydown(shift()); tree.onKeydown(home({control: true, shift: true})); - expect(tree.inputs.value()).toEqual(['Item 1', 'Item 0-1', 'Item 0-0', 'Item 0']); + expect(tree.inputs.values()).toEqual(['Item 1', 'Item 0-1', 'Item 0-0', 'Item 0']); }); it('should select the focused item and all visible items down to the last on Ctrl + Shift + End', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item0_0); tree.onKeydown(shift()); tree.onKeydown(end({control: true, shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + expect(tree.inputs.values()).toEqual(['Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); }); it('should not change selection when the tree is disabled', () => { @@ -873,50 +883,56 @@ describe('Tree Pattern', () => { const {tree} = createTree(treeExample, treeInputs); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual([]); + expect(tree.inputs.values()).toEqual([]); }); it('should not select disabled items on navigation', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: true, selectable: true}, - {value: 'C', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: true, selectable: true, expanded: false}, + {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.skipDisabled.set(true); + treeInputs.softDisabled.set(false); const {tree, allItems} = createTree(localTreeData, treeInputs); - treeInputs.value.set(['A']); + treeInputs.values.set(['A']); tree.listBehavior.goto(getItemByValue(allItems(), 'A')); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual(['C']); + expect(tree.inputs.values()).toEqual(['C']); }); it('should not select non-selectable items on navigation', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: false, selectable: false}, - {value: 'C', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: false, selectable: false, expanded: false}, + {value: 'C', disabled: false, selectable: true, expanded: false}, ]; const {tree, allItems} = createTree(localTreeData, treeInputs); - treeInputs.value.set(['A']); + treeInputs.values.set(['A']); tree.listBehavior.goto(getItemByValue(allItems(), 'A')); tree.onKeydown(down()); tree.onKeydown(down()); - expect(tree.inputs.value()).toEqual(['C']); + expect(tree.inputs.values()).toEqual(['C']); }); it('should deselect all except the focused item on Ctrl + A if all are selected', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item0_0); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + expect(tree.inputs.values()).toEqual([ + 'Item 0', + 'Item 0-0', + 'Item 0-1', + 'Item 1', + 'Item 2', + ]); tree.onKeydown(a({control: true})); - expect(tree.inputs.value()).toEqual(['Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0-0']); }); }); }); @@ -934,10 +950,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('follow'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -949,9 +965,9 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item1 = getItemByValue(allItems(), 'Item 1'); - tree.onPointerdown(createClickEvent(item1.element())); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should not change selection when the tree is disabled', () => { @@ -959,8 +975,8 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item1 = getItemByValue(allItems(), 'Item 1'); - tree.onPointerdown(createClickEvent(item1.element())); - expect(tree.inputs.value()).toEqual([]); + tree.onPointerdown(createClickEvent(item1.element()!)); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -976,10 +992,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('explicit'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -991,22 +1007,22 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item1 = getItemByValue(allItems(), 'Item 1'); - tree.onPointerdown(createClickEvent(item1.element())); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should not deselect item on click', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item1 = getItemByValue(allItems(), 'Item 1'); - tree.onPointerdown(createClickEvent(item1.element())); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); - tree.onPointerdown(createClickEvent(item1.element())); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(tree.activeItem()).toBe(item1); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should not change selection when the tree is disabled', () => { @@ -1014,8 +1030,8 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item1 = getItemByValue(allItems(), 'Item 1'); - tree.onPointerdown(createClickEvent(item1.element())); - expect(tree.inputs.value()).toEqual([]); + tree.onPointerdown(createClickEvent(item1.element()!)); + expect(tree.inputs.values()).toEqual([]); }); }); @@ -1031,10 +1047,10 @@ describe('Tree Pattern', () => { multi: signal(true), orientation: signal('vertical'), selectionMode: signal('explicit'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -1047,25 +1063,25 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); - tree.onPointerdown(createClickEvent(item0.element())); - expect(tree.inputs.value()).toEqual(['Item 0']); + tree.onPointerdown(createClickEvent(item0.element()!)); + expect(tree.inputs.values()).toEqual(['Item 0']); - tree.onPointerdown(createClickEvent(item1.element())); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 1']); + tree.onPointerdown(createClickEvent(item1.element()!)); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 1']); - tree.onPointerdown(createClickEvent(item0.element())); - expect(tree.inputs.value()).toEqual(['Item 1']); + tree.onPointerdown(createClickEvent(item0.element()!)); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should navigate and select range from anchor on shift + click', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(shift()); - tree.onPointerdown(createClickEvent(item1.element(), {shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1']); + tree.onPointerdown(createClickEvent(item1.element()!, {shift: true})); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1']); }); }); @@ -1081,10 +1097,10 @@ describe('Tree Pattern', () => { multi: signal(true), orientation: signal('vertical'), selectionMode: signal('follow'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -1097,10 +1113,10 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); - tree.onPointerdown(createClickEvent(item0.element())); - expect(tree.inputs.value()).toEqual(['Item 0']); - tree.onPointerdown(createClickEvent(item1.element())); - expect(tree.inputs.value()).toEqual(['Item 1']); + tree.onPointerdown(createClickEvent(item0.element()!)); + expect(tree.inputs.values()).toEqual(['Item 0']); + tree.onPointerdown(createClickEvent(item1.element()!)); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should navigate and toggle selection on ctrl + click', () => { @@ -1108,11 +1124,11 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); - tree.onPointerdown(createClickEvent(item0.element())); // Select and expand Item 0 - tree.onPointerdown(createClickEvent(item1.element(), {control: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 1']); - tree.onPointerdown(createClickEvent(item0.element(), {control: true})); - expect(tree.inputs.value()).toEqual(['Item 1']); + tree.onPointerdown(createClickEvent(item0.element()!)); // Select and expand Item 0 + tree.onPointerdown(createClickEvent(item1.element()!, {control: true})); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 1']); + tree.onPointerdown(createClickEvent(item0.element()!, {control: true})); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should navigate and select range from anchor on shift + click', () => { @@ -1120,10 +1136,16 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item2 = getItemByValue(allItems(), 'Item 2'); - tree.onPointerdown(createClickEvent(item0.element())); // Select and expand Item 0 + tree.onPointerdown(createClickEvent(item0.element()!)); // Select and expand Item 0 tree.onKeydown(shift()); - tree.onPointerdown(createClickEvent(item2.element(), {shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1', 'Item 2']); + tree.onPointerdown(createClickEvent(item2.element()!, {shift: true})); + expect(tree.inputs.values()).toEqual([ + 'Item 0', + 'Item 0-0', + 'Item 0-1', + 'Item 1', + 'Item 2', + ]); }); it('should select a new range on subsequent shift + clicks, deselecting previous range', () => { @@ -1131,36 +1153,36 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item1 = getItemByValue(allItems(), 'Item 1'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(shift()); - tree.onPointerdown(createClickEvent(item1.element(), {shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1']); + tree.onPointerdown(createClickEvent(item1.element()!, {shift: true})); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0', 'Item 0-1', 'Item 1']); - tree.onPointerdown(createClickEvent(item0_0.element(), {shift: true})); - expect(tree.inputs.value()).toEqual(['Item 0', 'Item 0-0']); + tree.onPointerdown(createClickEvent(item0_0.element()!, {shift: true})); + expect(tree.inputs.values()).toEqual(['Item 0', 'Item 0-0']); }); it('should not select disabled items on click', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: true, selectable: true}, + {value: 'A', disabled: true, selectable: true, expanded: false}, ]; const {tree, allItems} = createTree(localTreeData, treeInputs); const itemA = getItemByValue(allItems(), 'A'); - tree.onPointerdown(createClickEvent(itemA.element())); - expect(tree.inputs.value()).toEqual([]); + tree.onPointerdown(createClickEvent(itemA.element()!)); + expect(tree.inputs.values()).toEqual([]); expect(tree.activeItem()).toBe(itemA); }); it('should not select non-selectable items on click', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: false}, + {value: 'A', disabled: false, selectable: false, expanded: false}, ]; const {tree, allItems} = createTree(localTreeData, treeInputs); const itemA = getItemByValue(allItems(), 'A'); - tree.onPointerdown(createClickEvent(itemA.element())); - expect(tree.inputs.value()).toEqual([]); + tree.onPointerdown(createClickEvent(itemA.element()!)); + expect(tree.inputs.values()).toEqual([]); }); }); }); @@ -1177,10 +1199,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('explicit'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -1189,15 +1211,49 @@ describe('Tree Pattern', () => { }); it('should correctly compute visible state', () => { - const {allItems} = createTree(treeExample, treeInputs); + const {allItems} = createTree( + [ + { + value: 'Item 0', + children: [ + { + value: 'Item 0-0', + children: [ + {value: 'Item 0-0-0', disabled: false, selectable: true, expanded: false}, + ], + disabled: false, + selectable: true, + expanded: false, + }, + { + value: 'Item 0-1', + disabled: false, + selectable: true, + expanded: false, + }, + ], + disabled: false, + selectable: true, + expanded: false, + }, + ], + treeInputs, + ); const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); + const item0_0_0 = getItemByValue(allItems(), 'Item 0-0-0'); expect(item0_0.visible()).toBe(false); - item0.expansion.open(); + expect(item0_0_0.visible()).toBe(false); + item0.expanded.set(true); + expect(item0_0.visible()).toBe(true); + expect(item0_0_0.visible()).toBe(false); + item0_0.expanded.set(true); expect(item0_0.visible()).toBe(true); - item0.expansion.close(); + expect(item0_0_0.visible()).toBe(true); + item0.expanded.set(false); expect(item0_0.visible()).toBe(false); + expect(item0_0_0.visible()).toBe(false); }); it('should correctly compute expanded state', () => { @@ -1205,7 +1261,7 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); expect(item0.expanded()).toBe(false); - item0.expansion.open(); + item0.expanded.set(true); expect(item0.expanded()).toBe(true); }); @@ -1226,7 +1282,7 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); tree.listBehavior.goto(item0); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(right()); expect(tree.activeItem()).toBe(item0_0); @@ -1247,7 +1303,7 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); tree.listBehavior.goto(item0); - item0.expansion.open(); + item0.expanded.set(true); expect(item0.expanded()).toBe(true); tree.onKeydown(left()); @@ -1259,7 +1315,7 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item0_0); tree.onKeydown(left()); @@ -1295,9 +1351,9 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); expect(item0.expanded()).toBe(false); - tree.onPointerdown(createClickEvent(item0.element())); + tree.onPointerdown(createClickEvent(item0.element()!)); expect(item0.expanded()).toBe(true); - tree.onPointerdown(createClickEvent(item0.element())); + tree.onPointerdown(createClickEvent(item0.element()!)); expect(item0.expanded()).toBe(false); }); @@ -1306,7 +1362,7 @@ describe('Tree Pattern', () => { const item1 = getItemByValue(allItems(), 'Item 1'); expect(item1.expanded()).toBe(false); - tree.onPointerdown(createClickEvent(item1.element())); + tree.onPointerdown(createClickEvent(item1.element()!)); expect(item1.expanded()).toBe(false); }); @@ -1315,7 +1371,7 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); itemPatternInputsMap.get(item0.id())!.disabled.set(true); - tree.onPointerdown(createClickEvent(item0.element())); + tree.onPointerdown(createClickEvent(item0.element()!)); expect(item0.expanded()).toBe(false); }); @@ -1324,7 +1380,7 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - tree.onPointerdown(createClickEvent(item0.element())); + tree.onPointerdown(createClickEvent(item0.element()!)); expect(item0.expanded()).toBe(false); }); @@ -1340,11 +1396,11 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); tree.listBehavior.goto(item0); - item0.expansion.open(); + item0.expanded.set(true); tree.onKeydown(right()); expect(tree.activeItem()).toBe(item0_0); - expect(tree.inputs.value()).toEqual(['Item 0-0']); + expect(tree.inputs.values()).toEqual(['Item 0-0']); }); it('should navigate and select the parent on collapseKey if collapsed (vertical)', () => { @@ -1352,12 +1408,12 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item0_0); tree.onKeydown(left()); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual(['Item 0']); + expect(tree.inputs.values()).toEqual(['Item 0']); }); }); @@ -1373,12 +1429,12 @@ describe('Tree Pattern', () => { const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); tree.listBehavior.goto(item0); - item0.expansion.open(); - tree.inputs.value.set(['Item 1']); // pre-select something else + item0.expanded.set(true); + tree.inputs.values.set(['Item 1']); // pre-select something else tree.onKeydown(right({control: true})); expect(tree.activeItem()).toBe(item0_0); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); it('should navigate without select the parent on Ctrl + collapseKey if collapsed (vertical)', () => { @@ -1386,13 +1442,13 @@ describe('Tree Pattern', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); const item0_0 = getItemByValue(allItems(), 'Item 0-0'); - item0.expansion.open(); + item0.expanded.set(true); tree.listBehavior.goto(item0_0); - tree.inputs.value.set(['Item 1']); // pre-select something else + tree.inputs.values.set(['Item 1']); // pre-select something else tree.onKeydown(left({control: true})); expect(tree.activeItem()).toBe(item0); - expect(tree.inputs.value()).toEqual(['Item 1']); + expect(tree.inputs.values()).toEqual(['Item 1']); }); }); }); @@ -1409,10 +1465,10 @@ describe('Tree Pattern', () => { multi: signal(false), orientation: signal('vertical'), selectionMode: signal('explicit'), - skipDisabled: signal(true), + softDisabled: signal(true), textDirection: signal('ltr'), typeaheadDelay: signal(0), - value: signal([]), + values: signal([]), wrap: signal(false), nav: signal(false), currentType: signal('page'), @@ -1422,8 +1478,8 @@ describe('Tree Pattern', () => { it('should set activeIndex to the first visible focusable item if no selection', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: false, selectable: true, expanded: false}, ]; const {tree, allItems} = createTree(localTreeData, treeInputs); @@ -1431,12 +1487,12 @@ describe('Tree Pattern', () => { expect(treeInputs.activeItem()).toBe(allItems()[0]); }); - it('should set activeIndex to the first visible focusable disabled item if skipDisabled is false and no selection', () => { + it('should set activeIndex to the first visible focusable disabled item if softDisabled is true and no selection', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: true, selectable: true}, - {value: 'B', disabled: false, selectable: true}, + {value: 'A', disabled: true, selectable: true, expanded: false}, + {value: 'B', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.skipDisabled.set(false); + treeInputs.softDisabled.set(true); const {tree, allItems} = createTree(localTreeData, treeInputs); tree.setDefaultState(); @@ -1445,11 +1501,11 @@ describe('Tree Pattern', () => { it('should set activeIndex to the first selected visible focusable item', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: false, selectable: true}, - {value: 'C', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: false, selectable: true, expanded: false}, + {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.value.set(['B']); + treeInputs.values.set(['B']); const {tree, allItems} = createTree(localTreeData, treeInputs); tree.setDefaultState(); @@ -1458,39 +1514,39 @@ describe('Tree Pattern', () => { it('should prioritize the first selected item in visible order', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: false, selectable: true}, - {value: 'C', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: false, selectable: true, expanded: false}, + {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.value.set(['C', 'A']); + treeInputs.values.set(['C', 'A']); const {tree, allItems} = createTree(localTreeData, treeInputs); tree.setDefaultState(); expect(treeInputs.activeItem()).toBe(allItems()[0]); }); - it('should skip a selected disabled item if skipDisabled is true', () => { + it('should skip a selected disabled item if softDisabled is false', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: true, selectable: true}, - {value: 'C', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: true, selectable: true, expanded: false}, + {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.value.set(['B']); - treeInputs.skipDisabled.set(true); + treeInputs.values.set(['B']); + treeInputs.softDisabled.set(false); const {tree, allItems} = createTree(localTreeData, treeInputs); tree.setDefaultState(); expect(treeInputs.activeItem()).toBe(allItems()[0]); }); - it('should select a selected disabled item if skipDisabled is false', () => { + it('should select a selected disabled item if softDisabled is true', () => { const localTreeData: TestTreeItem[] = [ - {value: 'A', disabled: false, selectable: true}, - {value: 'B', disabled: true, selectable: true}, - {value: 'C', disabled: false, selectable: true}, + {value: 'A', disabled: false, selectable: true, expanded: false}, + {value: 'B', disabled: true, selectable: true, expanded: false}, + {value: 'C', disabled: false, selectable: true, expanded: false}, ]; - treeInputs.value.set(['B']); - treeInputs.skipDisabled.set(false); + treeInputs.values.set(['B']); + treeInputs.softDisabled.set(true); const {tree, allItems} = createTree(localTreeData, treeInputs); tree.setDefaultState(); @@ -1500,7 +1556,7 @@ describe('Tree Pattern', () => { it('should set activeIndex to first visible focusable item if selected item is not visible', () => { const {tree, allItems} = createTree(treeExample, treeInputs); const item0 = getItemByValue(allItems(), 'Item 0'); - treeInputs.value.set(['Item 0-0']); + treeInputs.values.set(['Item 0-0']); expect(item0.expanded()).toBe(false); expect(getItemByValue(allItems(), 'Item 0-0').visible()).toBe(false); diff --git a/src/aria/ui-patterns/tree/tree.ts b/src/aria/private/tree/tree.ts similarity index 75% rename from src/aria/ui-patterns/tree/tree.ts rename to src/aria/private/tree/tree.ts index 0f17ff45b7df..b9748c327234 100644 --- a/src/aria/ui-patterns/tree/tree.ts +++ b/src/aria/private/tree/tree.ts @@ -6,14 +6,16 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, signal} from '@angular/core'; +import {computed} from '@angular/core'; import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; import {List, ListInputs, ListItem} from '../behaviors/list/list'; -import {ExpansionItem, ExpansionControl, ListExpansion} from '../behaviors/expansion/expansion'; +import {ExpansionItem, ListExpansion} from '../behaviors/expansion/expansion'; import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/event-manager'; /** Represents the required inputs for a tree item. */ -export interface TreeItemInputs extends Omit, 'index'> { +export interface TreeItemInputs + extends Omit, 'index'>, + Omit { /** The parent item. */ parent: SignalLike | TreePattern>; @@ -32,55 +34,51 @@ export interface TreeItemInputs extends Omit, 'index'> { */ export class TreeItemPattern implements ListItem, ExpansionItem { /** A unique identifier for this item. */ - readonly id: SignalLike; + readonly id: SignalLike = () => this.inputs.id(); /** The value of this item. */ - readonly value: SignalLike; + readonly value: SignalLike = () => this.inputs.value(); /** A reference to the item element. */ - readonly element: SignalLike; + readonly element: SignalLike = () => this.inputs.element()!; /** Whether the item is disabled. */ - readonly disabled: SignalLike; + readonly disabled: SignalLike = () => this.inputs.disabled(); /** The text used by the typeahead search. */ - readonly searchTerm: SignalLike; + readonly searchTerm: SignalLike = () => this.inputs.searchTerm(); /** The tree pattern this item belongs to. */ - readonly tree: SignalLike>; + readonly tree: SignalLike> = () => this.inputs.tree(); /** The parent item. */ - readonly parent: SignalLike | TreePattern>; + readonly parent: SignalLike | TreePattern> = () => this.inputs.parent(); /** The children items. */ - readonly children: SignalLike[]>; + readonly children: SignalLike[]> = () => this.inputs.children(); /** The position of this item among its siblings. */ readonly index = computed(() => this.tree().visibleItems().indexOf(this)); - /** The unique identifier used by the expansion behavior. */ - readonly expansionId: SignalLike; - /** Controls expansion for child items. */ - readonly expansionManager: ListExpansion; - - /** Controls expansion for this item. */ - readonly expansion: ExpansionControl; + readonly expansionBehavior: ListExpansion; /** Whether the item is expandable. It's expandable if children item exist. */ - readonly expandable: SignalLike; + readonly expandable: SignalLike = () => this.inputs.hasChildren(); /** Whether the item is selectable. */ - readonly selectable: SignalLike; + readonly selectable: SignalLike = () => this.inputs.selectable(); + + /** Whether the item is expanded. */ + readonly expanded: WritableSignalLike; /** The level of the current item in a tree. */ readonly level: SignalLike = computed(() => this.parent().level() + 1); - /** Whether this item is currently expanded. */ - readonly expanded = computed(() => this.expansion.isExpanded()); - /** Whether this item is visible. */ - readonly visible = computed(() => this.parent().expanded()); + readonly visible: SignalLike = computed( + () => this.parent().expanded() && this.parent().visible(), + ); /** The number of items under the same parent at the same level. */ readonly setsize = computed(() => this.parent().children().length); @@ -91,8 +89,8 @@ export class TreeItemPattern implements ListItem, ExpansionItem { /** Whether the item is active. */ readonly active = computed(() => this.tree().activeItem() === this); - /** The tabindex of the item. */ - readonly tabindex = computed(() => this.tree().listBehavior.getItemTabindex(this)); + /** The tab index of the item. */ + readonly tabIndex = computed(() => this.tree().listBehavior.getItemTabindex(this)); /** Whether the item is selected. */ readonly selected: SignalLike = computed(() => { @@ -102,7 +100,7 @@ export class TreeItemPattern implements ListItem, ExpansionItem { if (!this.selectable()) { return undefined; } - return this.tree().value().includes(this.value()); + return this.tree().values().includes(this.value()); }); /** The current type of this item. */ @@ -113,32 +111,14 @@ export class TreeItemPattern implements ListItem, ExpansionItem { if (!this.selectable()) { return undefined; } - return this.tree().value().includes(this.value()) ? this.tree().currentType() : undefined; + return this.tree().values().includes(this.value()) ? this.tree().currentType() : undefined; }); constructor(readonly inputs: TreeItemInputs) { - this.id = inputs.id; - this.value = inputs.value; - this.element = inputs.element; - this.disabled = inputs.disabled; - this.searchTerm = inputs.searchTerm; - this.expansionId = inputs.id; - this.tree = inputs.tree; - this.parent = inputs.parent; - this.children = inputs.children; - this.expandable = inputs.hasChildren; - this.selectable = inputs.selectable; - this.expansion = new ExpansionControl({ - ...inputs, - expandable: this.expandable, - expansionId: this.expansionId, - expansionManager: this.parent().expansionManager, - }); - this.expansionManager = new ListExpansion({ + this.expanded = inputs.expanded; + this.expansionBehavior = new ListExpansion({ ...inputs, multiExpandable: () => true, - // TODO(ok7sai): allow pre-expanded tree items. - expandedIds: signal([]), items: this.children, disabled: computed(() => this.tree()?.disabled() ?? false), }); @@ -168,14 +148,13 @@ export interface TreeInputs extends Omit, V>, ' currentType: SignalLike<'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'>; } -export interface TreePattern extends TreeInputs {} /** Controls the state and interactions of a tree view. */ -export class TreePattern { +export class TreePattern implements TreeInputs { /** The list behavior for the tree. */ readonly listBehavior: List, V>; /** Controls expansion for direct children of the tree root (top-level items). */ - readonly expansionManager: ListExpansion; + readonly expansionBehavior: ListExpansion; /** The root level is 0. */ readonly level = () => 0; @@ -183,11 +162,14 @@ export class TreePattern { /** The root is always expanded. */ readonly expanded = () => true; - /** The tabindex of the tree. */ - readonly tabindex: SignalLike<-1 | 0> = computed(() => this.listBehavior.tabindex()); + /** The root is always visible. */ + readonly visible = () => true; + + /** The tab index of the tree. */ + readonly tabIndex: SignalLike<-1 | 0> = computed(() => this.listBehavior.tabIndex()); /** The id of the current active item. */ - readonly activedescendant = computed(() => this.listBehavior.activedescendant()); + readonly activeDescendant = computed(() => this.listBehavior.activeDescendant()); /** The direct children of the root (top-level tree items). */ readonly children = computed(() => @@ -200,12 +182,15 @@ export class TreePattern { /** Whether the tree selection follows focus. */ readonly followFocus = computed(() => this.inputs.selectionMode() === 'follow'); + /** Whether the tree direction is RTL. */ + readonly isRtl = computed(() => this.inputs.textDirection() === 'rtl'); + /** The key for navigating to the previous item. */ readonly prevKey = computed(() => { if (this.inputs.orientation() === 'vertical') { return 'ArrowUp'; } - return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + return this.isRtl() ? 'ArrowRight' : 'ArrowLeft'; }); /** The key for navigating to the next item. */ @@ -213,7 +198,7 @@ export class TreePattern { if (this.inputs.orientation() === 'vertical') { return 'ArrowDown'; } - return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + return this.isRtl() ? 'ArrowLeft' : 'ArrowRight'; }); /** The key for collapsing an item or moving to its parent. */ @@ -221,7 +206,7 @@ export class TreePattern { if (this.inputs.orientation() === 'horizontal') { return 'ArrowUp'; } - return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + return this.isRtl() ? 'ArrowRight' : 'ArrowLeft'; }); /** The key for expanding an item or moving to its first child. */ @@ -229,7 +214,7 @@ export class TreePattern { if (this.inputs.orientation() === 'horizontal') { return 'ArrowDown'; } - return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + return this.isRtl() ? 'ArrowLeft' : 'ArrowRight'; }); /** Represents the space key. Does nothing when the user is actively using typeahead. */ @@ -275,13 +260,13 @@ export class TreePattern { if (!this.followFocus() && this.inputs.multi()) { manager .on(this.dynamicSpaceKey, () => list.toggle()) - .on('Enter', () => list.toggle()) + .on('Enter', () => list.toggle(), {preventDefault: !this.nav()}) .on([Modifier.Ctrl, Modifier.Meta], 'A', () => list.toggleAll()); } if (!this.followFocus() && !this.inputs.multi()) { manager.on(this.dynamicSpaceKey, () => list.selectOne()); - manager.on('Enter', () => list.selectOne()); + manager.on('Enter', () => list.selectOne(), {preventDefault: !this.nav()}); } if (this.inputs.multi() && this.followFocus()) { @@ -329,63 +314,58 @@ export class TreePattern { }); /** A unique identifier for the tree. */ - id: SignalLike; + readonly id: SignalLike = () => this.inputs.id(); + + /** The host native element. */ + readonly element: SignalLike = () => this.inputs.element()!; /** Whether the tree is in navigation mode. */ - nav: SignalLike; + readonly nav: SignalLike = () => this.inputs.nav(); /** The aria-current type. */ - currentType: SignalLike<'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'>; + readonly currentType: SignalLike< + 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' + > = () => this.inputs.currentType(); /** All items in the tree, in document order (DFS-like, a flattened list). */ - allItems: SignalLike[]>; + readonly allItems: SignalLike[]> = () => this.inputs.allItems(); + + /** The focus strategy used by the tree. */ + readonly focusMode: SignalLike<'roving' | 'activedescendant'> = () => this.inputs.focusMode(); /** Whether the tree is disabled. */ - disabled: SignalLike; + readonly disabled: SignalLike = () => this.inputs.disabled(); /** The currently active item in the tree. */ - activeItem: WritableSignalLike | undefined> = signal(undefined); + readonly activeItem: WritableSignalLike | undefined>; - /** Whether disabled items should be skipped when navigating. */ - skipDisabled: SignalLike; + /** Whether disabled items should be focusable. */ + readonly softDisabled: SignalLike = () => this.inputs.softDisabled(); /** Whether the focus should wrap when navigating past the first or last item. */ - wrap: SignalLike; + readonly wrap: SignalLike = () => this.inputs.wrap(); /** The orientation of the tree. */ - orientation: SignalLike<'vertical' | 'horizontal'>; + readonly orientation: SignalLike<'vertical' | 'horizontal'> = () => this.inputs.orientation(); /** The text direction of the tree. */ - textDirection: SignalLike<'ltr' | 'rtl'>; + readonly textDirection: SignalLike<'ltr' | 'rtl'> = () => this.textDirection(); /** Whether multiple items can be selected at the same time. */ - multi: SignalLike; + readonly multi: SignalLike = computed(() => (this.nav() ? false : this.inputs.multi())); /** The selection mode of the tree. */ - selectionMode: SignalLike<'follow' | 'explicit'>; + readonly selectionMode: SignalLike<'follow' | 'explicit'> = () => this.inputs.selectionMode(); /** The delay in milliseconds to wait before clearing the typeahead buffer. */ - typeaheadDelay: SignalLike; + readonly typeaheadDelay: SignalLike = () => this.inputs.typeaheadDelay(); - /** The current value of the tree (the selected items). */ - value: WritableSignalLike; + /** The current selected items of the tree. */ + readonly values: WritableSignalLike; constructor(readonly inputs: TreeInputs) { - this.id = inputs.id; - this.nav = inputs.nav; - this.currentType = inputs.currentType; - this.allItems = inputs.allItems; - this.focusMode = inputs.focusMode; - this.disabled = inputs.disabled; this.activeItem = inputs.activeItem; - this.skipDisabled = inputs.skipDisabled; - this.wrap = inputs.wrap; - this.orientation = inputs.orientation; - this.textDirection = inputs.textDirection; - this.multi = computed(() => (this.nav() ? false : this.inputs.multi())); - this.selectionMode = inputs.selectionMode; - this.typeaheadDelay = inputs.typeaheadDelay; - this.value = inputs.value; + this.values = inputs.values; this.listBehavior = new List({ ...inputs, @@ -393,10 +373,8 @@ export class TreePattern { multi: this.multi, }); - this.expansionManager = new ListExpansion({ + this.expansionBehavior = new ListExpansion({ multiExpandable: () => true, - // TODO(ok7sai): allow pre-expanded tree items. - expandedIds: signal([]), items: this.children, disabled: this.disabled, }); @@ -462,7 +440,7 @@ export class TreePattern { if (item.expanded()) { this.collapse(); } else { - item.expansion.open(); + this.expansionBehavior.open(item); } } @@ -472,7 +450,7 @@ export class TreePattern { if (!item || !this.listBehavior.isFocusable(item)) return; if (item.expandable() && !item.expanded()) { - item.expansion.open(); + this.expansionBehavior.open(item); } else if ( item.expanded() && item.children().some(item => this.listBehavior.isFocusable(item)) @@ -485,7 +463,7 @@ export class TreePattern { expandSiblings(item?: TreeItemPattern) { item ??= this.activeItem(); const siblings = item?.parent()?.children(); - siblings?.forEach(item => item.expansion.open()); + siblings?.forEach(item => this.expansionBehavior.open(item)); } /** Collapses a tree item. */ @@ -494,7 +472,7 @@ export class TreePattern { if (!item || !this.listBehavior.isFocusable(item)) return; if (item.expandable() && item.expanded()) { - item.expansion.close(); + this.expansionBehavior.close(item); } else if (item.parent() && item.parent() !== this) { const parentItem = item.parent(); if (parentItem instanceof TreeItemPattern && this.listBehavior.isFocusable(parentItem)) { diff --git a/src/aria/ui-patterns/ui-pattern-rules.md b/src/aria/private/ui-pattern-rules.md similarity index 100% rename from src/aria/ui-patterns/ui-pattern-rules.md rename to src/aria/private/ui-pattern-rules.md diff --git a/src/aria/radio-group/BUILD.bazel b/src/aria/radio-group/BUILD.bazel deleted file mode 100644 index 74a189efbb42..000000000000 --- a/src/aria/radio-group/BUILD.bazel +++ /dev/null @@ -1,38 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite") - -package(default_visibility = ["//visibility:public"]) - -ng_project( - name = "radio-group", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//:node_modules/@angular/core", - "//src/aria/toolbar", - "//src/aria/ui-patterns", - "//src/cdk/a11y", - "//src/cdk/bidi", - ], -) - -ng_project( - name = "unit_test_sources", - testonly = True, - srcs = glob( - ["**/*.spec.ts"], - exclude = ["**/*.e2e.spec.ts"], - ), - deps = [ - ":radio-group", - "//:node_modules/@angular/core", - "//:node_modules/@angular/platform-browser", - "//src/cdk/testing/private", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [":unit_test_sources"], -) diff --git a/src/aria/radio-group/index.ts b/src/aria/radio-group/index.ts deleted file mode 100644 index 34aab88aafe3..000000000000 --- a/src/aria/radio-group/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -export {RadioGroup, RadioButton} from './radio-group'; diff --git a/src/aria/radio-group/radio-group.spec.ts b/src/aria/radio-group/radio-group.spec.ts deleted file mode 100644 index b0643cd5a1cf..000000000000 --- a/src/aria/radio-group/radio-group.spec.ts +++ /dev/null @@ -1,570 +0,0 @@ -import {Component, DebugElement, signal} from '@angular/core'; -import {RadioButton, RadioGroup} from './radio-group'; -import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {By} from '@angular/platform-browser'; -import {Direction} from '@angular/cdk/bidi'; -import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; - -describe('RadioGroup', () => { - let fixture: ComponentFixture; - let radioGroup: DebugElement; - let radioButtons: DebugElement[]; - let radioGroupInstance: RadioGroup; - let radioGroupElement: HTMLElement; - let radioButtonElements: HTMLElement[]; - - const keydown = (key: string) => { - radioGroupElement.dispatchEvent(new KeyboardEvent('keydown', {bubbles: true, key})); - fixture.detectChanges(); - }; - - const click = (index: number) => { - radioButtonElements[index].dispatchEvent(new PointerEvent('pointerdown', {bubbles: true})); - fixture.detectChanges(); - }; - - const space = () => keydown(' '); - const enter = () => keydown('Enter'); - const up = () => keydown('ArrowUp'); - const down = () => keydown('ArrowDown'); - const left = () => keydown('ArrowLeft'); - const right = () => keydown('ArrowRight'); - const home = () => keydown('Home'); - const end = () => keydown('End'); - - function setupRadioGroup(opts?: { - orientation?: 'horizontal' | 'vertical'; - disabled?: boolean; - readonly?: boolean; - value?: number | null; - skipDisabled?: boolean; - focusMode?: 'roving' | 'activedescendant'; - disabledOptions?: number[]; - options?: TestOption[]; - textDirection?: Direction; - }) { - TestBed.configureTestingModule({ - providers: [provideFakeDirectionality(opts?.textDirection ?? 'ltr')], - }); - - fixture = TestBed.createComponent(RadioGroupExample); - const testComponent = fixture.componentInstance; - - if (opts?.orientation !== undefined) { - testComponent.orientation = opts.orientation; - } - if (opts?.disabled !== undefined) { - testComponent.disabled = opts.disabled; - } - if (opts?.readonly !== undefined) { - testComponent.readonly = opts.readonly; - } - if (opts?.value !== undefined) { - testComponent.value = opts.value; - } - if (opts?.skipDisabled !== undefined) { - testComponent.skipDisabled = opts.skipDisabled; - } - if (opts?.focusMode !== undefined) { - testComponent.focusMode = opts.focusMode; - } - if (opts?.options !== undefined) { - testComponent.options.set(opts.options); - } - if (opts?.disabledOptions !== undefined) { - opts.disabledOptions.forEach(index => { - testComponent.options()[index].disabled = true; - }); - } - - fixture.detectChanges(); - defineTestVariables(fixture); - } - - function setupDefaultRadioGroup() { - TestBed.configureTestingModule({ - providers: [provideFakeDirectionality('ltr')], - }); - - const fixture = TestBed.createComponent(DefaultRadioGroupExample); - fixture.detectChanges(); - defineTestVariables(fixture); - } - - function defineTestVariables(fixture: ComponentFixture) { - radioGroup = fixture.debugElement.query(By.directive(RadioGroup)); - radioButtons = fixture.debugElement.queryAll(By.directive(RadioButton)); - radioGroupInstance = radioGroup.injector.get>(RadioGroup); - radioGroupElement = radioGroup.nativeElement; - radioButtonElements = radioButtons.map(radioButton => radioButton.nativeElement); - } - - afterEach(async () => { - await runAccessibilityChecks(radioGroupElement); - }); - - describe('ARIA attributes and roles', () => { - describe('default configuration', () => { - it('should correctly set the role attribute to "radiogroup"', () => { - setupDefaultRadioGroup(); - expect(radioGroupElement.getAttribute('role')).toBe('radiogroup'); - }); - - it('should correctly set the role attribute to "radio" for the radio buttons', () => { - setupDefaultRadioGroup(); - radioButtonElements.forEach(radioButtonElement => { - expect(radioButtonElement.getAttribute('role')).toBe('radio'); - }); - }); - - it('should set aria-orientation to "vertical"', () => { - setupDefaultRadioGroup(); - expect(radioGroupElement.getAttribute('aria-orientation')).toBe('vertical'); - }); - - it('should set aria-disabled to false', () => { - setupDefaultRadioGroup(); - expect(radioGroupElement.getAttribute('aria-disabled')).toBe('false'); - }); - - it('should set aria-readonly to false', () => { - setupDefaultRadioGroup(); - expect(radioGroupElement.getAttribute('aria-readonly')).toBe('false'); - }); - }); - - describe('custom configuration', () => { - it('should be able to set aria-orientation to "vertical"', () => { - setupRadioGroup({orientation: 'vertical'}); - expect(radioGroupElement.getAttribute('aria-orientation')).toBe('vertical'); - }); - - it('should be able to set aria-disabled to true', () => { - setupRadioGroup({disabled: true}); - expect(radioGroupElement.getAttribute('aria-disabled')).toBe('true'); - }); - - it('should be able to set aria-readonly to true', () => { - setupRadioGroup({readonly: true}); - expect(radioGroupElement.getAttribute('aria-readonly')).toBe('true'); - }); - }); - - describe('roving focus mode', () => { - it('should have tabindex="-1" when focusMode is "roving"', () => { - setupRadioGroup({focusMode: 'roving'}); - expect(radioGroupElement.getAttribute('tabindex')).toBe('-1'); - }); - - it('should set tabindex="0" when disabled', () => { - setupRadioGroup({disabled: true, focusMode: 'roving'}); - expect(radioGroupElement.getAttribute('tabindex')).toBe('0'); - }); - - it('should set initial focus on the selected option', () => { - setupRadioGroup({focusMode: 'roving', value: 3}); - expect(radioButtonElements[3].getAttribute('tabindex')).toBe('0'); - }); - - it('should set initial focus on the first option if none are selected', () => { - setupRadioGroup({focusMode: 'roving'}); - expect(radioButtonElements[0].getAttribute('tabindex')).toBe('0'); - }); - - it('should not have aria-activedescendant when focusMode is "roving"', () => { - setupRadioGroup({focusMode: 'roving'}); - expect(radioGroupElement.getAttribute('aria-activedescendant')).toBeNull(); - }); - }); - - describe('activedescendant focus mode', () => { - it('should have tabindex="0"', () => { - setupRadioGroup({focusMode: 'activedescendant'}); - expect(radioGroupElement.getAttribute('tabindex')).toBe('0'); - }); - - it('should set initial focus on the selected option', () => { - setupRadioGroup({focusMode: 'activedescendant', value: 3}); - expect(radioGroupElement.getAttribute('aria-activedescendant')).toBe( - radioButtonElements[3].id, - ); - }); - - it('should set initial focus on the first option if none are selected', () => { - setupRadioGroup({focusMode: 'activedescendant'}); - expect(radioGroupElement.getAttribute('aria-activedescendant')).toBe( - radioButtonElements[0].id, - ); - }); - }); - }); - - describe('value and selection', () => { - it('should select the radio button corresponding to the value input', () => { - setupRadioGroup(); - radioGroupInstance.value.set(1); - fixture.detectChanges(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('true'); - }); - - it('should update the value model when the value of a radio group is changed through the ui', () => { - setupRadioGroup(); - click(1); - expect(radioGroupInstance.value()).toBe(1); - }); - - describe('pointer interaction', () => { - it('should update the group value when a radio button is selected via pointer click', () => { - setupRadioGroup(); - click(1); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('true'); - }); - - it('should only allow one radio button to be selected at a time', () => { - setupRadioGroup(); - click(1); - click(2); - expect(radioButtonElements[0].getAttribute('aria-checked')).toBe('false'); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('false'); - expect(radioButtonElements[2].getAttribute('aria-checked')).toBe('true'); - expect(radioButtonElements[3].getAttribute('aria-checked')).toBe('false'); - expect(radioButtonElements[4].getAttribute('aria-checked')).toBe('false'); - }); - - it('should not change the value if the radio group is readonly', () => { - setupRadioGroup({readonly: true}); - click(3); - expect(radioButtonElements[3].getAttribute('aria-checked')).toBe('false'); - }); - - it('should not change the value if the radio group is disabled', () => { - setupRadioGroup({disabled: true}); - click(3); - expect(radioButtonElements[3].getAttribute('aria-checked')).toBe('false'); - }); - - it('should not change the value if a disabled radio button is clicked', () => { - setupRadioGroup({disabledOptions: [2]}); - click(2); - expect(radioButtonElements[2].getAttribute('aria-checked')).toBe('false'); - }); - - it('should not change the value if a radio button is clicked in a readonly group', () => { - setupRadioGroup({readonly: true}); - click(1); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('false'); - }); - }); - - describe('keyboard interaction', () => { - it('should update the group value on Space', () => { - setupRadioGroup(); - space(); - expect(radioButtonElements[0].getAttribute('aria-checked')).toBe('true'); - }); - - it('should update the group value on Enter', () => { - setupRadioGroup(); - enter(); - expect(radioButtonElements[0].getAttribute('aria-checked')).toBe('true'); - }); - - it('should not change the value if the radio group is readonly', () => { - setupRadioGroup({orientation: 'horizontal', readonly: true}); - right(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('false'); - }); - - it('should not change the value if the radio group is disabled', () => { - setupRadioGroup({orientation: 'horizontal', disabled: true}); - right(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('false'); - }); - - describe('horizontal orientation', () => { - it('should update the group value on ArrowRight', () => { - setupRadioGroup({orientation: 'horizontal'}); - right(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('true'); - }); - - it('should update the group value on ArrowLeft', () => { - setupRadioGroup({orientation: 'horizontal'}); - right(); - right(); - left(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('true'); - }); - - describe('text direction rtl', () => { - it('should update the group value on ArrowLeft', () => { - setupRadioGroup({orientation: 'horizontal', textDirection: 'rtl'}); - left(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('true'); - }); - - it('should update the group value on ArrowRight', () => { - setupRadioGroup({orientation: 'horizontal', textDirection: 'rtl'}); - left(); - left(); - right(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('true'); - }); - }); - }); - - describe('vertical orientation', () => { - it('should update the group value on ArrowDown', () => { - setupRadioGroup({orientation: 'vertical'}); - down(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('true'); - }); - - it('should update the group value on ArrowUp', () => { - setupRadioGroup({orientation: 'vertical'}); - down(); - down(); - up(); - expect(radioButtonElements[1].getAttribute('aria-checked')).toBe('true'); - }); - }); - }); - }); - - function runNavigationTests( - focusMode: 'activedescendant' | 'roving', - isFocused: (index: number) => boolean, - ) { - describe(`keyboard navigation (focusMode="${focusMode}")`, () => { - it('should move focus to and select the last enabled radio button on End', () => { - setupRadioGroup({focusMode}); - end(); - expect(isFocused(4)).toBe(true); - }); - - it('should move focus to and select the first enabled radio button on Home', () => { - setupRadioGroup({focusMode}); - end(); - home(); - expect(isFocused(0)).toBe(true); - }); - - it('should not allow keyboard navigation or selection if the group is disabled', () => { - setupRadioGroup({focusMode, orientation: 'horizontal', disabled: true}); - right(); - expect(isFocused(0)).toBe(false); - }); - - it('should allow keyboard navigation if the group is readonly', () => { - setupRadioGroup({focusMode, orientation: 'horizontal', readonly: true}); - right(); - expect(isFocused(1)).toBe(true); - }); - - describe('vertical orientation', () => { - it('should move focus to the next radio button on ArrowDown', () => { - setupRadioGroup({focusMode, orientation: 'vertical'}); - down(); - expect(isFocused(1)).toBe(true); - }); - - it('should move focus to the previous radio button on ArrowUp', () => { - setupRadioGroup({focusMode, orientation: 'vertical'}); - down(); - down(); - up(); - expect(isFocused(1)).toBe(true); - }); - - it('should skip disabled radio buttons (skipDisabled="true")', () => { - setupRadioGroup({ - focusMode, - orientation: 'vertical', - skipDisabled: true, - disabledOptions: [1, 2], - }); - down(); - expect(isFocused(3)).toBe(true); - }); - - it('should not skip disabled radio buttons (skipDisabled="false")', () => { - setupRadioGroup({ - focusMode, - orientation: 'vertical', - skipDisabled: false, - disabledOptions: [1, 2], - }); - down(); - expect(isFocused(1)).toBe(true); - }); - }); - - describe('horizontal orientation', () => { - it('should move focus to the next radio button on ArrowRight', () => { - setupRadioGroup({focusMode, orientation: 'horizontal'}); - right(); - expect(isFocused(1)).toBe(true); - }); - - it('should move focus to the previous radio button on ArrowLeft', () => { - setupRadioGroup({focusMode, orientation: 'horizontal'}); - right(); - right(); - left(); - expect(isFocused(1)).toBe(true); - }); - - it('should skip disabled radio buttons (skipDisabled="true")', () => { - setupRadioGroup({ - focusMode, - orientation: 'horizontal', - skipDisabled: true, - disabledOptions: [1, 2], - }); - right(); - expect(isFocused(3)).toBe(true); - }); - - it('should not skip disabled radio buttons (skipDisabled="false")', () => { - setupRadioGroup({ - focusMode, - orientation: 'horizontal', - skipDisabled: false, - disabledOptions: [1, 2], - }); - right(); - expect(isFocused(1)).toBe(true); - }); - - describe('text direction rtl', () => { - it('should move focus to the next radio button on ArrowLeft', () => { - setupRadioGroup({focusMode, textDirection: 'rtl', orientation: 'horizontal'}); - left(); - expect(isFocused(1)).toBe(true); - }); - - it('should move focus to the previous radio button on ArrowRight', () => { - setupRadioGroup({focusMode, textDirection: 'rtl', orientation: 'horizontal'}); - left(); - left(); - right(); - expect(isFocused(1)).toBe(true); - }); - - it('should skip disabled radio buttons when navigating', () => { - setupRadioGroup({ - focusMode, - skipDisabled: true, - textDirection: 'rtl', - disabledOptions: [1, 2], - orientation: 'horizontal', - }); - left(); - expect(isFocused(3)).toBe(true); - }); - }); - }); - }); - - describe(`pointer navigation (focusMode="${focusMode}")`, () => { - it('should move focus to the clicked radio button', () => { - setupRadioGroup({focusMode}); - click(3); - expect(isFocused(3)).toBe(true); - }); - - it('should move focus to the clicked radio button if the group is disabled (skipDisabled="true")', () => { - setupRadioGroup({focusMode, skipDisabled: true, disabled: true}); - click(3); - expect(isFocused(3)).toBe(false); - }); - - it('should not move focus to the clicked radio button if the group is disabled (skipDisabled="false")', () => { - setupRadioGroup({focusMode, skipDisabled: true, disabled: true}); - click(3); - expect(isFocused(0)).toBe(false); - }); - - it('should move focus to the clicked radio button if the group is readonly', () => { - setupRadioGroup({focusMode, readonly: true}); - click(3); - expect(isFocused(3)).toBe(true); - }); - }); - } - - runNavigationTests('roving', i => { - return radioButtonElements[i].getAttribute('tabindex') === '0'; - }); - - runNavigationTests('activedescendant', i => { - return radioGroupElement.getAttribute('aria-activedescendant') === radioButtonElements[i].id; - }); - - describe('failure cases', () => { - it('should handle an empty set of radio buttons gracefully', () => { - setupRadioGroup({options: []}); - expect(radioButtons.length).toBe(0); - }); - - describe('bad accessibility violations', () => { - it('should report when the selected radio button is disabled and skipDisabled is true', () => { - spyOn(console, 'error'); - setupRadioGroup({value: 1, skipDisabled: true, disabledOptions: [1]}); - expect(console.error).toHaveBeenCalled(); - }); - }); - }); -}); - -interface TestOption { - value: number; - label: string; - disabled: boolean; -} - -@Component({ - template: ` -
        - @for (option of options(); track option.value) { -
        {{ option.label }}
        - } -
        - `, - imports: [RadioGroup, RadioButton], -}) -class RadioGroupExample { - options = signal([ - {value: 0, label: '0', disabled: false}, - {value: 1, label: '1', disabled: false}, - {value: 2, label: '2', disabled: false}, - {value: 3, label: '3', disabled: false}, - {value: 4, label: '4', disabled: false}, - ]); - - value: number | null = null; - disabled = false; - readonly = false; - skipDisabled = true; - focusMode: 'roving' | 'activedescendant' = 'roving'; - orientation: 'horizontal' | 'vertical' = 'horizontal'; -} - -@Component({ - template: ` -
        -
        0
        -
        1
        -
        2
        -
        - `, - imports: [RadioGroup, RadioButton], -}) -class DefaultRadioGroupExample {} diff --git a/src/aria/radio-group/radio-group.ts b/src/aria/radio-group/radio-group.ts deleted file mode 100644 index a832139e92ec..000000000000 --- a/src/aria/radio-group/radio-group.ts +++ /dev/null @@ -1,249 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import { - afterRenderEffect, - booleanAttribute, - computed, - contentChildren, - Directive, - ElementRef, - inject, - input, - linkedSignal, - model, - signal, - WritableSignal, -} from '@angular/core'; -import { - RadioButtonPattern, - RadioGroupInputs, - RadioGroupPattern, - ToolbarRadioGroupInputs, - ToolbarRadioGroupPattern, -} from '@angular/aria/ui-patterns'; -import {Directionality} from '@angular/cdk/bidi'; -import {_IdGenerator} from '@angular/cdk/a11y'; -import {ToolbarWidgetGroup} from '@angular/aria/toolbar'; - -// TODO: Move mapSignal to it's own file so it can be reused across components. - -/** - * Creates a new writable signal (signal V) whose value is connected to the given original - * writable signal (signal T) such that updating signal V updates signal T and vice-versa. - * - * This function establishes a two-way synchronization between the source signal and the new mapped - * signal. When the source signal changes, the mapped signal updates by applying the `transform` - * function. When the mapped signal is explicitly set or updated, the change is propagated back to - * the source signal by applying the `reverse` function. - */ -export function mapSignal( - originalSignal: WritableSignal, - operations: { - transform: (value: T) => V; - reverse: (value: V) => T; - }, -) { - const mappedSignal = linkedSignal(() => operations.transform(originalSignal())); - const updateMappedSignal = mappedSignal.update; - const setMappedSignal = mappedSignal.set; - - mappedSignal.set = (newValue: V) => { - setMappedSignal(newValue); - originalSignal.set(operations.reverse(newValue)); - }; - - mappedSignal.update = (updateFn: (value: V) => V) => { - updateMappedSignal(oldValue => updateFn(oldValue)); - originalSignal.update(oldValue => operations.reverse(updateFn(operations.transform(oldValue)))); - }; - - return mappedSignal; -} - -/** - * A radio button group container. - * - * Radio groups are used to group multiple radio buttons or radio group labels so they function as - * a single form control. The RadioGroup is meant to be used in conjunction with RadioButton - * as follows: - * - * ```html - *
        - *
        Option 1
        - *
        Option 2
        - *
        Option 3
        - *
        - * ``` - */ -@Directive({ - selector: '[ngRadioGroup]', - exportAs: 'ngRadioGroup', - host: { - 'role': 'radiogroup', - 'class': 'ng-radio-group', - '[attr.tabindex]': 'pattern.tabindex()', - '[attr.aria-readonly]': 'pattern.readonly()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-orientation]': 'pattern.orientation()', - '[attr.aria-activedescendant]': 'pattern.activedescendant()', - '(keydown)': 'pattern.onKeydown($event)', - '(pointerdown)': 'pattern.onPointerdown($event)', - '(focusin)': 'onFocus()', - }, - hostDirectives: [ - { - directive: ToolbarWidgetGroup, - inputs: ['disabled'], - }, - ], -}) -export class RadioGroup { - /** A reference to the radio group element. */ - private readonly _elementRef = inject(ElementRef); - - /** A reference to the ToolbarWidgetGroup, if the radio group is in a toolbar. */ - private readonly _toolbarWidgetGroup = inject(ToolbarWidgetGroup); - - /** Whether the radio group is inside of a Toolbar. */ - private readonly _hasToolbar = computed(() => !!this._toolbarWidgetGroup.toolbar()); - - /** The RadioButtons nested inside of the RadioGroup. */ - private readonly _radioButtons = contentChildren(RadioButton, {descendants: true}); - - /** A signal wrapper for directionality. */ - protected textDirection = inject(Directionality).valueSignal; - - /** The RadioButton UIPatterns of the child RadioButtons. */ - protected items = computed(() => this._radioButtons().map(radio => radio.pattern)); - - /** Whether the radio group is vertically or horizontally oriented. */ - readonly orientation = input<'vertical' | 'horizontal'>('vertical'); - - /** Whether disabled items in the group should be skipped when navigating. */ - readonly skipDisabled = input(true, {transform: booleanAttribute}); - - /** The focus strategy used by the radio group. */ - readonly focusMode = input<'roving' | 'activedescendant'>('roving'); - - /** Whether the radio group is disabled. */ - readonly disabled = input(false, {transform: booleanAttribute}); - - /** Whether the radio group is readonly. */ - readonly readonly = input(false, {transform: booleanAttribute}); - - /** The value of the currently selected radio button. */ - readonly value = model(null); - - /** The internal selection state for the radio group. */ - private readonly _value = mapSignal(this.value, { - transform: value => (value !== null ? [value] : []), - reverse: values => (values.length === 0 ? null : values[0]), - }); - - /** The RadioGroup UIPattern. */ - readonly pattern: RadioGroupPattern; - - /** Whether the radio group has received focus yet. */ - private _hasFocused = signal(false); - - constructor() { - const inputs: RadioGroupInputs | ToolbarRadioGroupInputs = { - ...this, - items: this.items, - value: this._value, - activeItem: signal(undefined), - textDirection: this.textDirection, - element: () => this._elementRef.nativeElement, - getItem: e => { - if (!(e.target instanceof HTMLElement)) { - return undefined; - } - const element = e.target.closest('[role="radio"]'); - return this.items().find(i => i.element() === element); - }, - toolbar: this._toolbarWidgetGroup.toolbar, - }; - - this.pattern = this._hasToolbar() - ? new ToolbarRadioGroupPattern(inputs as ToolbarRadioGroupInputs) - : new RadioGroupPattern(inputs as RadioGroupInputs); - - if (this._hasToolbar()) { - this._toolbarWidgetGroup.controls.set(this.pattern as ToolbarRadioGroupPattern); - } - - afterRenderEffect(() => { - if (typeof ngDevMode === 'undefined' || ngDevMode) { - const violations = this.pattern.validate(); - for (const violation of violations) { - console.error(violation); - } - } - }); - - afterRenderEffect(() => { - if (!this._hasFocused() && !this._hasToolbar()) { - this.pattern.setDefaultState(); - } - }); - } - - onFocus() { - this._hasFocused.set(true); - } -} - -/** A selectable radio button in a RadioGroup. */ -@Directive({ - selector: '[ngRadioButton]', - exportAs: 'ngRadioButton', - host: { - 'role': 'radio', - 'class': 'ng-radio-button', - '[attr.data-active]': 'pattern.active()', - '[attr.tabindex]': 'pattern.tabindex()', - '[attr.aria-checked]': 'pattern.selected()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[id]': 'pattern.id()', - }, -}) -export class RadioButton { - /** A reference to the radio button element. */ - private readonly _elementRef = inject(ElementRef); - - /** The parent RadioGroup. */ - private readonly _radioGroup = inject(RadioGroup); - - /** A unique identifier for the radio button. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-radio-button-'); - - /** A unique identifier for the radio button. */ - readonly id = computed(() => this._generatedId); - - /** The value associated with the radio button. */ - readonly value = input.required(); - - /** The parent RadioGroup UIPattern. */ - readonly group = computed(() => this._radioGroup.pattern); - - /** A reference to the radio button element to be focused on navigation. */ - element = computed(() => this._elementRef.nativeElement); - - /** Whether the radio button is disabled. */ - disabled = input(false, {transform: booleanAttribute}); - - /** The RadioButton UIPattern. */ - pattern = new RadioButtonPattern({ - ...this, - id: this.id, - value: this.value, - group: this.group, - element: this.element, - }); -} diff --git a/src/aria/tabs/BUILD.bazel b/src/aria/tabs/BUILD.bazel index 8fbfc69b87c0..867c479d6d57 100644 --- a/src/aria/tabs/BUILD.bazel +++ b/src/aria/tabs/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite") +load("//tools/adev-api-extraction:extract_api_to_json.bzl", "extract_api_to_json") package(default_visibility = ["//visibility:public"]) @@ -10,8 +11,7 @@ ng_project( ], deps = [ "//:node_modules/@angular/core", - "//src/aria/deferred-content", - "//src/aria/ui-patterns", + "//src/aria/private", "//src/cdk/a11y", "//src/cdk/bidi", ], @@ -35,3 +35,23 @@ ng_web_test_suite( name = "unit_tests", deps = [":unit_test_sources"], ) + +filegroup( + name = "source-files", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), +) + +extract_api_to_json( + name = "json_api", + srcs = [ + ":source-files", + ], + entry_point = ":index.ts", + module_name = "@angular/aria/tabs", + output_name = "aria-tabs.json", + private_modules = [""], + repo = "angular/components", +) diff --git a/src/aria/tabs/tabs.spec.ts b/src/aria/tabs/tabs.spec.ts index d7a7d7367a34..8bf4ecfbeb6b 100644 --- a/src/aria/tabs/tabs.spec.ts +++ b/src/aria/tabs/tabs.spec.ts @@ -79,7 +79,7 @@ describe('Tabs', () => { orientation?: 'horizontal' | 'vertical'; disabled?: boolean; wrap?: boolean; - skipDisabled?: boolean; + softDisabled?: boolean; focusMode?: 'roving' | 'activedescendant'; selectionMode?: 'follow' | 'explicit'; } = {}, @@ -89,7 +89,7 @@ describe('Tabs', () => { if (options.orientation !== undefined) testComponent.orientation.set(options.orientation); if (options.disabled !== undefined) testComponent.disabled.set(options.disabled); if (options.wrap !== undefined) testComponent.wrap.set(options.wrap); - if (options.skipDisabled !== undefined) testComponent.skipDisabled.set(options.skipDisabled); + if (options.softDisabled !== undefined) testComponent.softDisabled.set(options.softDisabled); if (options.focusMode !== undefined) testComponent.focusMode.set(options.focusMode); if (options.selectionMode !== undefined) testComponent.selectionMode.set(options.selectionMode); @@ -273,12 +273,12 @@ describe('Tabs', () => { it('should move focus with ArrowRight', () => { expect(isTabFocused(0)).toBe(true); right(); - expect(isTabFocused(2)).toBe(true); + expect(isTabFocused(1)).toBe(true); }); it('should move focus with ArrowLeft', () => { right(); - expect(isTabFocused(2)).toBe(true); + expect(isTabFocused(1)).toBe(true); left(); expect(isTabFocused(0)).toBe(true); }); @@ -286,6 +286,8 @@ describe('Tabs', () => { it('should wrap focus with ArrowRight if wrap is true', () => { updateTabs({wrap: true}); right(); + expect(isTabFocused(1)).toBe(true); + right(); expect(isTabFocused(2)).toBe(true); right(); expect(isTabFocused(0)).toBe(true); @@ -294,6 +296,8 @@ describe('Tabs', () => { it('should not wrap focus with ArrowRight if wrap is false', () => { updateTabs({wrap: false}); right(); + expect(isTabFocused(1)).toBe(true); + right(); expect(isTabFocused(2)).toBe(true); right(); expect(isTabFocused(2)).toBe(true); @@ -326,15 +330,15 @@ describe('Tabs', () => { expect(isTabFocused(2)).toBe(true); }); - it('should skip disabled tabs if skipDisabled is true', () => { - updateTabs({skipDisabled: true}); + it('should skip disabled tabs if softDisabled is false', () => { + updateTabs({softDisabled: false}); expect(isTabFocused(0)).toBe(true); right(); expect(isTabFocused(2)).toBe(true); }); - it('should not skip disabled tabs if skipDisabled is false', () => { - updateTabs({skipDisabled: false}); + it('should not skip disabled tabs if softDisabled is true', () => { + updateTabs({softDisabled: true}); tabListElement.focus(); fixture.detectChanges(); expect(isTabFocused(0)).toBe(true); @@ -359,12 +363,12 @@ describe('Tabs', () => { it('should move focus with ArrowLeft (effectively next)', () => { expect(isTabFocused(0)).toBe(true); left(); - expect(isTabFocused(2)).toBe(true); + expect(isTabFocused(1)).toBe(true); }); it('should move focus with ArrowRight (effectively previous)', () => { left(); - expect(isTabFocused(2)).toBe(true); + expect(isTabFocused(1)).toBe(true); right(); expect(isTabFocused(0)).toBe(true); }); @@ -372,6 +376,9 @@ describe('Tabs', () => { it('should wrap focus with ArrowLeft if wrap is true', () => { updateTabs({wrap: true}); left(); + expect(isTabFocused(1)).toBe(true); + left(); + expect(isTabFocused(2)).toBe(true); left(); expect(isTabFocused(0)).toBe(true); }); @@ -402,12 +409,12 @@ describe('Tabs', () => { it('should move focus with ArrowDown', () => { expect(isTabFocused(0)).toBe(true); down(); - expect(isTabFocused(2)).toBe(true); + expect(isTabFocused(1)).toBe(true); }); it('should move focus with ArrowUp', () => { down(); - expect(isTabFocused(2)).toBe(true); + expect(isTabFocused(1)).toBe(true); up(); expect(isTabFocused(0)).toBe(true); }); @@ -415,6 +422,8 @@ describe('Tabs', () => { it('should wrap focus with ArrowDown if wrap is true', () => { updateTabs({wrap: true}); down(); + expect(isTabFocused(1)).toBe(true); + down(); expect(isTabFocused(2)).toBe(true); down(); expect(isTabFocused(0)).toBe(true); @@ -423,6 +432,8 @@ describe('Tabs', () => { it('should not wrap focus with ArrowDown if wrap is false', () => { updateTabs({wrap: false}); down(); + expect(isTabFocused(1)).toBe(true); + down(); expect(isTabFocused(2)).toBe(true); down(); expect(isTabFocused(2)).toBe(true); @@ -443,7 +454,7 @@ describe('Tabs', () => { }); it('should move focus to first tab with Home', () => { - down(); + end(); expect(isTabFocused(2)).toBe(true); home(); expect(isTabFocused(0)).toBe(true); @@ -644,7 +655,7 @@ describe('Tabs', () => { ], selectedTab: 'tab1', selectionMode: 'explicit', - skipDisabled: false, + softDisabled: true, }); expect(testComponent.selectedTab()).toBe('tab1'); right(); @@ -680,7 +691,7 @@ describe('Tabs', () => { [orientation]="orientation()" [disabled]="disabled()" [wrap]="wrap()" - [skipDisabled]="skipDisabled()" + [softDisabled]="softDisabled()" [focusMode]="focusMode()" [selectionMode]="selectionMode()"> @for (tabDef of tabsData(); track tabDef.value) { @@ -723,7 +734,7 @@ class TestTabsComponent { orientation = signal<'horizontal' | 'vertical'>('horizontal'); disabled = signal(false); wrap = signal(true); - skipDisabled = signal(true); + softDisabled = signal(true); focusMode = signal<'roving' | 'activedescendant'>('roving'); selectionMode = signal<'follow' | 'explicit'>('follow'); } diff --git a/src/aria/tabs/tabs.ts b/src/aria/tabs/tabs.ts index 537e685406ed..ad87f7f4c81a 100644 --- a/src/aria/tabs/tabs.ts +++ b/src/aria/tabs/tabs.ts @@ -6,7 +6,6 @@ * found in the LICENSE file at https://angular.dev/license */ -import {DeferredContent, DeferredContentAware} from '@angular/aria/deferred-content'; import {_IdGenerator} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; import { @@ -17,24 +16,28 @@ import { inject, input, model, - linkedSignal, signal, - Signal, afterRenderEffect, OnInit, OnDestroy, } from '@angular/core'; -import {TabListPattern, TabPanelPattern, TabPattern} from '@angular/aria/ui-patterns'; +import { + TabListPattern, + TabPanelPattern, + TabPattern, + DeferredContent, + DeferredContentAware, +} from '@angular/aria/private'; interface HasElement { - element: Signal; + element: HTMLElement; } /** * Sort directives by their document order. */ function sortDirectives(a: HasElement, b: HasElement) { - return (a.element().compareDocumentPosition(b.element()) & Node.DOCUMENT_POSITION_PRECEDING) > 0 + return (a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_PRECEDING) > 0 ? 1 : -1; } @@ -42,36 +45,43 @@ function sortDirectives(a: HasElement, b: HasElement) { /** * A Tabs container. * - * Represents a set of layered sections of content. The Tabs is a container meant to be used with - * TabList, Tab, and TabPanel as follows: + * The `ngTabs` directive represents a set of layered sections of content. It acts as the + * overarching container for a tabbed interface, coordinating the behavior of `ngTabList`, + * `ngTab`, and `ngTabPanel` directives. * * ```html *
        - *
          + *
            *
          • Tab 1
          • *
          • Tab 2
          • *
          • Tab 3
          • *
          * *
          - * Tab content 1 + * Content for Tab 1 *
          *
          - * Tab content 2 + * Content for Tab 2 *
          *
          - * Tab content 3 + * Content for Tab 3 *
          + *
        * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngTabs]', exportAs: 'ngTabs', - host: { - 'class': 'ng-tabs', - }, }) export class Tabs { + /** A reference to the host element. */ + private readonly _elementRef = inject(ElementRef); + + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The TabList nested inside of the container. */ private readonly _tablist = signal(undefined); @@ -79,14 +89,14 @@ export class Tabs { private readonly _unorderedPanels = signal(new Set()); /** The Tab UIPattern of the child Tabs. */ - tabs = computed(() => this._tablist()?.tabs()); + readonly _tabPatterns = computed(() => this._tablist()?._tabPatterns()); /** The TabPanel UIPattern of the child TabPanels. */ - unorderedTabpanels = computed(() => - [...this._unorderedPanels()].map(tabpanel => tabpanel.pattern), + readonly _unorderedTabpanelPatterns = computed(() => + [...this._unorderedPanels()].map(tabpanel => tabpanel._pattern), ); - register(child: TabList | TabPanel) { + _register(child: TabList | TabPanel) { if (child instanceof TabList) { this._tablist.set(child); } @@ -97,7 +107,7 @@ export class Tabs { } } - deregister(child: TabList | TabPanel) { + _unregister(child: TabList | TabPanel) { if (child instanceof TabList) { this._tablist.set(undefined); } @@ -112,44 +122,52 @@ export class Tabs { /** * A TabList container. * - * Controls a list of Tab(s). + * The `ngTabList` directive controls a list of `ngTab` elements. It manages keyboard + * navigation, selection, and the overall orientation of the tabs. It should be placed + * within an `ngTabs` container. + * + * ```html + *
          + *
        • First Tab
        • + *
        • Second Tab
        • + *
        + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngTabList]', exportAs: 'ngTabList', host: { 'role': 'tablist', - 'class': 'ng-tablist', - '[attr.tabindex]': 'pattern.tabindex()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-orientation]': 'pattern.orientation()', - '[attr.aria-activedescendant]': 'pattern.activedescendant()', - '(keydown)': 'pattern.onKeydown($event)', - '(pointerdown)': 'pattern.onPointerdown($event)', - '(focusin)': 'onFocus()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-orientation]': '_pattern.orientation()', + '[attr.aria-activedescendant]': '_pattern.activeDescendant()', + '(keydown)': '_pattern.onKeydown($event)', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(focusin)': '_onFocus()', }, }) export class TabList implements OnInit, OnDestroy { - /** A reference to the tab list element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The parent Tabs. */ private readonly _tabs = inject(Tabs); /** The Tabs nested inside of the TabList. */ private readonly _unorderedTabs = signal(new Set()); - /** The internal tab selection state. */ - private readonly _selection = linkedSignal(() => - this.selectedTab() ? [this.selectedTab()!] : [], - ); - /** Text direction. */ readonly textDirection = inject(Directionality).valueSignal; /** The Tab UIPatterns of the child Tabs. */ - readonly tabs = computed(() => - [...this._unorderedTabs()].sort(sortDirectives).map(tab => tab.pattern), + readonly _tabPatterns = computed(() => + [...this._unorderedTabs()].sort(sortDirectives).map(tab => tab._pattern), ); /** Whether the tablist is vertically or horizontally oriented. */ @@ -158,26 +176,36 @@ export class TabList implements OnInit, OnDestroy { /** Whether focus should wrap when navigating. */ readonly wrap = input(true, {transform: booleanAttribute}); - /** Whether disabled items in the list should be skipped when navigating. */ - readonly skipDisabled = input(true, {transform: booleanAttribute}); - - /** The focus strategy used by the tablist. */ + /** + * Whether to allow disabled items to receive focus. When `true`, disabled items are + * focusable but not interactive. When `false`, disabled items are skipped during navigation. + */ + readonly softDisabled = input(true, {transform: booleanAttribute}); + + /** + * The focus strategy used by the tablist. + * - `roving`: Focus is moved to the active tab using `tabindex`. + * - `activedescendant`: Focus remains on the tablist container, and `aria-activedescendant` is used to indicate the active tab. + */ readonly focusMode = input<'roving' | 'activedescendant'>('roving'); - /** The selection strategy used by the tablist. */ + /** + * The selection strategy used by the tablist. + * - `follow`: The focused tab is automatically selected. + * - `explicit`: Tabs are selected explicitly by the user (e.g., via click or spacebar). + */ readonly selectionMode = input<'follow' | 'explicit'>('follow'); - /** Whether the tablist is disabled. */ - readonly disabled = input(false, {transform: booleanAttribute}); - /** The current selected tab. */ readonly selectedTab = model(); + /** Whether the tablist is disabled. */ + readonly disabled = input(false, {transform: booleanAttribute}); + /** The TabList UIPattern. */ - readonly pattern: TabListPattern = new TabListPattern({ + readonly _pattern: TabListPattern = new TabListPattern({ ...this, - items: this.tabs, - value: this._selection, + items: this._tabPatterns, activeItem: signal(undefined), element: () => this._elementRef.nativeElement, }); @@ -186,119 +214,167 @@ export class TabList implements OnInit, OnDestroy { private _hasFocused = signal(false); constructor() { - afterRenderEffect(() => this.selectedTab.set(this._selection()[0])); - afterRenderEffect(() => { if (!this._hasFocused()) { - this.pattern.setDefaultState(); + this._pattern.setDefaultState(); + } + }); + + afterRenderEffect(() => { + const tab = this._pattern.selectedTab(); + if (tab) { + this.selectedTab.set(tab.value()); + } + }); + + afterRenderEffect(() => { + const value = this.selectedTab(); + if (value) { + this._pattern.open(value); } }); } - onFocus() { + _onFocus() { this._hasFocused.set(true); } ngOnInit() { - this._tabs.register(this); + this._tabs._register(this); } ngOnDestroy() { - this._tabs.deregister(this); + this._tabs._unregister(this); } - register(child: Tab) { + _register(child: Tab) { this._unorderedTabs().add(child); this._unorderedTabs.set(new Set(this._unorderedTabs())); } - deregister(child: Tab) { + _unregister(child: Tab) { this._unorderedTabs().delete(child); this._unorderedTabs.set(new Set(this._unorderedTabs())); } + + /** Opens the tab panel with the specified value. */ + open(value: string): boolean { + return this._pattern.open(value); + } } -/** A selectable tab in a TabList. */ +/** + * A selectable tab in a TabList. + * + * The `ngTab` directive represents an individual tab control within an `ngTabList`. It + * requires a `value` that uniquely identifies it and links it to a corresponding `ngTabPanel`. + * + * ```html + *
      • + * My Tab Label + *
      • + * ``` + * + * @developerPreview 21.0 + */ @Directive({ selector: '[ngTab]', exportAs: 'ngTab', host: { 'role': 'tab', - 'class': 'ng-tab', - '[attr.data-active]': 'pattern.active()', - '[attr.id]': 'pattern.id()', - '[attr.tabindex]': 'pattern.tabindex()', - '[attr.aria-selected]': 'pattern.selected()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-controls]': 'pattern.controls()', + '[attr.data-active]': 'active()', + '[attr.id]': '_pattern.id()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.aria-selected]': 'selected()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-controls]': '_pattern.controls()', }, }) export class Tab implements HasElement, OnInit, OnDestroy { - /** A reference to the tab element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The parent Tabs. */ private readonly _tabs = inject(Tabs); /** The parent TabList. */ private readonly _tabList = inject(TabList); - /** A global unique identifier for the tab. */ - private readonly _id = inject(_IdGenerator).getId('ng-tab-'); - - /** The host native element. */ - readonly element = computed(() => this._elementRef.nativeElement); + /** A unique identifier for the widget. */ + readonly id = input(inject(_IdGenerator).getId('ng-tab-', true)); /** The parent TabList UIPattern. */ - readonly tablist = computed(() => this._tabList.pattern); + private readonly _tablistPattern = computed(() => this._tabList._pattern); /** The TabPanel UIPattern associated with the tab */ - readonly tabpanel = computed(() => - this._tabs.unorderedTabpanels().find(tabpanel => tabpanel.value() === this.value()), + private readonly _tabpanelPattern = computed(() => + this._tabs._unorderedTabpanelPatterns().find(tabpanel => tabpanel.value() === this.value()), ); /** Whether a tab is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); - /** A local unique identifier for the tab. */ + /** The remote tabpanel unique identifier. */ readonly value = input.required(); + /** Whether the tab is active. */ + readonly active = computed(() => this._pattern.active()); + + /** Whether the tab is selected. */ + readonly selected = computed(() => this._pattern.selected()); + /** The Tab UIPattern. */ - readonly pattern: TabPattern = new TabPattern({ + readonly _pattern: TabPattern = new TabPattern({ ...this, - id: () => this._id, - tablist: this.tablist, - tabpanel: this.tabpanel, - value: this.value, + tablist: this._tablistPattern, + tabpanel: this._tabpanelPattern, + expanded: signal(false), + element: () => this.element, }); + /** Opens this tab panel. */ + open() { + this._pattern.open(); + } + ngOnInit() { - this._tabList.register(this); + this._tabList._register(this); } ngOnDestroy() { - this._tabList.deregister(this); + this._tabList._unregister(this); } } /** * A TabPanel container for the resources of layered content associated with a tab. * - * If a tabpanel is hidden due to its corresponding tab is not activated, the `inert` attribute - * will be applied to the tabpanel element to remove it from the accessibility tree and stop - * all the keyboard and pointer interactions. Note that this does not visually hide the tabpenl - * and a proper styling is required. + * The `ngTabPanel` directive holds the content for a specific tab. It is linked to an + * `ngTab` by a matching `value`. If a tab panel is hidden, the `inert` attribute will be + * applied to remove it from the accessibility tree. Proper styling is required for visual hiding. + * + * ```html + *
        + * + * + * + *
        + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngTabPanel]', exportAs: 'ngTabPanel', host: { 'role': 'tabpanel', - 'class': 'ng-tabpanel', - '[attr.id]': 'pattern.id()', - '[attr.tabindex]': 'pattern.tabindex()', - '[attr.inert]': 'pattern.hidden() ? true : null', - '[attr.aria-labelledby]': 'pattern.labelledBy()', + '[attr.id]': '_pattern.id()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.inert]': '!visible() ? true : null', + '[attr.aria-labelledby]': '_pattern.labelledBy()', }, hostDirectives: [ { @@ -308,6 +384,12 @@ export class Tab implements HasElement, OnInit, OnDestroy { ], }) export class TabPanel implements OnInit, OnDestroy { + /** A reference to the host element. */ + private readonly _elementRef = inject(ElementRef); + + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The DeferredContentAware host directive. */ private readonly _deferredContentAware = inject(DeferredContentAware); @@ -315,36 +397,54 @@ export class TabPanel implements OnInit, OnDestroy { private readonly _Tabs = inject(Tabs); /** A global unique identifier for the tab. */ - private readonly _id = inject(_IdGenerator).getId('ng-tabpanel-'); + readonly id = input(inject(_IdGenerator).getId('ng-tabpanel-', true)); /** The Tab UIPattern associated with the tabpanel */ - readonly tab = computed(() => this._Tabs.tabs()?.find(tab => tab.value() === this.value())); + private readonly _tabPattern = computed(() => + this._Tabs._tabPatterns()?.find(tab => tab.value() === this.value()), + ); /** A local unique identifier for the tabpanel. */ readonly value = input.required(); + /** Whether the tab panel is visible. */ + readonly visible = computed(() => !this._pattern.hidden()); + /** The TabPanel UIPattern. */ - readonly pattern: TabPanelPattern = new TabPanelPattern({ + readonly _pattern: TabPanelPattern = new TabPanelPattern({ ...this, - id: () => this._id, - tab: this.tab, + tab: this._tabPattern, }); constructor() { - afterRenderEffect(() => this._deferredContentAware.contentVisible.set(!this.pattern.hidden())); + afterRenderEffect(() => this._deferredContentAware.contentVisible.set(this.visible())); } ngOnInit() { - this._Tabs.register(this); + this._Tabs._register(this); } ngOnDestroy() { - this._Tabs.deregister(this); + this._Tabs._unregister(this); } } /** * A TabContent container for the lazy-loaded content. + * + * This structural directive should be applied to an `ng-template` within an `ngTabPanel`. + * It enables lazy loading of the tab's content, meaning the content is only rendered + * when the tab is activated for the first time. + * + * ```html + *
        + * + *

        This content will be loaded when 'myTabId' is selected.

        + *
        + *
        + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: 'ng-template[ngTabContent]', diff --git a/src/aria/toolbar/BUILD.bazel b/src/aria/toolbar/BUILD.bazel index adca3e19b5ea..5a9bebe48f6b 100644 --- a/src/aria/toolbar/BUILD.bazel +++ b/src/aria/toolbar/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") +load("//tools/adev-api-extraction:extract_api_to_json.bzl", "extract_api_to_json") package(default_visibility = ["//visibility:public"]) @@ -10,7 +11,7 @@ ng_project( ), deps = [ "//:node_modules/@angular/core", - "//src/aria/ui-patterns", + "//src/aria/private", "//src/cdk/a11y", "//src/cdk/bidi", ], @@ -34,3 +35,23 @@ ng_web_test_suite( name = "unit_tests", deps = [":unit_test_sources"], ) + +filegroup( + name = "source-files", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), +) + +extract_api_to_json( + name = "json_api", + srcs = [ + ":source-files", + ], + entry_point = ":index.ts", + module_name = "@angular/aria/toolbar", + output_name = "aria-toolbar.json", + private_modules = [""], + repo = "angular/components", +) diff --git a/src/aria/toolbar/toolbar.spec.ts b/src/aria/toolbar/toolbar.spec.ts index c6685baba582..a6dfe689debb 100644 --- a/src/aria/toolbar/toolbar.spec.ts +++ b/src/aria/toolbar/toolbar.spec.ts @@ -1,55 +1,68 @@ -import {Component, inject, signal} from '@angular/core'; +import {Component, DebugElement, Directive, inject, signal} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; -import {Direction} from '@angular/cdk/bidi'; -import {provideFakeDirectionality} from '@angular/cdk/testing/private'; import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from './toolbar'; - -interface ModifierKeys { - ctrlKey?: boolean; - shiftKey?: boolean; - altKey?: boolean; - metaKey?: boolean; -} +import {provideFakeDirectionality, runAccessibilityChecks} from '@angular/cdk/testing/private'; describe('Toolbar', () => { - let fixture: ComponentFixture; - let testComponent: TestToolbarComponent; + let fixture: ComponentFixture; let toolbarElement: HTMLElement; - let widgetElements: HTMLElement[]; - let testWidgetGroupInstance: TestToolbarWidgetGroup; - const keydown = (key: string, modifierKeys: ModifierKeys = {}) => { - const event = new KeyboardEvent('keydown', {key, bubbles: true, ...modifierKeys}); - toolbarElement.dispatchEvent(event); + const keydown = (key: string, target?: HTMLElement, modifierKeys: {} = {}) => { + const eventTarget = target || toolbarElement; + eventTarget.dispatchEvent( + new KeyboardEvent('keydown', { + key, + bubbles: true, + ...modifierKeys, + }), + ); fixture.detectChanges(); - defineTestVariables(); }; - const pointerDown = (target: HTMLElement, eventInit: PointerEventInit = {}) => { - target.dispatchEvent(new PointerEvent('pointerdown', {bubbles: true, ...eventInit})); + const click = (element: HTMLElement, eventInit?: PointerEventInit) => { + element.dispatchEvent(new PointerEvent('click', {bubbles: true, ...eventInit})); fixture.detectChanges(); - defineTestVariables(); }; - const up = (modifierKeys?: ModifierKeys) => keydown('ArrowUp', modifierKeys); - const down = (modifierKeys?: ModifierKeys) => keydown('ArrowDown', modifierKeys); - const left = (modifierKeys?: ModifierKeys) => keydown('ArrowLeft', modifierKeys); - const right = (modifierKeys?: ModifierKeys) => keydown('ArrowRight', modifierKeys); - const home = (modifierKeys?: ModifierKeys) => keydown('Home', modifierKeys); - const end = (modifierKeys?: ModifierKeys) => keydown('End', modifierKeys); - const enter = (modifierKeys?: ModifierKeys) => keydown('Enter', modifierKeys); - const space = (modifierKeys?: ModifierKeys) => keydown(' ', modifierKeys); - const click = (target: HTMLElement) => pointerDown(target); - - function setupTestToolbar(textDirection: Direction = 'ltr') { + const right = (target?: HTMLElement, modifierKeys?: {}) => + keydown('ArrowRight', target, modifierKeys); + const left = (target?: HTMLElement, modifierKeys?: {}) => + keydown('ArrowLeft', target, modifierKeys); + const up = (target?: HTMLElement, modifierKeys?: {}) => keydown('ArrowUp', target, modifierKeys); + const down = (target?: HTMLElement, modifierKeys?: {}) => + keydown('ArrowDown', target, modifierKeys); + const home = (target?: HTMLElement, modifierKeys?: {}) => keydown('Home', target, modifierKeys); + const end = (target?: HTMLElement, modifierKeys?: {}) => keydown('End', target, modifierKeys); + + function setupToolbar( + opts: { + orientation?: 'vertical' | 'horizontal'; + softDisabled?: boolean; + disabled?: boolean; + wrap?: boolean; + textDirection?: 'ltr' | 'rtl'; + } = {}, + ) { TestBed.configureTestingModule({ - imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup, TestToolbarComponent], - providers: [provideFakeDirectionality(textDirection)], + imports: [ToolbarExample], + providers: [provideFakeDirectionality(opts?.textDirection ?? 'ltr')], }); - - fixture = TestBed.createComponent(TestToolbarComponent); - testComponent = fixture.componentInstance; + fixture = TestBed.createComponent(ToolbarExample); + const testComponent = fixture.componentInstance as ToolbarExample; + + if (opts.orientation) { + testComponent.orientation.set(opts.orientation); + } + if (opts.softDisabled !== undefined) { + testComponent.softDisabled.set(opts.softDisabled); + } + if (opts.disabled !== undefined) { + testComponent.disabled.set(opts.disabled); + } + if (opts.wrap !== undefined) { + testComponent.wrap.set(opts.wrap); + } fixture.detectChanges(); defineTestVariables(); @@ -57,421 +70,611 @@ describe('Toolbar', () => { function defineTestVariables() { const toolbarDebugElement = fixture.debugElement.query(By.directive(Toolbar)); - const widgetDebugElements = fixture.debugElement.queryAll(By.css('[data-value="widget"]')); - const testWidgetGroupElement = fixture.debugElement.query(By.directive(TestToolbarWidgetGroup)); - toolbarElement = toolbarDebugElement.nativeElement as HTMLElement; - widgetElements = widgetDebugElements.map(debugEl => debugEl.nativeElement); - testWidgetGroupInstance = testWidgetGroupElement.componentInstance as TestToolbarWidgetGroup; } - function updateToolbar( - config: { - disabled?: boolean; - widgetGroupDisabled?: boolean; - orientation?: 'horizontal' | 'vertical'; - wrap?: boolean; - skipDisabled?: boolean; - } = {}, - ) { - if (config.disabled !== undefined) testComponent.disabled.set(config.disabled); - if (config.widgetGroupDisabled !== undefined) - testComponent.widgetGroupDisabled.set(config.widgetGroupDisabled); - if (config.orientation !== undefined) testComponent.orientation.set(config.orientation); - if (config.wrap !== undefined) testComponent.wrap.set(config.wrap); - if (config.skipDisabled !== undefined) testComponent.skipDisabled.set(config.skipDisabled); - - fixture.detectChanges(); - defineTestVariables(); + function getWidgetEl(text: string): HTMLElement | null { + const widgets = getWidgetEls(); + return widgets.find(widget => widget.textContent?.trim() === text) || null; } - describe('ARIA attributes and roles', () => { - describe('default configuration', () => { - beforeEach(() => { - setupTestToolbar(); - }); - - it('should correctly set the role attribute to "toolbar" for Toolbar', () => { - expect(toolbarElement.getAttribute('role')).toBe('toolbar'); - }); - - it('should set aria-orientation to "horizontal" by default', () => { - expect(toolbarElement.getAttribute('aria-orientation')).toBe('horizontal'); - }); - - it('should set aria-disabled to "false" by default for the toolbar', () => { - expect(toolbarElement.getAttribute('aria-disabled')).toBe('false'); - }); - - it('should set aria-disabled to "false" by default for widgets', () => { - expect(widgetElements[0].getAttribute('aria-disabled')).toBe('false'); - expect(widgetElements[1].getAttribute('aria-disabled')).toBe('true'); - expect(widgetElements[2].getAttribute('aria-disabled')).toBe('false'); - }); - - it('should set initial focus (tabindex="0") on the first non-disabled widget', () => { - expect(widgetElements[0].getAttribute('tabindex')).toBe('0'); - expect(widgetElements[1].getAttribute('tabindex')).toBe('-1'); - expect(widgetElements[2].getAttribute('tabindex')).toBe('-1'); - }); - - it('should not have aria-activedescendant by default', () => { - expect(toolbarElement.hasAttribute('aria-activedescendant')).toBe(false); - }); - }); - - describe('custom configuration', () => { - beforeEach(() => { - setupTestToolbar(); - }); + function getWidgetEls(): HTMLElement[] { + return fixture.debugElement + .queryAll(By.directive(ToolbarWidget)) + .map((debugEl: DebugElement) => debugEl.nativeElement as HTMLElement); + } - it('should set aria-orientation to "vertical"', () => { - updateToolbar({orientation: 'vertical'}); - expect(toolbarElement.getAttribute('aria-orientation')).toBe('vertical'); - }); + afterEach(async () => await runAccessibilityChecks(fixture.nativeElement)); - it('should set aria-disabled to "true" for the toolbar', () => { - updateToolbar({disabled: true}); - expect(toolbarElement.getAttribute('aria-disabled')).toBe('true'); + describe('Navigation', () => { + describe('with horizontal orientation', () => { + it('should navigate on click', () => { + setupToolbar(); + const item3 = getWidgetEl('item 3')!; + click(item3); + expect(document.activeElement).toBe(item3); }); - it('should set aria-disabled to "true" for all widgets when toolbar is disabled', () => { - updateToolbar({disabled: true}); - expect(widgetElements[0].getAttribute('aria-disabled')).toBe('true'); - expect(widgetElements[1].getAttribute('aria-disabled')).toBe('true'); - expect(widgetElements[2].getAttribute('aria-disabled')).toBe('true'); - }); - }); - }); + describe('with ltr text direction', () => { + beforeEach(() => setupToolbar()); - describe('keyboard navigation', () => { - describe('LTR', () => { - beforeEach(() => { - setupTestToolbar('ltr'); - updateToolbar({widgetGroupDisabled: true}); - }); - - describe('vertical orientation', () => { - beforeEach(() => { - updateToolbar({orientation: 'vertical'}); + it('should navigate next on ArrowRight', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - it('should move focus to the next widget on ArrowDown', () => { - down(); - expect(document.activeElement).toBe(widgetElements[2]); + it('should navigate prev on ArrowLeft', () => { + const item1 = getWidgetEl('item 1')!; + click(item1); + left(); + expect(document.activeElement).toBe(getWidgetEl('item 0')); }); - it('should move focus to the previous widget on ArrowUp', () => { + it('should not navigate next on ArrowDown when not in a widget group', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); down(); - expect(document.activeElement).toBe(widgetElements[2]); + expect(document.activeElement).toBe(item0); + }); + it('should not navigate prev on ArrowUp when not in a widget group', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); up(); - expect(document.activeElement).toBe(widgetElements[0]); + expect(document.activeElement).toBe(item0); }); - }); - describe('horizontal orientation', () => { - beforeEach(() => { - updateToolbar({orientation: 'horizontal'}); + it('should navigate next in a widget group on ArrowDown', () => { + const item2 = getWidgetEl('item 2')!; + click(item2); + down(); + expect(document.activeElement).toBe(getWidgetEl('item 3')); }); - it('should move focus to the next widget on ArrowRight', () => { - right(); - expect(document.activeElement).toBe(widgetElements[2]); + it('should navigate prev in a widget group on ArrowUp', () => { + const item3 = getWidgetEl('item 3')!; + click(item3); + up(); + expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should move focus to the previous widget on ArrowLeft', () => { - right(); - expect(document.activeElement).toBe(widgetElements[2]); - - left(); - expect(document.activeElement).toBe(widgetElements[0]); + it('should navigate last to first in a widget group on ArrowDown', () => { + const item4 = getWidgetEl('item 4')!; + click(item4); + down(); + expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - }); - - it('should move focus to the last enabled widget on End', () => { - end(); - expect(document.activeElement).toBe(widgetElements[2]); - }); - - it('should move focus to the first enabled widget on Home', () => { - end(); - expect(document.activeElement).toBe(widgetElements[2]); - home(); - expect(document.activeElement).toBe(widgetElements[0]); - }); - - it('should skip disabled widgets with arrow keys if skipDisabled=true', () => { - updateToolbar({skipDisabled: true}); - right(); - expect(document.activeElement).toBe(widgetElements[2]); - }); - - it('should not skip disabled widgets with arrow keys if skipDisabled=false', () => { - updateToolbar({skipDisabled: false}); - right(); - expect(document.activeElement).toBe(widgetElements[1]); - }); - - it('should wrap focus from last to first when wrap is true', () => { - updateToolbar({wrap: true}); - end(); - expect(document.activeElement).toBe(widgetElements[2]); + it('should navigate first to last in a widget group on ArrowUp', () => { + const item2 = getWidgetEl('item 2')!; + click(item2); + up(); + expect(document.activeElement).toBe(getWidgetEl('item 4')); + }); - right(); - expect(document.activeElement).toBe(widgetElements[0]); - }); + describe('with wrap false', () => { + beforeEach(() => { + fixture.componentInstance.wrap.set(false); + fixture.detectChanges(); + }); + + it('should not wrap from last to first', () => { + const item5 = getWidgetEl('item 5')!; + click(item5); + right(); + expect(document.activeElement).toBe(item5); + }); + + it('should not wrap from first to last', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + left(); + expect(document.activeElement).toBe(item0); + }); + }); - it('should not wrap focus from last to first when wrap is false', () => { - updateToolbar({wrap: false}); - end(); - expect(document.activeElement).toBe(widgetElements[2]); + describe('with softDisabled true', () => { + beforeEach(() => { + fixture.componentInstance.softDisabled.set(true); + fixture.detectChanges(); + }); + + it('should not skip disabled items when navigating next', () => { + fixture.componentInstance.widgets[1].disabled.set(true); + fixture.detectChanges(); + click(getWidgetEl('item 0')!); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 1')); + }); + + it('should not skip disabled items when navigating prev', () => { + fixture.componentInstance.widgets[1].disabled.set(true); + fixture.detectChanges(); + const item2 = getWidgetEl('item 2')!; + click(item2); + left(); + expect(document.activeElement).toBe(getWidgetEl('item 1')); + }); + + it('should not skip disabled groups when navigating next', () => { + fixture.componentInstance.groups[0].disabled.set(true); + fixture.detectChanges(); + const item1 = getWidgetEl('item 1')!; + click(item1); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 2')); + }); + + it('should not skip disabled groups when navigating prev', () => { + fixture.componentInstance.groups[0].disabled.set(true); + fixture.detectChanges(); + const item5 = getWidgetEl('item 5')!; + click(item5); + left(); + expect(document.activeElement).toBe(getWidgetEl('item 4')); + }); + + it('should navigate to the last item on End', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + end(); + expect(document.activeElement).toBe(getWidgetEl('item 5')); + }); + + it('should navigate to the first item on Home', () => { + const item5 = getWidgetEl('item 5')!; + click(item5); + home(); + expect(document.activeElement).toBe(getWidgetEl('item 0')); + }); + + describe('with wrap true', () => { + beforeEach(() => { + fixture.componentInstance.wrap.set(true); + fixture.detectChanges(); + }); + + it('should wrap from last to first', () => { + const item5 = getWidgetEl('item 5')!; + click(item5); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 0')); + }); + + it('should wrap from first to last', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + left(); + expect(document.activeElement).toBe(getWidgetEl('item 5')); + }); + }); + }); - right(); - expect(document.activeElement).toBe(widgetElements[2]); + describe('with softDisabled false', () => { + beforeEach(() => { + fixture.componentInstance.softDisabled.set(false); + fixture.detectChanges(); + }); + + it('should not navigate to disabled items on click', () => { + fixture.componentInstance.widgets[1].disabled.set(true); + fixture.detectChanges(); + const item1 = getWidgetEl('item 1')!; + click(item1); + expect(document.activeElement).not.toBe(item1); + }); + + it('should skip disabled items when navigating next', () => { + fixture.componentInstance.widgets[1].disabled.set(true); + fixture.detectChanges(); + const item0 = getWidgetEl('item 0')!; + click(item0); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 2')); + }); + + it('should skip disabled items when navigating prev', () => { + fixture.componentInstance.widgets[1].disabled.set(true); + fixture.detectChanges(); + const item2 = getWidgetEl('item 2')!; + click(item2); + left(); + expect(document.activeElement).toBe(getWidgetEl('item 0')); + }); + + it('should not navigate to items in disabled groups on click', () => { + fixture.componentInstance.groups[0].disabled.set(true); + fixture.detectChanges(); + const item3 = getWidgetEl('item 3')!; + click(item3); + expect(document.activeElement).not.toBe(item3); + }); + + it('should skip disabled groups when navigating next', () => { + fixture.componentInstance.groups[0].disabled.set(true); + fixture.detectChanges(); + const item1 = getWidgetEl('item 1')!; + click(item1); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 5')); + }); + + it('should skip disabled groups when navigating prev', () => { + fixture.componentInstance.groups[0].disabled.set(true); + fixture.detectChanges(); + const item5 = getWidgetEl('item 5')!; + click(item5); + left(); + expect(document.activeElement).toBe(getWidgetEl('item 1')); + }); + + it('should navigate to the last focusable item on End', () => { + fixture.componentInstance.widgets[5].disabled.set(true); + fixture.detectChanges(); + const item0 = getWidgetEl('item 0')!; + click(item0); + end(); + expect(document.activeElement).toBe(getWidgetEl('item 4')); + }); + + it('should navigate to the first focusable item on Home', () => { + fixture.componentInstance.widgets[0].disabled.set(true); + fixture.detectChanges(); + const item5 = getWidgetEl('item 5')!; + click(item5); + home(); + expect(document.activeElement).toBe(getWidgetEl('item 1')); + }); + + describe('with wrap true', () => { + beforeEach(() => { + fixture.componentInstance.wrap.set(true); + fixture.detectChanges(); + }); + + it('should wrap from last to first focusable item', () => { + fixture.componentInstance.widgets[0].disabled.set(true); + fixture.detectChanges(); + const item5 = getWidgetEl('item 5')!; + click(item5); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 1')); + }); + + it('should wrap from first to last focusable item', () => { + fixture.componentInstance.widgets[5].disabled.set(true); + fixture.detectChanges(); + const item0 = getWidgetEl('item 0')!; + click(item0); + left(); + expect(document.activeElement).toBe(getWidgetEl('item 4')); + }); + }); + + describe('with wrap false', () => { + beforeEach(() => { + fixture.componentInstance.wrap.set(false); + fixture.detectChanges(); + }); + + it('should not wrap from last to first focusable item', () => { + fixture.componentInstance.widgets[0].disabled.set(true); + fixture.detectChanges(); + const item5 = getWidgetEl('item 5')!; + click(item5); + right(); + expect(document.activeElement).toBe(item5); + }); + + it('should not wrap from first to last focusable item', () => { + fixture.componentInstance.widgets[5].disabled.set(true); + fixture.detectChanges(); + const item0 = getWidgetEl('item 0')!; + click(item0); + left(); + expect(document.activeElement).toBe(item0); + }); + }); + }); }); - }); - describe('RTL', () => { - beforeEach(() => { - setupTestToolbar('rtl'); - updateToolbar({widgetGroupDisabled: true, orientation: 'horizontal'}); - }); + describe('with rtl text direction', () => { + beforeEach(() => setupToolbar({textDirection: 'rtl'})); - describe('horizontal orientation', () => { - it('should move focus to the next widget on ArrowLeft', () => { - left(); - expect(document.activeElement).toBe(widgetElements[2]); + it('should navigate on click', () => { + const item3 = getWidgetEl('item 3')!; + click(item3); + expect(document.activeElement).toBe(item3); }); - it('should move focus to the previous widget on ArrowRight', () => { + it('should navigate next on ArrowLeft', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); left(); - expect(document.activeElement).toBe(widgetElements[2]); - - right(); - expect(document.activeElement).toBe(widgetElements[0]); + expect(document.activeElement).toBe(getWidgetEl('item 1')); }); - }); - }); - }); - - describe('pointer navigation', () => { - beforeEach(() => setupTestToolbar()); - - it('should move focus to the clicked widget', () => { - click(widgetElements[2]); - expect(document.activeElement).toBe(widgetElements[2]); - }); - - it('should move focus to the clicked disabled widget if skipDisabled=false', () => { - updateToolbar({skipDisabled: false}); - click(widgetElements[1]); - expect(document.activeElement).toBe(widgetElements[1]); - }); - - it('should not move focus to the clicked disabled widget if skipDisabled=true', () => { - updateToolbar({skipDisabled: true}); - const initiallyFocused = document.activeElement; - - click(widgetElements[1]); - - expect(document.activeElement).toBe(initiallyFocused); - }); - }); - - describe('widget group', () => { - describe('LTR', () => { - beforeEach(() => { - setupTestToolbar('ltr'); - const widgetGroupElement = testWidgetGroupInstance.toolbarWidgetGroup.element(); - click(widgetGroupElement); - testWidgetGroupInstance.lastAction.set(undefined); - }); - describe('vertical orientation', () => { - beforeEach(() => { - updateToolbar({orientation: 'vertical'}); + it('should navigate prev on ArrowRight', () => { + click(getWidgetEl('item 1')!); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 0')); }); - it('should call "next" on ArrowDown', () => { + it('should not navigate next on ArrowDown when not in a widget group', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); down(); - expect(testWidgetGroupInstance.lastAction()).toBe('next'); + expect(document.activeElement).toBe(item0); }); - it('should call "prev" on ArrowUp', () => { + it('should not navigate prev on ArrowUp when not in a widget group', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); up(); - expect(testWidgetGroupInstance.lastAction()).toBe('prev'); + expect(document.activeElement).toBe(item0); }); - it('should call "next" with wrap on ArrowRight', () => { - right(); - expect(testWidgetGroupInstance.lastAction()).toBe('nextWithWrap'); + it('should navigate next in a widget group on ArrowDown', () => { + const item2 = getWidgetEl('item 2')!; + click(item2); + down(); + expect(document.activeElement).toBe(getWidgetEl('item 3')); }); - it('should call "prev" with wrap on ArrowLeft', () => { - left(); - expect(testWidgetGroupInstance.lastAction()).toBe('prevWithWrap'); + it('should navigate prev in a widget group on ArrowUp', () => { + const item3 = getWidgetEl('item 3')!; + click(item3); + up(); + expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - }); - describe('horizontal orientation', () => { - beforeEach(() => { - updateToolbar({orientation: 'horizontal'}); + it('should navigate first to last in a widget group on ArrowUp', () => { + const item2 = getWidgetEl('item 2')!; + click(item2); + up(); + expect(document.activeElement).toBe(getWidgetEl('item 4')); }); - it('should call "next" on ArrowRight', () => { - right(); - expect(testWidgetGroupInstance.lastAction()).toBe('next'); + it('should navigate last to first in a widget group on ArrowDown', () => { + const item4 = getWidgetEl('item 4')!; + click(item4); + down(); + expect(document.activeElement).toBe(getWidgetEl('item 2')); }); + }); + }); - it('should call "prev" on ArrowLeft', () => { - left(); - expect(testWidgetGroupInstance.lastAction()).toBe('prev'); - }); + describe('with vertical orientation', () => { + beforeEach(() => setupToolbar({orientation: 'vertical'})); - it('should call "next" with wrap on ArrowDown', () => { - down(); - expect(testWidgetGroupInstance.lastAction()).toBe('nextWithWrap'); - }); + it('should navigate next on ArrowDown', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + down(); + expect(document.activeElement).toBe(getWidgetEl('item 1')); + }); - it('should call "prev" with wrap on ArrowUp', () => { - up(); - expect(testWidgetGroupInstance.lastAction()).toBe('prevWithWrap'); - }); + it('should navigate prev on ArrowUp', () => { + const item1 = getWidgetEl('item 1')!; + click(item1); + up(); + expect(document.activeElement).toBe(getWidgetEl('item 0')); }); - it('should call "unfocus" on Home key', () => { - home(); - expect(testWidgetGroupInstance.lastAction()).toBe('unfocus'); - expect(document.activeElement).toBe(widgetElements[0]); + it('should not navigate next on ArrowRight when not in a widget group', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + right(); + expect(document.activeElement).toBe(item0); }); - it('should call "unfocus" on End key', () => { - end(); - expect(testWidgetGroupInstance.lastAction()).toBe('unfocus'); - expect(document.activeElement).toBe(widgetElements[2]); + it('should not navigate prev on ArrowLeft when not in a widget group', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + left(); + expect(document.activeElement).toBe(item0); }); - it('should call "trigger" on Enter key', () => { - enter(); - expect(testWidgetGroupInstance.lastAction()).toBe('trigger'); + it('should navigate next in a widget group on ArrowRight', () => { + const item2 = getWidgetEl('item 2')!; + click(item2); + right(); + expect(document.activeElement).toBe(getWidgetEl('item 3')); }); - it('should call "trigger" on Space key', () => { - space(); - expect(testWidgetGroupInstance.lastAction()).toBe('trigger'); + it('should navigate prev in a widget group on ArrowLeft', () => { + const item3 = getWidgetEl('item 3')!; + click(item3); + left(); + expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should call "first" when navigating into a group from the previous widget', () => { - click(widgetElements[0]); + it('should navigate last to first in a widget group on ArrowRight', () => { + const item4 = getWidgetEl('item 4')!; + click(item4); right(); - expect(testWidgetGroupInstance.lastAction()).toBe('first'); + expect(document.activeElement).toBe(getWidgetEl('item 2')); }); - it('should call "last" when navigating into a group from the next widget', () => { - click(widgetElements[2]); + it('should navigate first to last in a widget group on ArrowLeft', () => { + const item2 = getWidgetEl('item 2')!; + click(item2); left(); - expect(testWidgetGroupInstance.lastAction()).toBe('last'); + expect(document.activeElement).toBe(getWidgetEl('item 4')); }); + }); + + describe('with disabled toolbar', () => { + it('should not navigate on any key press', () => { + setupToolbar({disabled: true}); + const item0 = getWidgetEl('item 0')!; + const initialActiveElement = document.activeElement; + click(item0); + expect(document.activeElement).toBe(initialActiveElement); + + right(); + expect(document.activeElement).toBe(initialActiveElement); - it('should call "goto" on click', () => { - click(testWidgetGroupInstance.toolbarWidgetGroup.element()); - expect(testWidgetGroupInstance.lastAction()).toBe('goto'); + left(); + expect(document.activeElement).toBe(initialActiveElement); + + down(); + expect(document.activeElement).toBe(initialActiveElement); + + up(); + expect(document.activeElement).toBe(initialActiveElement); + + home(); + expect(document.activeElement).toBe(initialActiveElement); + + end(); + expect(document.activeElement).toBe(initialActiveElement); }); }); - describe('RTL', () => { + describe('with wrapped toolbar widgets', () => { beforeEach(() => { - setupTestToolbar('rtl'); - const widgetGroupElement = testWidgetGroupInstance.toolbarWidgetGroup.element(); - click(widgetGroupElement); - testWidgetGroupInstance.lastAction.set(undefined); - updateToolbar({orientation: 'horizontal'}); + TestBed.configureTestingModule({imports: [WrappedToolbarExample]}); + fixture = TestBed.createComponent(WrappedToolbarExample) as any; + fixture.detectChanges(); }); - describe('horizontal orientation', () => { - it('should call "next" on ArrowLeft', () => { - left(); - expect(testWidgetGroupInstance.lastAction()).toBe('next'); - }); - - it('should call "prev" on ArrowRight', () => { - right(); - expect(testWidgetGroupInstance.lastAction()).toBe('prev'); - }); + it('should navigate on click', () => { + const widgets = fixture.debugElement + .queryAll(By.css('[toolbar-button]')) + .map((debugEl: DebugElement) => debugEl.nativeElement as HTMLElement); + click(widgets[0]); + expect(document.activeElement).toBe(widgets[0]); }); }); }); -}); -@Component({ - template: 'a black box', - selector: 'testWidgetGroup', - hostDirectives: [ - { - directive: ToolbarWidgetGroup, - inputs: ['disabled'], - }, - ], -}) -class TestToolbarWidgetGroup { - readonly toolbarWidgetGroup = inject(ToolbarWidgetGroup); - readonly lastAction = signal(undefined); - - constructor() { - this.toolbarWidgetGroup.controls.set({ - isOnFirstItem: () => false, - isOnLastItem: () => false, - next: wrap => { - this.lastAction.set(wrap ? 'nextWithWrap' : 'next'); - }, - prev: wrap => { - this.lastAction.set(wrap ? 'prevWithWrap' : 'prev'); - }, - first: () => { - this.lastAction.set('first'); - }, - last: () => { - this.lastAction.set('last'); - }, - unfocus: () => { - this.lastAction.set('unfocus'); - }, - trigger: () => { - this.lastAction.set('trigger'); - }, - goto: () => { - this.lastAction.set('goto'); - }, - setDefaultState: () => { - this.lastAction.set('setDefaultState'); - }, + describe('Selection', () => { + beforeEach(() => setupToolbar()); + + it('should toggle the active item on Enter', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + keydown('Enter'); + expect(item0.getAttribute('aria-pressed')).toBe('false'); + keydown('Enter'); + expect(item0.getAttribute('aria-pressed')).toBe('true'); + }); + + it('should toggle the active item on Space', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + keydown(' '); + expect(item0.getAttribute('aria-pressed')).toBe('false'); + keydown(' '); + expect(item0.getAttribute('aria-pressed')).toBe('true'); + }); + + it('should toggle the active item on click', () => { + const item0 = getWidgetEl('item 0')!; + click(item0); + expect(item0.getAttribute('aria-pressed')).toBe('true'); + click(item0); + expect(item0.getAttribute('aria-pressed')).toBe('false'); }); - } -} + + it('should be able to select multiple items in the toolbar', () => { + const item0 = getWidgetEl('item 0')!; + const item1 = getWidgetEl('item 1')!; + click(item0); + click(item1); + expect(item0.getAttribute('aria-pressed')).toBe('true'); + expect(item1.getAttribute('aria-pressed')).toBe('true'); + }); + + it('should not be able to select multiple items in a group', () => { + const item2 = getWidgetEl('item 2')!; + const item3 = getWidgetEl('item 3')!; + click(item2); + click(item3); + expect(item2.getAttribute('aria-pressed')).toBe('false'); + expect(item3.getAttribute('aria-pressed')).toBe('true'); + }); + + it('should not select disabled items', () => { + fixture.componentInstance.widgets[1].disabled.set(true); + fixture.detectChanges(); + const item1 = getWidgetEl('item 1')!; + click(item1); + expect(item1.getAttribute('aria-pressed')).toBe('false'); + }); + + it('should not select items in a disabled group', () => { + fixture.componentInstance.groups[0].disabled.set(true); + fixture.detectChanges(); + const item2 = getWidgetEl('item 2')!; + click(item2); + expect(item2.getAttribute('aria-pressed')).toBe('false'); + }); + }); +}); @Component({ template: `
        - - - - + + +
        + + + +
        +
        `, - imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup, TestToolbarWidgetGroup], + imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], }) -class TestToolbarComponent { +class ToolbarExample { orientation = signal<'vertical' | 'horizontal'>('horizontal'); + softDisabled = signal(true); disabled = signal(false); - widgetGroupDisabled = signal(false); wrap = signal(true); - skipDisabled = signal(true); + + widgets = [ + {disabled: signal(false)}, + {disabled: signal(false)}, + {disabled: signal(false)}, + {disabled: signal(false)}, + {disabled: signal(false)}, + {disabled: signal(false)}, + ]; + + groups = [{disabled: signal(false)}]; } + +@Directive({ + selector: 'button[toolbar-button]', + hostDirectives: [{directive: ToolbarWidget, inputs: ['value', 'disabled']}], + host: { + type: 'button', + class: 'example-button material-symbols-outlined', + '[aria-label]': 'widget.value()', + }, +}) +export class SimpleToolbarButton { + widget = inject(ToolbarWidget); +} + +@Component({ + template: ` +
        + + +
        + `, + imports: [Toolbar, SimpleToolbarButton], +}) +class WrappedToolbarExample {} diff --git a/src/aria/toolbar/toolbar.ts b/src/aria/toolbar/toolbar.ts index 9d3b20f4c829..35a5701f2d27 100644 --- a/src/aria/toolbar/toolbar.ts +++ b/src/aria/toolbar/toolbar.ts @@ -15,84 +15,93 @@ import { input, booleanAttribute, signal, - Signal, OnInit, OnDestroy, + contentChildren, + model, } from '@angular/core'; import { ToolbarPattern, ToolbarWidgetPattern, ToolbarWidgetGroupPattern, - ToolbarWidgetGroupControls, -} from '@angular/aria/ui-patterns'; + SignalLike, +} from '@angular/aria/private'; import {Directionality} from '@angular/cdk/bidi'; import {_IdGenerator} from '@angular/cdk/a11y'; interface HasElement { - element: Signal; + element: HTMLElement; } /** * Sort directives by their document order. */ function sortDirectives(a: HasElement, b: HasElement) { - return (a.element().compareDocumentPosition(b.element()) & Node.DOCUMENT_POSITION_PRECEDING) > 0 + return (a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_PRECEDING) > 0 ? 1 : -1; } /** - * A toolbar widget container. - * - * Widgets such as radio groups or buttons are nested within a toolbar to allow for a single - * place of reference for focus and navigation. The Toolbar is meant to be used in conjunction - * with ToolbarWidget and RadioGroup as follows: + * A toolbar widget container for a group of interactive widgets, such as + * buttons or radio groups. It provides a single point of reference for keyboard navigation + * and focus management. It supports various orientations and disabled states. * * ```html - *
        - * - *
        - * - * - * - *
        + *
        + * + * + * + *
        + * + * + * + *
        *
        * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngToolbar]', exportAs: 'ngToolbar', host: { 'role': 'toolbar', - 'class': 'ng-toolbar', - '[attr.tabindex]': 'pattern.tabindex()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-orientation]': 'pattern.orientation()', - '(keydown)': 'pattern.onKeydown($event)', - '(pointerdown)': 'pattern.onPointerdown($event)', - '(focusin)': 'onFocus()', + '[attr.tabindex]': '_pattern.tabIndex()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-orientation]': '_pattern.orientation()', + '(keydown)': '_pattern.onKeydown($event)', + '(click)': '_pattern.onClick($event)', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(focusin)': '_onFocus()', }, }) export class Toolbar { - /** A reference to the toolbar element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The TabList nested inside of the container. */ - private readonly _widgets = signal(new Set | ToolbarWidgetGroup>()); + private readonly _widgets = signal(new Set>()); - /** A signal wrapper for directionality. */ + /** Text direction. */ readonly textDirection = inject(Directionality).valueSignal; /** Sorted UIPatterns of the child widgets */ - readonly items = computed(() => - [...this._widgets()].sort(sortDirectives).map(widget => widget.pattern), + readonly _itemPatterns = computed(() => + [...this._widgets()].sort(sortDirectives).map(widget => widget._pattern), ); /** Whether the toolbar is vertically or horizontally oriented. */ readonly orientation = input<'vertical' | 'horizontal'>('horizontal'); - /** Whether disabled items in the group should be skipped when navigating. */ - readonly skipDisabled = input(false, {transform: booleanAttribute}); + /** + * Whether to allow disabled items to receive focus. When `true`, disabled items are + * focusable but not interactive. When `false`, disabled items are skipped during navigation. + */ + softDisabled = input(true, {transform: booleanAttribute}); /** Whether the toolbar is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); @@ -100,22 +109,27 @@ export class Toolbar { /** Whether focus should wrap when navigating. */ readonly wrap = input(true, {transform: booleanAttribute}); + /** The values of the selected widgets within the toolbar. */ + readonly values = model([]); + /** The toolbar UIPattern. */ - readonly pattern: ToolbarPattern = new ToolbarPattern({ + readonly _pattern: ToolbarPattern = new ToolbarPattern({ ...this, + items: this._itemPatterns, activeItem: signal(undefined), textDirection: this.textDirection, element: () => this._elementRef.nativeElement, getItem: e => this._getItem(e), + values: this.values, }); /** Whether the toolbar has received focus yet. */ - private _hasFocused = signal(false); + private _hasBeenFocused = signal(false); constructor() { afterRenderEffect(() => { if (typeof ngDevMode === 'undefined' || ngDevMode) { - const violations = this.pattern.validate(); + const violations = this._pattern.validate(); for (const violation of violations) { console.error(violation); } @@ -123,17 +137,17 @@ export class Toolbar { }); afterRenderEffect(() => { - if (!this._hasFocused()) { - this.pattern.setDefaultState(); + if (!this._hasBeenFocused()) { + this._pattern.setDefaultState(); } }); } - onFocus() { - this._hasFocused.set(true); + _onFocus() { + this._hasBeenFocused.set(true); } - register(widget: ToolbarWidget | ToolbarWidgetGroup) { + _register(widget: ToolbarWidget) { const widgets = this._widgets(); if (!widgets.has(widget)) { widgets.add(widget); @@ -141,7 +155,7 @@ export class Toolbar { } } - unregister(widget: ToolbarWidget | ToolbarWidgetGroup) { + _unregister(widget: ToolbarWidget) { const widgets = this._widgets(); if (widgets.delete(widget)) { this._widgets.set(new Set(widgets)); @@ -150,121 +164,133 @@ export class Toolbar { /** Finds the toolbar item associated with a given element. */ private _getItem(element: Element) { - const widgetTarget = element.closest('.ng-toolbar-widget'); - const groupTarget = element.closest('.ng-toolbar-widget-group'); - return this.items().find( - widget => widget.element() === widgetTarget || widget.element() === groupTarget, - ); + return this._itemPatterns().find(item => item.element()?.contains(element)); } } /** * A widget within a toolbar. * - * A widget is anything that is within a toolbar. It should be applied to any native HTML element - * that has the purpose of acting as a widget navigatable within a toolbar. + * The `ngToolbarWidget` directive should be applied to any native HTML element that acts + * as an interactive widget within an `ngToolbar` or `ngToolbarWidgetGroup`. It enables + * keyboard navigation and selection within the toolbar. + * + * ```html + * + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngToolbarWidget]', exportAs: 'ngToolbarWidget', host: { - 'class': 'ng-toolbar-widget', - '[attr.data-active]': 'pattern.active()', - '[attr.tabindex]': 'pattern.tabindex()', + '[attr.data-active]': 'active()', + '[attr.tabindex]': '_pattern.tabIndex()', '[attr.inert]': 'hardDisabled() ? true : null', '[attr.disabled]': 'hardDisabled() ? true : null', - '[attr.aria-disabled]': 'pattern.disabled()', - '[id]': 'pattern.id()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[id]': '_pattern.id()', }, }) export class ToolbarWidget implements OnInit, OnDestroy { - /** A reference to the widget element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The parent Toolbar. */ private readonly _toolbar = inject(Toolbar); /** A unique identifier for the widget. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-toolbar-widget-'); - - /** A unique identifier for the widget. */ - readonly id = computed(() => this._generatedId); + readonly id = input(inject(_IdGenerator).getId('ng-toolbar-widget-', true)); /** The parent Toolbar UIPattern. */ - readonly toolbar = computed(() => this._toolbar.pattern); - - /** A reference to the widget element to be focused on navigation. */ - readonly element = computed(() => this._elementRef.nativeElement); + readonly _toolbarPattern = computed(() => this._toolbar._pattern); /** Whether the widget is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); /** Whether the widget is 'hard' disabled, which is different from `aria-disabled`. A hard disabled widget cannot receive focus. */ - readonly hardDisabled = computed(() => this.pattern.disabled() && this._toolbar.skipDisabled()); + readonly hardDisabled = computed(() => this._pattern.disabled() && !this._toolbar.softDisabled()); + + /** The optional ToolbarWidgetGroup this widget belongs to. */ + readonly _group = inject(ToolbarWidgetGroup, {optional: true}); + + /** The value associated with the widget. */ + readonly value = input.required(); + + /** Whether the widget is currently active (focused). */ + readonly active = computed(() => this._pattern.active()); + + /** Whether the widget is selected (only relevant in a selection group). */ + readonly selected = () => this._pattern.selected(); + + private readonly _groupPattern: SignalLike< + ToolbarWidgetGroupPattern, V> | undefined + > = () => this._group?._pattern; /** The ToolbarWidget UIPattern. */ - readonly pattern = new ToolbarWidgetPattern({ + readonly _pattern = new ToolbarWidgetPattern({ ...this, + group: this._groupPattern, + toolbar: this._toolbarPattern, id: this.id, - element: this.element, - disabled: computed(() => this._toolbar.disabled() || this.disabled()), + value: this.value, + element: () => this.element, }); ngOnInit() { - this._toolbar.register(this); + this._toolbar._register(this); } ngOnDestroy() { - this._toolbar.unregister(this); + this._toolbar._unregister(this); } } /** - * A directive that groups toolbar widgets, used for more complex widgets like radio groups that - * have their own internal navigation. + * A directive that groups toolbar widgets, used for more complex widgets like radio groups + * that have their own internal navigation. + * + * @developerPreview 21.0 */ @Directive({ - host: { - '[class.ng-toolbar-widget-group]': '!!toolbar()', - }, + selector: '[ngToolbarWidgetGroup]', + exportAs: 'ngToolbarWidgetGroup', }) -export class ToolbarWidgetGroup implements OnInit, OnDestroy { - /** A reference to the widget element. */ +export class ToolbarWidgetGroup { + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The parent Toolbar. */ private readonly _toolbar = inject(Toolbar, {optional: true}); - /** A unique identifier for the widget. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-toolbar-widget-group-'); - - /** A unique identifier for the widget. */ - readonly id = computed(() => this._generatedId); + /** The list of child widgets within the group. */ + private readonly _widgets = contentChildren(ToolbarWidget, {descendants: true}); /** The parent Toolbar UIPattern. */ - readonly toolbar = computed(() => this._toolbar?.pattern); - - /** A reference to the widget element to be focused on navigation. */ - readonly element = computed(() => this._elementRef.nativeElement); + private readonly _toolbarPattern = computed(() => this._toolbar?._pattern); /** Whether the widget group is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); - /** The controls that can be performed on the widget group. */ - readonly controls = signal(undefined); + /** The list of toolbar items within the group. */ + private readonly _itemPatterns = () => this._widgets().map(w => w._pattern); + + /** Whether the group allows multiple widgets to be selected. */ + readonly multi = input(false, {transform: booleanAttribute}); /** The ToolbarWidgetGroup UIPattern. */ - readonly pattern = new ToolbarWidgetGroupPattern({ + readonly _pattern = new ToolbarWidgetGroupPattern, V>({ ...this, - id: this.id, - element: this.element, + items: this._itemPatterns, + toolbar: this._toolbarPattern, }); - - ngOnInit() { - this._toolbar?.register(this); - } - - ngOnDestroy() { - this._toolbar?.unregister(this); - } } diff --git a/src/aria/tree/BUILD.bazel b/src/aria/tree/BUILD.bazel index 06577efb9b90..8b9d4aa686b6 100644 --- a/src/aria/tree/BUILD.bazel +++ b/src/aria/tree/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite") +load("//tools/adev-api-extraction:extract_api_to_json.bzl", "extract_api_to_json") package(default_visibility = ["//visibility:public"]) @@ -11,8 +12,7 @@ ng_project( deps = [ "//:node_modules/@angular/core", "//src/aria/combobox", - "//src/aria/deferred-content", - "//src/aria/ui-patterns", + "//src/aria/private", "//src/cdk/a11y", "//src/cdk/bidi", ], @@ -37,3 +37,23 @@ ng_web_test_suite( name = "unit_tests", deps = [":unit_test_sources"], ) + +filegroup( + name = "source-files", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), +) + +extract_api_to_json( + name = "json_api", + srcs = [ + ":source-files", + ], + entry_point = ":index.ts", + module_name = "@angular/aria/tree", + output_name = "aria-tree.json", + private_modules = [""], + repo = "angular/components", +) diff --git a/src/aria/tree/tree.spec.ts b/src/aria/tree/tree.spec.ts index f19eb29f433b..7ef922ac8f1d 100644 --- a/src/aria/tree/tree.spec.ts +++ b/src/aria/tree/tree.spec.ts @@ -75,12 +75,12 @@ describe('Tree', () => { function updateTree( config: { nodes?: TestTreeNode[]; - value?: string[]; + values?: string[]; disabled?: boolean; orientation?: 'horizontal' | 'vertical'; multi?: boolean; wrap?: boolean; - skipDisabled?: boolean; + softDisabled?: boolean; focusMode?: 'roving' | 'activedescendant'; selectionMode?: 'follow' | 'explicit'; nav?: boolean; @@ -88,12 +88,12 @@ describe('Tree', () => { } = {}, ) { if (config.nodes !== undefined) testComponent.nodes.set(config.nodes); - if (config.value !== undefined) treeInstance.value.set(config.value); + if (config.values !== undefined) treeInstance.values.set(config.values); if (config.disabled !== undefined) testComponent.disabled.set(config.disabled); if (config.orientation !== undefined) testComponent.orientation.set(config.orientation); if (config.multi !== undefined) testComponent.multi.set(config.multi); if (config.wrap !== undefined) testComponent.wrap.set(config.wrap); - if (config.skipDisabled !== undefined) testComponent.skipDisabled.set(config.skipDisabled); + if (config.softDisabled !== undefined) testComponent.softDisabled.set(config.softDisabled); if (config.focusMode !== undefined) testComponent.focusMode.set(config.focusMode); if (config.selectionMode !== undefined) testComponent.selectionMode.set(config.selectionMode); if (config.nav !== undefined) testComponent.nav.set(config.nav); @@ -149,7 +149,7 @@ describe('Tree', () => { click(berriesEl); const vegetablesEl = getTreeItemElementByValue('vegetables')!; click(vegetablesEl); - updateTree({value: []}); + updateTree({values: []}); } afterEach(async () => { @@ -282,7 +282,7 @@ describe('Tree', () => { it('should set aria-selected to "true" for selected items', () => { expandAll(); - updateTree({value: ['apple']}); + updateTree({values: ['apple']}); const appleItem = getTreeItemElementByValue('apple')!; expect(appleItem.getAttribute('aria-selected')).toBe('true'); @@ -299,7 +299,7 @@ describe('Tree', () => { it('should set aria-current to specific current type when nav="true"', () => { expandAll(); - updateTree({nav: true, value: ['apple']}); + updateTree({nav: true, values: ['apple']}); const appleItem = getTreeItemElementByValue('apple')!; const bananaItem = getTreeItemElementByValue('banana')!; @@ -312,7 +312,7 @@ describe('Tree', () => { it('should not set aria-current when not selectable', () => { expandAll(); - updateTree({nav: true, value: ['apple']}); + updateTree({nav: true, values: ['apple']}); const appleItem = getTreeItemElementByValue('apple')!; expect(appleItem.getAttribute('aria-current')).toBe('page'); @@ -323,7 +323,7 @@ describe('Tree', () => { it('should not set aria-selected when nav="true"', () => { expandAll(); - updateTree({value: ['apple'], nav: true}); + updateTree({values: ['apple'], nav: true}); const appleItem = getTreeItemElementByValue('apple')!; expect(appleItem.hasAttribute('aria-selected')).toBe(false); @@ -333,7 +333,7 @@ describe('Tree', () => { it('should not set aria-selected when not selectable', () => { expandAll(); - updateTree({value: ['apple']}); + updateTree({values: ['apple']}); const appleItem = getTreeItemElementByValue('apple')!; expect(appleItem.getAttribute('aria-selected')).toBe('true'); @@ -352,7 +352,13 @@ describe('Tree', () => { expect(treeElement.getAttribute('tabindex')).toBe('-1'); }); - it('should set tabindex="0" for the tree when disabled', () => { + it('should set tabindex="0" for the tree when disabled when softDisabled is false', () => { + updateTree({disabled: true, focusMode: 'roving', softDisabled: false}); + + expect(treeElement.getAttribute('tabindex')).toBe('0'); + }); + + it('should set tabindex="0" for the tree when disabled when softDisabled is true', () => { updateTree({disabled: true, focusMode: 'roving'}); expect(treeElement.getAttribute('tabindex')).toBe('0'); @@ -371,7 +377,7 @@ describe('Tree', () => { }); it('should set initial focus (tabindex="0") on the first selected item', () => { - updateTree({value: ['vegetables', 'dairy'], focusMode: 'roving'}); + updateTree({values: ['vegetables', 'dairy'], focusMode: 'roving'}); const fruitsItem = getTreeItemElementByValue('fruits')!; const vegetablesItem = getTreeItemElementByValue('vegetables')!; @@ -405,7 +411,7 @@ describe('Tree', () => { }); it('should set aria-activedescendant to the ID of the first selected item', () => { - updateTree({value: ['vegetables', 'dairy'], focusMode: 'activedescendant'}); + updateTree({values: ['vegetables', 'dairy'], focusMode: 'activedescendant'}); const vegetablesItem = getTreeItemElementByValue('vegetables')!; expect(treeElement.getAttribute('aria-activedescendant')).toBe(vegetablesItem.id); @@ -433,7 +439,7 @@ describe('Tree', () => { it('should select items based on the initial value input', () => { setupTestTree(); expandAll(); - updateTree({value: ['apple', 'strawberry', 'carrot']}); + updateTree({values: ['apple', 'strawberry', 'carrot']}); expect(getTreeItemElementByValue('apple')!.getAttribute('aria-selected')).toBe('true'); expect(getTreeItemElementByValue('strawberry')!.getAttribute('aria-selected')).toBe('true'); @@ -454,12 +460,12 @@ describe('Tree', () => { const bananaEl = getTreeItemElementByValue('banana')!; click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + expect(treeInstance.values()).toEqual(['apple']); expect(appleEl.getAttribute('aria-selected')).toBe('true'); expect(bananaEl.getAttribute('aria-selected')).toBe('false'); click(bananaEl); - expect(treeInstance.value()).toEqual(['banana']); + expect(treeInstance.values()).toEqual(['banana']); expect(appleEl.getAttribute('aria-selected')).toBe('false'); expect(bananaEl.getAttribute('aria-selected')).toBe('true'); }); @@ -487,11 +493,11 @@ describe('Tree', () => { const carrotEl = getTreeItemElementByValue('carrot')!; click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + expect(treeInstance.values()).toEqual(['apple']); expect(appleEl.getAttribute('aria-selected')).toBe('true'); shiftClick(carrotEl); - expect(treeInstance.value()).toEqual([ + expect(treeInstance.values()).toEqual([ 'apple', 'banana', 'berries', @@ -505,24 +511,24 @@ describe('Tree', () => { const bananaEl = getTreeItemElementByValue('banana')!; click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + expect(treeInstance.values()).toEqual(['apple']); click(bananaEl); - expect(treeInstance.value()).toEqual(['apple', 'banana']); + expect(treeInstance.values()).toEqual(['apple', 'banana']); click(appleEl); - expect(treeInstance.value()).toEqual(['banana']); + expect(treeInstance.values()).toEqual(['banana']); }); describe('selectable=false', () => { it('should not select an item on click', () => { - updateTree({value: ['banana']}); + updateTree({values: ['banana']}); updateTreeItemByValue('apple', {selectable: false}); const appleEl = getTreeItemElementByValue('apple')!; click(appleEl); - expect(treeInstance.value()).not.toContain('apple'); - expect(treeInstance.value()).toContain('banana'); + expect(treeInstance.values()).not.toContain('apple'); + expect(treeInstance.values()).toContain('banana'); }); }); }); @@ -539,13 +545,13 @@ describe('Tree', () => { ctrlClick(appleEl); ctrlClick(bananaEl); - expect(treeInstance.value()).toEqual(['apple', 'banana']); + expect(treeInstance.values()).toEqual(['apple', 'banana']); click(carrotEl); - expect(treeInstance.value()).toEqual(['carrot']); + expect(treeInstance.values()).toEqual(['carrot']); click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + expect(treeInstance.values()).toEqual(['apple']); }); it('should add to selection with ctrl+click and toggle individual items', () => { @@ -553,13 +559,13 @@ describe('Tree', () => { const berriesEl = getTreeItemElementByValue('berries')!; click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + expect(treeInstance.values()).toEqual(['apple']); ctrlClick(berriesEl); - expect(treeInstance.value()).toEqual(['apple', 'berries']); + expect(treeInstance.values()).toEqual(['apple', 'berries']); ctrlClick(appleEl); - expect(treeInstance.value()).toEqual(['berries']); + expect(treeInstance.values()).toEqual(['berries']); }); it('should select a range with shift+click, anchoring from last selected/focused', () => { @@ -569,10 +575,10 @@ describe('Tree', () => { const broccoliEl = getTreeItemElementByValue('broccoli')!; click(appleEl); - expect(treeInstance.value()).toEqual(['apple']); + expect(treeInstance.values()).toEqual(['apple']); shiftClick(carrotEl); - expect(treeInstance.value()).toEqual([ + expect(treeInstance.values()).toEqual([ 'apple', 'banana', 'berries', @@ -581,10 +587,10 @@ describe('Tree', () => { ]); click(berriesEl); - expect(treeInstance.value()).toEqual(['berries']); + expect(treeInstance.values()).toEqual(['berries']); shiftClick(broccoliEl); - expect(treeInstance.value()).toEqual([ + expect(treeInstance.values()).toEqual([ 'berries', 'strawberry', 'blueberry', @@ -603,9 +609,9 @@ describe('Tree', () => { click(appleEl); shiftClick(berriesEl); - expect(treeInstance.value()).not.toContain('banana'); - expect(treeInstance.value()).toContain('apple'); - expect(treeInstance.value()).toContain('berries'); + expect(treeInstance.values()).not.toContain('banana'); + expect(treeInstance.values()).toContain('apple'); + expect(treeInstance.values()).toContain('berries'); }); it('should not toggle selection of an item on simple click', () => { @@ -613,17 +619,17 @@ describe('Tree', () => { const appleEl = getTreeItemElementByValue('apple')!; click(appleEl); - expect(treeInstance.value()).not.toContain('apple'); + expect(treeInstance.values()).not.toContain('apple'); }); it('should not add to selection with ctrl+click', () => { - updateTree({value: ['banana']}); + updateTree({values: ['banana']}); updateTreeItemByValue('apple', {selectable: false}); const appleEl = getTreeItemElementByValue('apple')!; ctrlClick(appleEl); - expect(treeInstance.value()).not.toContain('apple'); - expect(treeInstance.value()).toContain('banana'); + expect(treeInstance.values()).not.toContain('apple'); + expect(treeInstance.values()).toContain('banana'); }); }); }); @@ -644,47 +650,47 @@ describe('Tree', () => { it('should select the focused item with Enter and deselect others', () => { enter(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); down(); enter(); - expect(treeInstance.value()).toEqual(['vegetables']); + expect(treeInstance.values()).toEqual(['vegetables']); }); it('should select the focused item with Space and deselect others', () => { space(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); down(); space(); - expect(treeInstance.value()).toEqual(['vegetables']); + expect(treeInstance.values()).toEqual(['vegetables']); }); it('should move focus with arrows without changing selection until Enter/Space', () => { enter(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); down(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); down(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); enter(); - expect(treeInstance.value()).toEqual(['grains']); + expect(treeInstance.values()).toEqual(['grains']); }); describe('selectable=false', () => { it('should not select the focused item with Enter', () => { updateTreeItemByValue('fruits', {selectable: false}); enter(); - expect(treeInstance.value()).toEqual([]); + expect(treeInstance.values()).toEqual([]); }); it('should not select the focused item with Space', () => { updateTreeItemByValue('fruits', {selectable: false}); space(); - expect(treeInstance.value()).toEqual([]); + expect(treeInstance.values()).toEqual([]); }); }); }); @@ -695,45 +701,45 @@ describe('Tree', () => { }); it('should select an item when it becomes focused with ArrowDown and deselect others', () => { - updateTree({value: ['fruits']}); - expect(treeInstance.value()).toEqual(['fruits']); + updateTree({values: ['fruits']}); + expect(treeInstance.values()).toEqual(['fruits']); down(); - expect(treeInstance.value()).toEqual(['vegetables']); + expect(treeInstance.values()).toEqual(['vegetables']); down(); - expect(treeInstance.value()).toEqual(['grains']); + expect(treeInstance.values()).toEqual(['grains']); }); it('should select an item when it becomes focused with ArrowUp and deselect others', () => { - updateTree({value: ['grains']}); + updateTree({values: ['grains']}); up(); - expect(treeInstance.value()).toEqual(['vegetables']); + expect(treeInstance.values()).toEqual(['vegetables']); }); it('should select the first item with Home and deselect others', () => { - updateTree({value: ['grains']}); - expect(treeInstance.value()).toEqual(['grains']); + updateTree({values: ['grains']}); + expect(treeInstance.values()).toEqual(['grains']); home(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); }); it('should select the last visible item with End and deselect others', () => { - updateTree({value: ['fruits']}); - expect(treeInstance.value()).toEqual(['fruits']); + updateTree({values: ['fruits']}); + expect(treeInstance.values()).toEqual(['fruits']); end(); - expect(treeInstance.value()).toEqual(['dairy']); + expect(treeInstance.values()).toEqual(['dairy']); }); it('should select an item via typeahead and deselect others', () => { - updateTree({value: ['fruits']}); - expect(treeInstance.value()).toEqual(['fruits']); + updateTree({values: ['fruits']}); + expect(treeInstance.values()).toEqual(['fruits']); type('V'); - expect(treeInstance.value()).toEqual(['vegetables']); + expect(treeInstance.values()).toEqual(['vegetables']); }); }); }); @@ -751,35 +757,35 @@ describe('Tree', () => { it('should toggle selection of the focused item with Space, leaving other selections intact', () => { space(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); down(); space(); - expect(treeInstance.value().sort()).toEqual(['fruits', 'vegetables']); + expect(treeInstance.values().sort()).toEqual(['fruits', 'vegetables']); }); it('should move focus with arrows without changing selection', () => { space(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); down(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); }); it('should extend selection downwards with Shift+ArrowDown', () => { shift(); down({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'vegetables']); + expect(treeInstance.values().sort()).toEqual(['fruits', 'vegetables']); down({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'grains', 'vegetables']); + expect(treeInstance.values().sort()).toEqual(['fruits', 'grains', 'vegetables']); }); it('should extend selection upwards with Shift+ArrowUp', () => { end(); shift(); up({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['dairy', 'grains']); + expect(treeInstance.values().sort()).toEqual(['dairy', 'grains']); }); it('Ctrl+A should select all enabled visible items, then deselect all', () => { @@ -793,7 +799,7 @@ describe('Tree', () => { updateTreeItemByValue('broccoli', {disabled: true}); keydown('A', {ctrlKey: true}); - expect(treeInstance.value().sort()).toEqual([ + expect(treeInstance.values().sort()).toEqual([ 'apple', 'banana', 'berries', @@ -804,25 +810,25 @@ describe('Tree', () => { ]); keydown('A', {ctrlKey: true}); - expect(treeInstance.value()).toEqual([]); + expect(treeInstance.values()).toEqual([]); }); it('Ctrl+ArrowKey should move focus without changing selection', () => { space(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); down({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); up({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); }); describe('selectable=false', () => { it('should not toggle selection of the focused item with Space', () => { updateTreeItemByValue('fruits', {selectable: false}); space(); - expect(treeInstance.value()).toEqual([]); + expect(treeInstance.values()).toEqual([]); }); it('should not extend selection with Shift+ArrowDown', () => { @@ -830,8 +836,8 @@ describe('Tree', () => { shift(); down({shiftKey: true}); down({shiftKey: true}); - expect(treeInstance.value()).not.toContain('vegetables'); - expect(treeInstance.value().sort()).toEqual(['fruits', 'grains']); + expect(treeInstance.values()).not.toContain('vegetables'); + expect(treeInstance.values().sort()).toEqual(['fruits', 'grains']); }); it('Ctrl+A should not select non-selectable items', () => { @@ -839,7 +845,7 @@ describe('Tree', () => { updateTreeItemByValue('apple', {selectable: false}); updateTreeItemByValue('carrot', {selectable: false}); keydown('A', {ctrlKey: true}); - const value = treeInstance.value(); + const value = treeInstance.values(); expect(value).not.toContain('apple'); expect(value).not.toContain('carrot'); expect(value).toContain('banana'); @@ -854,98 +860,98 @@ describe('Tree', () => { }); it('should select the focused item and deselect others on ArrowDown', () => { - updateTree({value: ['fruits']}); - expect(treeInstance.value()).toEqual(['fruits']); + updateTree({values: ['fruits']}); + expect(treeInstance.values()).toEqual(['fruits']); down(); - expect(treeInstance.value()).toEqual(['vegetables']); + expect(treeInstance.values()).toEqual(['vegetables']); }); it('should select the focused item and deselect others on ArrowUp', () => { - updateTree({value: ['vegetables']}); - expect(treeInstance.value()).toEqual(['vegetables']); + updateTree({values: ['vegetables']}); + expect(treeInstance.values()).toEqual(['vegetables']); up(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); }); it('should move focus without changing selection on Ctrl+ArrowDown', () => { - updateTree({value: ['fruits']}); + updateTree({values: ['fruits']}); expect(getFocusedTreeItemValue()).toBe('fruits'); down({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); expect(getFocusedTreeItemValue()).toBe('vegetables'); }); it('should move focus without changing selection on Ctrl+ArrowUp', () => { - updateTree({value: ['fruits']}); + updateTree({values: ['fruits']}); down({ctrlKey: true}); expect(getFocusedTreeItemValue()).toBe('vegetables'); up({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); expect(getFocusedTreeItemValue()).toBe('fruits'); }); it('should toggle selection of the focused item on Ctrl+Space, adding to existing selection', () => { - updateTree({value: ['fruits']}); + updateTree({values: ['fruits']}); down({ctrlKey: true}); expect(getFocusedTreeItemValue()).toBe('vegetables'); space({ctrlKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'vegetables']); + expect(treeInstance.values().sort()).toEqual(['fruits', 'vegetables']); space({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); }); it('should toggle selection of the focused item on Ctrl+Enter, adding to existing selection', () => { - updateTree({value: ['fruits']}); + updateTree({values: ['fruits']}); down({ctrlKey: true}); expect(getFocusedTreeItemValue()).toBe('vegetables'); enter({ctrlKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'vegetables']); + expect(treeInstance.values().sort()).toEqual(['fruits', 'vegetables']); enter({ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); }); it('should extend selection downwards with Shift+ArrowDown', () => { right(); // Expands fruits - updateTree({value: ['fruits']}); + updateTree({values: ['fruits']}); shift(); down({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['apple', 'fruits']); + expect(treeInstance.values().sort()).toEqual(['apple', 'fruits']); down({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['apple', 'banana', 'fruits']); + expect(treeInstance.values().sort()).toEqual(['apple', 'banana', 'fruits']); }); it('should extend selection upwards with Shift+ArrowUp', () => { - updateTree({value: ['grains']}); + updateTree({values: ['grains']}); shift(); up({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['grains', 'vegetables']); + expect(treeInstance.values().sort()).toEqual(['grains', 'vegetables']); up({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['fruits', 'grains', 'vegetables']); + expect(treeInstance.values().sort()).toEqual(['fruits', 'grains', 'vegetables']); }); it('should select a range with Shift+Space, anchoring from last selected/focused item', () => { right(); // Expands fruits - updateTree({value: ['fruits']}); + updateTree({values: ['fruits']}); down({ctrlKey: true}); down({ctrlKey: true}); expect(getFocusedTreeItemValue()).toBe('banana'); space({shiftKey: true}); - expect(treeInstance.value().sort()).toEqual(['apple', 'banana', 'fruits']); + expect(treeInstance.values().sort()).toEqual(['apple', 'banana', 'fruits']); }); it('Ctrl+A: select all enabled visible items; second Ctrl+A deselects all except focused', () => { @@ -953,7 +959,7 @@ describe('Tree', () => { updateTreeItemByValue('vegetables', {disabled: true}); keydown('A', {ctrlKey: true}); - expect(treeInstance.value().sort()).toEqual([ + expect(treeInstance.values().sort()).toEqual([ 'apple', 'banana', 'berries', @@ -963,13 +969,13 @@ describe('Tree', () => { ]); keydown('A', {ctrlKey: true}); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); }); it('typeahead should select the focused item and deselect others', () => { - updateTree({value: ['fruits']}); + updateTree({values: ['fruits']}); type('V'); - expect(treeInstance.value()).toEqual(['vegetables']); + expect(treeInstance.values()).toEqual(['vegetables']); expect(getFocusedTreeItemValue()).toBe('vegetables'); }); @@ -977,14 +983,14 @@ describe('Tree', () => { it('should not select an item on ArrowDown', () => { updateTreeItemByValue('vegetables', {selectable: false}); down(); - expect(treeInstance.value()).not.toContain('vegetables'); - expect(treeInstance.value()).toEqual([]); + expect(treeInstance.values()).not.toContain('vegetables'); + expect(treeInstance.values()).toEqual([]); }); it('should not toggle selection of the focused item on Ctrl+Space', () => { updateTreeItemByValue('fruits', {selectable: false}); space({ctrlKey: true}); - expect(treeInstance.value()).toEqual([]); + expect(treeInstance.values()).toEqual([]); }); it('should not extend selection with Shift+ArrowDown', () => { @@ -992,38 +998,38 @@ describe('Tree', () => { shift(); down({shiftKey: true}); down({shiftKey: true}); - expect(treeInstance.value()).not.toContain('vegetables'); - expect(treeInstance.value().sort()).toEqual(['fruits', 'grains']); + expect(treeInstance.values()).not.toContain('vegetables'); + expect(treeInstance.values().sort()).toEqual(['fruits', 'grains']); }); it('typeahead should not select the focused item', () => { updateTreeItemByValue('vegetables', {selectable: false}); type('v'); expect(getFocusedTreeItemValue()).toBe('vegetables'); - expect(treeInstance.value()).not.toContain('vegetables'); + expect(treeInstance.values()).not.toContain('vegetables'); }); }); - it('should not select disabled items during Shift+ArrowKey navigation even if skipDisabled is false', () => { + it('should not select disabled items during Shift+ArrowKey navigation even if softDisabled is true', () => { right(); // Expands fruits updateTreeItemByValue('banana', {disabled: true}); - updateTree({value: ['apple'], skipDisabled: false}); + updateTree({values: ['apple'], softDisabled: true}); expect(getFocusedTreeItemValue()).toBe('apple'); keydown('Shift'); down({shiftKey: true}); expect(getFocusedTreeItemValue()).toBe('banana'); - expect(treeInstance.value().sort()).toEqual(['apple']); + expect(treeInstance.values().sort()).toEqual(['apple']); down({shiftKey: true}); // Focus 'berries' expect(getFocusedTreeItemValue()).toBe('berries'); - expect(treeInstance.value().sort()).toEqual(['apple', 'berries']); + expect(treeInstance.values().sort()).toEqual(['apple', 'berries']); }); it('should not change selection if tree is disabled', () => { - updateTree({value: ['fruits'], disabled: true}); + updateTree({values: ['fruits'], disabled: true}); down(); - expect(treeInstance.value()).toEqual(['fruits']); + expect(treeInstance.values()).toEqual(['fruits']); }); }); }); @@ -1031,6 +1037,78 @@ describe('Tree', () => { }); describe('expansion and collapse', () => { + it('should expand items by setting expanded input', () => { + setupTestTree(); + updateTree({ + nodes: [ + { + value: 'fruits', + label: 'Fruits', + children: [ + {value: 'apple', label: 'Apple'}, + {value: 'banana', label: 'Banana'}, + { + value: 'berries', + label: 'Berries', + children: [ + {value: 'strawberry', label: 'Strawberry'}, + {value: 'blueberry', label: 'Blueberry'}, + ], + expanded: true, + }, + ], + expanded: true, + }, + ], + }); + const fruitsEl = getTreeItemElementByValue('fruits')!; + const berriesEl = getTreeItemElementByValue('berries')!; + expect(fruitsEl.getAttribute('aria-expanded')).toBe('true'); + expect(berriesEl.getAttribute('aria-expanded')).toBe('true'); + }); + + it('should not affect selected item when collapse', () => { + setupTestTree(); + updateTree({ + nodes: [ + { + value: 'fruits', + label: 'Fruits', + children: [ + {value: 'apple', label: 'Apple'}, + {value: 'banana', label: 'Banana'}, + { + value: 'berries', + label: 'Berries', + children: [ + {value: 'strawberry', label: 'Strawberry'}, + {value: 'blueberry', label: 'Blueberry'}, + ], + expanded: true, + }, + ], + expanded: true, + }, + ], + }); + const blueberryEl = getTreeItemElementByValue('blueberry')!; + const berriesEl = getTreeItemElementByValue('berries')!; + const fruits = getTreeItemElementByValue('fruits')!; + + click(blueberryEl); + expect(treeInstance.values()).toEqual(['blueberry']); + + left(); + left(); // collapse berries + expect(berriesEl.getAttribute('aria-expanded')).toBe('false'); + expect(treeInstance.values()).toEqual(['blueberry']); + + left(); + left(); // collapse fruits + expect(fruits.getAttribute('aria-expanded')).toBe('false'); + expect(treeInstance.values()).toEqual(['blueberry']); + }); + describe('LTR', () => { beforeEach(() => { setupTestTree(); @@ -1259,20 +1337,20 @@ describe('Tree', () => { expect(isFocused('fruits')).toBe(true); }); - it('should skip disabled items with ArrowDown if skipDisabled=true', () => { + it('should skip disabled items with ArrowDown if softDisabled=false', () => { right(); // Expands fruits updateTreeItemByValue('apple', {disabled: true}); - updateTree({skipDisabled: true}); + updateTree({softDisabled: false}); expect(isFocused('fruits')).toBe(true); down(); expect(isFocused('banana')).toBe(true); }); - it('should not skip disabled items with ArrowDown if skipDisabled=false', () => { + it('should not skip disabled items with ArrowDown if softDisabled=true', () => { right(); // Expands fruits updateTreeItemByValue('apple', {disabled: true}); - updateTree({skipDisabled: false}); + updateTree({softDisabled: true}); expect(isFocused('fruits')).toBe(true); down(); @@ -1315,7 +1393,8 @@ describe('Tree', () => { }); }); - it('should move focus to the last enabled visible item on End', () => { + it('should move focus to the last enabled visible item on End (softDisabled="false")', () => { + updateTree({softDisabled: false}); right(); // Expands fruits updateTreeItemByValue('dairy', {disabled: true}); updateTreeItemByValue('grains', {disabled: true}); @@ -1324,7 +1403,8 @@ describe('Tree', () => { expect(isFocused('berries')).toBe(true); }); - it('should move focus to the first enabled visible item on Home', () => { + it('should move focus to the first enabled visible item on Home (softDisabled="false")', () => { + updateTree({softDisabled: false}); end(); updateTreeItemByValue('fruits', {disabled: true}); home(); @@ -1386,9 +1466,9 @@ describe('Tree', () => { expect(isFocused('vegetables')).toBe(true); }); - it('should move focus to the clicked disabled item if skipDisabled=false', () => { + it('should move focus to the clicked disabled item if softDisabled=true', () => { updateTreeItemByValue('vegetables', {disabled: true}); - updateTree({skipDisabled: false}); + updateTree({softDisabled: true}); const vegetablesEl = getTreeItemElementByValue('vegetables')!; click(vegetablesEl); expect(isFocused('vegetables')).toBe(true); @@ -1408,27 +1488,27 @@ describe('Tree', () => { updateTree({selectionMode: 'follow'}); type('Gr'); expect(isFocused('grains')).toBe(true); - expect(treeInstance.value()).toEqual(['grains']); + expect(treeInstance.values()).toEqual(['grains']); }); it('should not select the focused item if selectionMode is "explicit"', () => { updateTree({selectionMode: 'explicit'}); type('Gr'); expect(isFocused('grains')).toBe(true); - expect(treeInstance.value()).toEqual([]); + expect(treeInstance.values()).toEqual([]); }); - it('should skip disabled items with typeahead if skipDisabled=true', () => { + it('should skip disabled items with typeahead if softDisabled=false', () => { right(); // Expands fruits updateTreeItemByValue('banana', {disabled: true}); - updateTree({skipDisabled: true}); + updateTree({softDisabled: false}); type('B'); expect(isFocused('berries')).toBe(true); }); - it('should focus disabled items with typeahead if skipDisabled=false', () => { + it('should focus disabled items with typeahead if softDisabled=true', () => { updateTreeItemByValue('vegetables', {disabled: true}); - updateTree({skipDisabled: false}); + updateTree({softDisabled: true}); type('V'); expect(isFocused('vegetables')).toBe(true); }); @@ -1443,6 +1523,7 @@ interface TestTreeNode { label: string; disabled?: boolean; selectable?: boolean; + expanded?: boolean; children?: TestTreeNode[]; } @@ -1454,10 +1535,10 @@ interface TestTreeNode { [selectionMode]="selectionMode()" [multi]="multi()" [wrap]="wrap()" - [skipDisabled]="skipDisabled()" + [softDisabled]="softDisabled()" [orientation]="orientation()" [disabled]="disabled()" - [(value)]="value" + [(values)]="values" [nav]="nav()" [currentType]="currentType()" #tree="ngTree" @@ -1474,6 +1555,7 @@ interface TestTreeNode { [label]="node.label" [disabled]="!!node.disabled" [selectable]="node.selectable ?? true" + [expanded]="node.expanded ?? false" [parent]="parent" [attr.data-value]="node.value" #treeItem="ngTreeItem" @@ -1525,12 +1607,12 @@ class TestTreeComponent { {value: 'grains', label: 'Grains'}, {value: 'dairy', label: 'Dairy'}, ]); - value = signal([]); + values = signal([]); disabled = signal(false); orientation = signal<'vertical' | 'horizontal'>('vertical'); multi = signal(false); wrap = signal(true); - skipDisabled = signal(true); + softDisabled = signal(true); focusMode = signal<'roving' | 'activedescendant'>('roving'); selectionMode = signal<'explicit' | 'follow'>('explicit'); nav = signal(false); diff --git a/src/aria/tree/tree.ts b/src/aria/tree/tree.ts index 045dc951493f..efe4ade8a0c4 100644 --- a/src/aria/tree/tree.ts +++ b/src/aria/tree/tree.ts @@ -20,84 +20,101 @@ import { OnInit, OnDestroy, untracked, + afterNextRender, } from '@angular/core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {Directionality} from '@angular/cdk/bidi'; -import {DeferredContent, DeferredContentAware} from '@angular/aria/deferred-content'; -import {ComboboxTreePattern, TreeItemPattern, TreePattern} from '@angular/aria/ui-patterns'; +import { + ComboboxTreePattern, + TreeItemPattern, + TreePattern, + DeferredContent, + DeferredContentAware, +} from '@angular/aria/private'; import {ComboboxPopup} from '../combobox'; interface HasElement { - element: Signal; + element: HTMLElement; } /** * Sort directives by their document order. */ function sortDirectives(a: HasElement, b: HasElement) { - return (a.element().compareDocumentPosition(b.element()) & Node.DOCUMENT_POSITION_PRECEDING) > 0 + return (a.element.compareDocumentPosition(b.element) & Node.DOCUMENT_POSITION_PRECEDING) > 0 ? 1 : -1; } /** - * A Tree container. - * - * Transforms nested lists into an accessible, ARIA-compliant tree structure. + * A container that transforms nested lists into an accessible, ARIA-compliant tree structure. + * It manages the overall state of the tree, including selection, expansion, and keyboard + * navigation. * * ```html *
          - *
        • Leaf Item 1
        • - *
        • - * Parent Item 1 - *
            - * - *
          • Child Item 1.1
          • - *
          • Child Item 1.2
          • - *
            - *
          - *
        • - *
        • Disabled Leaf Item 2
        • + * *
        + * + * + * @for (node of nodes; track node.name) { + *
      • + * {{ node.name }} + * @if (node.children) { + *
          + * + * + * + *
        + * } + *
      • + * } + *
        * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngTree]', exportAs: 'ngTree', host: { - 'class': 'ng-tree', 'role': 'tree', '[attr.id]': 'id()', - '[attr.aria-orientation]': 'pattern.orientation()', - '[attr.aria-multiselectable]': 'pattern.multi()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-activedescendant]': 'pattern.activedescendant()', - '[tabindex]': 'pattern.tabindex()', - '(keydown)': 'pattern.onKeydown($event)', - '(pointerdown)': 'pattern.onPointerdown($event)', - '(focusin)': 'onFocus()', + '[attr.aria-orientation]': '_pattern.orientation()', + '[attr.aria-multiselectable]': '_pattern.multi()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-activedescendant]': '_pattern.activeDescendant()', + '[tabindex]': '_pattern.tabIndex()', + '(keydown)': '_pattern.onKeydown($event)', + '(pointerdown)': '_pattern.onPointerdown($event)', + '(focusin)': '_onFocus()', }, - hostDirectives: [{directive: ComboboxPopup}], + hostDirectives: [ComboboxPopup], }) export class Tree { - /** A unique identifier for the tree. */ - private readonly _generatedId = inject(_IdGenerator).getId('ng-tree-'); + /** A reference to the host element. */ + private readonly _elementRef = inject(ElementRef); - // TODO(wagnermaciel): https://github.com/angular/components/pull/30495#discussion_r1972601144. - /** A unique identifier for the tree. */ - protected id = computed(() => this._generatedId); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; /** A reference to the parent combobox popup, if one exists. */ private readonly _popup = inject>(ComboboxPopup, { optional: true, }); - /** A reference to the tree element. */ - private readonly _elementRef = inject(ElementRef); - /** All TreeItem instances within this tree. */ private readonly _unorderedItems = signal(new Set>()); + /** A unique identifier for the tree. */ + readonly id = input(inject(_IdGenerator).getId('ng-tree-', true)); + /** Orientation of the tree. */ readonly orientation = input<'vertical' | 'horizontal'>('vertical'); @@ -107,39 +124,53 @@ export class Tree { /** Whether the tree is disabled. */ readonly disabled = input(false, {transform: booleanAttribute}); - /** The selection strategy used by the tree. */ + /** + * The selection strategy used by the tree. + * - `explicit`: Items are selected explicitly by the user (e.g., via click or spacebar). + * - `follow`: The focused item is automatically selected. + */ readonly selectionMode = input<'explicit' | 'follow'>('explicit'); - /** The focus strategy used by the tree. */ + /** + * The focus strategy used by the tree. + * - `roving`: Focus is moved to the active item using `tabindex`. + * - `activedescendant`: Focus remains on the tree container, and `aria-activedescendant` is used to indicate the active item. + */ readonly focusMode = input<'roving' | 'activedescendant'>('roving'); /** Whether navigation wraps. */ readonly wrap = input(true, {transform: booleanAttribute}); - /** Whether to skip disabled items during navigation. */ - readonly skipDisabled = input(true, {transform: booleanAttribute}); + /** + * Whether to allow disabled items to receive focus. When `true`, disabled items are + * focusable but not interactive. When `false`, disabled items are skipped during navigation. + */ + readonly softDisabled = input(true, {transform: booleanAttribute}); - /** Typeahead delay. */ - readonly typeaheadDelay = input(0.5); + /** The delay in seconds before the typeahead search is reset. */ + readonly typeaheadDelay = input(500); - /** Selected item values. */ - readonly value = model([]); + /** The values of the currently selected items. */ + readonly values = model([]); /** Text direction. */ readonly textDirection = inject(Directionality).valueSignal; /** Whether the tree is in navigation mode. */ - readonly nav = input(false); + readonly nav = input(false, {transform: booleanAttribute}); - /** The aria-current type. */ + /** + * The `aria-current` type. It can be used in navigation trees to indicate the currently active item. + * See https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-current for more details. + */ readonly currentType = input<'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'>( 'page', ); /** The UI pattern for the tree. */ - readonly pattern: TreePattern; + readonly _pattern: TreePattern; - /** Whether the tree has received focus yet. */ + /** Whether the tree has received focus since it was rendered. */ private _hasFocused = signal(false); constructor() { @@ -147,24 +178,24 @@ export class Tree { ...this, id: this.id, allItems: computed(() => - [...this._unorderedItems()].sort(sortDirectives).map(item => item.pattern), + [...this._unorderedItems()].sort(sortDirectives).map(item => item._pattern), ), activeItem: signal | undefined>(undefined), - element: () => this._elementRef.nativeElement, - combobox: () => this._popup?.combobox?.pattern, + combobox: () => this._popup?.combobox?._pattern, + element: () => this.element, }; - this.pattern = this._popup?.combobox + this._pattern = this._popup?.combobox ? new ComboboxTreePattern(inputs) : new TreePattern(inputs); if (this._popup?.combobox) { - this._popup?.controls?.set(this.pattern as ComboboxTreePattern); + this._popup?._controls?.set(this._pattern as ComboboxTreePattern); } afterRenderEffect(() => { if (!this._hasFocused()) { - this.pattern.setDefaultState(); + this._pattern.setDefaultState(); } }); @@ -173,68 +204,85 @@ export class Tree { const activeItem = untracked(() => inputs.activeItem()); if (!items.some(i => i === activeItem) && activeItem) { - this.pattern.listBehavior.unfocus(); + this._pattern.listBehavior.unfocus(); } }); afterRenderEffect(() => { + if (!(this._pattern instanceof ComboboxTreePattern)) return; + const items = inputs.allItems(); - const value = untracked(() => this.value()); + const values = untracked(() => this.values()); - if (items && value.some(v => !items.some(i => i.value() === v))) { - this.value.set(value.filter(v => items.some(i => i.value() === v))); + if (items && values.some(v => !items.some(i => i.value() === v))) { + this.values.set(values.filter(v => items.some(i => i.value() === v))); } }); } - onFocus() { + _onFocus() { this._hasFocused.set(true); } - register(child: TreeItem) { + _register(child: TreeItem) { this._unorderedItems().add(child); this._unorderedItems.set(new Set(this._unorderedItems())); } - unregister(child: TreeItem) { + _unregister(child: TreeItem) { this._unorderedItems().delete(child); this._unorderedItems.set(new Set(this._unorderedItems())); } + + scrollActiveItemIntoView(options: ScrollIntoViewOptions = {block: 'nearest'}) { + this._pattern.inputs.activeItem()?.element()?.scrollIntoView(options); + } } /** - * A selectable and expandable Tree Item in a Tree. + * A selectable and expandable item in an `ngTree`. + * + * The `ngTreeItem` directive represents an individual node within an `ngTree`. It can be + * selected, expanded (if it has children), and disabled. The `parent` input establishes + * the hierarchical relationship within the tree. + * + * ```html + *
      • + * Item Label + *
      • + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: '[ngTreeItem]', exportAs: 'ngTreeItem', host: { - 'class': 'ng-treeitem', - '[attr.data-active]': 'pattern.active()', + '[attr.data-active]': 'active()', 'role': 'treeitem', - '[id]': 'pattern.id()', - '[attr.aria-expanded]': 'pattern.expandable() ? pattern.expanded() : null', - '[attr.aria-selected]': 'pattern.selected()', - '[attr.aria-current]': 'pattern.current()', - '[attr.aria-disabled]': 'pattern.disabled()', - '[attr.aria-level]': 'pattern.level()', - '[attr.aria-setsize]': 'pattern.setsize()', - '[attr.aria-posinset]': 'pattern.posinset()', - '[attr.tabindex]': 'pattern.tabindex()', + '[id]': '_pattern.id()', + '[attr.aria-expanded]': '_expanded()', + '[attr.aria-selected]': 'selected()', + '[attr.aria-current]': '_pattern.current()', + '[attr.aria-disabled]': '_pattern.disabled()', + '[attr.aria-level]': 'level()', + '[attr.aria-setsize]': '_pattern.setsize()', + '[attr.aria-posinset]': '_pattern.posinset()', + '[attr.tabindex]': '_pattern.tabIndex()', }, }) export class TreeItem extends DeferredContentAware implements OnInit, OnDestroy, HasElement { - /** A reference to the tree item element. */ + /** A reference to the host element. */ private readonly _elementRef = inject(ElementRef); - /** A unique identifier for the tree item. */ - private readonly _id = inject(_IdGenerator).getId('ng-tree-item-'); + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; /** The owned tree item group. */ private readonly _group = signal | undefined>(undefined); - /** The host native element. */ - readonly element = computed(() => this._elementRef.nativeElement); + /** A unique identifier for the tree item. */ + readonly id = input(inject(_IdGenerator).getId('ng-tree-item-', true)); /** The value of the tree item. */ readonly value = input.required(); @@ -248,11 +296,14 @@ export class TreeItem extends DeferredContentAware implements OnInit, OnDestr /** Whether the tree item is selectable. */ readonly selectable = input(true); + /** Whether the tree item is expanded. */ + readonly expanded = model(false); + /** Optional label for typeahead. Defaults to the element's textContent. */ readonly label = input(); /** Search term for typeahead. */ - readonly searchTerm = computed(() => this.label() ?? this.element().textContent); + readonly searchTerm = computed(() => this.label() ?? this.element.textContent); /** The tree root. */ readonly tree: Signal> = computed(() => { @@ -262,57 +313,96 @@ export class TreeItem extends DeferredContentAware implements OnInit, OnDestr return (this.parent() as TreeItemGroup).ownedBy().tree(); }); + /** Whether the item is active. */ + readonly active = computed(() => this._pattern.active()); + + /** The level of the current item in a tree. */ + readonly level = computed(() => this._pattern.level()); + + /** Whether the item is selected. */ + readonly selected = computed(() => this._pattern.selected()); + + /** Whether this item is visible due to all of its parents being expanded. */ + readonly visible = computed(() => this._pattern.visible()); + + /** Whether the tree is expanded. Use this value for aria-expanded. */ + protected readonly _expanded: Signal = computed(() => + this._pattern.expandable() ? this._pattern.expanded() : undefined, + ); + /** The UI pattern for this item. */ - pattern: TreeItemPattern; + _pattern: TreeItemPattern; constructor() { super(); - this.preserveContent.set(true); + afterNextRender(() => { + if (this.tree()._pattern instanceof ComboboxTreePattern) { + this.preserveContent.set(true); + } + }); // Connect the group's hidden state to the DeferredContentAware's visibility. afterRenderEffect(() => { - this.tree().pattern instanceof ComboboxTreePattern + this.tree()._pattern instanceof ComboboxTreePattern ? this.contentVisible.set(true) - : this.contentVisible.set(this.pattern.expanded()); + : this.contentVisible.set(this._pattern.expanded()); }); } ngOnInit() { - this.parent().register(this); - this.tree().register(this); + this.parent()._register(this); + this.tree()._register(this); - const treePattern = computed(() => this.tree().pattern); + const treePattern = computed(() => this.tree()._pattern); const parentPattern = computed(() => { if (this.parent() instanceof Tree) { return treePattern(); } - return (this.parent() as TreeItemGroup).ownedBy().pattern; + return (this.parent() as TreeItemGroup).ownedBy()._pattern; }); - this.pattern = new TreeItemPattern({ + this._pattern = new TreeItemPattern({ ...this, - id: () => this._id, tree: treePattern, parent: parentPattern, - children: computed(() => this._group()?.children() ?? []), + children: computed(() => this._group()?._childPatterns() ?? []), hasChildren: computed(() => !!this._group()), + element: () => this.element, + searchTerm: () => this.searchTerm() ?? '', }); } ngOnDestroy() { - this.parent().unregister(this); - this.tree().unregister(this); + this.parent()._unregister(this); + this.tree()._unregister(this); } - register(group: TreeItemGroup) { + _register(group: TreeItemGroup) { this._group.set(group); } - unregister() { + _unregister() { this._group.set(undefined); } } /** - * Contains children tree itmes. + * Group that contains children tree items. + * + * The `ngTreeItemGroup` structural directive should be applied to an `ng-template` that + * wraps the child `ngTreeItem` elements. It is used to define a group of children for an + * expandable `ngTreeItem`. The `ownedBy` input links the group to its parent `ngTreeItem`. + * + * ```html + *
      • + * Parent Item + *
          + * + *
        • Child Item
        • + *
          + *
        + *
      • + * ``` + * + * @developerPreview 21.0 */ @Directive({ selector: 'ng-template[ngTreeItemGroup]', @@ -320,6 +410,12 @@ export class TreeItem extends DeferredContentAware implements OnInit, OnDestr hostDirectives: [DeferredContent], }) export class TreeItemGroup implements OnInit, OnDestroy { + /** A reference to the host element. */ + private readonly _elementRef = inject(ElementRef); + + /** A reference to the host element. */ + readonly element = this._elementRef.nativeElement as HTMLElement; + /** The DeferredContent host directive. */ private readonly _deferredContent = inject(DeferredContent); @@ -327,8 +423,8 @@ export class TreeItemGroup implements OnInit, OnDestroy { private readonly _unorderedItems = signal(new Set>()); /** Child items within this group. */ - readonly children = computed[]>(() => - [...this._unorderedItems()].sort(sortDirectives).map(c => c.pattern), + readonly _childPatterns = computed[]>(() => + [...this._unorderedItems()].sort(sortDirectives).map(c => c._pattern), ); /** Tree item that owns the group. */ @@ -336,19 +432,19 @@ export class TreeItemGroup implements OnInit, OnDestroy { ngOnInit() { this._deferredContent.deferredContentAware.set(this.ownedBy()); - this.ownedBy().register(this); + this.ownedBy()._register(this); } ngOnDestroy() { - this.ownedBy().unregister(); + this.ownedBy()._unregister(); } - register(child: TreeItem) { + _register(child: TreeItem) { this._unorderedItems().add(child); this._unorderedItems.set(new Set(this._unorderedItems())); } - unregister(child: TreeItem) { + _unregister(child: TreeItem) { this._unorderedItems().delete(child); this._unorderedItems.set(new Set(this._unorderedItems())); } diff --git a/src/aria/ui-patterns/BUILD.bazel b/src/aria/ui-patterns/BUILD.bazel deleted file mode 100644 index 2b19780986e7..000000000000 --- a/src/aria/ui-patterns/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -load("//tools:defaults.bzl", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "ui-patterns", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//:node_modules/@angular/core", - "//src/aria/ui-patterns/accordion", - "//src/aria/ui-patterns/behaviors/signal-like", - "//src/aria/ui-patterns/combobox", - "//src/aria/ui-patterns/grid", - "//src/aria/ui-patterns/listbox", - "//src/aria/ui-patterns/menu", - "//src/aria/ui-patterns/radio-group", - "//src/aria/ui-patterns/tabs", - "//src/aria/ui-patterns/toolbar", - "//src/aria/ui-patterns/tree", - ], -) diff --git a/src/aria/ui-patterns/accordion/accordion.ts b/src/aria/ui-patterns/accordion/accordion.ts deleted file mode 100644 index 3855da7f5b8b..000000000000 --- a/src/aria/ui-patterns/accordion/accordion.ts +++ /dev/null @@ -1,225 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed} from '@angular/core'; -import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager'; -import { - ExpansionItem, - ExpansionControl, - ListExpansion, - ListExpansionInputs, -} from '../behaviors/expansion/expansion'; -import {ListFocus, ListFocusInputs, ListFocusItem} from '../behaviors/list-focus/list-focus'; -import { - ListNavigation, - ListNavigationInputs, - ListNavigationItem, -} from '../behaviors/list-navigation/list-navigation'; -import {SignalLike} from '../behaviors/signal-like/signal-like'; - -/** Inputs of the AccordionGroupPattern. */ -export type AccordionGroupInputs = Omit< - ListNavigationInputs & - ListFocusInputs & - Omit, - 'focusMode' ->; - -const focusMode = () => 'roving' as const; - -export interface AccordionGroupPattern extends AccordionGroupInputs {} -/** A pattern controls the nested Accordions. */ -export class AccordionGroupPattern { - /** Controls navigation for the group. */ - navigation: ListNavigation; - - /** Controls focus for the group. */ - focusManager: ListFocus; - - /** Controls expansion for the group. */ - expansionManager: ListExpansion; - - constructor(readonly inputs: AccordionGroupInputs) { - this.wrap = inputs.wrap; - this.orientation = inputs.orientation; - this.textDirection = inputs.textDirection; - this.activeItem = inputs.activeItem; - this.disabled = inputs.disabled; - this.multiExpandable = inputs.multiExpandable; - this.items = inputs.items; - this.expandedIds = inputs.expandedIds; - this.skipDisabled = inputs.skipDisabled; - this.focusManager = new ListFocus({ - ...inputs, - focusMode, - }); - this.navigation = new ListNavigation({ - ...inputs, - focusMode, - focusManager: this.focusManager, - }); - this.expansionManager = new ListExpansion({ - ...inputs, - }); - } -} - -/** Inputs for the AccordionTriggerPattern. */ -export type AccordionTriggerInputs = Omit & - Omit & { - /** A local unique identifier for the trigger. */ - value: SignalLike; - - /** The parent accordion group that controls this trigger. */ - accordionGroup: SignalLike; - - /** The accordion panel controlled by this trigger. */ - accordionPanel: SignalLike; - }; - -export interface AccordionTriggerPattern extends AccordionTriggerInputs {} -/** A pattern controls the expansion state of an accordion. */ -export class AccordionTriggerPattern { - /** Whether this tab has expandable content. */ - expandable: SignalLike; - - /** The unique identifier used by the expansion behavior. */ - expansionId: SignalLike; - - /** Whether an accordion is expanded. */ - expanded: SignalLike; - - /** Controls the expansion state for the trigger. */ - expansionControl: ExpansionControl; - - /** Whether the trigger is active. */ - active = computed(() => this.inputs.accordionGroup().activeItem() === this); - - /** Id of the accordion panel controlled by the trigger. */ - controls = computed(() => this.inputs.accordionPanel()?.id()); - - /** The tabindex of the trigger. */ - tabindex = computed(() => (this.inputs.accordionGroup().focusManager.isFocusable(this) ? 0 : -1)); - - /** Whether the trigger is disabled. Disabling an accordion group disables all the triggers. */ - disabled = computed(() => this.inputs.disabled() || this.inputs.accordionGroup().disabled()); - - /** The index of the trigger within its accordion group. */ - index = computed(() => this.inputs.accordionGroup().items().indexOf(this)); - - constructor(readonly inputs: AccordionTriggerInputs) { - this.id = inputs.id; - this.element = inputs.element; - this.value = inputs.value; - this.expansionControl = new ExpansionControl({ - ...inputs, - expansionId: inputs.value, - expandable: () => true, - expansionManager: inputs.accordionGroup().expansionManager, - }); - this.expandable = this.expansionControl.isExpandable; - this.expansionId = this.expansionControl.expansionId; - this.expanded = this.expansionControl.isExpanded; - } - - /** The key used to navigate to the previous accordion trigger. */ - prevKey = computed(() => { - if (this.inputs.accordionGroup().orientation() === 'vertical') { - return 'ArrowUp'; - } - return this.inputs.accordionGroup().textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; - }); - - /** The key used to navigate to the next accordion trigger. */ - nextKey = computed(() => { - if (this.inputs.accordionGroup().orientation() === 'vertical') { - return 'ArrowDown'; - } - return this.inputs.accordionGroup().textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; - }); - - /** The keydown event manager for the accordion trigger. */ - keydown = computed(() => { - return new KeyboardEventManager() - .on(this.prevKey, () => this.inputs.accordionGroup().navigation.prev()) - .on(this.nextKey, () => this.inputs.accordionGroup().navigation.next()) - .on('Home', () => this.inputs.accordionGroup().navigation.first()) - .on('End', () => this.inputs.accordionGroup().navigation.last()) - .on(' ', () => this.expansionControl.toggle()) - .on('Enter', () => this.expansionControl.toggle()); - }); - - /** The pointerdown event manager for the accordion trigger. */ - pointerdown = computed(() => { - return new PointerEventManager().on(e => { - const item = this._getItem(e); - - if (item) { - this.inputs.accordionGroup().navigation.goto(item); - this.expansionControl.toggle(); - } - }); - }); - - /** Handles keydown events on the trigger, delegating to the group if not disabled. */ - onKeydown(event: KeyboardEvent): void { - this.keydown().handle(event); - } - - /** Handles pointerdown events on the trigger, delegating to the group if not disabled. */ - onPointerdown(event: PointerEvent): void { - this.pointerdown().handle(event); - } - - /** Handles focus events on the trigger. This ensures the tabbing changes the active index. */ - onFocus(event: FocusEvent): void { - const item = this._getItem(event); - - if (item && this.inputs.accordionGroup().focusManager.isFocusable(item)) { - this.inputs.accordionGroup().focusManager.focus(item); - } - } - - private _getItem(e: Event) { - if (!(e.target instanceof HTMLElement)) { - return; - } - - const element = e.target.closest('[role="button"]'); - return this.inputs - .accordionGroup() - .items() - .find(i => i.element() === element); - } -} - -/** Represents the required inputs for the AccordionPanelPattern. */ -export interface AccordionPanelInputs { - /** A global unique identifier for the panel. */ - id: SignalLike; - - /** A local unique identifier for the panel, matching its trigger's value. */ - value: SignalLike; - - /** The parent accordion trigger that controls this panel. */ - accordionTrigger: SignalLike; -} - -export interface AccordionPanelPattern extends AccordionPanelInputs {} -/** Represents an accordion panel. */ -export class AccordionPanelPattern { - /** Whether the accordion panel is hidden. True if the associated trigger is not expanded. */ - hidden: SignalLike; - - constructor(readonly inputs: AccordionPanelInputs) { - this.id = inputs.id; - this.value = inputs.value; - this.accordionTrigger = inputs.accordionTrigger; - this.hidden = computed(() => inputs.accordionTrigger()?.expanded() === false); - } -} diff --git a/src/aria/ui-patterns/behaviors/expansion/expansion.ts b/src/aria/ui-patterns/behaviors/expansion/expansion.ts deleted file mode 100644 index 7c0ed74b9fe7..000000000000 --- a/src/aria/ui-patterns/behaviors/expansion/expansion.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ -import {computed} from '@angular/core'; -import {SignalLike, WritableSignalLike} from '../signal-like/signal-like'; - -/** Represents an item that can be expanded or collapsed. */ -export interface ExpansionItem { - /** Whether the item is expandable. */ - expandable: SignalLike; - - /** Used to uniquely identify an expansion item. */ - expansionId: SignalLike; - - /** Whether the expansion is disabled. */ - disabled: SignalLike; -} - -export interface ExpansionControl extends ExpansionItem {} -/** - * Controls a single item's expansion state and interactions, - * delegating actual state changes to an Expansion manager. - */ -export class ExpansionControl { - /** Whether this specific item is currently expanded. Derived from the Expansion manager. */ - readonly isExpanded = computed(() => this.inputs.expansionManager.isExpanded(this)); - - /** Whether this item can be expanded. */ - readonly isExpandable = computed(() => this.inputs.expansionManager.isExpandable(this)); - - constructor(readonly inputs: ExpansionItem & {expansionManager: ListExpansion}) { - this.expansionId = inputs.expansionId; - this.expandable = inputs.expandable; - this.disabled = inputs.disabled; - } - - /** Requests the Expansion manager to open this item. */ - open() { - this.inputs.expansionManager.open(this); - } - - /** Requests the Expansion manager to close this item. */ - close() { - this.inputs.expansionManager.close(this); - } - - /** Requests the Expansion manager to toggle this item. */ - toggle() { - this.inputs.expansionManager.toggle(this); - } -} - -/** Represents the required inputs for an expansion behavior. */ -export interface ListExpansionInputs { - /** Whether multiple items can be expanded at once. */ - multiExpandable: SignalLike; - - /** An array of ids of the currently expanded items. */ - expandedIds: WritableSignalLike; - - /** An array of expansion items. */ - items: SignalLike; - - /** Whether all expansions are disabled. */ - disabled: SignalLike; -} - -/** Manages the expansion state of a list of items. */ -export class ListExpansion { - /** A signal holding an array of ids of the currently expanded items. */ - expandedIds: WritableSignalLike; - - constructor(readonly inputs: ListExpansionInputs) { - this.expandedIds = inputs.expandedIds; - } - - /** Opens the specified item. */ - open(item: ExpansionItem) { - if (!this.isExpandable(item)) return; - if (this.isExpanded(item)) return; - if (!this.inputs.multiExpandable()) { - this.closeAll(); - } - this.expandedIds.update(ids => ids.concat(item.expansionId())); - } - - /** Closes the specified item. */ - close(item: ExpansionItem) { - if (this.isExpandable(item)) { - this.expandedIds.update(ids => ids.filter(id => id !== item.expansionId())); - } - } - - /** Toggles the expansion state of the specified item. */ - toggle(item: ExpansionItem) { - this.expandedIds().includes(item.expansionId()) ? this.close(item) : this.open(item); - } - - /** Opens all focusable items in the list. */ - openAll() { - if (this.inputs.multiExpandable()) { - for (const item of this.inputs.items()) { - this.open(item); - } - } - } - - /** Closes all focusable items in the list. */ - closeAll() { - for (const item of this.inputs.items()) { - this.close(item); - } - } - - /** Checks whether the specified item is expandable / collapsible. */ - isExpandable(item: ExpansionItem) { - return !this.inputs.disabled() && !item.disabled() && item.expandable(); - } - - /** Checks whether the specified item is currently expanded. */ - isExpanded(item: ExpansionItem): boolean { - return this.expandedIds().includes(item.expansionId()); - } -} diff --git a/src/aria/ui-patterns/behaviors/grid/BUILD.bazel b/src/aria/ui-patterns/behaviors/grid/BUILD.bazel deleted file mode 100644 index 551c00f0bfb3..000000000000 --- a/src/aria/ui-patterns/behaviors/grid/BUILD.bazel +++ /dev/null @@ -1,15 +0,0 @@ -load("//tools:defaults.bzl", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "grid", - srcs = glob( - ["**/*.ts"], - exclude = ["**/*.spec.ts"], - ), - deps = [ - "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/signal-like", - ], -) diff --git a/src/aria/ui-patterns/behaviors/grid/grid.ts b/src/aria/ui-patterns/behaviors/grid/grid.ts deleted file mode 100644 index f6a2e46bab65..000000000000 --- a/src/aria/ui-patterns/behaviors/grid/grid.ts +++ /dev/null @@ -1,262 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed, linkedSignal} from '@angular/core'; -import {SignalLike} from '../signal-like/signal-like'; -import {GridData, BaseGridCell, GridDataInputs, RowCol} from './grid-data'; -import {GridFocus, GridFocusCell, GridFocusInputs} from './grid-focus'; -import { - direction, - GridNavigation, - GridNavigationCell, - GridNavigationInputs, -} from './grid-navigation'; -import {GridSelectionCell, GridSelectionInputs, GridSelection} from './grid-selection'; - -/** A type that represents a cell in a grid, combining all cell-related interfaces. */ -export type GridCell = BaseGridCell & GridFocusCell & GridNavigationCell & GridSelectionCell; - -/** Represents the required inputs for a grid. */ -export interface GridInputs - extends GridDataInputs, - GridFocusInputs, - GridNavigationInputs, - GridSelectionInputs { - /** Whether selection is enabled for the grid. */ - enableSelection: SignalLike; -} - -/** The main class that orchestrates the grid behaviors. */ -export class Grid { - /** The underlying data structure for the grid. */ - readonly data: GridData; - - /** Controls focus for the grid. */ - readonly focusBehavior: GridFocus; - - /** Controls navigation for the grid. */ - readonly navigationBehavior: GridNavigation; - - /** Controls selection for the grid. */ - readonly selectionBehavior: GridSelection; - - /** The anchor point for range selection, linked to the active coordinates. */ - readonly selectionAnchor = linkedSignal(() => this.focusBehavior.activeCoords()); - - /** The `tabindex` for the grid container. */ - readonly gridTabIndex = computed(() => this.focusBehavior.gridTabIndex()); - - /** Whether the grid is in a disabled state. */ - readonly gridDisabled = computed(() => this.focusBehavior.gridDisabled()); - - /** The ID of the active descendant for ARIA `activedescendant` focus management. */ - readonly activeDescendant = computed(() => this.focusBehavior.activeDescendant()); - - constructor(readonly inputs: GridInputs) { - this.data = new GridData(inputs); - this.focusBehavior = new GridFocus({...inputs, grid: this.data}); - this.navigationBehavior = new GridNavigation({ - ...inputs, - grid: this.data, - gridFocus: this.focusBehavior, - }); - this.selectionBehavior = new GridSelection({ - ...inputs, - grid: this.data, - gridFocus: this.focusBehavior, - }); - } - - /** Gets the 1-based row index of a cell. */ - rowIndex(cell: T): number | undefined { - const index = this.data.getCoords(cell)?.row; - return index !== undefined ? index + 1 : undefined; - } - - /** Gets the 1-based column index of a cell. */ - colIndex(cell: T): number | undefined { - const index = this.data.getCoords(cell)?.col; - return index !== undefined ? index + 1 : undefined; - } - - /** Gets the `tabindex` for a given cell. */ - cellTabIndex(cell: T): -1 | 0 { - return this.focusBehavior.getCellTabindex(cell); - } - - /** Navigates to the cell above the currently active cell. */ - up(): boolean { - return this.navigationBehavior.advance(direction.Up); - } - - /** Extends the selection to the cell above the selection anchor. */ - rangeSelectUp(): void { - const coords = this.navigationBehavior.peek(direction.Up, this.selectionAnchor()); - if (coords === undefined) return; - - this._rangeSelectCoords(coords); - } - - /** Navigates to the cell below the currently active cell. */ - down(): boolean { - return this.navigationBehavior.advance(direction.Down); - } - - /** Extends the selection to the cell below the selection anchor. */ - rangeSelectDown(): void { - const coords = this.navigationBehavior.peek(direction.Down, this.selectionAnchor()); - if (coords === undefined) return; - - this._rangeSelectCoords(coords); - } - - /** Navigates to the cell to the left of the currently active cell. */ - left(): boolean { - return this.navigationBehavior.advance(direction.Left); - } - - /** Extends the selection to the cell to the left of the selection anchor. */ - rangeSelectLeft(): void { - const coords = this.navigationBehavior.peek(direction.Left, this.selectionAnchor()); - if (coords === undefined) return; - - this._rangeSelectCoords(coords); - } - - /** Navigates to the cell to the right of the currently active cell. */ - right(): boolean { - return this.navigationBehavior.advance(direction.Right); - } - - /** Extends the selection to the cell to the right of the selection anchor. */ - rangeSelectRight(): void { - const coords = this.navigationBehavior.peek(direction.Right, this.selectionAnchor()); - if (coords === undefined) return; - - this._rangeSelectCoords(coords); - } - - /** Navigates to the first focusable cell in the grid. */ - first(): boolean { - return this.navigationBehavior.first(); - } - - /** Navigates to the first focusable cell in the current row. */ - firstInRow(): boolean { - return this.navigationBehavior.first(this.focusBehavior.activeCoords().row); - } - - /** Navigates to the last focusable cell in the grid. */ - last(): boolean { - return this.navigationBehavior.last(); - } - - /** Navigates to the last focusable cell in the current row. */ - lastInRow(): boolean { - return this.navigationBehavior.last(this.focusBehavior.activeCoords().row); - } - - /** Selects all cells in the current row. */ - selectRow(): void { - const row = this.focusBehavior.activeCoords().row; - this.selectionBehavior.deselectAll(); - this.selectionBehavior.select({row, col: 0}, {row, col: this.data.maxColCount()}); - } - - /** Selects all cells in the current column. */ - selectCol(): void { - const col = this.focusBehavior.activeCoords().col; - this.selectionBehavior.deselectAll(); - this.selectionBehavior.select({row: 0, col}, {row: this.data.maxRowCount(), col}); - } - - /** Selects all selectable cells in the grid. */ - selectAll(): void { - this.selectionBehavior.selectAll(); - } - - /** Navigates to and focuses the given cell. */ - gotoCell(cell: T): boolean { - return this.navigationBehavior.gotoCell(cell); - } - - /** Toggles the selection state of the given cell. */ - toggleSelect(cell: T): void { - const coords = this.data.getCoords(cell); - if (coords === undefined) return; - - this.selectionBehavior.toggle(coords); - } - - /** Extends the selection from the anchor to the given cell. */ - rangeSelect(cell: T): void { - const coords = this.data.getCoords(cell); - if (coords === undefined) return; - - this._rangeSelectCoords(coords); - } - - /** Extends the selection to the given coordinates. */ - private _rangeSelectCoords(coords: RowCol): void { - const activeCell = this.focusBehavior.activeCell(); - const anchorCell = this.data.getCell(coords); - if (activeCell === undefined || anchorCell === undefined) { - return; - } - - const allCoords = [ - ...this.data.getAllCoords(activeCell)!, - ...this.data.getAllCoords(anchorCell)!, - ]; - const allRows = allCoords.map(c => c.row); - const allCols = allCoords.map(c => c.col); - const fromCoords = { - row: Math.min(...allRows), - col: Math.min(...allCols), - }; - const toCoords = { - row: Math.max(...allRows), - col: Math.max(...allCols), - }; - - this.selectionBehavior.deselectAll(); - this.selectionBehavior.select(fromCoords, toCoords); - this.selectionAnchor.set(coords); - } - - /** Resets the active state of the grid if it is empty or stale. */ - resetState(): boolean { - if (this.focusBehavior.stateEmpty()) { - const firstFocusableCoords = this.navigationBehavior.peekFirst(); - if (firstFocusableCoords === undefined) { - return false; - } - - return this.focusBehavior.focusCoordinates(firstFocusableCoords); - } - - if (this.focusBehavior.stateStale()) { - // Try focus on the same active cell after if a reordering happened. - if (this.focusBehavior.focusCell(this.focusBehavior.activeCell()!)) { - return true; - } - - // If the active cell is no longer exist, focus on the coordinates instead. - if (this.focusBehavior.focusCoordinates(this.focusBehavior.activeCoords())) { - return true; - } - - // If the cooridnates no longer valid, go back to the first available cell. - if (this.focusBehavior.focusCoordinates(this.navigationBehavior.peekFirst()!)) { - return true; - } - } - - return false; - } -} diff --git a/src/aria/ui-patterns/grid/cell.ts b/src/aria/ui-patterns/grid/cell.ts deleted file mode 100644 index 4ab9c2b8ff49..000000000000 --- a/src/aria/ui-patterns/grid/cell.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed} from '@angular/core'; -import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; -import {GridCell} from '../behaviors/grid'; -import type {GridPattern} from './grid'; -import type {GridRowPattern} from './row'; -import type {GridCellWidgetPattern} from './widget'; - -/** The inputs for the `GridCellPattern`. */ -export interface GridCellInputs extends GridCell { - /** The `GridPattern` that this cell belongs to. */ - grid: SignalLike; - - /** The `GridRowPattern` that this cell belongs to. */ - row: SignalLike; - - /** The widget pattern contained within this cell, if any. */ - widget: SignalLike; - - /** The index of this cell's row within the grid. */ - rowIndex: SignalLike; - - /** The index of this cell's column within the grid. */ - colIndex: SignalLike; -} - -/** The UI pattern for a grid cell. */ -export class GridCellPattern implements GridCell { - /** A unique identifier for the cell. */ - readonly id: SignalLike; - - /** Whether a cell is disabled. */ - readonly disabled: SignalLike; - - /** Whether the cell is selected. */ - readonly selected: WritableSignalLike; - - /** Whether the cell is selectable. */ - readonly selectable: SignalLike; - - /** The number of rows the cell should span. */ - readonly rowSpan: SignalLike; - - /** The number of columns the cell should span. */ - readonly colSpan: SignalLike; - - /** The `aria-selected` attribute for the cell. */ - readonly ariaSelected = computed(() => - this.inputs.grid().inputs.enableSelection() && this.selectable() ? this.selected() : undefined, - ); - - /** The `aria-rowindex` attribute for the cell. */ - readonly ariaRowIndex = computed( - () => - this.inputs.row().rowIndex() ?? - this.inputs.rowIndex() ?? - this.inputs.grid().gridBehavior.rowIndex(this), - ); - - /** The `aria-colindex` attribute for the cell. */ - readonly ariaColIndex = computed( - () => this.inputs.colIndex() ?? this.inputs.grid().gridBehavior.colIndex(this), - ); - - /** The html element that should receive focus. */ - readonly element: SignalLike = computed( - () => this.inputs.widget()?.element() ?? this.inputs.element(), - ); - - /** Whether the cell is active. */ - readonly active = computed(() => this.inputs.grid().activeCell() === this); - - /** The internal tab index calculation for the cell. */ - private readonly _tabIndex: SignalLike<-1 | 0> = computed(() => - this.inputs.grid().gridBehavior.cellTabIndex(this), - ); - - /** The `tabindex` for the cell. If the cell contains a widget, the cell's tabindex is -1. */ - readonly tabIndex: SignalLike<-1 | 0> = computed(() => - this.inputs.widget() !== undefined ? -1 : this._tabIndex(), - ); - - /** Whether the widget within the cell is activated. */ - readonly widgetActivated: SignalLike = computed( - () => this.inputs.widget()?.inputs.activate() ?? false, - ); - - constructor(readonly inputs: GridCellInputs) { - this.id = inputs.id; - this.disabled = inputs.disabled; - this.rowSpan = inputs.rowSpan; - this.colSpan = inputs.colSpan; - this.selected = inputs.selected; - this.selectable = inputs.selectable; - } - - /** Gets the `tabindex` for the widget within the cell. */ - widgetTabIndex(): -1 | 0 { - return this._tabIndex(); - } -} diff --git a/src/aria/ui-patterns/grid/grid.ts b/src/aria/ui-patterns/grid/grid.ts deleted file mode 100644 index 00480ab38f9e..000000000000 --- a/src/aria/ui-patterns/grid/grid.ts +++ /dev/null @@ -1,243 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed, signal} from '@angular/core'; -import {SignalLike} from '../behaviors/signal-like/signal-like'; -import {KeyboardEventManager, PointerEventManager, Modifier} from '../behaviors/event-manager'; -import {Grid, GridInputs as GridBehaviorInputs} from '../behaviors/grid'; -import type {GridRowPattern} from './row'; -import type {GridCellPattern} from './cell'; - -/** Represents the required inputs for the grid pattern. */ -export interface GridInputs extends Omit, 'cells'> { - /** The html element of the grid. */ - element: SignalLike; - - /** The rows that make up the grid. */ - rows: SignalLike; - - /** A function that returns the grid cell associated with a given element. */ - getCell: (e: Element) => GridCellPattern | undefined; -} - -/** The UI pattern for a grid, handling keyboard navigation, focus, and selection. */ -export class GridPattern { - /** The underlying grid behavior that this pattern is built on. */ - readonly gridBehavior: Grid; - - /** The cells in the grid. */ - readonly cells = computed(() => this.gridBehavior.data.cells()); - - /** The tab index for the grid. */ - readonly tabIndex = computed(() => this.gridBehavior.gridTabIndex()); - - /** Whether the grid is disabled. */ - readonly disabled = computed(() => this.gridBehavior.gridDisabled()); - - /** The ID of the currently active descendant cell. */ - readonly activeDescendant = computed(() => this.gridBehavior.activeDescendant()); - - /** The currently active cell. */ - readonly activeCell = computed(() => this.gridBehavior.focusBehavior.activeCell()); - - /** Whether to pause grid navigation. */ - readonly pauseNavigation = computed(() => - this.gridBehavior.data - .cells() - .flat() - .reduce((res, c) => res || c.widgetActivated(), false), - ); - - /** Whether the focus is in the grid. */ - readonly isFocused = signal(false); - - /** Whether the user is currently dragging to select a range of cells. */ - readonly dragging = signal(false); - - /** The keydown event manager for the grid. */ - readonly keydown = computed(() => { - const manager = new KeyboardEventManager(); - - if (this.pauseNavigation()) { - return manager; - } - - manager - .on('ArrowUp', () => this.gridBehavior.up()) - .on('ArrowDown', () => this.gridBehavior.down()) - .on('ArrowLeft', () => this.gridBehavior.left()) - .on('ArrowRight', () => this.gridBehavior.right()) - .on('Home', () => this.gridBehavior.firstInRow()) - .on('End', () => this.gridBehavior.lastInRow()) - .on([Modifier.Ctrl], 'Home', () => this.gridBehavior.first()) - .on([Modifier.Ctrl], 'End', () => this.gridBehavior.last()); - - if (this.inputs.enableSelection()) { - manager - .on(Modifier.Shift, 'ArrowUp', () => this.gridBehavior.rangeSelectUp()) - .on(Modifier.Shift, 'ArrowDown', () => this.gridBehavior.rangeSelectDown()) - .on(Modifier.Shift, 'ArrowLeft', () => this.gridBehavior.rangeSelectLeft()) - .on(Modifier.Shift, 'ArrowRight', () => this.gridBehavior.rangeSelectRight()) - .on([Modifier.Ctrl, Modifier.Meta], 'A', () => this.gridBehavior.selectAll()) - .on([Modifier.Shift], ' ', () => this.gridBehavior.selectRow()) - .on([Modifier.Ctrl, Modifier.Meta], ' ', () => this.gridBehavior.selectCol()); - } - - return manager; - }); - - /** The pointerdown event manager for the grid. */ - readonly pointerdown = computed(() => { - const manager = new PointerEventManager(); - - manager.on(e => { - const cell = this.inputs.getCell(e.target as Element); - if (!cell) return; - - this.gridBehavior.gotoCell(cell); - - if (this.inputs.enableSelection()) { - this.dragging.set(true); - } - }); - - if (this.inputs.enableSelection()) { - manager - .on([Modifier.Ctrl, Modifier.Meta], e => { - const cell = this.inputs.getCell(e.target as Element); - if (!cell) return; - - this.gridBehavior.toggleSelect(cell); - }) - .on(Modifier.Shift, e => { - const cell = this.inputs.getCell(e.target as Element); - if (!cell) return; - - this.gridBehavior.rangeSelect(cell); - this.dragging.set(true); - }); - } - - return manager; - }); - - /** The pointerup event manager for the grid. */ - readonly pointerup = computed(() => { - const manager = new PointerEventManager(); - - if (this.inputs.enableSelection()) { - manager.on([Modifier.Shift, Modifier.None], () => { - this.dragging.set(false); - }); - } - - return manager; - }); - - constructor(readonly inputs: GridInputs) { - this.gridBehavior = new Grid({ - ...inputs, - cells: computed(() => this.inputs.rows().map(row => row.inputs.cells())), - }); - } - - /** Handles keydown events on the grid. */ - onKeydown(event: KeyboardEvent) { - if (!this.disabled()) { - this.keydown().handle(event); - } - } - - /** Handles pointerdown events on the grid. */ - onPointerdown(event: PointerEvent) { - if (!this.disabled()) { - this.pointerdown().handle(event); - } - } - - /** Handles pointermove events on the grid. */ - onPointermove(event: PointerEvent) { - if (this.disabled()) return; - if (!this.inputs.enableSelection()) return; - if (!this.dragging()) return; - - const cell = this.inputs.getCell(event.target as Element); - if (!cell) return; - - this.gridBehavior.rangeSelect(cell); - } - - /** Handles pointerup events on the grid. */ - onPointerup(event: PointerEvent) { - if (!this.disabled()) { - this.pointerup().handle(event); - } - } - - /** Handles focusin events on the grid. */ - onFocusIn(event: FocusEvent) { - this.isFocused.set(true); - - const cell = this.inputs.getCell(event.target as Element); - if (!cell) return; - - this.gridBehavior.gotoCell(cell); - } - - /** Indicates maybe the losing focus is caused by row/cell deletion. */ - private readonly _maybeDeletion = signal(false); - - /** Handles focusout events on the grid. */ - onFocusOut(event: FocusEvent) { - const parentEl = this.inputs.element(); - const targetEl = event.relatedTarget as Node | null; - - // If a `relatedTarget` is null, then it can be caused by either - // - Clicking on a non-focusable element, or - // - The focused element is removed from the page. - if (targetEl === null) { - this._maybeDeletion.set(true); - } - - if (parentEl.contains(targetEl)) return; - this.isFocused.set(false); - } - - /** Indicates the losing focus is certainly caused by row/cell deletion. */ - private readonly _deletion = signal(false); - - /** Resets the active state of the grid if it is empty or stale. */ - resetStateEffect(): void { - const hasReset = this.gridBehavior.resetState(); - - // If the active state has been reset right after a focusout event, then - // we know it's caused by a row/cell deletion. - if (hasReset && this._maybeDeletion()) { - this._deletion.set(true); - } - - if (this._maybeDeletion()) { - this._maybeDeletion.set(false); - } - } - - /** Focuses on the active cell element. */ - focusEffect(): void { - const activeCell = this.activeCell(); - const hasFocus = this.isFocused(); - const deletion = this._deletion(); - const isRoving = this.inputs.focusMode() === 'roving'; - if (activeCell !== undefined && isRoving && (hasFocus || deletion)) { - activeCell.element().focus(); - - if (deletion) { - this._deletion.set(false); - } - } - } -} diff --git a/src/aria/ui-patterns/grid/widget.ts b/src/aria/ui-patterns/grid/widget.ts deleted file mode 100644 index e25688dcda5b..000000000000 --- a/src/aria/ui-patterns/grid/widget.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed} from '@angular/core'; -import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; -import type {GridCellPattern} from './cell'; - -/** The inputs for the `GridCellWidgetPattern`. */ -export interface GridCellWidgetInputs { - /** The `GridCellPattern` that this widget belongs to. */ - cell: SignalLike; - - /** The html element that should receive focus. */ - element: SignalLike; - - /** - * Whether the widget is activated, which pauses grid navigation to allow interaction - * with the widget. - */ - activate: WritableSignalLike; -} - -/** The UI pattern for a widget inside a grid cell. */ -export class GridCellWidgetPattern { - /** The html element that should receive focus. */ - readonly element: SignalLike; - - /** The `tabindex` for the widget. */ - readonly tabIndex: SignalLike<-1 | 0> = computed(() => this.inputs.cell().widgetTabIndex()); - - /** Whether the widget is in an active state (i.e. its containing cell is active). */ - readonly active: SignalLike = computed(() => this.inputs.cell().active()); - - constructor(readonly inputs: GridCellWidgetInputs) { - this.element = inputs.element; - } -} diff --git a/src/aria/ui-patterns/radio-group/BUILD.bazel b/src/aria/ui-patterns/radio-group/BUILD.bazel deleted file mode 100644 index 27756de9c140..000000000000 --- a/src/aria/ui-patterns/radio-group/BUILD.bazel +++ /dev/null @@ -1,37 +0,0 @@ -load("//tools:defaults.bzl", "ng_project", "ng_web_test_suite", "ts_project") - -package(default_visibility = ["//visibility:public"]) - -ts_project( - name = "radio-group", - srcs = [ - "radio-button.ts", - "radio-group.ts", - "toolbar-radio-group.ts", - ], - deps = [ - "//:node_modules/@angular/core", - "//src/aria/ui-patterns/behaviors/event-manager", - "//src/aria/ui-patterns/behaviors/list", - "//src/aria/ui-patterns/behaviors/signal-like", - "//src/aria/ui-patterns/toolbar", - ], -) - -ng_project( - name = "unit_test_sources", - testonly = True, - srcs = glob(["**/*.spec.ts"]), - deps = [ - ":radio-group", - "//:node_modules/@angular/core", - "//src/aria/ui-patterns/toolbar", - "//src/cdk/keycodes", - "//src/cdk/testing/private", - ], -) - -ng_web_test_suite( - name = "unit_tests", - deps = [":unit_test_sources"], -) diff --git a/src/aria/ui-patterns/radio-group/radio-button.ts b/src/aria/ui-patterns/radio-group/radio-button.ts deleted file mode 100644 index 5c4258301057..000000000000 --- a/src/aria/ui-patterns/radio-group/radio-button.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed} from '@angular/core'; -import {SignalLike} from '../behaviors/signal-like/signal-like'; -import {ListItem} from '../behaviors/list/list'; -import type {RadioGroupPattern} from './radio-group'; - -/** Represents the required inputs for a radio button in a radio group. */ -export interface RadioButtonInputs - extends Omit, 'searchTerm' | 'index' | 'selectable'> { - /** A reference to the parent radio group. */ - group: SignalLike | undefined>; -} - -/** Represents a radio button within a radio group. */ -export class RadioButtonPattern { - /** A unique identifier for the radio button. */ - readonly id: SignalLike; - - /** The value associated with the radio button. */ - readonly value: SignalLike; - - /** The position of the radio button within the group. */ - readonly index: SignalLike = computed( - () => this.group()?.listBehavior.inputs.items().indexOf(this) ?? -1, - ); - - /** Whether the radio button is currently the active one (focused). */ - readonly active = computed(() => this.group()?.listBehavior.inputs.activeItem() === this); - - /** Whether the radio button is selected. */ - readonly selected: SignalLike = computed( - () => !!this.group()?.listBehavior.inputs.value().includes(this.value()), - ); - - /** Whether the radio button is selectable. */ - readonly selectable = () => true; - - /** Whether the radio button is disabled. */ - readonly disabled: SignalLike; - - /** A reference to the parent radio group. */ - readonly group: SignalLike | undefined>; - - /** The tabindex of the radio button. */ - readonly tabindex = computed(() => this.group()?.listBehavior.getItemTabindex(this)); - - /** The HTML element associated with the radio button. */ - readonly element: SignalLike; - - /** The search term for typeahead. */ - readonly searchTerm = () => ''; // Radio groups do not support typeahead. - - constructor(readonly inputs: RadioButtonInputs) { - this.id = inputs.id; - this.value = inputs.value; - this.group = inputs.group; - this.element = inputs.element; - this.disabled = inputs.disabled; - } -} diff --git a/src/aria/ui-patterns/radio-group/radio-group.ts b/src/aria/ui-patterns/radio-group/radio-group.ts deleted file mode 100644 index 93026a3decd9..000000000000 --- a/src/aria/ui-patterns/radio-group/radio-group.ts +++ /dev/null @@ -1,175 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed, signal} from '@angular/core'; -import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager'; -import {List, ListInputs} from '../behaviors/list/list'; -import {SignalLike} from '../behaviors/signal-like/signal-like'; -import {RadioButtonPattern} from './radio-button'; - -/** Represents the required inputs for a radio group. */ -export type RadioGroupInputs = Omit< - ListInputs, V>, - 'multi' | 'selectionMode' | 'wrap' | 'typeaheadDelay' -> & { - /** Whether the radio group is disabled. */ - disabled: SignalLike; - - /** Whether the radio group is readonly. */ - readonly: SignalLike; - - /** A function that returns the radio button associated with a given element. */ - getItem: (e: PointerEvent) => RadioButtonPattern | undefined; -}; - -/** Controls the state of a radio group. */ -export class RadioGroupPattern { - /** The list behavior for the radio group. */ - readonly listBehavior: List, V>; - - /** Whether the radio group is vertically or horizontally oriented. */ - readonly orientation: SignalLike<'vertical' | 'horizontal'>; - - /** Whether focus should wrap when navigating. */ - readonly wrap = signal(false); - - /** The selection strategy used by the radio group. */ - readonly selectionMode = signal<'follow' | 'explicit'>('follow'); - - /** Whether the radio group is disabled. */ - readonly disabled = computed(() => this.inputs.disabled() || this.listBehavior.disabled()); - - /** The currently selected radio button. */ - readonly selectedItem = computed(() => this.listBehavior.selectionBehavior.selectedItems()[0]); - - /** Whether the radio group is readonly. */ - readonly readonly = computed(() => this.selectedItem()?.disabled() || this.inputs.readonly()); - - /** The tabindex of the radio group. */ - readonly tabindex = computed(() => this.listBehavior.tabindex()); - - /** The id of the current active radio button (if using activedescendant). */ - readonly activedescendant = computed(() => this.listBehavior.activedescendant()); - - /** The key used to navigate to the previous radio button. */ - private readonly _prevKey = computed(() => { - if (this.inputs.orientation() === 'vertical') { - return 'ArrowUp'; - } - return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; - }); - - /** The key used to navigate to the next radio button. */ - private readonly _nextKey = computed(() => { - if (this.inputs.orientation() === 'vertical') { - return 'ArrowDown'; - } - return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; - }); - - /** The keydown event manager for the radio group. */ - readonly keydown = computed(() => { - const manager = new KeyboardEventManager(); - - // Readonly mode allows navigation but not selection changes. - if (this.readonly()) { - return manager - .on(this._prevKey, () => this.listBehavior.prev()) - .on(this._nextKey, () => this.listBehavior.next()) - .on('Home', () => this.listBehavior.first()) - .on('End', () => this.listBehavior.last()); - } - - // Default behavior: navigate and select on arrow keys, home, end. - // Space/Enter also select the focused item. - return manager - .on(this._prevKey, () => this.listBehavior.prev({selectOne: true})) - .on(this._nextKey, () => this.listBehavior.next({selectOne: true})) - .on('Home', () => this.listBehavior.first({selectOne: true})) - .on('End', () => this.listBehavior.last({selectOne: true})) - .on(' ', () => this.listBehavior.selectOne()) - .on('Enter', () => this.listBehavior.selectOne()); - }); - - /** The pointerdown event manager for the radio group. */ - readonly pointerdown = computed(() => { - const manager = new PointerEventManager(); - - if (this.readonly()) { - // Navigate focus only in readonly mode. - return manager.on(e => this.listBehavior.goto(this.inputs.getItem(e)!)); - } - - // Default behavior: navigate and select on click. - return manager.on(e => this.listBehavior.goto(this.inputs.getItem(e)!, {selectOne: true})); - }); - - constructor(readonly inputs: RadioGroupInputs) { - this.orientation = inputs.orientation; - this.listBehavior = new List({ - ...inputs, - wrap: this.wrap, - selectionMode: this.selectionMode, - multi: () => false, - typeaheadDelay: () => 0, // Radio groups do not support typeahead. - }); - } - - /** Handles keydown events for the radio group. */ - onKeydown(event: KeyboardEvent) { - if (!this.disabled()) { - this.keydown().handle(event); - } - } - - /** Handles pointerdown events for the radio group. */ - onPointerdown(event: PointerEvent) { - if (!this.disabled()) { - this.pointerdown().handle(event); - } - } - - /** - * Sets the radio group to its default initial state. - * - * Sets the active index to the selected radio button if one exists and is focusable. - * Otherwise, sets the active index to the first focusable radio button. - */ - setDefaultState() { - let firstItem: RadioButtonPattern | null = null; - - for (const item of this.inputs.items()) { - if (this.listBehavior.isFocusable(item)) { - if (!firstItem) { - firstItem = item; - } - if (item.selected()) { - this.inputs.activeItem.set(item); - return; - } - } - } - - if (firstItem) { - this.inputs.activeItem.set(firstItem); - } - } - - /** Validates the state of the radio group and returns a list of accessibility violations. */ - validate(): string[] { - const violations: string[] = []; - - if (this.selectedItem()?.disabled() && this.inputs.skipDisabled()) { - violations.push( - "Accessibility Violation: The selected radio button is disabled while 'skipDisabled' is true, making the selection unreachable via keyboard.", - ); - } - - return violations; - } -} diff --git a/src/aria/ui-patterns/radio-group/radio.spec.ts b/src/aria/ui-patterns/radio-group/radio.spec.ts deleted file mode 100644 index adff7b0ac5d2..000000000000 --- a/src/aria/ui-patterns/radio-group/radio.spec.ts +++ /dev/null @@ -1,308 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {signal, WritableSignal} from '@angular/core'; -import {RadioGroupInputs, RadioGroupPattern} from './radio-group'; -import {RadioButtonPattern} from './radio-button'; -import {createKeyboardEvent} from '@angular/cdk/testing/private'; -import {ModifierKeys} from '@angular/cdk/testing'; - -type TestInputs = RadioGroupInputs; -type TestRadio = RadioButtonPattern & { - disabled: WritableSignal; -}; -type TestRadioGroup = RadioGroupPattern; - -const up = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 38, 'ArrowUp', mods); -const down = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 40, 'ArrowDown', mods); -const left = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 37, 'ArrowLeft', mods); -const right = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 39, 'ArrowRight', mods); -const home = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 36, 'Home', mods); -const end = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 35, 'End', mods); -const space = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 32, ' ', mods); -const enter = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 13, 'Enter', mods); - -describe('RadioGroup Pattern', () => { - function getRadioGroup(inputs: Partial & Pick) { - return new RadioGroupPattern({ - items: inputs.items, - value: inputs.value ?? signal([]), - activeItem: signal(undefined), - element: signal(document.createElement('div')), - readonly: inputs.readonly ?? signal(false), - disabled: inputs.disabled ?? signal(false), - skipDisabled: inputs.skipDisabled ?? signal(true), - focusMode: inputs.focusMode ?? signal('roving'), - textDirection: inputs.textDirection ?? signal('ltr'), - orientation: inputs.orientation ?? signal('vertical'), - getItem: e => inputs.items().find(i => i.element() === e.target), - }); - } - - function getRadios(radioGroup: TestRadioGroup, values: string[]): TestRadio[] { - return values.map((value, index) => { - const element = document.createElement('div'); - element.role = 'radio'; - return new RadioButtonPattern({ - value: signal(value), - id: signal(`radio-${index}`), - disabled: signal(false), - group: signal(radioGroup), - element: signal(element), - }); - }) as TestRadio[]; - } - - function getPatterns(values: string[], inputs: Partial = {}) { - const radioButtons = signal([]); - const radioGroup = getRadioGroup({...inputs, items: radioButtons}); - radioButtons.set(getRadios(radioGroup, values)); - radioGroup.inputs.activeItem.set(radioButtons()[0]); - return {radioGroup, radioButtons: radioButtons()}; - } - - function getDefaultPatterns(inputs: Partial = {}) { - return getPatterns(['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], inputs); - } - - describe('Keyboard Navigation', () => { - it('should navigate next on ArrowDown', () => { - const {radioGroup, radioButtons} = getDefaultPatterns(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - radioGroup.onKeydown(down()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - }); - - it('should navigate prev on ArrowUp', () => { - const {radioGroup, radioButtons} = getDefaultPatterns(); - radioGroup.inputs.activeItem.set(radioButtons[1]); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - radioGroup.onKeydown(up()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should navigate next on ArrowRight (horizontal)', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({orientation: signal('horizontal')}); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - radioGroup.onKeydown(right()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - }); - - it('should navigate prev on ArrowLeft (horizontal)', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({orientation: signal('horizontal')}); - radioGroup.inputs.activeItem.set(radioButtons[1]); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - radioGroup.onKeydown(left()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should navigate next on ArrowLeft (horizontal & rtl)', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({ - textDirection: signal('rtl'), - orientation: signal('horizontal'), - }); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - radioGroup.onKeydown(left()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - }); - - it('should navigate prev on ArrowRight (horizontal & rtl)', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({ - textDirection: signal('rtl'), - orientation: signal('horizontal'), - }); - radioGroup.inputs.activeItem.set(radioButtons[1]); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - radioGroup.onKeydown(right()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should navigate to the first radio on Home', () => { - const {radioGroup, radioButtons} = getDefaultPatterns(); - radioGroup.inputs.activeItem.set(radioButtons[4]); - - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[4]); - radioGroup.onKeydown(home()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should navigate to the last radio on End', () => { - const {radioGroup, radioButtons} = getDefaultPatterns(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - radioGroup.onKeydown(end()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[4]); - }); - - it('should skip disabled radios when skipDisabled is true', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({skipDisabled: signal(true)}); - radioButtons[1].disabled.set(true); - radioGroup.onKeydown(down()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[2]); - radioGroup.onKeydown(up()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should not skip disabled radios when skipDisabled is false', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({skipDisabled: signal(false)}); - radioButtons[1].disabled.set(true); - radioGroup.onKeydown(down()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - radioGroup.onKeydown(up()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should be able to navigate in readonly mode', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({readonly: signal(true)}); - radioGroup.onKeydown(down()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - radioGroup.onKeydown(up()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - radioGroup.onKeydown(end()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[4]); - radioGroup.onKeydown(home()); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - }); - - describe('Keyboard Selection', () => { - let radioGroup: TestRadioGroup; - - beforeEach(() => { - radioGroup = getDefaultPatterns({value: signal([])}).radioGroup; - }); - - it('should select a radio on Space', () => { - radioGroup.onKeydown(space()); - expect(radioGroup.inputs.value()).toEqual(['Apple']); - }); - - it('should select a radio on Enter', () => { - radioGroup.onKeydown(enter()); - expect(radioGroup.inputs.value()).toEqual(['Apple']); - }); - - it('should select the focused radio on navigation (implicit selection)', () => { - radioGroup.onKeydown(down()); - expect(radioGroup.inputs.value()).toEqual(['Banana']); - radioGroup.onKeydown(up()); - expect(radioGroup.inputs.value()).toEqual(['Apple']); - radioGroup.onKeydown(end()); - expect(radioGroup.inputs.value()).toEqual(['Elderberry']); - radioGroup.onKeydown(home()); - expect(radioGroup.inputs.value()).toEqual(['Apple']); - }); - - it('should not be able to change selection when in readonly mode', () => { - const readonly = radioGroup.inputs.readonly as WritableSignal; - readonly.set(true); - radioGroup.onKeydown(space()); - expect(radioGroup.inputs.value()).toEqual([]); - - radioGroup.onKeydown(down()); // Navigation still works - expect(radioGroup.inputs.activeItem()).toBe(radioGroup.inputs.items()[1]); - expect(radioGroup.inputs.value()).toEqual([]); // Selection doesn't change - - radioGroup.onKeydown(enter()); - expect(radioGroup.inputs.value()).toEqual([]); - }); - - it('should not select a disabled radio via keyboard', () => { - const {radioGroup, radioButtons} = getPatterns(['A', 'B', 'C'], { - skipDisabled: signal(false), - }); - radioButtons[1].disabled.set(true); - - radioGroup.onKeydown(down()); // Focus B (disabled) - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - expect(radioGroup.inputs.value()).toEqual([]); // Should not select B - - radioGroup.onKeydown(space()); // Try selecting B with space - expect(radioGroup.inputs.value()).toEqual([]); - - radioGroup.onKeydown(enter()); // Try selecting B with enter - expect(radioGroup.inputs.value()).toEqual([]); - - radioGroup.onKeydown(down()); // Focus C - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[2]); - expect(radioGroup.inputs.value()).toEqual(['C']); // Selects C on navigation - }); - }); - - describe('Pointer Events', () => { - function click(radios: TestRadio[], index: number) { - return { - target: radios[index].element(), - } as unknown as PointerEvent; - } - - it('should select a radio on click', () => { - const {radioGroup, radioButtons} = getDefaultPatterns(); - radioGroup.onPointerdown(click(radioButtons, 1)); - expect(radioGroup.inputs.value()).toEqual(['Banana']); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - }); - - it('should not select a disabled radio on click', () => { - const {radioGroup, radioButtons} = getDefaultPatterns(); - radioButtons[1].disabled.set(true); - radioGroup.onPointerdown(click(radioButtons, 1)); - expect(radioGroup.inputs.value()).toEqual([]); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); // Active index shouldn't change - }); - - it('should only update active index when readonly', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({readonly: signal(true)}); - radioGroup.onPointerdown(click(radioButtons, 1)); - expect(radioGroup.inputs.value()).toEqual([]); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); // Active index should update - }); - }); - - describe('#setDefaultState', () => { - it('should set the active index to the first radio', () => { - const {radioGroup, radioButtons} = getDefaultPatterns(); - radioGroup.setDefaultState(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should set the active index to the first focusable radio', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({skipDisabled: signal(true)}); - radioButtons[0].disabled.set(true); - radioGroup.setDefaultState(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - }); - - it('should set the active index to the selected radio', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({value: signal(['Cherry'])}); - radioGroup.setDefaultState(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[2]); - }); - - it('should set the active index to the first focusable radio if selected is disabled', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({ - value: signal(['Cherry']), - skipDisabled: signal(true), - }); - radioButtons[2].disabled.set(true); // Disable Cherry - radioGroup.setDefaultState(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); // Defaults to first focusable - }); - }); - - describe('validate', () => { - it('should report a violation if the selected item is disabled and skipDisabled is true', () => { - const {radioGroup, radioButtons} = getDefaultPatterns({ - value: signal(['Banana']), - skipDisabled: signal(true), - }); - radioButtons[1].disabled.set(true); // Disable the selected item. - const violations = radioGroup.validate(); - expect(violations.length).toBe(1); - }); - }); -}); diff --git a/src/aria/ui-patterns/radio-group/toolbar-radio-group.spec.ts b/src/aria/ui-patterns/radio-group/toolbar-radio-group.spec.ts deleted file mode 100644 index 6b009f8aae7c..000000000000 --- a/src/aria/ui-patterns/radio-group/toolbar-radio-group.spec.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {signal, WritableSignal} from '@angular/core'; -import {ToolbarRadioGroupInputs, ToolbarRadioGroupPattern} from './toolbar-radio-group'; -import {RadioButtonPattern} from './radio-button'; -import {ToolbarPattern} from './../toolbar/toolbar'; -import {createKeyboardEvent} from '@angular/cdk/testing/private'; -import {ModifierKeys} from '@angular/cdk/testing'; - -type TestInputs = ToolbarRadioGroupInputs; -type TestRadio = RadioButtonPattern & { - disabled: WritableSignal; -}; -type TestRadioGroup = ToolbarRadioGroupPattern; - -const down = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 40, 'ArrowDown', mods); -const space = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 32, ' ', mods); -const enter = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 13, 'Enter', mods); - -describe('ToolbarRadioGroup Pattern', () => { - function getToolbarRadioGroup(inputs: Partial & Pick) { - return new ToolbarRadioGroupPattern({ - items: inputs.items, - value: inputs.value ?? signal([]), - activeItem: signal(undefined), - element: signal(document.createElement('div')), - readonly: inputs.readonly ?? signal(false), - disabled: inputs.disabled ?? signal(false), - skipDisabled: inputs.skipDisabled ?? signal(true), - focusMode: inputs.focusMode ?? signal('roving'), - textDirection: inputs.textDirection ?? signal('ltr'), - orientation: inputs.orientation ?? signal('vertical'), - toolbar: inputs.toolbar ?? signal(undefined), - getItem: e => inputs.items().find(i => i.element() === e.target), - }); - } - - function getRadios(radioGroup: TestRadioGroup, values: string[]): TestRadio[] { - return values.map((value, index) => { - const element = document.createElement('div'); - element.role = 'radio'; - return new RadioButtonPattern({ - value: signal(value), - id: signal(`radio-${index}`), - disabled: signal(false), - group: signal(radioGroup), - element: signal(element), - }); - }) as TestRadio[]; - } - - function getPatterns(values: string[], inputs: Partial = {}) { - const radioButtons = signal([]); - const radioGroup = getToolbarRadioGroup({...inputs, items: radioButtons}); - radioButtons.set(getRadios(radioGroup, values)); - radioGroup.inputs.activeItem.set(radioButtons()[0]); - return {radioGroup, radioButtons: radioButtons()}; - } - - function getDefaultPatterns(inputs: Partial = {}) { - return getPatterns(['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'], inputs); - } - - let radioGroup: TestRadioGroup; - let radioButtons: TestRadio[]; - let toolbar: ToolbarPattern; - - beforeEach(() => { - toolbar = new ToolbarPattern({ - items: signal([]), - activeItem: signal(undefined), - element: signal(document.createElement('div')), - orientation: signal('horizontal'), - textDirection: signal('ltr'), - disabled: signal(false), - skipDisabled: signal(true), - wrap: signal(false), - getItem: (e: Element) => undefined, - }); - const patterns = getDefaultPatterns({ - toolbar: signal(toolbar), - }); - radioButtons = patterns.radioButtons; - radioGroup = patterns.radioGroup; - }); - - it('should ignore keyboard navigation when within a toolbar', () => { - radioGroup.inputs.activeItem.set(radioButtons[0]); - const initialActive = radioGroup.inputs.activeItem(); - radioGroup.onKeydown(down()); - expect(radioGroup.inputs.activeItem()).toBe(initialActive); - }); - - it('should ignore keyboard selection when within a toolbar', () => { - expect(radioGroup.inputs.value()).toEqual([]); - radioGroup.onKeydown(space()); - expect(radioGroup.inputs.value()).toEqual([]); - radioGroup.onKeydown(enter()); - expect(radioGroup.inputs.value()).toEqual([]); - }); - - it('should ignore pointer events when within a toolbar', () => { - radioGroup.inputs.activeItem.set(radioButtons[0]); - const initialActive = radioGroup.inputs.activeItem(); - expect(radioGroup.inputs.value()).toEqual([]); - - const clickEvent = { - target: radioButtons[1].element(), - } as unknown as PointerEvent; - radioGroup.onPointerdown(clickEvent); - - expect(radioGroup.inputs.activeItem()).toBe(initialActive); - expect(radioGroup.inputs.value()).toEqual([]); - }); - - describe('Toolbar Widget Group controls', () => { - beforeEach(() => { - radioGroup.inputs.activeItem.set(radioButtons[0]); - }); - - it('should correctly report when on the first item', () => { - radioGroup.inputs.activeItem.set(radioButtons[0]); - expect(radioGroup.isOnFirstItem()).toBe(true); - radioGroup.inputs.activeItem.set(radioButtons[1]); - expect(radioGroup.isOnFirstItem()).toBe(false); - }); - - it('should correctly report when on the last item', () => { - radioGroup.inputs.activeItem.set(radioButtons[4]); - expect(radioGroup.isOnLastItem()).toBe(true); - radioGroup.inputs.activeItem.set(radioButtons[3]); - expect(radioGroup.isOnLastItem()).toBe(false); - }); - - it('should handle "next" control', () => { - radioGroup.next(false); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[1]); - }); - - it('should handle "prev" control', () => { - radioGroup.inputs.activeItem.set(radioButtons[1]); - radioGroup.prev(false); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should handle "first" control', () => { - radioGroup.first(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should handle "last" control', () => { - radioGroup.last(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[4]); - }); - - it('should handle "unfocus" control by clearing active item', () => { - radioGroup.unfocus(); - expect(radioGroup.inputs.activeItem()).toBe(undefined); - }); - - it('should handle "trigger" control to select an item', () => { - expect(radioGroup.inputs.value()).toEqual([]); - radioGroup.trigger(); - expect(radioGroup.inputs.value()).toEqual(['Apple']); - }); - - it('should not "trigger" selection when readonly', () => { - (radioGroup.inputs.readonly as WritableSignal).set(true); - expect(radioGroup.inputs.value()).toEqual([]); - radioGroup.trigger(); - expect(radioGroup.inputs.value()).toEqual([]); - }); - - it('should handle "goto" control', () => { - const event = {target: radioButtons[2].element()} as unknown as PointerEvent; - radioGroup.goto(event); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[2]); - expect(radioGroup.inputs.value()).toEqual(['Cherry']); - }); - - it('should handle "goto" control in readonly mode (no selection)', () => { - (radioGroup.inputs.readonly as WritableSignal).set(true); - const event = {target: radioButtons[2].element()} as unknown as PointerEvent; - radioGroup.goto(event); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[2]); - expect(radioGroup.inputs.value()).toEqual([]); - }); - - it('should handle "setDefaultState" control', () => { - radioGroup.inputs.activeItem.set(undefined); - radioGroup.setDefaultState(); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should wrap on "next" with wrap', () => { - radioGroup.inputs.activeItem.set(radioButtons[4]); - radioGroup.next(true); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[0]); - }); - - it('should wrap on "prev" with wrap', () => { - radioGroup.prev(true); - expect(radioGroup.inputs.activeItem()).toBe(radioButtons[4]); - }); - }); -}); diff --git a/src/aria/ui-patterns/radio-group/toolbar-radio-group.ts b/src/aria/ui-patterns/radio-group/toolbar-radio-group.ts deleted file mode 100644 index 80b7d0de16c8..000000000000 --- a/src/aria/ui-patterns/radio-group/toolbar-radio-group.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {SignalLike} from '../behaviors/signal-like/signal-like'; -import {RadioGroupInputs, RadioGroupPattern} from './radio-group'; -import type {ToolbarPattern} from '../toolbar/toolbar'; -import type {ToolbarWidgetGroupControls} from '../toolbar/toolbar-widget-group'; - -/** Represents the required inputs for a toolbar controlled radio group. */ -export type ToolbarRadioGroupInputs = RadioGroupInputs & { - /** The toolbar controlling the radio group. */ - toolbar: SignalLike | undefined>; -}; - -/** Controls the state of a radio group in a toolbar. */ -export class ToolbarRadioGroupPattern - extends RadioGroupPattern - implements ToolbarWidgetGroupControls -{ - constructor(override readonly inputs: ToolbarRadioGroupInputs) { - if (!!inputs.toolbar()) { - inputs.orientation = inputs.toolbar()!.orientation; - inputs.skipDisabled = inputs.toolbar()!.skipDisabled; - } - - super(inputs); - } - - /** Noop. The toolbar handles keydown events. */ - override onKeydown(_: KeyboardEvent): void {} - - /** Noop. The toolbar handles pointerdown events. */ - override onPointerdown(_: PointerEvent): void {} - - /** Whether the radio group is currently on the first item. */ - isOnFirstItem() { - return this.listBehavior.navigationBehavior.peekPrev() === undefined; - } - - /** Whether the radio group is currently on the last item. */ - isOnLastItem() { - return this.listBehavior.navigationBehavior.peekNext() === undefined; - } - - /** Navigates to the next radio button in the group. */ - next(wrap: boolean) { - this.wrap.set(wrap); - this.listBehavior.next(); - this.wrap.set(false); - } - - /** Navigates to the previous radio button in the group. */ - prev(wrap: boolean) { - this.wrap.set(wrap); - this.listBehavior.prev(); - this.wrap.set(false); - } - - /** Navigates to the first radio button in the group. */ - first() { - this.listBehavior.first(); - } - - /** Navigates to the last radio button in the group. */ - last() { - this.listBehavior.last(); - } - - /** Removes focus from the radio group. */ - unfocus() { - this.inputs.activeItem.set(undefined); - } - - /** Triggers the action of the currently active radio button in the group. */ - trigger() { - if (this.readonly()) return; - this.listBehavior.selectOne(); - } - - /** Navigates to the radio button targeted by a pointer event. */ - goto(e: PointerEvent) { - this.listBehavior.goto(this.inputs.getItem(e)!, { - selectOne: !this.readonly(), - }); - } -} diff --git a/src/aria/ui-patterns/tabs/tabs.spec.ts b/src/aria/ui-patterns/tabs/tabs.spec.ts deleted file mode 100644 index 6e7d381480a7..000000000000 --- a/src/aria/ui-patterns/tabs/tabs.spec.ts +++ /dev/null @@ -1,361 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {signal} from '@angular/core'; -import { - TabInputs, - TabPattern, - TabListInputs, - TabListPattern, - TabPanelInputs, - TabPanelPattern, -} from './tabs'; -import {SignalLike, WritableSignalLike} from '../behaviors/signal-like/signal-like'; -import {createKeyboardEvent} from '@angular/cdk/testing/private'; -import {ModifierKeys} from '@angular/cdk/testing'; - -// Converts the SignalLike type to WritableSignalLike type for controlling test inputs. -type WritableSignalOverrides = { - [K in keyof O as O[K] extends SignalLike ? K : never]: O[K] extends SignalLike - ? WritableSignalLike - : never; -}; - -type TestTabListInputs = TabListInputs & WritableSignalOverrides; -type TestTabInputs = TabInputs & WritableSignalOverrides; -type TestTabPanelInputs = TabPanelInputs & WritableSignalOverrides; - -const up = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 38, 'ArrowUp', mods); -const down = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 40, 'ArrowDown', mods); -const left = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 37, 'ArrowLeft', mods); -const right = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 39, 'ArrowRight', mods); -const home = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 36, 'Home', mods); -const end = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 35, 'End', mods); -const space = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 32, ' ', mods); -const enter = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 13, 'Enter', mods); - -function createTabElement(): HTMLElement { - const element = document.createElement('div'); - element.role = 'tab'; - return element; -} - -describe('Tabs Pattern', () => { - let tabListInputs: TestTabListInputs; - let tabListPattern: TabListPattern; - let tabInputs: TestTabInputs[]; - let tabPatterns: TabPattern[]; - let tabPanelInputs: TestTabPanelInputs[]; - let tabPanelPatterns: TabPanelPattern[]; - - beforeEach(() => { - // Initiate TabListPattern. - tabListInputs = { - orientation: signal('horizontal'), - wrap: signal(true), - textDirection: signal('ltr'), - selectionMode: signal('follow'), - focusMode: signal('roving'), - disabled: signal(false), - activeItem: signal(undefined), - skipDisabled: signal(true), - items: signal([]), - value: signal(['tab-1']), - element: signal(document.createElement('div')), - }; - tabListPattern = new TabListPattern(tabListInputs); - - // Initiate a list of TabPatterns. - tabInputs = [ - { - tablist: signal(tabListPattern), - tabpanel: signal(undefined), - id: signal('tab-1-id'), - element: signal(createTabElement()), - disabled: signal(false), - value: signal('tab-1'), - }, - { - tablist: signal(tabListPattern), - tabpanel: signal(undefined), - id: signal('tab-2-id'), - element: signal(createTabElement()), - disabled: signal(false), - value: signal('tab-2'), - }, - { - tablist: signal(tabListPattern), - tabpanel: signal(undefined), - id: signal('tab-3-id'), - element: signal(createTabElement()), - disabled: signal(false), - value: signal('tab-3'), - }, - ]; - tabPatterns = [ - new TabPattern(tabInputs[0]), - new TabPattern(tabInputs[1]), - new TabPattern(tabInputs[2]), - ]; - - // Initiate a list of TabPanelPatterns. - tabPanelInputs = [ - { - id: signal('tabpanel-1-id'), - tab: signal(undefined), - value: signal('tab-1'), - }, - { - id: signal('tabpanel-2-id'), - tab: signal(undefined), - value: signal('tab-2'), - }, - { - id: signal('tabpanel-3-id'), - tab: signal(undefined), - value: signal('tab-3'), - }, - ]; - tabPanelPatterns = [ - new TabPanelPattern(tabPanelInputs[0]), - new TabPanelPattern(tabPanelInputs[1]), - new TabPanelPattern(tabPanelInputs[2]), - ]; - - // Binding between tabs and tabpanels. - tabInputs[0].tabpanel.set(tabPanelPatterns[0]); - tabInputs[1].tabpanel.set(tabPanelPatterns[1]); - tabInputs[2].tabpanel.set(tabPanelPatterns[2]); - tabPanelInputs[0].tab.set(tabPatterns[0]); - tabPanelInputs[1].tab.set(tabPatterns[1]); - tabPanelInputs[2].tab.set(tabPatterns[2]); - tabListInputs.items.set(tabPatterns); - tabListInputs.activeItem.set(tabPatterns[0]); - }); - - it('sets the selected tab by setting `value`.', () => { - expect(tabPatterns[0].selected()).toBeTrue(); - expect(tabPatterns[1].selected()).toBeFalse(); - tabListInputs.value.set(['tab-2']); - expect(tabPatterns[0].selected()).toBeFalse(); - expect(tabPatterns[1].selected()).toBeTrue(); - }); - - it('sets a tabpanel to be not hidden if a tab is selected.', () => { - tabListInputs.value.set(['tab-1']); - expect(tabPatterns[0].selected()).toBeTrue(); - expect(tabPanelPatterns[0].hidden()).toBeFalse(); - }); - - it('sets a tabpanel to be hidden if a tab is not selected.', () => { - tabListInputs.value.set(['tab-1']); - expect(tabPatterns[1].selected()).toBeFalse(); - expect(tabPanelPatterns[1].hidden()).toBeTrue(); - }); - - it('should set a tabpanel tabindex to 0 if the tab is selected.', () => { - tabListInputs.value.set(['tab-1']); - expect(tabPatterns[0].tabindex()).toBe(0); - }); - - it('should set a tabpanel tabindex to -1 if the tab is not selected.', () => { - tabListInputs.value.set(['tab-1']); - expect(tabPatterns[1].tabindex()).toBe(-1); - expect(tabPatterns[2].tabindex()).toBe(-1); - }); - - it('should set a tabpanel aria-labelledby pointing to its tab id.', () => { - expect(tabPanelPatterns[0].labelledBy()).toBe('tab-1-id'); - expect(tabPanelPatterns[1].labelledBy()).toBe('tab-2-id'); - expect(tabPanelPatterns[2].labelledBy()).toBe('tab-3-id'); - }); - - it('gets a controlled tabpanel id from a tab.', () => { - expect(tabPanelPatterns[0].id()).toBe('tabpanel-1-id'); - expect(tabPatterns[0].controls()).toBe('tabpanel-1-id'); - expect(tabPanelPatterns[1].id()).toBe('tabpanel-2-id'); - expect(tabPatterns[1].controls()).toBe('tabpanel-2-id'); - expect(tabPanelPatterns[2].id()).toBe('tabpanel-3-id'); - expect(tabPatterns[2].controls()).toBe('tabpanel-3-id'); - }); - - describe('#setDefaultState', () => { - it('should not set activeIndex if there are no tabs', () => { - tabListInputs.items.set([]); - tabListInputs.activeItem.set(tabPatterns[10]); - tabListPattern.setDefaultState(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[10]); - }); - - it('should not set activeIndex if no tabs are focusable', () => { - tabInputs.forEach(input => input.disabled.set(true)); - tabListInputs.activeItem.set(tabPatterns[10]); - tabListPattern.setDefaultState(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[10]); - }); - - it('should set activeIndex to the first focusable tab if no tabs are selected', () => { - tabListInputs.activeItem.set(tabPatterns[2]); - tabListInputs.value.set([]); - tabInputs[0].disabled.set(true); - tabListPattern.setDefaultState(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[1]); - }); - - it('should set activeIndex to the first focusable and selected tab', () => { - tabListInputs.activeItem.set(tabPatterns[0]); - tabListInputs.value.set([tabPatterns[2].value()]); - tabListPattern.setDefaultState(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[2]); - }); - - it('should set activeIndex to the first focusable tab when the selected tab is not focusable', () => { - tabListInputs.value.set([tabPatterns[1].value()]); - tabInputs[1].disabled.set(true); - tabListPattern.setDefaultState(); - expect(tabListInputs.activeItem()).toBe(tabPatterns[0]); - }); - }); - - describe('Keyboard Navigation', () => { - it('does not handle keyboard event if a tablist is disabled.', () => { - expect(tabPatterns[1].active()).toBeFalse(); - tabListInputs.disabled.set(true); - tabListPattern.onKeydown(right()); - expect(tabPatterns[1].active()).toBeFalse(); - }); - - it('skips the disabled tab when `skipDisabled` is set to true.', () => { - tabInputs[1].disabled.set(true); - tabListPattern.onKeydown(right()); - expect(tabPatterns[0].active()).toBeFalse(); - expect(tabPatterns[1].active()).toBeFalse(); - expect(tabPatterns[2].active()).toBeTrue(); - }); - - it('does not skip the disabled tab when `skipDisabled` is set to false.', () => { - tabListInputs.skipDisabled.set(false); - tabInputs[1].disabled.set(true); - tabListPattern.onKeydown(right()); - expect(tabPatterns[0].active()).toBeFalse(); - expect(tabPatterns[1].active()).toBeTrue(); - expect(tabPatterns[2].active()).toBeFalse(); - }); - - it('selects a tab by focus if `selectionMode` is "follow".', () => { - expect(tabPatterns[0].selected()).toBeTrue(); - expect(tabPatterns[1].selected()).toBeFalse(); - tabListPattern.onKeydown(right()); - expect(tabPatterns[0].selected()).toBeFalse(); - expect(tabPatterns[1].selected()).toBeTrue(); - }); - - it('selects a tab by enter key if `selectionMode` is "explicit".', () => { - tabListInputs.selectionMode.set('explicit'); - expect(tabPatterns[0].selected()).toBeTrue(); - expect(tabPatterns[1].selected()).toBeFalse(); - tabListPattern.onKeydown(right()); - expect(tabPatterns[0].selected()).toBeTrue(); - expect(tabPatterns[1].selected()).toBeFalse(); - tabListPattern.onKeydown(enter()); - expect(tabPatterns[0].selected()).toBeFalse(); - expect(tabPatterns[1].selected()).toBeTrue(); - }); - - it('selects a tab by space key if `selectionMode` is "explicit".', () => { - tabListInputs.selectionMode.set('explicit'); - expect(tabPatterns[0].selected()).toBeTrue(); - expect(tabPatterns[1].selected()).toBeFalse(); - tabListPattern.onKeydown(right()); - expect(tabPatterns[0].selected()).toBeTrue(); - expect(tabPatterns[1].selected()).toBeFalse(); - tabListPattern.onKeydown(space()); - expect(tabPatterns[0].selected()).toBeFalse(); - expect(tabPatterns[1].selected()).toBeTrue(); - }); - - it('uses left key to navigate to the previous tab when `orientation` is set to "horizontal".', () => { - tabListInputs.activeItem.set(tabPatterns[1]); - expect(tabPatterns[1].active()).toBeTrue(); - tabListPattern.onKeydown(left()); - expect(tabPatterns[0].active()).toBeTrue(); - }); - - it('uses right key to navigate to the next tab when `orientation` is set to "horizontal".', () => { - tabListInputs.activeItem.set(tabPatterns[1]); - expect(tabPatterns[1].active()).toBeTrue(); - tabListPattern.onKeydown(right()); - expect(tabPatterns[2].active()).toBeTrue(); - }); - - it('uses up key to navigate to the previous tab when `orientation` is set to "vertical".', () => { - tabListInputs.orientation.set('vertical'); - tabListInputs.activeItem.set(tabPatterns[1]); - expect(tabPatterns[1].active()).toBeTrue(); - tabListPattern.onKeydown(up()); - expect(tabPatterns[0].active()).toBeTrue(); - }); - - it('uses down key to navigate to the next tab when `orientation` is set to "vertical".', () => { - tabListInputs.orientation.set('vertical'); - tabListInputs.activeItem.set(tabPatterns[1]); - expect(tabPatterns[1].active()).toBeTrue(); - tabListPattern.onKeydown(down()); - expect(tabPatterns[2].active()).toBeTrue(); - }); - - it('uses home key to navigate to the first tab.', () => { - tabListInputs.activeItem.set(tabPatterns[1]); - expect(tabPatterns[1].active()).toBeTrue(); - tabListPattern.onKeydown(home()); - expect(tabPatterns[0].active()).toBeTrue(); - }); - - it('uses end key to navigate to the last tab.', () => { - tabListInputs.activeItem.set(tabPatterns[1]); - expect(tabPatterns[1].active()).toBeTrue(); - tabListPattern.onKeydown(end()); - expect(tabPatterns[2].active()).toBeTrue(); - }); - - it('moves to the last tab from first tab when navigating to the previous tab if `wrap` is set to true', () => { - expect(tabPatterns[0].active()).toBeTrue(); - tabListPattern.onKeydown(left()); - expect(tabPatterns[2].active()).toBeTrue(); - }); - - it('moves to the first tab from last tab when navigating to the next tab if `wrap` is set to true', () => { - tabListPattern.onKeydown(end()); - expect(tabPatterns[2].active()).toBeTrue(); - tabListPattern.onKeydown(right()); - expect(tabPatterns[0].active()).toBeTrue(); - }); - - it('stays on the first tab when navigating to the previous tab if `wrap` is set to false', () => { - tabListInputs.wrap.set(false); - expect(tabPatterns[0].active()).toBeTrue(); - tabListPattern.onKeydown(left()); - expect(tabPatterns[0].active()).toBeTrue(); - }); - - it('stays on the last tab when navigating to the next tab if `wrap` is set to false', () => { - tabListInputs.wrap.set(false); - tabListPattern.onKeydown(end()); - expect(tabPatterns[2].active()).toBeTrue(); - tabListPattern.onKeydown(right()); - expect(tabPatterns[2].active()).toBeTrue(); - }); - - it('changes the navigation direction with `rtl` mode.', () => { - tabListInputs.textDirection.set('rtl'); - tabListInputs.activeItem.set(tabPatterns[1]); - tabListPattern.onKeydown(left()); - expect(tabPatterns[2].active()).toBeTrue(); - }); - }); -}); diff --git a/src/aria/ui-patterns/toolbar/toolbar-widget-group.ts b/src/aria/ui-patterns/toolbar/toolbar-widget-group.ts deleted file mode 100644 index 297dafa1a0a7..000000000000 --- a/src/aria/ui-patterns/toolbar/toolbar-widget-group.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed} from '@angular/core'; -import {SignalLike} from '../behaviors/signal-like/signal-like'; -import {ListItem} from '../behaviors/list/list'; -import type {ToolbarPattern} from './toolbar'; - -/** An interface that allows sub patterns to expose the necessary controls for the toolbar. */ -export interface ToolbarWidgetGroupControls { - /** Whether the widget group is currently on the first item. */ - isOnFirstItem(): boolean; - - /** Whether the widget group is currently on the last item. */ - isOnLastItem(): boolean; - - /** Navigates to the next widget in the group. */ - next(wrap: boolean): void; - - /** Navigates to the previous widget in the group. */ - prev(wrap: boolean): void; - - /** Navigates to the first widget in the group. */ - first(): void; - - /** Navigates to the last widget in the group. */ - last(): void; - - /** Removes focus from the widget group. */ - unfocus(): void; - - /** Triggers the action of the currently active widget in the group. */ - trigger(): void; - - /** Navigates to the widget targeted by a pointer event. */ - goto(event: PointerEvent): void; - - /** Sets the widget group to its default initial state. */ - setDefaultState(): void; -} - -/** Represents the required inputs for a toolbar widget group. */ -export interface ToolbarWidgetGroupInputs - extends Omit, 'searchTerm' | 'value' | 'index' | 'selectable'> { - /** A reference to the parent toolbar. */ - toolbar: SignalLike | undefined>; - - /** The controls for the sub patterns associated with the toolbar. */ - controls: SignalLike; -} - -/** A group of widgets within a toolbar that provides nested navigation. */ -export class ToolbarWidgetGroupPattern implements ListItem { - /** A unique identifier for the widget. */ - readonly id: SignalLike; - - /** The html element that should receive focus. */ - readonly element: SignalLike; - - /** Whether the widget is disabled. */ - readonly disabled: SignalLike; - - /** A reference to the parent toolbar. */ - readonly toolbar: SignalLike | undefined>; - - /** The text used by the typeahead search. */ - readonly searchTerm = () => ''; // Unused because toolbar does not support typeahead. - - /** The value associated with the widget. */ - readonly value = () => '' as V; // Unused because toolbar does not support selection. - - /** Whether the widget is selectable. */ - readonly selectable = () => true; // Unused because toolbar does not support selection. - - /** The position of the widget within the toolbar. */ - readonly index = computed(() => this.toolbar()?.inputs.items().indexOf(this) ?? -1); - - /** The actions that can be performed on the widget group. */ - readonly controls: SignalLike = computed( - () => this.inputs.controls() ?? this._defaultControls, - ); - - /** Default toolbar widget group controls when no controls provided. */ - private readonly _defaultControls: ToolbarWidgetGroupControls = { - isOnFirstItem: () => true, - isOnLastItem: () => true, - next: () => {}, - prev: () => {}, - first: () => {}, - last: () => {}, - unfocus: () => {}, - trigger: () => {}, - goto: () => {}, - setDefaultState: () => {}, - }; - - constructor(readonly inputs: ToolbarWidgetGroupInputs) { - this.id = inputs.id; - this.element = inputs.element; - this.disabled = inputs.disabled; - this.toolbar = inputs.toolbar; - } -} diff --git a/src/aria/ui-patterns/toolbar/toolbar.spec.ts b/src/aria/ui-patterns/toolbar/toolbar.spec.ts deleted file mode 100644 index 2f1cfa673f27..000000000000 --- a/src/aria/ui-patterns/toolbar/toolbar.spec.ts +++ /dev/null @@ -1,466 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {signal, WritableSignal} from '@angular/core'; -import {ToolbarInputs, ToolbarPattern} from './toolbar'; -import {ToolbarWidgetInputs, ToolbarWidgetPattern} from './toolbar-widget'; -import { - ToolbarWidgetGroupControls, - ToolbarWidgetGroupInputs, - ToolbarWidgetGroupPattern, -} from './toolbar-widget-group'; -import {createKeyboardEvent} from '@angular/cdk/testing/private'; -import {ModifierKeys} from '@angular/cdk/testing'; -import {SignalLike} from '../behaviors/signal-like/signal-like'; - -// Converts the SignalLike type to WritableSignal type for controlling test inputs. -type WritableSignalOverrides = { - [K in keyof O as O[K] extends SignalLike ? K : never]: O[K] extends SignalLike - ? WritableSignal - : never; -}; - -type TestToolbarInputs = Omit< - ToolbarInputs & WritableSignalOverrides>, - 'items' | 'element' | 'getItem' ->; -type TestToolbarWidgetInputs = Omit< - ToolbarWidgetInputs & WritableSignalOverrides>, - 'element' | 'id' | 'toolbar' ->; -type TestToolbarWidgetGroupInputs = Omit< - ToolbarWidgetGroupInputs & WritableSignalOverrides>, - 'element' | 'id' | 'toolbar' ->; - -const up = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 38, 'ArrowUp', mods); -const down = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 40, 'ArrowDown', mods); -const left = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 37, 'ArrowLeft', mods); -const right = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 39, 'ArrowRight', mods); -const home = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 36, 'Home', mods); -const end = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 35, 'End', mods); -const space = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 32, ' ', mods); -const enter = (mods?: ModifierKeys) => createKeyboardEvent('keydown', 13, 'Enter', mods); -const click = (target: Element) => - ({target, stopPropagation: () => {}, preventDefault: () => {}}) as unknown as PointerEvent; - -describe('Toolbar Pattern', () => { - function createToolbar( - widgets: (TestToolbarWidgetInputs | TestToolbarWidgetGroupInputs)[], - toolbarInputs: TestToolbarInputs, - ) { - const items = signal<(ToolbarWidgetPattern | ToolbarWidgetGroupPattern)[]>([]); - const toolbar = new ToolbarPattern({ - ...toolbarInputs, - items, - element: signal(document.createElement('div')), - getItem: target => items().find(widget => widget.element() === target), - }); - - const widgetPatterns = widgets.map((widgetInputs, index) => { - const id = `widget-${index}`; - const element = document.createElement('div'); - element.id = id; - - if ('controls' in widgetInputs) { - // It's a group - element.classList.add('toolbar-widget-group'); - return new ToolbarWidgetGroupPattern({ - ...widgetInputs, - id: signal(id), - element: signal(element), - toolbar: signal(toolbar), - }); - } else { - // It's a widget - element.classList.add('toolbar-widget'); - return new ToolbarWidgetPattern({ - ...widgetInputs, - id: signal(id), - element: signal(element), - toolbar: signal(toolbar), - }); - } - }); - items.set(widgetPatterns); - return {toolbar, items: widgetPatterns}; - } - - describe('Keyboard Navigation', () => { - let toolbar: ToolbarPattern; - let toolbarInputs: TestToolbarInputs; - let widgetInputs: (TestToolbarWidgetInputs | TestToolbarWidgetGroupInputs)[]; - let items: (ToolbarWidgetPattern | ToolbarWidgetGroupPattern)[]; - - beforeEach(() => { - toolbarInputs = { - activeItem: signal(undefined), - orientation: signal('horizontal'), - textDirection: signal('ltr'), - disabled: signal(false), - skipDisabled: signal(true), - wrap: signal(false), - }; - widgetInputs = [ - {disabled: signal(false)}, - {disabled: signal(false)}, - { - disabled: signal(false), - controls: signal(undefined), - }, - {disabled: signal(false)}, - ]; - const {toolbar: newToolbar, items: newItems} = createToolbar( - widgetInputs, - toolbarInputs, - ); - toolbar = newToolbar; - items = newItems; - toolbarInputs.activeItem.set(items[0]); - }); - - it('should navigate next on ArrowRight (horizontal)', () => { - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[1]); - }); - - it('should navigate prev on ArrowLeft (horizontal)', () => { - toolbarInputs.activeItem.set(items[1]); - toolbar.onKeydown(left()); - expect(toolbarInputs.activeItem()).toBe(items[0]); - }); - - it('should navigate next on ArrowDown (vertical)', () => { - toolbarInputs.orientation.set('vertical'); - toolbar.onKeydown(down()); - expect(toolbarInputs.activeItem()).toBe(items[1]); - }); - - it('should navigate prev on ArrowUp (vertical)', () => { - toolbarInputs.orientation.set('vertical'); - toolbarInputs.activeItem.set(items[1]); - toolbar.onKeydown(up()); - expect(toolbarInputs.activeItem()).toBe(items[0]); - }); - - it('should navigate next on ArrowLeft (rtl)', () => { - toolbarInputs.textDirection.set('rtl'); - toolbar.onKeydown(left()); - expect(toolbarInputs.activeItem()).toBe(items[1]); - }); - - it('should navigate prev on ArrowRight (rtl)', () => { - toolbarInputs.textDirection.set('rtl'); - toolbarInputs.activeItem.set(items[1]); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[0]); - }); - - it('should navigate to the first item on Home', () => { - toolbarInputs.activeItem.set(items[3]); - toolbar.onKeydown(home()); - expect(toolbarInputs.activeItem()).toBe(items[0]); - }); - - it('should navigate to the last item on End', () => { - toolbar.onKeydown(end()); - expect(toolbarInputs.activeItem()).toBe(items[3]); - }); - - it('should skip a disabled toolbar widget when skipDisabled is true', () => { - widgetInputs[1].disabled.set(true); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[2]); - }); - - it('should not skip disabled items when skipDisabled is false', () => { - toolbarInputs.skipDisabled.set(false); - widgetInputs[1].disabled.set(true); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[1]); - }); - - it('should wrap back to the first item when wrap is true', () => { - toolbarInputs.wrap.set(true); - toolbarInputs.activeItem.set(items[3]); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[0]); - }); - - it('should not wrap when wrap is false', () => { - toolbarInputs.activeItem.set(items[3]); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[3]); - }); - - it('should not navigate when the toolbar is disabled', () => { - toolbarInputs.disabled.set(true); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[0]); - }); - }); - - describe('Pointer Events', () => { - let toolbar: ToolbarPattern; - let toolbarInputs: TestToolbarInputs; - let items: (ToolbarWidgetPattern | ToolbarWidgetGroupPattern)[]; - - beforeEach(() => { - toolbarInputs = { - activeItem: signal(undefined), - orientation: signal('horizontal'), - textDirection: signal('ltr'), - disabled: signal(false), - skipDisabled: signal(true), - wrap: signal(false), - }; - const widgetInputs = [ - {disabled: signal(false)}, - {disabled: signal(false)}, - { - disabled: signal(false), - controls: signal(undefined), - }, - {disabled: signal(false)}, - ]; - const {toolbar: newToolbar, items: newItems} = createToolbar( - widgetInputs, - toolbarInputs, - ); - toolbar = newToolbar; - items = newItems; - toolbarInputs.activeItem.set(items[0]); - }); - - it('should set the active item on pointerdown', () => { - toolbar.onPointerdown(click(items[1].element())); - expect(toolbarInputs.activeItem()).toBe(items[1]); - }); - - it('should not set the active item on pointerdown if the toolbar is disabled', () => { - toolbarInputs.disabled.set(true); - toolbar.onPointerdown(click(items[1].element())); - expect(toolbarInputs.activeItem()).toBe(items[0]); - }); - }); - - describe('#setDefaultState', () => { - let toolbar: ToolbarPattern; - let toolbarInputs: TestToolbarInputs; - let widgetInputs: (TestToolbarWidgetInputs | TestToolbarWidgetGroupInputs)[]; - let items: (ToolbarWidgetPattern | ToolbarWidgetGroupPattern)[]; - - beforeEach(() => { - toolbarInputs = { - activeItem: signal(undefined), - orientation: signal('horizontal'), - textDirection: signal('ltr'), - disabled: signal(false), - skipDisabled: signal(true), - wrap: signal(false), - }; - widgetInputs = [ - {disabled: signal(false)}, - {disabled: signal(false)}, - { - disabled: signal(false), - controls: signal(undefined), - }, - ]; - const {toolbar: newToolbar, items: newItems} = createToolbar( - widgetInputs, - toolbarInputs, - ); - toolbar = newToolbar; - items = newItems; - }); - - it('should set the active item to the first focusable widget', () => { - toolbar.setDefaultState(); - expect(toolbarInputs.activeItem()).toBe(items[0]); - }); - - it('should skip disabled widgets and set the next focusable widget as active', () => { - widgetInputs[0].disabled.set(true); - toolbar.setDefaultState(); - expect(toolbarInputs.activeItem()).toBe(items[1]); - }); - - it('should call "setDefaultState" on a widget group if it is the first focusable item', () => { - const fakeControls = jasmine.createSpyObj('fakeControls', [ - 'setDefaultState', - ]); - (widgetInputs[2] as TestToolbarWidgetGroupInputs).controls.set(fakeControls); - - widgetInputs[0].disabled.set(true); - widgetInputs[1].disabled.set(true); - toolbar.setDefaultState(); - expect(toolbarInputs.activeItem()).toBe(items[2]); - expect(fakeControls.setDefaultState).toHaveBeenCalled(); - }); - }); - - describe('Widget Group', () => { - let toolbar: ToolbarPattern; - let toolbarInputs: TestToolbarInputs; - let items: (ToolbarWidgetPattern | ToolbarWidgetGroupPattern)[]; - let fakeControls: jasmine.SpyObj; - - beforeEach(() => { - fakeControls = jasmine.createSpyObj('fakeControls', [ - 'next', - 'prev', - 'first', - 'last', - 'unfocus', - 'trigger', - 'goto', - 'setDefaultState', - 'isOnFirstItem', - 'isOnLastItem', - ]); - toolbarInputs = { - activeItem: signal(undefined), - orientation: signal('horizontal'), - textDirection: signal('ltr'), - disabled: signal(false), - skipDisabled: signal(true), - wrap: signal(false), - }; - const widgetInputs = [ - {disabled: signal(false)}, - { - disabled: signal(false), - controls: signal(fakeControls), - }, - {disabled: signal(false)}, - ]; - const {toolbar: newToolbar, items: newItems} = createToolbar( - widgetInputs, - toolbarInputs, - ); - toolbar = newToolbar; - items = newItems; - - // Set the widget group as the active item for tests. - toolbarInputs.activeItem.set(items[1]); - }); - - it('should call "next" on the group handler when navigating next (horizontal)', () => { - fakeControls.isOnLastItem.and.returnValue(false); - toolbar.onKeydown(right()); - expect(fakeControls.next).toHaveBeenCalledWith(false); - }); - - it('should call "next" on the group handler when navigating next (vertical)', () => { - fakeControls.isOnLastItem.and.returnValue(false); - toolbarInputs.orientation.set('vertical'); - toolbar.onKeydown(down()); - expect(fakeControls.next).toHaveBeenCalledWith(false); - }); - - it('should navigate to the next widget if the group allows it', () => { - fakeControls.isOnLastItem.and.returnValue(true); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[2]); - expect(fakeControls.unfocus).toHaveBeenCalled(); - }); - - it('should not navigate to the next widget if the group prevents it', () => { - fakeControls.isOnLastItem.and.returnValue(false); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[1]); - expect(fakeControls.next).toHaveBeenCalledWith(false); - }); - - it('should call "prev" on the group handler when navigating previous (horizontal)', () => { - fakeControls.isOnFirstItem.and.returnValue(false); - toolbar.onKeydown(left()); - expect(fakeControls.prev).toHaveBeenCalledWith(false); - }); - - it('should call "prev" on the group handler when navigating previous (vertical)', () => { - fakeControls.isOnFirstItem.and.returnValue(false); - toolbarInputs.orientation.set('vertical'); - toolbar.onKeydown(up()); - expect(fakeControls.prev).toHaveBeenCalledWith(false); - }); - - it('should navigate to the previous widget if the group allows it', () => { - fakeControls.isOnFirstItem.and.returnValue(true); - toolbar.onKeydown(left()); - expect(toolbarInputs.activeItem()).toBe(items[0]); - expect(fakeControls.unfocus).toHaveBeenCalled(); - }); - - it('should not navigate to the previous widget if the group prevents it', () => { - fakeControls.isOnFirstItem.and.returnValue(false); - toolbar.onKeydown(left()); - expect(toolbarInputs.activeItem()).toBe(items[1]); - expect(fakeControls.prev).toHaveBeenCalledWith(false); - }); - - it('should call "unfocus" on the group handler on Home', () => { - toolbar.onKeydown(home()); - expect(fakeControls.unfocus).toHaveBeenCalled(); - expect(toolbarInputs.activeItem()).toBe(items[0]); // Also moves focus - }); - - it('should call "unfocus" on the group handler on End', () => { - toolbar.onKeydown(end()); - expect(fakeControls.unfocus).toHaveBeenCalled(); - expect(toolbarInputs.activeItem()).toBe(items[2]); // Also moves focus - }); - - it('should call "trigger" on the group handler on Enter', () => { - toolbar.onKeydown(enter()); - expect(fakeControls.trigger).toHaveBeenCalled(); - }); - - it('should call "trigger" on the group handler on Space', () => { - toolbar.onKeydown(space()); - expect(fakeControls.trigger).toHaveBeenCalled(); - }); - - it('should call "next" with wrap on the group handler (horizontal)', () => { - toolbar.onKeydown(down()); - expect(fakeControls.next).toHaveBeenCalledWith(true); - }); - - it('should call "next" with wrap on the group handler (vertical)', () => { - toolbarInputs.orientation.set('vertical'); - toolbar.onKeydown(right()); - expect(fakeControls.next).toHaveBeenCalledWith(true); - }); - - it('should call "prev" with wrap on the group handler (horizontal)', () => { - toolbar.onKeydown(up()); - expect(fakeControls.prev).toHaveBeenCalledWith(true); - }); - - it('should call "prev" with wrap on the group handler (vertical)', () => { - toolbarInputs.orientation.set('vertical'); - toolbar.onKeydown(left()); - expect(fakeControls.prev).toHaveBeenCalledWith(true); - }); - - it('should call "first" when navigating into a group from the previous item', () => { - toolbarInputs.activeItem.set(items[0]); - toolbar.onKeydown(right()); - expect(toolbarInputs.activeItem()).toBe(items[1]); - expect(fakeControls.first).toHaveBeenCalled(); - }); - - it('should call "last" when navigating into a group from the next item', () => { - toolbarInputs.activeItem.set(items[2]); - toolbar.onKeydown(left()); - expect(toolbarInputs.activeItem()).toBe(items[1]); - expect(fakeControls.last).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/aria/ui-patterns/toolbar/toolbar.ts b/src/aria/ui-patterns/toolbar/toolbar.ts deleted file mode 100644 index ff704976cc6a..000000000000 --- a/src/aria/ui-patterns/toolbar/toolbar.ts +++ /dev/null @@ -1,247 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {computed, signal} from '@angular/core'; -import {SignalLike} from '../behaviors/signal-like/signal-like'; -import {KeyboardEventManager, PointerEventManager} from '../behaviors/event-manager'; -import {List, ListInputs} from '../behaviors/list/list'; -import {ToolbarWidgetPattern} from './toolbar-widget'; -import {ToolbarWidgetGroupPattern} from './toolbar-widget-group'; - -/** Represents the required inputs for a toolbar. */ -export type ToolbarInputs = Omit< - ListInputs | ToolbarWidgetGroupPattern, V>, - 'multi' | 'typeaheadDelay' | 'value' | 'selectionMode' | 'focusMode' -> & { - /** A function that returns the toolbar item associated with a given element. */ - getItem: (e: Element) => ToolbarWidgetPattern | ToolbarWidgetGroupPattern | undefined; -}; - -/** Controls the state of a toolbar. */ -export class ToolbarPattern { - /** The list behavior for the toolbar. */ - readonly listBehavior: List | ToolbarWidgetGroupPattern, V>; - - /** Whether the tablist is vertically or horizontally oriented. */ - readonly orientation: SignalLike<'vertical' | 'horizontal'>; - - /** Whether disabled items in the group should be skipped when navigating. */ - readonly skipDisabled: SignalLike; - - /** Whether the toolbar is disabled. */ - readonly disabled = computed(() => this.listBehavior.disabled()); - - /** The tabindex of the toolbar (if using activedescendant). */ - readonly tabindex = computed(() => this.listBehavior.tabindex()); - - /** The id of the current active widget (if using activedescendant). */ - readonly activedescendant = computed(() => this.listBehavior.activedescendant()); - - /** The key used to navigate to the previous widget. */ - private readonly _prevKey = computed(() => { - if (this.inputs.orientation() === 'vertical') { - return 'ArrowUp'; - } - return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; - }); - - /** The key used to navigate to the next widget. */ - private readonly _nextKey = computed(() => { - if (this.inputs.orientation() === 'vertical') { - return 'ArrowDown'; - } - return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; - }); - - /** The alternate key used to navigate to the previous widget. */ - private readonly _altPrevKey = computed(() => { - if (this.inputs.orientation() === 'vertical') { - return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; - } - return 'ArrowUp'; - }); - - /** The alternate key used to navigate to the next widget. */ - private readonly _altNextKey = computed(() => { - if (this.inputs.orientation() === 'vertical') { - return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; - } - return 'ArrowDown'; - }); - - /** The keydown event manager for the toolbar. */ - private readonly _keydown = computed(() => { - const manager = new KeyboardEventManager(); - - return manager - .on(this._nextKey, () => this._next()) - .on(this._prevKey, () => this._prev()) - .on(this._altNextKey, () => this._groupNext()) - .on(this._altPrevKey, () => this._groupPrev()) - .on(' ', () => this._trigger()) - .on('Enter', () => this._trigger()) - .on('Home', () => this._first()) - .on('End', () => this._last()); - }); - - /** The pointerdown event manager for the toolbar. */ - private readonly _pointerdown = computed(() => new PointerEventManager().on(e => this._goto(e))); - - /** Navigates to the next widget in the toolbar. */ - private _next() { - const item = this.inputs.activeItem(); - if (item instanceof ToolbarWidgetGroupPattern) { - if (!item.disabled() && !item.controls().isOnLastItem()) { - item.controls().next(false); - return; - } - item.controls().unfocus(); - } - - this.listBehavior.next(); - const newItem = this.inputs.activeItem(); - if (newItem instanceof ToolbarWidgetGroupPattern) { - newItem.controls().first(); - } - } - - /** Navigates to the previous widget in the toolbar. */ - private _prev() { - const item = this.inputs.activeItem(); - if (item instanceof ToolbarWidgetGroupPattern) { - if (!item.disabled() && !item.controls().isOnFirstItem()) { - item.controls().prev(false); - return; - } - item.controls().unfocus(); - } - - this.listBehavior.prev(); - const newItem = this.inputs.activeItem(); - if (newItem instanceof ToolbarWidgetGroupPattern) { - newItem.controls().last(); - } - } - - private _groupNext() { - const item = this.inputs.activeItem(); - if (item instanceof ToolbarWidgetPattern) return; - item?.controls().next(true); - } - - private _groupPrev() { - const item = this.inputs.activeItem(); - if (item instanceof ToolbarWidgetPattern) return; - item?.controls().prev(true); - } - - /** Triggers the action of the currently active widget. */ - private _trigger() { - const item = this.inputs.activeItem(); - if (item instanceof ToolbarWidgetGroupPattern) { - item.controls().trigger(); - } - } - - /** Navigates to the first widget in the toolbar. */ - private _first() { - const item = this.inputs.activeItem(); - if (item instanceof ToolbarWidgetGroupPattern) { - item.controls().unfocus(); - } - - this.listBehavior.first(); - const newItem = this.inputs.activeItem(); - if (newItem instanceof ToolbarWidgetGroupPattern) { - newItem.controls().first(); - } - } - - /** Navigates to the last widget in the toolbar. */ - private _last() { - const item = this.inputs.activeItem(); - if (item instanceof ToolbarWidgetGroupPattern) { - item.controls().unfocus(); - } - - this.listBehavior.last(); - const newItem = this.inputs.activeItem(); - if (newItem instanceof ToolbarWidgetGroupPattern) { - newItem.controls().last(); - } - } - - /** Navigates to the widget targeted by a pointer event. */ - private _goto(e: PointerEvent) { - const item = this.inputs.getItem(e.target as Element); - if (!item) return; - - this.listBehavior.goto(item); - if (item instanceof ToolbarWidgetGroupPattern) { - item.controls().goto(e); - } - } - - constructor(readonly inputs: ToolbarInputs) { - this.orientation = inputs.orientation; - this.skipDisabled = inputs.skipDisabled; - - this.listBehavior = new List({ - ...inputs, - multi: () => false, - focusMode: () => 'roving', - selectionMode: () => 'explicit', - value: signal([] as V[]), - typeaheadDelay: () => 0, // Toolbar widgets do not support typeahead. - }); - } - - /** Handles keydown events for the toolbar. */ - onKeydown(event: KeyboardEvent) { - if (this.disabled()) return; - this._keydown().handle(event); - } - - /** Handles pointerdown events for the toolbar. */ - onPointerdown(event: PointerEvent) { - if (this.disabled()) return; - this._pointerdown().handle(event); - } - - /** - * Sets the toolbar to its default initial state. - * - * Sets the active index to the selected widget if one exists and is focusable. - * Otherwise, sets the active index to the first focusable widget. - */ - setDefaultState() { - let firstItem: ToolbarWidgetPattern | ToolbarWidgetGroupPattern | null = null; - - for (const item of this.inputs.items()) { - if (this.listBehavior.isFocusable(item)) { - if (!firstItem) { - firstItem = item; - } - } - } - - if (firstItem) { - this.inputs.activeItem.set(firstItem); - } - if (firstItem instanceof ToolbarWidgetGroupPattern) { - firstItem.controls().setDefaultState(); - } - } - - /** Validates the state of the toolbar and returns a list of accessibility violations. */ - validate(): string[] { - const violations: string[] = []; - - return violations; - } -} diff --git a/src/cdk/a11y/id-generator.ts b/src/cdk/a11y/id-generator.ts index 55bae7a0a551..843940cf0b85 100644 --- a/src/cdk/a11y/id-generator.ts +++ b/src/cdk/a11y/id-generator.ts @@ -19,12 +19,14 @@ const counters: Record = {}; @Injectable({providedIn: 'root'}) export class _IdGenerator { private _appId = inject(APP_ID); + private static _infix = `a${Math.floor(Math.random() * 100000).toString()}`; /** * Generates a unique ID with a specific prefix. * @param prefix Prefix to add to the ID. + * @param randomize Add a randomized infix string. */ - getId(prefix: string): string { + getId(prefix: string, randomize: boolean = false): string { // Omit the app ID if it's the default `ng`. Since the vast majority of pages have one // Angular app on them, we can reduce the amount of breakages by not adding it. if (this._appId !== 'ng') { @@ -35,6 +37,6 @@ export class _IdGenerator { counters[prefix] = 0; } - return `${prefix}${counters[prefix]++}`; + return `${prefix}${randomize ? _IdGenerator._infix + '-' : ''}${counters[prefix]++}`; } } diff --git a/src/cdk/dialog/dialog.ts b/src/cdk/dialog/dialog.ts index 45ac68f5961c..93ea177750bf 100644 --- a/src/cdk/dialog/dialog.ts +++ b/src/cdk/dialog/dialog.ts @@ -401,7 +401,8 @@ export class Dialog implements OnDestroy { sibling !== overlayContainer && sibling.nodeName !== 'SCRIPT' && sibling.nodeName !== 'STYLE' && - !sibling.hasAttribute('aria-live') + !sibling.hasAttribute('aria-live') && + !sibling.hasAttribute('popover') ) { this._ariaHiddenElements.set(sibling, sibling.getAttribute('aria-hidden')); sibling.setAttribute('aria-hidden', 'true'); diff --git a/src/cdk/overlay/_index.scss b/src/cdk/overlay/_index.scss index 6952e9dcb9ba..db174855c55b 100644 --- a/src/cdk/overlay/_index.scss +++ b/src/cdk/overlay/_index.scss @@ -185,6 +185,41 @@ $backdrop-animation-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default; // block scrolling on a page that doesn't have a scrollbar in the first place. overflow-y: scroll; } + + .cdk-overlay-popover { + background: none; + border: none; + padding: 0; + outline: 0; + overflow: visible; + position: fixed; + pointer-events: none; + white-space: normal; + color: inherit; + text-decoration: none; + + // These are important so the overlay can be measured before it's fully inserted. + width: 100%; + height: 100%; + + // Chrome sets a user agent style of `inset: 0` which combined + // with `align-self` can break the positioning (see #29809). + inset: auto; + + // Some older versions of Chrome won't render the popover properly without these. + top: 0; + left: 0; + + // For the time being we're using our `.cdk-overlay-backdrop` element instead of the native one. + &::backdrop { + display: none; + } + + .cdk-overlay-backdrop { + position: fixed; + z-index: auto; + } + } } /// Emits structural styles required for cdk/overlay to function. diff --git a/src/cdk/overlay/overlay-config.ts b/src/cdk/overlay/overlay-config.ts index fa1d96551280..c49ed9c1eb2b 100644 --- a/src/cdk/overlay/overlay-config.ts +++ b/src/cdk/overlay/overlay-config.ts @@ -61,6 +61,12 @@ export class OverlayConfig { */ disposeOnNavigation?: boolean = false; + /** + * Whether the overlay should be rendered as a native popover element, + * rather than placing it inside of the overlay container. + */ + usePopover?: boolean; + constructor(config?: OverlayConfig) { if (config) { // Use `Iterable` instead of `Array` because TypeScript, as of 3.6.3, diff --git a/src/cdk/overlay/overlay-directives.spec.ts b/src/cdk/overlay/overlay-directives.spec.ts index 22a161ba1889..ef11a388e13a 100644 --- a/src/cdk/overlay/overlay-directives.spec.ts +++ b/src/cdk/overlay/overlay-directives.spec.ts @@ -626,6 +626,19 @@ describe('Overlay directives', () => { expect(target.style.transformOrigin).toContain('left bottom'); }); + + it('should match the trigger width', () => { + const trigger = fixture.nativeElement.querySelector('#trigger') as HTMLElement; + trigger.style.width = '128px'; + + fixture.componentInstance.matchWidth = true; + fixture.componentInstance.isOpen = true; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + + const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + expect(pane.style.width).toBe('128px'); + }); }); describe('outputs', () => { @@ -742,11 +755,11 @@ describe('Overlay directives', () => { @Component({ template: ` - - + + - { [cdkConnectedOverlayMinWidth]="minWidth" [cdkConnectedOverlayMinHeight]="minHeight" [cdkConnectedOverlayPositions]="positionOverrides" - [cdkConnectedOverlayTransformOriginOn]="transformOriginSelector"> + [cdkConnectedOverlayTransformOriginOn]="transformOriginSelector" + [cdkConnectedOverlayMatchWidth]="matchWidth">

        Menu content

        `, imports: [OverlayModule], @@ -809,12 +823,14 @@ class ConnectedOverlayDirectiveTest { detachHandler = jasmine.createSpy('detachHandler'); attachResult: HTMLElement; transformOriginSelector: string; + matchWidth = false; } @Component({ template: ` - - Menu content`, + + Menu content + `, imports: [OverlayModule], }) class ConnectedOverlayPropertyInitOrder { diff --git a/src/cdk/overlay/overlay-directives.ts b/src/cdk/overlay/overlay-directives.ts index 5f4e711b7fc9..f69d079035a9 100644 --- a/src/cdk/overlay/overlay-directives.ts +++ b/src/cdk/overlay/overlay-directives.ts @@ -29,7 +29,7 @@ import { import {_getEventTarget} from '../platform'; import {Subscription} from 'rxjs'; import {takeWhile} from 'rxjs/operators'; -import {createOverlayRef} from './overlay'; +import {createOverlayRef, OVERLAY_DEFAULT_CONFIG} from './overlay'; import {OverlayConfig} from './overlay-config'; import {OverlayRef} from './overlay-ref'; import {ConnectedOverlayPositionChange, ViewportMargin} from './position/connected-position'; @@ -38,6 +38,7 @@ import { createFlexibleConnectedPositionStrategy, FlexibleConnectedPositionStrategy, FlexibleConnectedPositionStrategyOrigin, + FlexibleOverlayPopoverLocation, } from './position/flexible-connected-position-strategy'; import {createRepositionScrollStrategy, ScrollStrategy} from './scroll/index'; @@ -96,6 +97,41 @@ export class CdkOverlayOrigin { constructor() {} } +/** + * Injection token that can be used to configure the + * default options for the `CdkConnectedOverlay` directive. + */ +export const CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG = new InjectionToken( + 'cdk-connected-overlay-default-config', +); + +/** Object used to configure the `CdkConnectedOverlay` directive. */ +export interface CdkConnectedOverlayConfig { + origin?: CdkOverlayOrigin | FlexibleConnectedPositionStrategyOrigin; + positions?: ConnectedPosition[]; + positionStrategy?: FlexibleConnectedPositionStrategy; + offsetX?: number; + offsetY?: number; + width?: number | string; + height?: number | string; + minWidth?: number | string; + minHeight?: number | string; + backdropClass?: string | string[]; + panelClass?: string | string[]; + viewportMargin?: ViewportMargin; + scrollStrategy?: ScrollStrategy; + disableClose?: boolean; + transformOriginSelector?: string; + hasBackdrop?: boolean; + lockPosition?: boolean; + flexibleDimensions?: boolean; + growAfterOpen?: boolean; + push?: boolean; + disposeOnNavigation?: boolean; + usePopover?: FlexibleOverlayPopoverLocation | null; + matchWidth?: boolean; +} + /** * Directive to facilitate declarative creation of an * Overlay using a FlexibleConnectedPositionStrategy. @@ -118,7 +154,6 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { private _offsetY: number; private _position: FlexibleConnectedPositionStrategy; private _scrollStrategyFactory = inject(CDK_CONNECTED_OVERLAY_SCROLL_STRATEGY); - private _disposeOnNavigation = false; private _ngZone = inject(NgZone); /** Origin for the connected overlay. */ @@ -214,11 +249,22 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { /** Whether the overlay should be disposed of when the user goes backwards/forwards in history. */ @Input({alias: 'cdkConnectedOverlayDisposeOnNavigation', transform: booleanAttribute}) - get disposeOnNavigation(): boolean { - return this._disposeOnNavigation; - } - set disposeOnNavigation(value: boolean) { - this._disposeOnNavigation = value; + disposeOnNavigation: boolean = false; + + /** Whether the connected overlay should be rendered inside a popover element or the overlay container. */ + @Input({alias: 'cdkConnectedOverlayUsePopover'}) + usePopover: FlexibleOverlayPopoverLocation | null; + + /** Whether the overlay should match the trigger's width. */ + @Input({alias: 'cdkConnectedOverlayMatchWidth', transform: booleanAttribute}) + matchWidth: boolean = false; + + /** Shorthand for setting multiple overlay options at once. */ + @Input('cdkConnectedOverlay') + set _config(value: string | CdkConnectedOverlayConfig) { + if (typeof value !== 'string') { + this._assignConfig(value); + } } /** Event emitted when the backdrop is clicked. */ @@ -246,9 +292,16 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { constructor() { const templateRef = inject>(TemplateRef); const viewContainerRef = inject(ViewContainerRef); + const defaultConfig = inject(CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG, {optional: true}); + const globalConfig = inject(OVERLAY_DEFAULT_CONFIG, {optional: true}); + this.usePopover = globalConfig?.usePopover === false ? null : 'global'; this._templatePortal = new TemplatePortal(templateRef, viewContainerRef); this.scrollStrategy = this._scrollStrategyFactory(); + + if (defaultConfig) { + this._assignConfig(defaultConfig); + } } /** The associated overlay reference. */ @@ -273,7 +326,7 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { if (this._position) { this._updatePositionStrategy(this._position); this._overlayRef?.updateSize({ - width: this.width, + width: this._getWidth(), minWidth: this.minWidth, height: this.height, minHeight: this.minHeight, @@ -327,12 +380,9 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { scrollStrategy: this.scrollStrategy, hasBackdrop: this.hasBackdrop, disposeOnNavigation: this.disposeOnNavigation, + usePopover: !!this.usePopover, }); - if (this.width || this.width === 0) { - overlayConfig.width = this.width; - } - if (this.height || this.height === 0) { overlayConfig.height = this.height; } @@ -376,7 +426,8 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { .withGrowAfterOpen(this.growAfterOpen) .withViewportMargin(this.viewportMargin) .withLockedPosition(this.lockPosition) - .withTransformOriginOn(this.transformOriginSelector); + .withTransformOriginOn(this.transformOriginSelector) + .withPopoverLocation(this.usePopover === null ? 'global' : this.usePopover); } /** Returns the position strategy of the overlay to be set on the overlay config */ @@ -410,23 +461,35 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { return null; } + private _getWidth() { + if (this.width) { + return this.width; + } + + // Null check `getBoundingClientRect` in case this is called during SSR. + return this.matchWidth ? this._getOriginElement()?.getBoundingClientRect?.().width : undefined; + } + /** Attaches the overlay. */ attachOverlay() { if (!this._overlayRef) { this._createOverlay(); - } else { - // Update the overlay size, in case the directive's inputs have changed - this._overlayRef.getConfig().hasBackdrop = this.hasBackdrop; } - if (!this._overlayRef!.hasAttached()) { - this._overlayRef!.attach(this._templatePortal); + const ref = this._overlayRef!; + + // Update the overlay size, in case the directive's inputs have changed + ref.getConfig().hasBackdrop = this.hasBackdrop; + ref.updateSize({width: this._getWidth()}); + + if (!ref.hasAttached()) { + ref.attach(this._templatePortal); } if (this.hasBackdrop) { - this._backdropSubscription = this._overlayRef!.backdropClick().subscribe(event => { - this.backdropClick.emit(event); - }); + this._backdropSubscription = ref + .backdropClick() + .subscribe(event => this.backdropClick.emit(event)); } else { this._backdropSubscription.unsubscribe(); } @@ -457,4 +520,30 @@ export class CdkConnectedOverlay implements OnDestroy, OnChanges { this._positionSubscription.unsubscribe(); this.open = false; } + + private _assignConfig(config: CdkConnectedOverlayConfig) { + this.origin = config.origin ?? this.origin; + this.positions = config.positions ?? this.positions; + this.positionStrategy = config.positionStrategy ?? this.positionStrategy; + this.offsetX = config.offsetX ?? this.offsetX; + this.offsetY = config.offsetY ?? this.offsetY; + this.width = config.width ?? this.width; + this.height = config.height ?? this.height; + this.minWidth = config.minWidth ?? this.minWidth; + this.minHeight = config.minHeight ?? this.minHeight; + this.backdropClass = config.backdropClass ?? this.backdropClass; + this.panelClass = config.panelClass ?? this.panelClass; + this.viewportMargin = config.viewportMargin ?? this.viewportMargin; + this.scrollStrategy = config.scrollStrategy ?? this.scrollStrategy; + this.disableClose = config.disableClose ?? this.disableClose; + this.transformOriginSelector = config.transformOriginSelector ?? this.transformOriginSelector; + this.hasBackdrop = config.hasBackdrop ?? this.hasBackdrop; + this.lockPosition = config.lockPosition ?? this.lockPosition; + this.flexibleDimensions = config.flexibleDimensions ?? this.flexibleDimensions; + this.growAfterOpen = config.growAfterOpen ?? this.growAfterOpen; + this.push = config.push ?? this.push; + this.disposeOnNavigation = config.disposeOnNavigation ?? this.disposeOnNavigation; + this.usePopover = config.usePopover ?? this.usePopover; + this.matchWidth = config.matchWidth ?? this.matchWidth; + } } diff --git a/src/cdk/overlay/overlay-ref.ts b/src/cdk/overlay/overlay-ref.ts index 4a0ef74ca1a6..000e5ca8576d 100644 --- a/src/cdk/overlay/overlay-ref.ts +++ b/src/cdk/overlay/overlay-ref.ts @@ -32,6 +32,11 @@ export type ImmutableObject = { readonly [P in keyof T]: T[P]; }; +/** Checks if a value is an element. */ +export function isElement(value: any): value is Element { + return value && (value as Element).nodeType === 1; +} + /** * Reference to an overlay that has been created with the Overlay service. * Used to manipulate or dispose of said overlay. @@ -117,16 +122,10 @@ export class OverlayRef implements PortalOutlet { attach(portal: Portal): any { // Insert the host into the DOM before attaching the portal, otherwise // the animations module will skip animations on repeat attachments. - if (!this._host.parentElement && this._previousHostParent) { - this._previousHostParent.appendChild(this._host); - } + this._attachHost(); const attachResult = this._portalOutlet.attach(portal); - - if (this._positionStrategy) { - this._positionStrategy.attach(this); - } - + this._positionStrategy?.attach(this); this._updateStackingOrder(); this._updateElementSize(); this._updateElementDirection(); @@ -409,6 +408,31 @@ export class OverlayRef implements PortalOutlet { this._pane.style.pointerEvents = enablePointer ? '' : 'none'; } + private _attachHost() { + if (!this._host.parentElement) { + const customInsertionPoint = this._config.usePopover + ? this._positionStrategy?.getPopoverInsertionPoint?.() + : null; + + if (isElement(customInsertionPoint)) { + customInsertionPoint.after(this._host); + } else if (customInsertionPoint?.type === 'parent') { + customInsertionPoint.element.appendChild(this._host); + } else { + this._previousHostParent?.appendChild(this._host); + } + } + + if (this._config.usePopover) { + // We need the try/catch because the browser will throw if the + // host or any of the parents are outside the DOM. Also note + // the string access which is there for compatibility with Closure. + try { + this._host['showPopover'](); + } catch {} + } + } + /** Attaches a backdrop for this overlay. */ private _attachBackdrop() { const showingClass = 'cdk-overlay-backdrop-showing'; @@ -426,9 +450,14 @@ export class OverlayRef implements PortalOutlet { this._toggleClasses(this._backdropRef.element, this._config.backdropClass, true); } - // Insert the backdrop before the pane in the DOM order, - // in order to handle stacked overlays properly. - this._host.parentElement!.insertBefore(this._backdropRef.element, this._host); + if (this._config.usePopover) { + // When using popovers, the backdrop needs to be inside the popover. + this._host.prepend(this._backdropRef.element); + } else { + // Insert the backdrop before the pane in the DOM order, + // in order to handle stacked overlays properly. + this._host.parentElement!.insertBefore(this._backdropRef.element, this._host); + } // Add class to fade-in the backdrop after one frame. if (!this._animationsDisabled && typeof requestAnimationFrame !== 'undefined') { @@ -448,7 +477,7 @@ export class OverlayRef implements PortalOutlet { * in its original DOM position. */ private _updateStackingOrder() { - if (this._host.nextSibling) { + if (!this._config.usePopover && this._host.nextSibling) { this._host.parentNode!.appendChild(this._host); } } diff --git a/src/cdk/overlay/overlay.spec.ts b/src/cdk/overlay/overlay.spec.ts index 20fcb3f9d5b1..400a7d9a9285 100644 --- a/src/cdk/overlay/overlay.spec.ts +++ b/src/cdk/overlay/overlay.spec.ts @@ -141,9 +141,9 @@ describe('Overlay', () => { expect(overlayContainerElement.textContent).toBe(''); }); - it('should ensure that the most-recently-attached overlay is on top', () => { - let pizzaOverlayRef = createOverlayRef(injector); - let cakeOverlayRef = createOverlayRef(injector); + it('should ensure that the most-recently-attached overlay is on top when popovers are disabled', () => { + let pizzaOverlayRef = createOverlayRef(injector, {usePopover: false}); + let cakeOverlayRef = createOverlayRef(injector, {usePopover: false}); pizzaOverlayRef.attach(componentPortal); cakeOverlayRef.attach(templatePortal); @@ -850,7 +850,7 @@ describe('Overlay', () => { }); it('should insert the backdrop before the overlay host in the DOM order', () => { - const overlayRef = createOverlayRef(injector, config); + const overlayRef = createOverlayRef(injector, {...config, usePopover: false}); overlayRef.attach(componentPortal); viewContainerFixture.detectChanges(); diff --git a/src/cdk/overlay/overlay.ts b/src/cdk/overlay/overlay.ts index 935196bd2c3e..18c016236c2a 100644 --- a/src/cdk/overlay/overlay.ts +++ b/src/cdk/overlay/overlay.ts @@ -20,6 +20,7 @@ import { RendererFactory2, DOCUMENT, Renderer2, + InjectionToken, } from '@angular/core'; import {_IdGenerator} from '../a11y'; import {_CdkPrivateStyleLoader} from '../private'; @@ -27,10 +28,20 @@ import {OverlayKeyboardDispatcher} from './dispatchers/overlay-keyboard-dispatch import {OverlayOutsideClickDispatcher} from './dispatchers/overlay-outside-click-dispatcher'; import {OverlayConfig} from './overlay-config'; import {_CdkOverlayStyleLoader, OverlayContainer} from './overlay-container'; -import {OverlayRef} from './overlay-ref'; +import {isElement, OverlayRef} from './overlay-ref'; import {OverlayPositionBuilder} from './position/overlay-position-builder'; import {ScrollStrategyOptions} from './scroll/index'; +/** Object used to configure the default options for overlays. */ +export interface OverlayDefaultConfig { + usePopover?: boolean; +} + +/** Injection token used to configure the default options for CDK overlays. */ +export const OVERLAY_DEFAULT_CONFIG = new InjectionToken( + 'OVERLAY_DEFAULT_CONFIG', +); + /** * Creates an overlay. * @param injector Injector to use when resolving the overlay's dependencies. @@ -47,25 +58,50 @@ export function createOverlayRef(injector: Injector, config?: OverlayConfig): Ov const idGenerator = injector.get(_IdGenerator); const appRef = injector.get(ApplicationRef); const directionality = injector.get(Directionality); + const renderer = + injector.get(Renderer2, null, {optional: true}) || + injector.get(RendererFactory2).createRenderer(null, null); - const host = doc.createElement('div'); - const pane = doc.createElement('div'); + const overlayConfig = new OverlayConfig(config); + const defaultUsePopover = + injector.get(OVERLAY_DEFAULT_CONFIG, null, {optional: true})?.usePopover ?? true; + + overlayConfig.direction = overlayConfig.direction || directionality.value; + + if (!('showPopover' in doc.body)) { + overlayConfig.usePopover = false; + } else { + overlayConfig.usePopover = config?.usePopover ?? defaultUsePopover; + } + const pane = doc.createElement('div'); + const host = doc.createElement('div'); pane.id = idGenerator.getId('cdk-overlay-'); pane.classList.add('cdk-overlay-pane'); host.appendChild(pane); - overlayContainer.getContainerElement().appendChild(host); - const portalOutlet = new DomPortalOutlet(pane, appRef, injector); - const overlayConfig = new OverlayConfig(config); - const renderer = - injector.get(Renderer2, null, {optional: true}) || - injector.get(RendererFactory2).createRenderer(null, null); + if (overlayConfig.usePopover) { + host.setAttribute('popover', 'manual'); + host.classList.add('cdk-overlay-popover'); + } - overlayConfig.direction = overlayConfig.direction || directionality.value; + const customInsertionPoint = overlayConfig.usePopover + ? overlayConfig.positionStrategy?.getPopoverInsertionPoint?.() + : null; + + // Note: this is redundant since the host will be moved into its final location immediately + // after. We're keeping it as a temporary workaround, because an internal team depends on + // this behavior. We can roll this back after January 5th 2026. + overlayContainer.getContainerElement().appendChild(host); + + if (isElement(customInsertionPoint)) { + customInsertionPoint.after(host); + } else if (customInsertionPoint?.type === 'parent') { + customInsertionPoint.element.appendChild(host); + } return new OverlayRef( - portalOutlet, + new DomPortalOutlet(pane, appRef, injector), host, pane, overlayConfig, diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts index 2f09db158279..45014d01ed85 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.spec.ts @@ -33,6 +33,7 @@ describe('FlexibleConnectedPositionStrategy', () => { let overlayRef: OverlayRef; let viewport: ViewportRuler; let injector: Injector; + let portal: ComponentPortal; beforeEach(() => { injector = TestBed.inject(Injector); @@ -50,7 +51,8 @@ describe('FlexibleConnectedPositionStrategy', () => { function attachOverlay(config: OverlayConfig) { overlayRef = createOverlayRef(injector, config); - overlayRef.attach(new ComponentPortal(TestOverlay)); + portal = new ComponentPortal(TestOverlay); + overlayRef.attach(portal); TestBed.inject(ApplicationRef).tick(); } @@ -125,7 +127,7 @@ describe('FlexibleConnectedPositionStrategy', () => { origin.remove(); }); - it('should for the virtual keyboard offset when positioning the overlay', () => { + it('should account for the virtual keyboard offset when positioning the overlay', () => { const originElement = createPositionedBlockElement(); document.body.appendChild(originElement); @@ -138,6 +140,7 @@ describe('FlexibleConnectedPositionStrategy', () => { overlayContainer.getContainerElement().style.top = '-100px'; attachOverlay({ + usePopover: false, positionStrategy: createFlexibleConnectedPositionStrategy(injector, originElement) .withFlexibleDimensions(false) .withPush(false) @@ -2640,7 +2643,7 @@ describe('FlexibleConnectedPositionStrategy', () => { attachOverlay({positionStrategy}); expect(overlayRef.hostElement.style.left).toBeTruthy(); - expect(overlayRef.hostElement.style.right).toBeFalsy(); + expect(overlayRef.hostElement.style.right).toBe('auto'); }); it('should use `right` when positioning an element at the end', () => { @@ -2656,7 +2659,7 @@ describe('FlexibleConnectedPositionStrategy', () => { attachOverlay({positionStrategy}); expect(overlayRef.hostElement.style.right).toBeTruthy(); - expect(overlayRef.hostElement.style.left).toBeFalsy(); + expect(overlayRef.hostElement.style.left).toBe('auto'); }); }); @@ -2677,7 +2680,7 @@ describe('FlexibleConnectedPositionStrategy', () => { }); expect(overlayRef.hostElement.style.right).toBeTruthy(); - expect(overlayRef.hostElement.style.left).toBeFalsy(); + expect(overlayRef.hostElement.style.left).toBe('auto'); }); it('should use `left` when positioning an element at the end', () => { @@ -2693,7 +2696,7 @@ describe('FlexibleConnectedPositionStrategy', () => { attachOverlay({positionStrategy, direction: 'rtl'}); expect(overlayRef.hostElement.style.left).toBeTruthy(); - expect(overlayRef.hostElement.style.right).toBeFalsy(); + expect(overlayRef.hostElement.style.right).toBe('auto'); }); }); @@ -2711,7 +2714,7 @@ describe('FlexibleConnectedPositionStrategy', () => { attachOverlay({positionStrategy}); expect(overlayRef.hostElement.style.top).toBeTruthy(); - expect(overlayRef.hostElement.style.bottom).toBeFalsy(); + expect(overlayRef.hostElement.style.bottom).toBe('auto'); }); it('should use `bottom` when positioning at element along the bottom', () => { @@ -2727,7 +2730,7 @@ describe('FlexibleConnectedPositionStrategy', () => { attachOverlay({positionStrategy}); expect(overlayRef.hostElement.style.bottom).toBeTruthy(); - expect(overlayRef.hostElement.style.top).toBeFalsy(); + expect(overlayRef.hostElement.style.top).toBe('auto'); }); }); }); @@ -2951,6 +2954,97 @@ describe('FlexibleConnectedPositionStrategy', () => { expect(overlayClassList).toContain('custom-panel-class'); }); }); + + describe('DOM location', () => { + let positionStrategy: FlexibleConnectedPositionStrategy; + let containerElement: HTMLElement; + let originElement: HTMLElement; + let customHostElement: HTMLElement; + + beforeEach(() => { + containerElement = overlayContainer.getContainerElement(); + originElement = createPositionedBlockElement(); + customHostElement = createBlockElement('span'); + document.body.appendChild(originElement); + document.body.appendChild(customHostElement); + + positionStrategy = createFlexibleConnectedPositionStrategy(injector, originElement) + .withPopoverLocation('inline') + .withPositions([ + { + overlayX: 'start', + overlayY: 'top', + originX: 'start', + originY: 'bottom', + }, + ]); + }); + + afterEach(() => { + originElement.remove(); + customHostElement.remove(); + }); + + it('should place the overlay inside the overlay container by default', () => { + attachOverlay({positionStrategy, usePopover: false}); + expect(containerElement.contains(overlayRef.hostElement)).toBe(true); + expect(overlayRef.hostElement.getAttribute('popover')).toBeFalsy(); + }); + + it('should be able to opt into placing the overlay inside an adjacent popover element', () => { + if (!('showPopover' in document.body)) { + return; + } + + attachOverlay({positionStrategy, usePopover: true}); + + expect(containerElement.contains(overlayRef.hostElement)).toBe(false); + expect(originElement.nextElementSibling).toBe(overlayRef.hostElement); + expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual'); + }); + + it('should re-attach the popover next to the origin element', () => { + if (!('showPopover' in document.body)) { + return; + } + + attachOverlay({positionStrategy, usePopover: true}); + expect(originElement.nextElementSibling).toBe(overlayRef.hostElement); + + overlayRef.detach(); + TestBed.inject(ApplicationRef).tick(); + expect(overlayRef.hostElement.parentNode).toBeFalsy(); + + overlayRef.attach(portal); + expect(originElement.nextElementSibling).toBe(overlayRef.hostElement); + }); + + it('should insert the overlay as a child of a custom element', () => { + if (!('showPopover' in document.body)) { + return; + } + + positionStrategy.withPopoverLocation({type: 'parent', element: customHostElement}); + attachOverlay({positionStrategy, usePopover: true}); + + expect(containerElement.contains(overlayRef.hostElement)).toBe(false); + expect(customHostElement.contains(overlayRef.hostElement)).toBe(true); + expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual'); + }); + + it('should insert the overlay as a child of the origin', () => { + if (!('showPopover' in document.body)) { + return; + } + + positionStrategy.withPopoverLocation({type: 'parent', element: originElement}); + attachOverlay({positionStrategy, usePopover: true}); + + expect(containerElement.contains(overlayRef.hostElement)).toBe(false); + expect(originElement.contains(overlayRef.hostElement)).toBe(true); + expect(overlayRef.hostElement.getAttribute('popover')).toBe('manual'); + }); + }); }); /** Creates an absolutely positioned, display: block element with a default size. */ diff --git a/src/cdk/overlay/position/flexible-connected-position-strategy.ts b/src/cdk/overlay/position/flexible-connected-position-strategy.ts index fbc133659c0b..b31c87411b08 100644 --- a/src/cdk/overlay/position/flexible-connected-position-strategy.ts +++ b/src/cdk/overlay/position/flexible-connected-position-strategy.ts @@ -22,7 +22,7 @@ import {isElementScrolledOutsideView, isElementClippedByScrolling} from './scrol import {coerceCssPixelValue, coerceArray} from '../../coercion'; import {Platform} from '../../platform'; import {OverlayContainer} from '../overlay-container'; -import {OverlayRef} from '../overlay-ref'; +import {isElement, OverlayRef} from '../overlay-ref'; // TODO: refactor clipping detection into a separate thing (part of scrolling module) // TODO: doesn't handle both flexible width and height when it has to scroll along both axis. @@ -63,6 +63,12 @@ export function createFlexibleConnectedPositionStrategy( ); } +/** Supported locations in the DOM for connected overlays. */ +export type FlexibleOverlayPopoverLocation = + | 'global' + | 'inline' + | {type: 'parent'; element: Element}; + /** * A strategy for positioning overlays. Using this strategy, an overlay is given an * implicit position relative some origin element. The relative position is defined in terms of @@ -158,6 +164,9 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { /** Amount by which the overlay was pushed in each axis during the last time it was positioned. */ private _previousPushAmount: {x: number; y: number} | null; + /** Configures where in the DOM to insert the overlay when popovers are enabled. */ + private _popoverLocation: FlexibleOverlayPopoverLocation = 'global'; + /** Observable sequence of position changes. */ positionChanges: Observable = this._positionChanges; @@ -511,6 +520,36 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { return this; } + /** + * Determines where in the DOM the overlay will be rendered when popover mode is enabled. + * @param location Configures the location in the DOM. Supports the following values: + * - `global` - The default which inserts the overlay inside the overlay container. + * - `inline` - Inserts the overlay next to the trigger. + * - {type: 'parent', element: element} - Inserts the overlay to a child of a custom parent + * element. + */ + withPopoverLocation(location: FlexibleOverlayPopoverLocation): this { + this._popoverLocation = location; + return this; + } + + /** @docs-private */ + getPopoverInsertionPoint(): Element | null | {type: 'parent'; element: Element} { + if (this._popoverLocation === 'global') { + return null; + } else if (this._popoverLocation !== 'inline') { + return this._popoverLocation; + } + + if (this._origin instanceof ElementRef) { + return this._origin.nativeElement; + } else if (isElement(this._origin)) { + return this._origin; + } else { + return null; + } + } + /** * Gets the (x, y) coordinate of a connection point on the origin based on a relative position. */ @@ -889,18 +928,19 @@ export class FlexibleConnectedPositionStrategy implements PositionStrategy { if (this._hasExactPosition()) { styles.top = styles.left = '0'; - styles.bottom = styles.right = styles.maxHeight = styles.maxWidth = ''; + styles.bottom = styles.right = 'auto'; + styles.maxHeight = styles.maxWidth = ''; styles.width = styles.height = '100%'; } else { const maxHeight = this._overlayRef.getConfig().maxHeight; const maxWidth = this._overlayRef.getConfig().maxWidth; - styles.height = coerceCssPixelValue(boundingBoxRect.height); - styles.top = coerceCssPixelValue(boundingBoxRect.top); - styles.bottom = coerceCssPixelValue(boundingBoxRect.bottom); styles.width = coerceCssPixelValue(boundingBoxRect.width); - styles.left = coerceCssPixelValue(boundingBoxRect.left); - styles.right = coerceCssPixelValue(boundingBoxRect.right); + styles.height = coerceCssPixelValue(boundingBoxRect.height); + styles.top = coerceCssPixelValue(boundingBoxRect.top) || 'auto'; + styles.bottom = coerceCssPixelValue(boundingBoxRect.bottom) || 'auto'; + styles.left = coerceCssPixelValue(boundingBoxRect.left) || 'auto'; + styles.right = coerceCssPixelValue(boundingBoxRect.right) || 'auto'; // Push the pane content towards the proper direction. if (position.overlayX === 'center') { diff --git a/src/cdk/overlay/position/global-position-strategy.spec.ts b/src/cdk/overlay/position/global-position-strategy.spec.ts index 7897d619beb2..9d83675b063f 100644 --- a/src/cdk/overlay/position/global-position-strategy.spec.ts +++ b/src/cdk/overlay/position/global-position-strategy.spec.ts @@ -12,6 +12,7 @@ import { describe('GlobalPositonStrategy', () => { let overlayRef: OverlayRef; let injector: Injector; + let portal: ComponentPortal; beforeEach(() => { injector = TestBed.inject(Injector); @@ -25,7 +26,7 @@ describe('GlobalPositonStrategy', () => { }); function attachOverlay(config: OverlayConfig): OverlayRef { - const portal = new ComponentPortal(BlankPortal); + portal = new ComponentPortal(BlankPortal); overlayRef = createOverlayRef(injector, config); overlayRef.attach(portal); TestBed.inject(ApplicationRef).tick(); diff --git a/src/cdk/overlay/position/global-position-strategy.ts b/src/cdk/overlay/position/global-position-strategy.ts index c174079b2b4a..2c0f11dabcc4 100644 --- a/src/cdk/overlay/position/global-position-strategy.ts +++ b/src/cdk/overlay/position/global-position-strategy.ts @@ -18,8 +18,6 @@ const wrapperClass = 'cdk-global-overlay-wrapper'; * @param injector Injector used to resolve dependencies for the strategy. */ export function createGlobalPositionStrategy(_injector: Injector): GlobalPositionStrategy { - // Note: `injector` is unused, but we may need it in - // the future which would introduce a breaking change. return new GlobalPositionStrategy(); } diff --git a/src/cdk/overlay/position/position-strategy.ts b/src/cdk/overlay/position/position-strategy.ts index 3138281fa66d..34c548d1da13 100644 --- a/src/cdk/overlay/position/position-strategy.ts +++ b/src/cdk/overlay/position/position-strategy.ts @@ -21,4 +21,10 @@ export interface PositionStrategy { /** Cleans up any DOM modifications made by the position strategy, if necessary. */ dispose(): void; + + /** + * Gets the element in the DOM after which to insert + * the overlay when it is rendered out as a popover. + */ + getPopoverInsertionPoint?(): Element | null | {type: 'parent'; element: Element}; } diff --git a/src/cdk/overlay/public-api.ts b/src/cdk/overlay/public-api.ts index 43a89dfdffea..95819b1ce4ea 100644 --- a/src/cdk/overlay/public-api.ts +++ b/src/cdk/overlay/public-api.ts @@ -11,9 +11,14 @@ export * from './position/connected-position'; export * from './scroll/index'; export * from './overlay-module'; export * from './dispatchers/index'; -export {Overlay, createOverlayRef} from './overlay'; +export {Overlay, createOverlayRef, OverlayDefaultConfig, OVERLAY_DEFAULT_CONFIG} from './overlay'; export {OverlayContainer} from './overlay-container'; -export {CdkOverlayOrigin, CdkConnectedOverlay} from './overlay-directives'; +export { + CdkOverlayOrigin, + CdkConnectedOverlay, + CdkConnectedOverlayConfig, + CDK_CONNECTED_OVERLAY_DEFAULT_CONFIG, +} from './overlay-directives'; export {FullscreenOverlayContainer} from './fullscreen-overlay-container'; export {OverlayRef, OverlaySizeConfig} from './overlay-ref'; export {ViewportRuler} from '../scrolling'; @@ -27,6 +32,7 @@ export { createGlobalPositionStrategy, } from './position/global-position-strategy'; export { + FlexibleOverlayPopoverLocation, ConnectedPosition, FlexibleConnectedPositionStrategy, FlexibleConnectedPositionStrategyOrigin, diff --git a/src/cdk/private/BUILD.bazel b/src/cdk/private/BUILD.bazel index 47ab5a1a5b2f..82598dc142ea 100644 --- a/src/cdk/private/BUILD.bazel +++ b/src/cdk/private/BUILD.bazel @@ -11,6 +11,7 @@ ng_project( assets = [":visually-hidden-styles"], deps = [ "//:node_modules/@angular/core", + "//:node_modules/@angular/platform-browser", ], ) diff --git a/src/cdk/private/inner-html.ts b/src/cdk/private/inner-html.ts new file mode 100644 index 000000000000..d7417e7daa94 --- /dev/null +++ b/src/cdk/private/inner-html.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {SecurityContext} from '@angular/core'; +import {DomSanitizer, SafeHtml} from '@angular/platform-browser'; +import {trustedHTMLFromString} from './trusted-types'; + +// !!!Note!!! this file isn't synced into g3, but is replaced with a version that uses +// internal-specific APIs. The internal version may have to be updated if the signature of +// the function changes. + +/** Sanitizes and sets the `innerHTML` of an element. */ +export function _setInnerHtml(element: HTMLElement, html: SafeHtml, sanitizer: DomSanitizer): void { + const cleanHtml = sanitizer.sanitize(SecurityContext.HTML, html); + + if (cleanHtml === null && (typeof ngDevMode === 'undefined' || ngDevMode)) { + throw new Error(`Could not sanitize HTML: ${html}`); + } + + element.innerHTML = trustedHTMLFromString(cleanHtml || '') as unknown as string; +} diff --git a/src/cdk/private/public-api.ts b/src/cdk/private/public-api.ts index daa7b818d389..195f75b68f0c 100644 --- a/src/cdk/private/public-api.ts +++ b/src/cdk/private/public-api.ts @@ -8,3 +8,4 @@ export * from './style-loader'; export * from './visually-hidden/visually-hidden'; +export {TrustedHTML, trustedHTMLFromString} from './trusted-types'; diff --git a/src/material/icon/trusted-types.ts b/src/cdk/private/trusted-types.ts similarity index 75% rename from src/material/icon/trusted-types.ts rename to src/cdk/private/trusted-types.ts index 01f75fa2640b..febe9fa717f6 100644 --- a/src/material/icon/trusted-types.ts +++ b/src/cdk/private/trusted-types.ts @@ -6,21 +6,17 @@ * found in the LICENSE file at https://angular.dev/license */ -/** - * @fileoverview - * A module to facilitate use of a Trusted Types policy internally within - * Angular Material. It lazily constructs the Trusted Types policy, providing - * helper utilities for promoting strings to Trusted Types. When Trusted Types - * are not available, strings are used as a fallback. - * @security All use of this module is security-sensitive and should go through - * security review. - */ +// A module to facilitate use of a Trusted Types policy internally within +// Angular Material. It lazily constructs the Trusted Types policy, providing +// helper utilities for promoting strings to Trusted Types. When Trusted Types +// are not available, strings are used as a fallback. +// All use of this module is security-sensitive and should go through security review. -export declare interface TrustedHTML { +export interface TrustedHTML { __brand__: 'TrustedHTML'; } -export declare interface TrustedTypePolicyFactory { +interface TrustedTypePolicyFactory { createPolicy( policyName: string, policyOptions: { @@ -29,7 +25,7 @@ export declare interface TrustedTypePolicyFactory { ): TrustedTypePolicy; } -export declare interface TrustedTypePolicy { +interface TrustedTypePolicy { createHTML(input: string): TrustedHTML; } @@ -61,7 +57,8 @@ function getPolicy(): TrustedTypePolicy | null { /** * Unsafely promote a string to a TrustedHTML, falling back to strings when * Trusted Types are not available. - * @security This is a security-sensitive function; any use of this function + * + * Important!!! This is a security-sensitive function; any use of this function * must go through security review. In particular, it must be assured that the * provided string will never cause an XSS vulnerability if used in a context * that will be interpreted as HTML by a browser, e.g. when assigning to diff --git a/src/cdk/schematics/BUILD.bazel b/src/cdk/schematics/BUILD.bazel index f646e959fcec..9aa20e413dc6 100644 --- a/src/cdk/schematics/BUILD.bazel +++ b/src/cdk/schematics/BUILD.bazel @@ -93,6 +93,7 @@ ts_project( "//:node_modules/@schematics/angular", "//:node_modules/@types/jasmine", "//:node_modules/@types/node", + "//:node_modules/parse5", "//:node_modules/typescript", "//src/cdk/schematics/testing", "//src/cdk/schematics/update-tool", diff --git a/src/cdk/testing/testbed/fake-events/event-objects.ts b/src/cdk/testing/testbed/fake-events/event-objects.ts index 5674f1a77cf2..dd3cc6b62517 100644 --- a/src/cdk/testing/testbed/fake-events/event-objects.ts +++ b/src/cdk/testing/testbed/fake-events/event-objects.ts @@ -35,7 +35,7 @@ export function createMouseEvent( bubbles: true, cancelable: true, composed: true, // Required for shadow DOM events. - view: window, + view: getEventView(), detail: 1, relatedTarget: null, screenX, @@ -85,7 +85,7 @@ export function createPointerEvent( bubbles: true, cancelable: true, composed: true, // Required for shadow DOM events. - view: window, + view: getEventView(), clientX, clientY, ...options, @@ -139,7 +139,7 @@ export function createKeyboardEvent( bubbles: true, cancelable: true, composed: true, // Required for shadow DOM events. - view: window, + view: getEventView(), keyCode, key, shiftKey: modifiers.shift, @@ -165,3 +165,13 @@ export function createFakeEvent(type: string, bubbles = false, cancelable = true function defineReadonlyEventProperty(event: Event, propertyName: string, value: any) { Object.defineProperty(event, propertyName, {get: () => value, configurable: true}); } + +/** Gets the `view` that should be passed to synthetically-created DOM events */ +function getEventView(): Window | undefined { + // Passing `window` as the `view` on for events when the environment is using jsdom + // ends up throwing `member view is not of type Window` (see #32389). Leave it as + // `undefined` for such cases. + return typeof window !== 'undefined' && window && !(window as Window & {jsdom?: unknown}).jsdom + ? window + : undefined; +} diff --git a/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html b/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html index 28b6ca833bae..212a917e6252 100644 --- a/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html +++ b/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.html @@ -2,16 +2,7 @@ Wrap (ArrowKey-only) Multi Disabled - Skip Disabled - - - Expanded Items - - @for (item of items; track item) { - {{item}} - } - - + Soft Disabled

        -

        -
        +

        This is the content for Item 1.

        @@ -40,12 +30,12 @@

        -

        -
        +

        This is the content for Item 2.

        @@ -55,12 +45,12 @@

        -

        -
        +

        This is the content for Item 3.

        @@ -69,12 +59,12 @@

        -

        -
        +

        This is the content for Item 4

        @@ -83,12 +73,12 @@

        -

        -
        +

        This is the content for Item 5

        diff --git a/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.ts b/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.ts index 8f238c915eb8..039c82777309 100644 --- a/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.ts +++ b/src/components-examples/aria/accordion/accordion-configurable/accordion-configurable-example.ts @@ -1,4 +1,4 @@ -import {Component, computed, model, Signal} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {FormControl, ReactiveFormsModule} from '@angular/forms'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatFormFieldModule} from '@angular/material/form-field'; @@ -35,8 +35,14 @@ export class AccordionConfigurableExample { wrap = new FormControl(true, {nonNullable: true}); multi = new FormControl(true, {nonNullable: true}); disabled = new FormControl(false, {nonNullable: true}); - skipDisabled = new FormControl(true, {nonNullable: true}); - expandedIds = model(['item1']); + softDisabled = new FormControl(true, {nonNullable: true}); + + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); // Example items items = ['item1', 'item2', 'item3', 'item4', 'item5']; diff --git a/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.html b/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.html index cda513b3a334..abc529f832c8 100644 --- a/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.html +++ b/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.html @@ -2,17 +2,16 @@ ngAccordionGroup class="example-accordion-group" [multiExpandable]="true" - [skipDisabled]="false" - [(value)]="expandedIds" + [softDisabled]="true" >

        -

        -
        +

        This is the content for Item 1.

        @@ -21,12 +20,12 @@

        -

        -
        +

        This is the content for Item 2. This should not be expandable if trigger is disabled.

        @@ -35,12 +34,12 @@

        -

        -
        +

        This is the content for Item 3.

        @@ -49,12 +48,12 @@

        -

        -
        +

        This is the content for Item 4

        @@ -63,12 +62,12 @@

        -

        -
        +

        This is the content for Item 5

        diff --git a/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.ts b/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.ts index f695846b5237..2194152013e0 100644 --- a/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.ts +++ b/src/components-examples/aria/accordion/accordion-disabled-focusable/accordion-disabled-focusable-example.ts @@ -1,4 +1,4 @@ -import {Component, computed, model, Signal} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,12 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionDisabledFocusableExample { - expandedIds = model([]); + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); expansionIcon(item: string): Signal { return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); diff --git a/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.html b/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.html index 5ab83008e14d..3b4a6bdd8f0f 100644 --- a/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.html +++ b/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.html @@ -2,17 +2,16 @@ ngAccordionGroup class="example-accordion-group" [multiExpandable]="true" - [skipDisabled]="true" - [(value)]="expandedIds" + [softDisabled]="false" >

        -

        -
        +

        This is the content for Item 1.

        @@ -21,12 +20,12 @@

        -

        -
        +

        This is the content for Item 2. This should not be reachable or expandable.

        @@ -35,12 +34,12 @@

        -

        -
        +

        This is the content for Item 3.

        @@ -49,12 +48,12 @@

        -

        -
        +

        This is the content for Item 4

        @@ -63,12 +62,12 @@

        -

        -
        +

        This is the content for Item 5

        diff --git a/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.ts b/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.ts index 7eb682a25c9e..696db21568d1 100644 --- a/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.ts +++ b/src/components-examples/aria/accordion/accordion-disabled-skipped/accordion-disabled-skipped-example.ts @@ -1,4 +1,4 @@ -import {Component, computed, model, Signal} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,12 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionDisabledSkippedExample { - expandedIds = model([]); + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); expansionIcon(item: string): Signal { return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); diff --git a/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.html b/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.html index c77ba9d15824..572a4d5ca697 100644 --- a/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.html +++ b/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.html @@ -1,12 +1,12 @@ -
        +

        -

        -
        +

        This is the content for Item 1.

        @@ -16,12 +16,12 @@

        -

        -
        +

        This is the content for Item 2.

        @@ -31,12 +31,12 @@

        -

        -
        +

        This is the content for Item 3.

        @@ -45,12 +45,12 @@

        -

        -
        +

        This is the content for Item 4

        @@ -59,12 +59,12 @@

        -

        -
        +

        This is the content for Item 5

        diff --git a/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.ts b/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.ts index f00ed2704457..d668c9be4e43 100644 --- a/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.ts +++ b/src/components-examples/aria/accordion/accordion-disabled/accordion-disabled-example.ts @@ -1,4 +1,4 @@ -import {Component, computed, model, Signal} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,12 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionDisabledExample { - expandedIds = model(['item1']); + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); expansionIcon(item: string): Signal { return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); diff --git a/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.html b/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.html index fdcc50516d28..b657bce20ff5 100644 --- a/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.html +++ b/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.html @@ -2,16 +2,15 @@ ngAccordionGroup class="example-accordion-group" [multiExpandable]="true" - [(value)]="expandedIds" >

        -

        -
        +

        This is the content for Item 1. Multiple items can be expanded.

        @@ -21,12 +20,12 @@

        -

        -
        +

        This is the content for Item 2.

        @@ -36,14 +35,14 @@

        -

        @@ -54,14 +53,14 @@

        -

        @@ -72,14 +71,14 @@

        -

        diff --git a/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.ts b/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.ts index 910a4bef104c..41d0b82cde32 100644 --- a/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.ts +++ b/src/components-examples/aria/accordion/accordion-multi-expansion/accordion-multi-expansion-example.ts @@ -1,4 +1,4 @@ -import {Component, computed, model, Signal} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,12 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionMultiExpansionExample { - expandedIds = model([]); + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); expansionIcon(item: string): Signal { return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); diff --git a/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.html b/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.html index 0a29a42116f1..fca03e697d10 100644 --- a/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.html +++ b/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.html @@ -2,16 +2,15 @@ ngAccordionGroup class="example-accordion-group" [multiExpandable]="false" - [(value)]="expandedIds" >

        -

        -
        +

        This is the content for Item 1.

        @@ -21,12 +20,12 @@

        -

        -
        +

        This is the content for Item 2.

        @@ -36,14 +35,14 @@

        -

        @@ -54,14 +53,14 @@

        -

        @@ -72,14 +71,14 @@

        -

        diff --git a/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.ts b/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.ts index 7804ed8a4baf..a9558c406eb3 100644 --- a/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.ts +++ b/src/components-examples/aria/accordion/accordion-single-expansion/accordion-single-expansion-example.ts @@ -1,4 +1,4 @@ -import {Component, computed, model, Signal} from '@angular/core'; +import {Component, computed, Signal, viewChildren} from '@angular/core'; import {MatIconModule} from '@angular/material/icon'; import { AccordionGroup, @@ -15,7 +15,12 @@ import { imports: [MatIconModule, AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], }) export class AccordionSingleExpansionExample { - expandedIds = model([]); + triggers = viewChildren(AccordionTrigger); + expandedIds = computed(() => + this.triggers() + .filter(t => t.expanded()) + .map(t => t.panelId()), + ); expansionIcon(item: string): Signal { return computed(() => (this.expandedIds().includes(item) ? 'expand_less' : 'expand_more')); diff --git a/src/components-examples/aria/accordion/cdk-accordion-configurable/cdk-accordion-configurable-example.html b/src/components-examples/aria/accordion/cdk-accordion-configurable/cdk-accordion-configurable-example.html index 8827996ad539..c2dcc08d4ca7 100644 --- a/src/components-examples/aria/accordion/cdk-accordion-configurable/cdk-accordion-configurable-example.html +++ b/src/components-examples/aria/accordion/cdk-accordion-configurable/cdk-accordion-configurable-example.html @@ -2,7 +2,7 @@ Wrap (ArrowKey-only) Multi Disabled - Skip Disabled + Soft Disabled Expanded Items @@ -19,18 +19,18 @@ class="example-accordion-group" [multiExpandable]="multi.value" [disabled]="disabled.value" - [skipDisabled]="skipDisabled.value" + [softDisabled]="softDisabled.value" [wrap]="wrap.value" - [(value)]="expandedIds" + [(expandedPanels)]="expandedIds" >

        -

        -
        +

        This is the content for Item 1.

        @@ -40,12 +40,12 @@

        -

        -
        +

        This is the content for Item 2.

        @@ -55,14 +55,14 @@

        -

        @@ -73,14 +73,14 @@

        -

        @@ -91,14 +91,14 @@

        -

        diff --git a/src/components-examples/aria/combobox/BUILD.bazel b/src/components-examples/aria/combobox/BUILD.bazel index 6d08b7469803..7eebc8fcaf3a 100644 --- a/src/components-examples/aria/combobox/BUILD.bazel +++ b/src/components-examples/aria/combobox/BUILD.bazel @@ -16,6 +16,7 @@ ng_project( "//src/aria/combobox", "//src/aria/listbox", "//src/aria/tree", + "//src/cdk/overlay", ], ) diff --git a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts index a2cb39aef496..5393e0af1fd3 100644 --- a/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts +++ b/src/components-examples/aria/combobox/combobox-auto-select/combobox-auto-select-example.ts @@ -46,10 +46,8 @@ export class ComboboxAutoSelectExample { afterRenderEffect(() => { const popover = this.popover()!; const combobox = this.combobox()!; - combobox.pattern.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); - - // TODO(wagnermaciel): Make this easier for developers to do. - this.listbox()?.pattern.inputs.activeItem()?.element().scrollIntoView({block: 'nearest'}); + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + this.listbox()?.scrollActiveItemIntoView(); }); } @@ -57,12 +55,12 @@ export class ComboboxAutoSelectExample { const popover = this.popover()!; const combobox = this.combobox()!; - const comboboxRect = combobox.pattern.inputs.inputEl()?.getBoundingClientRect(); + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); const popoverEl = popover.nativeElement; if (comboboxRect) { popoverEl.style.width = `${comboboxRect.width}px`; - popoverEl.style.top = `${comboboxRect.bottom}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; popoverEl.style.left = `${comboboxRect.left - 1}px`; } diff --git a/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.css b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.css new file mode 100644 index 000000000000..68d4cf47c377 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.css @@ -0,0 +1,222 @@ +.example-combobox-container { + position: relative; + width: 100%; + display: flex; + flex-direction: column; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-container:has([readonly='true']) { + width: 225px; +} + +.example-combobox-input-container { + display: flex; + position: relative; + align-items: center; + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-input { + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-input[readonly='true'] { + cursor: pointer; + padding: 0.7rem 1rem; +} + +.example-combobox-container:focus-within .example-combobox-input { + outline: 1.5px solid var(--mat-sys-primary); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--mat-sys-primary) 25%, transparent); +} + +.example-icon { + width: 24px; + height: 24px; + font-size: 20px; + display: grid; + place-items: center; + pointer-events: none; +} + +.example-search-icon { + padding: 0 0.5rem; + position: absolute; + opacity: 0.8; +} + +.example-arrow-icon { + padding: 0 0.5rem; + position: absolute; + right: 0; + opacity: 0.8; + transition: transform 0.2s ease; +} + +.example-combobox-input[aria-expanded='true'] + .example-arrow-icon { + transform: rotate(180deg); +} + +.example-combobox-input { + width: 100%; + border: none; + outline: none; + font-size: 1rem; + padding: 0.7rem 1rem 0.7rem 2.5rem; + background-color: var(--mat-sys-surface); +} + +.example-popover { + margin: 0; + padding: 0; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); + background-color: var(--mat-sys-surface); +} + +.example-listbox { + display: flex; + flex-direction: column; + overflow: auto; + max-height: 10rem; + padding: 0.5rem; + gap: 4px; +} + +.example-option { + cursor: pointer; + padding: 0.3rem 1rem; + border-radius: var(--mat-sys-corner-extra-small); + display: flex; + overflow: hidden; + flex-shrink: 0; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.example-option-text { + flex: 1; +} + +.example-checkbox-blank-icon, +.example-option[aria-selected='true'] .example-checkbox-filled-icon { + display: flex; + align-items: center; +} + +.example-checkbox-filled-icon, +.example-option[aria-selected='true'] .example-checkbox-blank-icon { + display: none; +} + +.example-checkbox-blank-icon { + opacity: 0.6; +} + +.example-selected-icon { + visibility: hidden; +} + +.example-option[aria-selected='true'] .example-selected-icon { + visibility: visible; +} + +.example-option[aria-selected='true'] { + color: var(--mat-sys-primary); + background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); +} + +.example-option:hover { + background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); +} + +.example-combobox-container:focus-within [data-active='true'] { + outline: 2px solid color-mix(in srgb, var(--mat-sys-primary) 80%, transparent); +} + +.example-tree { + padding: 10px; + overflow-x: scroll; +} + +.example-tree-item { + cursor: pointer; + list-style: none; + text-decoration: none; + display: flex; + align-items: center; + gap: 1rem; + padding: 0.3rem 1rem; +} + +li[aria-expanded='false'] + ul[role='group'] { + display: none; +} + +ul[role='group'] { + padding-inline-start: 1rem; +} + +.example-icon { + margin: 0; + width: 24px; +} + +.example-parent-icon { + transition: transform 0.2s ease; +} + +.example-tree-item[aria-expanded='true'] .example-parent-icon { + transform: rotate(90deg); +} + +.example-selected-icon { + visibility: hidden; + margin-left: auto; +} + +.example-tree-item[aria-current] .example-selected-icon, +.example-tree-item[aria-selected='true'] .example-selected-icon { + visibility: visible; +} + +.example-dialog { + position: absolute; + left: auto; + right: auto; + top: auto; + bottom: auto; + padding: 0; + border: 1px solid var(--mat-sys-outline); + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-dialog .example-combobox-input-container { + border-radius: 0; +} + +.example-dialog .example-combobox-container, +.example-dialog .example-combobox-input-container { + border: none; +} + +.example-dialog .example-combobox-input { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.example-dialog .example-combobox-container:focus-within .example-combobox-input { + outline: none; + box-shadow: none; +} + +.example-dialog .example-combobox-input-container { + border-bottom: 1px solid var(--mat-sys-outline); +} + +.example-dialog::backdrop { + opacity: 0; +} diff --git a/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.html b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.html new file mode 100644 index 000000000000..67eae7296522 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.html @@ -0,0 +1,49 @@ +
        +
        + + arrow_drop_down +
        + + + +
        + +
        + search + +
        + + +
        + @for (option of options(); track option) { +
        + {{option}} + +
        + } +
        +
        +
        +
        +
        +
        diff --git a/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.ts b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.ts new file mode 100644 index 000000000000..fc0de32bcc93 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-dialog/combobox-dialog-example.ts @@ -0,0 +1,145 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxDialog, + ComboboxInput, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + signal, + untracked, + viewChild, +} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +/** @title Combobox with a dialog popup. */ +@Component({ + selector: 'combobox-dialog-example', + templateUrl: 'combobox-dialog-example.html', + styleUrl: 'combobox-dialog-example.css', + imports: [ + ComboboxDialog, + Combobox, + ComboboxInput, + ComboboxPopupContainer, + Listbox, + Option, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComboboxDialogExample { + dialog = viewChild(ComboboxDialog); + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); + + value = signal(''); + searchString = signal(''); + + options = computed(() => + states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), + ); + + selectedStates = signal([]); + + constructor() { + afterRenderEffect(() => { + if (this.dialog() && this.combobox()?.expanded()) { + untracked(() => this.listbox()?.gotoFirst()); + this.positionDialog(); + } + }); + + afterRenderEffect(() => { + if (this.selectedStates().length > 0) { + untracked(() => this.dialog()?.close()); + this.value.set(this.selectedStates()[0]); + this.searchString.set(''); + } + }); + + afterRenderEffect(() => this.listbox()?.scrollActiveItemIntoView()); + } + + // TODO(wagnermaciel): Switch to using the CDK for positioning. + + positionDialog() { + const dialog = this.dialog()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + + const scrollY = window.scrollY; + + if (comboboxRect) { + dialog.element.style.width = `${comboboxRect.width}px`; + dialog.element.style.top = `${comboboxRect.bottom + scrollY + 4}px`; + dialog.element.style.left = `${comboboxRect.left - 1}px`; + } + } +} + +const states = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; diff --git a/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.html b/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.html new file mode 100644 index 000000000000..a791c1d1f5b4 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.html @@ -0,0 +1,33 @@ +
        +
        + search + +
        + +
        + +
        + @for (option of options(); track option) { +
        + {{option}} + +
        + } +
        +
        +
        +
        diff --git a/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.ts b/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.ts new file mode 100644 index 000000000000..bf8e2808fd40 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-disabled/combobox-disabled-example.ts @@ -0,0 +1,132 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + ElementRef, + signal, + viewChild, +} from '@angular/core'; +import {FormsModule} from '@angular/forms'; + +/** @title Disabled combobox example. */ +@Component({ + selector: 'combobox-disabled-example', + templateUrl: 'combobox-disabled-example.html', + styleUrl: '../combobox-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComboboxDisabledExample { + popover = viewChild('popover'); + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); + + searchString = signal(''); + + options = computed(() => + states.filter(state => state.toLowerCase().startsWith(this.searchString().toLowerCase())), + ); + + constructor() { + afterRenderEffect(() => { + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + + this.listbox()?.scrollActiveItemIntoView(); + }); + } + + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; + + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; + } + + popover.nativeElement.showPopover(); + } +} + +const states = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; diff --git a/src/components-examples/aria/combobox/combobox-examples.css b/src/components-examples/aria/combobox/combobox-examples.css index 54e96b72490d..266f47980eb6 100644 --- a/src/components-examples/aria/combobox/combobox-examples.css +++ b/src/components-examples/aria/combobox/combobox-examples.css @@ -1,23 +1,35 @@ .example-combobox-container { position: relative; - width: 300px; + width: 100%; display: flex; - overflow: hidden; flex-direction: column; border: 1px solid var(--mat-sys-outline); border-radius: var(--mat-sys-corner-extra-small); } -.example-combobox-container:has(.example-combobox-input[aria-expanded='true']) { - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; +.example-combobox-container:has([readonly='true']:not([aria-disabled='true'])) { + width: 200px; } .example-combobox-input-container { display: flex; - overflow: hidden; position: relative; align-items: center; + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-input { + border-radius: var(--mat-sys-corner-extra-small); +} + +.example-combobox-input[readonly='true']:not([aria-disabled='true']) { + cursor: pointer; + padding: 0.7rem 1rem; +} + +.example-combobox-container:focus-within .example-combobox-input { + outline: 1.5px solid var(--mat-sys-primary); + box-shadow: 0 0 0 4px color-mix(in srgb, var(--mat-sys-primary) 25%, transparent); } .example-icon { @@ -35,6 +47,18 @@ opacity: 0.8; } +.example-arrow-icon { + padding: 0 0.5rem; + position: absolute; + right: 0; + opacity: 0.8; + transition: transform 0.2s ease; +} + +.example-combobox-input[aria-expanded='true'] + .example-arrow-icon { + transform: rotate(180deg); +} + .example-combobox-input { width: 100%; border: none; @@ -48,8 +72,7 @@ margin: 0; padding: 0; border: 1px solid var(--mat-sys-outline); - border-bottom-right-radius: var(--mat-sys-corner-extra-small); - border-bottom-left-radius: var(--mat-sys-corner-extra-small); + border-radius: var(--mat-sys-corner-extra-small); background-color: var(--mat-sys-surface); } @@ -59,6 +82,7 @@ overflow: auto; max-height: 10rem; padding: 0.5rem; + gap: 4px; } .example-option { @@ -70,6 +94,26 @@ flex-shrink: 0; align-items: center; justify-content: space-between; + gap: 1rem; +} + +.example-option-text { + flex: 1; +} + +.example-checkbox-blank-icon, +.example-option[aria-selected='true'] .example-checkbox-filled-icon { + display: flex; + align-items: center; +} + +.example-checkbox-filled-icon, +.example-option[aria-selected='true'] .example-checkbox-blank-icon { + display: none; +} + +.example-checkbox-blank-icon { + opacity: 0.6; } .example-selected-icon { @@ -80,26 +124,17 @@ visibility: visible; } -.example-option[inert], -.example-tree-item[inert] { - display: none; +.example-option[aria-selected='true'] { + color: var(--mat-sys-primary); + background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); +} + +.example-option:hover { + background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); } .example-combobox-container:focus-within [data-active='true'] { - background: color-mix( - in srgb, - var(--mat-sys-on-surface) calc(var(--mat-sys-focus-state-layer-opacity) * 100%), - transparent - ); -} - -.example-combobox-container:focus-within [data-active='true'][aria-selected='true'] { - background: color-mix( - in srgb, - var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), - transparent - ); - color: var(--mat-sys-primary); + outline: 2px solid color-mix(in srgb, var(--mat-sys-primary) 80%, transparent); } .example-tree { @@ -147,3 +182,8 @@ ul[role='group'] { .example-tree-item[aria-selected='true'] .example-selected-icon { visibility: visible; } + +.example-combobox-container:has([aria-disabled='true']) { + opacity: 0.4; + cursor: default; +} diff --git a/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts b/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts index 074310e88c1f..86f8ef5e07cf 100644 --- a/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts +++ b/src/components-examples/aria/combobox/combobox-highlight/combobox-highlight-example.ts @@ -55,10 +55,9 @@ export class ComboboxHighlightExample { afterRenderEffect(() => { const popover = this.popover()!; const combobox = this.combobox()!; - combobox.pattern.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); - // TODO(wagnermaciel): Make this easier for developers to do. - this.listbox()?.pattern.inputs.activeItem()?.element().scrollIntoView({block: 'nearest'}); + this.listbox()?.scrollActiveItemIntoView(); }); } @@ -66,12 +65,12 @@ export class ComboboxHighlightExample { const popover = this.popover()!; const combobox = this.combobox()!; - const comboboxRect = combobox.pattern.inputs.inputEl()?.getBoundingClientRect(); + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); const popoverEl = popover.nativeElement; if (comboboxRect) { popoverEl.style.width = `${comboboxRect.width}px`; - popoverEl.style.top = `${comboboxRect.bottom}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; popoverEl.style.left = `${comboboxRect.left - 1}px`; } diff --git a/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts b/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts index 1493d28eb0b2..4075b3cdae62 100644 --- a/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts +++ b/src/components-examples/aria/combobox/combobox-manual/combobox-manual-example.ts @@ -55,10 +55,9 @@ export class ComboboxManualExample { afterRenderEffect(() => { const popover = this.popover()!; const combobox = this.combobox()!; - combobox.pattern.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); - // TODO(wagnermaciel): Make this easier for developers to do. - this.listbox()?.pattern.inputs.activeItem()?.element().scrollIntoView({block: 'nearest'}); + this.listbox()?.scrollActiveItemIntoView(); }); } @@ -66,12 +65,12 @@ export class ComboboxManualExample { const popover = this.popover()!; const combobox = this.combobox()!; - const comboboxRect = combobox.pattern.inputs.inputEl()?.getBoundingClientRect(); + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); const popoverEl = popover.nativeElement; if (comboboxRect) { popoverEl.style.width = `${comboboxRect.width}px`; - popoverEl.style.top = `${comboboxRect.bottom}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; popoverEl.style.left = `${comboboxRect.left - 1}px`; } diff --git a/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.html b/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.html new file mode 100644 index 000000000000..0bffcd456559 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.html @@ -0,0 +1,26 @@ +
        +
        + {{ displayValue() }} + + arrow_drop_down +
        + + + +
        +
        + @for (label of labels; track label.value) { +
        + {{label.icon}} + {{label.value}} + check +
        + } +
        +
        +
        +
        +
        diff --git a/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.ts b/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.ts new file mode 100644 index 000000000000..ef1b9a3a4e3d --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-readonly-disabled/combobox-readonly-disabled-example.ts @@ -0,0 +1,94 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + signal, + viewChild, + viewChildren, +} from '@angular/core'; +import {OverlayModule} from '@angular/cdk/overlay'; + +/** @title Disabled readonly combobox. */ +@Component({ + selector: 'combobox-readonly-disabled-example', + templateUrl: 'combobox-readonly-disabled-example.html', + styleUrl: '../select-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComboboxReadonlyDisabledExample { + /** The string that is displayed in the combobox. */ + displayValue = signal(''); + + /** The combobox listbox popup. */ + listbox = viewChild>(Listbox); + + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** The labels that are available for selection. */ + labels = [ + {value: 'Important', icon: 'label'}, + {value: 'Starred', icon: 'star'}, + {value: 'Work', icon: 'work'}, + {value: 'Personal', icon: 'person'}, + {value: 'To Do', icon: 'checklist'}, + {value: 'Later', icon: 'schedule'}, + {value: 'Read', icon: 'menu_book'}, + {value: 'Travel', icon: 'flight'}, + ]; + + constructor() { + // Updates the display value when the listbox values change. + afterRenderEffect(() => { + const values = this.listbox()?.values() || []; + if (values.length === 0) { + this.displayValue.set('Select a label'); + } else if (values.length === 1) { + this.displayValue.set(values[0]); + } else { + this.displayValue.set(`${values[0]} + ${values.length - 1} more`); + } + }); + + // Scrolls to the active item when the active option changes. + // The slight delay here is to ensure animations are done before scrolling. + afterRenderEffect(() => { + const option = this.options().find(opt => opt.active()); + setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); + }); + + // Resets the listbox scroll position when the combobox is closed. + afterRenderEffect(() => { + if (!this.combobox()?.expanded()) { + setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); + } + }); + } +} diff --git a/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.html b/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.html new file mode 100644 index 000000000000..07a5bc78a105 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.html @@ -0,0 +1,26 @@ +
        +
        + {{ displayValue() }} + + arrow_drop_down +
        + + + +
        +
        + @for (label of labels; track label.value) { +
        + {{label.icon}} + {{label.value}} + check +
        + } +
        +
        +
        +
        +
        diff --git a/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.ts b/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.ts new file mode 100644 index 000000000000..1e6a6eb4c6d1 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-readonly-multiselect/combobox-readonly-multiselect-example.ts @@ -0,0 +1,94 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + signal, + viewChild, + viewChildren, +} from '@angular/core'; + +/** @title Readonly multiselectable combobox. */ +@Component({ + selector: 'combobox-readonly-multiselect-example', + templateUrl: 'combobox-readonly-multiselect-example.html', + styleUrl: '../select-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComboboxReadonlyMultiselectExample { + /** The string that is displayed in the combobox. */ + displayValue = signal(''); + + /** The combobox listbox popup. */ + listbox = viewChild>(Listbox); + + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** The labels that are available for selection. */ + labels = [ + {value: 'Important', icon: 'label'}, + {value: 'Starred', icon: 'star'}, + {value: 'Work', icon: 'work'}, + {value: 'Personal', icon: 'person'}, + {value: 'To Do', icon: 'checklist'}, + {value: 'Later', icon: 'schedule'}, + {value: 'Read', icon: 'menu_book'}, + {value: 'Travel', icon: 'flight'}, + ]; + + constructor() { + // Updates the display value when the listbox values change. + afterRenderEffect(() => { + const values = this.listbox()?.values() || []; + if (values.length === 0) { + this.displayValue.set('Select a label'); + } else if (values.length === 1) { + this.displayValue.set(values[0]); + } else { + this.displayValue.set(`${values[0]} + ${values.length - 1} more`); + } + }); + + // Scrolls to the active item when the active option changes. + // The slight delay here is to ensure animations are done before scrolling. + afterRenderEffect(() => { + const option = this.options().find(opt => opt.active()); + setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); + }); + + // Resets the listbox scroll position when the combobox is closed. + afterRenderEffect(() => { + if (!this.combobox()?.expanded()) { + setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); + } + }); + } +} diff --git a/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html new file mode 100644 index 000000000000..eb10faa22c2b --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.html @@ -0,0 +1,26 @@ +
        +
        + {{ displayValue() }} + + arrow_drop_down +
        + + + +
        +
        + @for (label of labels; track label.value) { +
        + {{label.icon}} + {{label.value}} + check +
        + } +
        +
        +
        +
        +
        diff --git a/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts new file mode 100644 index 000000000000..264d221920b4 --- /dev/null +++ b/src/components-examples/aria/combobox/combobox-readonly/combobox-readonly-example.ts @@ -0,0 +1,89 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + signal, + viewChild, + viewChildren, +} from '@angular/core'; +import {OverlayModule} from '@angular/cdk/overlay'; + +/** @title Readonly combobox. */ +@Component({ + selector: 'combobox-readonly-example', + templateUrl: 'combobox-readonly-example.html', + styleUrl: '../select-examples.css', + imports: [ + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComboboxReadonlyExample { + /** The string that is displayed in the combobox. */ + displayValue = signal(''); + + /** The combobox listbox popup. */ + listbox = viewChild>(Listbox); + + /** The options available in the listbox. */ + options = viewChildren>(Option); + + /** A reference to the ng aria combobox. */ + combobox = viewChild>(Combobox); + + /** The labels that are available for selection. */ + labels = [ + {value: 'Important', icon: 'label'}, + {value: 'Starred', icon: 'star'}, + {value: 'Work', icon: 'work'}, + {value: 'Personal', icon: 'person'}, + {value: 'To Do', icon: 'checklist'}, + {value: 'Later', icon: 'schedule'}, + {value: 'Read', icon: 'menu_book'}, + {value: 'Travel', icon: 'flight'}, + ]; + + constructor() { + // Updates the display value when the listbox values change. + afterRenderEffect(() => { + const values = this.listbox()?.values() || []; + const displayValue = values.length ? values[0] : 'Select a label'; + this.displayValue.set(displayValue); + }); + + // Scrolls to the active item when the active option changes. + // The slight delay here is to ensure animations are done before scrolling. + afterRenderEffect(() => { + const option = this.options().find(opt => opt.active()); + setTimeout(() => option?.element.scrollIntoView({block: 'nearest'}), 50); + }); + + // Resets the listbox scroll position when the combobox is closed. + afterRenderEffect(() => { + if (!this.combobox()?.expanded()) { + setTimeout(() => this.listbox()?.element.scrollTo(0, 0), 150); + } + }); + } +} diff --git a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.html b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.html index 4ef3639366a4..fe7a5fa8af62 100644 --- a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.html +++ b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.html @@ -34,6 +34,7 @@ [parent]="parent" [value]="node.name" [label]="node.name" + [selectable]="!node.children" #treeItem="ngTreeItem" class="example-tree-item example-selectable example-stateful" > diff --git a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts index 2ccc5c197a82..d35c710b8dfe 100644 --- a/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts +++ b/src/components-examples/aria/combobox/combobox-tree-auto-select/combobox-tree-auto-select-example.ts @@ -81,10 +81,8 @@ export class ComboboxTreeAutoSelectExample { afterRenderEffect(() => { const popover = this.popover()!; const combobox = this.combobox()!; - combobox.pattern.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); - - // TODO(wagnermaciel): Make this easier for developers to do. - this.tree()?.pattern.inputs.activeItem()?.element().scrollIntoView({block: 'nearest'}); + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + this.tree()?.scrollActiveItemIntoView(); }); } @@ -92,12 +90,12 @@ export class ComboboxTreeAutoSelectExample { const popover = this.popover()!; const combobox = this.combobox()!; - const comboboxRect = combobox.pattern.inputs.inputEl()?.getBoundingClientRect(); + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); const popoverEl = popover.nativeElement; if (comboboxRect) { popoverEl.style.width = `${comboboxRect.width}px`; - popoverEl.style.top = `${comboboxRect.bottom}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; popoverEl.style.left = `${comboboxRect.left - 1}px`; } diff --git a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.html b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.html index 7315770195e8..14f4f5999c41 100644 --- a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.html +++ b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.html @@ -34,6 +34,7 @@ [parent]="parent" [value]="node.name" [label]="node.name" + [selectable]="!node.children" #treeItem="ngTreeItem" class="example-tree-item example-selectable example-stateful" > diff --git a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts index 013aaec24480..aefd4830422e 100644 --- a/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts +++ b/src/components-examples/aria/combobox/combobox-tree-highlight/combobox-tree-highlight-example.ts @@ -81,10 +81,8 @@ export class ComboboxTreeHighlightExample { afterRenderEffect(() => { const popover = this.popover()!; const combobox = this.combobox()!; - combobox.pattern.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); - - // TODO(wagnermaciel): Make this easier for developers to do. - this.tree()?.pattern.inputs.activeItem()?.element().scrollIntoView({block: 'nearest'}); + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + this.tree()?.scrollActiveItemIntoView(); }); } @@ -92,12 +90,12 @@ export class ComboboxTreeHighlightExample { const popover = this.popover()!; const combobox = this.combobox()!; - const comboboxRect = combobox.pattern.inputs.inputEl()?.getBoundingClientRect(); + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); const popoverEl = popover.nativeElement; if (comboboxRect) { popoverEl.style.width = `${comboboxRect.width}px`; - popoverEl.style.top = `${comboboxRect.bottom}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; popoverEl.style.left = `${comboboxRect.left - 1}px`; } diff --git a/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.html b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.html index 994e956f3535..41c67e436ab0 100644 --- a/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.html +++ b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.html @@ -34,6 +34,7 @@ [parent]="parent" [value]="node.name" [label]="node.name" + [selectable]="!node.children" #treeItem="ngTreeItem" class="example-tree-item example-selectable example-stateful" > diff --git a/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts index 3e2d77bda2ae..5a7967f17d9f 100644 --- a/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts +++ b/src/components-examples/aria/combobox/combobox-tree-manual/combobox-tree-manual-example.ts @@ -81,10 +81,8 @@ export class ComboboxTreeManualExample { afterRenderEffect(() => { const popover = this.popover()!; const combobox = this.combobox()!; - combobox.pattern.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); - - // TODO(wagnermaciel): Make this easier for developers to do. - this.tree()?.pattern.inputs.activeItem()?.element().scrollIntoView({block: 'nearest'}); + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + this.tree()?.scrollActiveItemIntoView(); }); } @@ -92,12 +90,12 @@ export class ComboboxTreeManualExample { const popover = this.popover()!; const combobox = this.combobox()!; - const comboboxRect = combobox.pattern.inputs.inputEl()?.getBoundingClientRect(); + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); const popoverEl = popover.nativeElement; if (comboboxRect) { popoverEl.style.width = `${comboboxRect.width}px`; - popoverEl.style.top = `${comboboxRect.bottom}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; popoverEl.style.left = `${comboboxRect.left - 1}px`; } diff --git a/src/components-examples/aria/combobox/index.ts b/src/components-examples/aria/combobox/index.ts index ca1e1f5c5dde..e19dfd098d76 100644 --- a/src/components-examples/aria/combobox/index.ts +++ b/src/components-examples/aria/combobox/index.ts @@ -1,6 +1,13 @@ +export {ComboboxDialogExample} from './combobox-dialog/combobox-dialog-example'; export {ComboboxManualExample} from './combobox-manual/combobox-manual-example'; export {ComboboxAutoSelectExample} from './combobox-auto-select/combobox-auto-select-example'; export {ComboboxHighlightExample} from './combobox-highlight/combobox-highlight-example'; +export {ComboboxDisabledExample} from './combobox-disabled/combobox-disabled-example'; + +export {ComboboxReadonlyExample} from './combobox-readonly/combobox-readonly-example'; +export {ComboboxReadonlyMultiselectExample} from './combobox-readonly-multiselect/combobox-readonly-multiselect-example'; +export {ComboboxReadonlyDisabledExample} from './combobox-readonly-disabled/combobox-readonly-disabled-example'; + export {ComboboxTreeManualExample} from './combobox-tree-manual/combobox-tree-manual-example'; export {ComboboxTreeAutoSelectExample} from './combobox-tree-auto-select/combobox-tree-auto-select-example'; export {ComboboxTreeHighlightExample} from './combobox-tree-highlight/combobox-tree-highlight-example'; diff --git a/src/components-examples/aria/combobox/select-examples.css b/src/components-examples/aria/combobox/select-examples.css new file mode 100644 index 000000000000..2e4dde0cd4d1 --- /dev/null +++ b/src/components-examples/aria/combobox/select-examples.css @@ -0,0 +1,120 @@ +.example-select { + display: flex; + position: relative; + align-items: center; + color: var(--mat-sys-on-primary); + font-size: var(--mat-sys-label-large); + background-color: var(--mat-sys-primary); + border-radius: var(--mat-sys-corner-extra-large); +} + +.example-select:has([ngComboboxInput][aria-disabled='true']) { + opacity: 0.6; + cursor: default; +} + +.example-select:hover { + background-color: color-mix(in srgb, var(--mat-sys-primary) 90%, transparent); +} + +[ngComboboxInput] { + opacity: 0; + cursor: pointer; + padding: 0 3rem; + height: 3rem; + border: none; +} + +[ngCombobox]:focus-within .example-select { + outline-offset: 2px; + outline: 2px solid var(--mat-sys-primary); +} + +.example-combobox-text { + left: 2rem; + position: absolute; +} + +.example-arrow { + right: 1rem; + position: absolute; + pointer-events: none; + transition: transform 150ms ease-in-out; +} + +[ngComboboxInput][aria-expanded='true'] ~ .example-arrow { + transform: rotate(180deg); +} + +.example-popup-container { + width: 100%; + padding: 0.5rem; + margin-top: 8px; + border-radius: var(--mat-sys-corner-large); + background-color: var(--mat-sys-surface-container); + + max-height: 13rem; + opacity: 1; + visibility: visible; + transition: max-height 150ms ease-out, visibility 0s, opacity 25ms ease-out; +} + +[ngListbox] { + gap: 4px; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; +} + +[ngCombobox]:has([ngComboboxInput][aria-expanded='false']) .example-popup-container { + max-height: 0; + opacity: 0; + visibility: hidden; + transition: max-height 150ms ease-in, visibility 0s 150ms, opacity 150ms ease-in; +} + +[ngCombobox]:has([ngComboboxInput][aria-expanded='true']) [ngListbox] { + display: flex; +} + +[ngOption] { + display: flex; + cursor: pointer; + align-items: center; + padding: 0 1rem; + min-height: 3rem; + color: var(--mat-sys-on-surface); + font-size: var(--mat-sys-label-large); + border-radius: var(--mat-sys-corner-extra-large); +} + +[ngOption]:hover { + background-color: color-mix(in srgb, var(--mat-sys-on-surface) 5%, transparent); +} + +[ngOption][data-active='true'] { + background-color: color-mix(in srgb, var(--mat-sys-on-surface) 10%, transparent); +} + +[ngOption][aria-selected='true'] { + color: var(--mat-sys-primary); + background-color: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); +} + +.example-option-icon { + padding-right: 1rem; +} + +.example-option-check, +.example-option-icon { + font-size: var(--mat-sys-label-large); +} + +[ngOption]:not([aria-selected='true']) .example-option-check { + display: none; +} + +.example-option-text { + flex: 1; +} diff --git a/src/components-examples/aria/grid/BUILD.bazel b/src/components-examples/aria/grid/BUILD.bazel index 2f11f723254e..b16742afd8b4 100644 --- a/src/components-examples/aria/grid/BUILD.bazel +++ b/src/components-examples/aria/grid/BUILD.bazel @@ -17,6 +17,7 @@ ng_project( "//src/material/checkbox", "//src/material/form-field", "//src/material/icon", + "//src/material/input", "//src/material/select", ], ) diff --git a/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.css b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.css new file mode 100644 index 000000000000..9499799e4509 --- /dev/null +++ b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.css @@ -0,0 +1,75 @@ +.example-grid-container { + max-width: fit-content; +} + +.example-grid-calendar-debug { + padding-bottom: 12px; +} + +.example-grid-calendar-control { + display: flex; + justify-content: space-around; + align-items: center; +} + +.example-grid-calendar-control-button { + background-color: transparent; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + width: 80px; + border: 2px solid color-mix(in srgb, var(--mat-sys-outline) 40%, transparent); + cursor: pointer; +} + +.example-grid-calendar-control-button:hover, +.example-grid-calendar-control-button:focus { + background: color-mix( + in srgb, + var(--mat-sys-on-surface) calc(var(--mat-sys-hover-state-layer-opacity) * 100%), + transparent + ); +} + +.example-calendar-cell { + width: 50px; + height: 50px; + text-align: center; + vertical-align: middle; +} + +.example-calendar-cell[aria-disabled='true'] { + color: color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent); +} + +.example-calendar-day-button { + width: 90%; + height: 90%; + border-radius: 50%; + border: 0; + background-color: transparent; +} + +.example-calendar-day-button:hover { + background: color-mix( + in srgb, + var(--mat-sys-on-surface) calc(var(--mat-sys-hover-state-layer-opacity) * 100%), + transparent + ); +} + +[data-active='true'] .example-calendar-day-button { + outline: 3px dashed var(--mat-sys-outline); + outline-offset: 3px; +} + +[data-active='true']:focus-within .example-calendar-day-button { + outline: 3px dashed var(--mat-sys-tertiary); + outline-offset: 3px; +} + +[aria-selected='true'] .example-calendar-day-button { + color: var(--mat-sys-on-primary); + background: color-mix(in srgb, var(--mat-sys-primary) 80%, transparent); +} diff --git a/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.html b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.html new file mode 100644 index 000000000000..63d85bd843f0 --- /dev/null +++ b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.html @@ -0,0 +1,61 @@ +
        +
        Selected Date: {{displayActiveDate()}}
        +
        + +
        {{monthYearLabel()}}
        + +
        + + + + + @for (day of weekdays(); track day.long) { + + } + + + + @for (week of weeks(); track week) { + + @if ($first) { + @for (day of daysFromPrevMonth(); track day) { + + } + } + + @for (day of week; track day) { + + } + + @if ($last && week.length < 7) { + @for (day of [].constructor(7 - week.length); track $index) { + + } + } + + } +
        + {{day.long}} + +
        {{day}} + + {{$index + 1}}
        +
        diff --git a/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.ts b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.ts new file mode 100644 index 000000000000..7f1e844e48b6 --- /dev/null +++ b/src/components-examples/aria/grid/grid-calendar/grid-calendar-example.ts @@ -0,0 +1,143 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ +import { + inject, + Component, + WritableSignal, + signal, + Signal, + computed, + untracked, + afterRenderEffect, +} from '@angular/core'; +import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core'; +import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; + +const DAYS_PER_WEEK = 7; + +interface CalendarCell { + displayName: string; + ariaLabel: string; + date: D; + selected: WritableSignal; +} + +/** @title Grid Calendar. */ +@Component({ + selector: 'grid-calendar-example', + templateUrl: 'grid-calendar-example.html', + styleUrls: ['../grid-common.css', 'grid-calendar-example.css'], + imports: [Grid, GridRow, GridCell, GridCellWidget], +}) +export class GridCalendarExample { + private readonly _dateAdapter = inject>(DateAdapter, {optional: true})!; + private readonly _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!; + private readonly _firstWeekOffset: Signal = computed(() => { + const firstOfMonth = this._dateAdapter.createDate( + this._dateAdapter.getYear(this.viewMonth()), + this._dateAdapter.getMonth(this.viewMonth()), + 1, + ); + + return ( + (DAYS_PER_WEEK + + this._dateAdapter.getDayOfWeek(firstOfMonth) - + this._dateAdapter.getFirstDayOfWeek()) % + DAYS_PER_WEEK + ); + }); + + private readonly _activeDate: WritableSignal = signal(this._dateAdapter.today()); + readonly displayActiveDate: Signal = computed(() => + this._dateAdapter.format(this._activeDate(), this._dateFormats.display), + ); + + readonly monthYearLabel: Signal = computed(() => + this._dateAdapter + .format(this.viewMonth(), this._dateFormats.display.monthYearLabel) + .toLocaleUpperCase(), + ); + readonly prevMonthNumDays: Signal = computed(() => + this._dateAdapter.getNumDaysInMonth(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)), + ); + readonly daysFromPrevMonth: Signal = computed(() => { + const days: number[] = []; + for (let i = this._firstWeekOffset() - 1; i >= 0; i--) { + days.push(this.prevMonthNumDays() - i); + } + return days; + }); + readonly viewMonth: WritableSignal = signal(this._dateAdapter.today()); + readonly weekdays: Signal<{long: string; narrow: string}[]> = computed(() => { + const firstDayOfWeek = this._dateAdapter.getFirstDayOfWeek(); + const narrowWeekdays = this._dateAdapter.getDayOfWeekNames('narrow'); + const longWeekdays = this._dateAdapter.getDayOfWeekNames('long'); + + // Rotate the labels for days of the week based on the configured first day of the week. + const weekdays = longWeekdays.map((long, i) => { + return {long, narrow: narrowWeekdays[i]}; + }); + return weekdays.slice(firstDayOfWeek).concat(weekdays.slice(0, firstDayOfWeek)); + }); + readonly weeks: Signal[][]> = computed(() => + this._createWeekCells(this.viewMonth()), + ); + + readonly scrolledUp = signal(false); + readonly scrolledDown = signal(false); + + constructor() { + afterRenderEffect(() => { + for (const day of this.weeks().flat()) { + if (day.selected()) { + this._activeDate.set(day.date); + return; + } + } + }); + } + + nextMonth(): void { + this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), 1)); + } + + prevMonth(): void { + this.viewMonth.set(this._dateAdapter.addCalendarMonths(this.viewMonth(), -1)); + } + + private _createWeekCells(viewMonth: D): CalendarCell[][] { + const daysInMonth = this._dateAdapter.getNumDaysInMonth(viewMonth); + const dateNames = this._dateAdapter.getDateNames(); + const weeks: CalendarCell[][] = [[]]; + for (let i = 0, cell = this._firstWeekOffset(); i < daysInMonth; i++, cell++) { + if (cell == DAYS_PER_WEEK) { + weeks.push([]); + cell = 0; + } + const date = this._dateAdapter.createDate( + this._dateAdapter.getYear(viewMonth), + this._dateAdapter.getMonth(viewMonth), + i + 1, + ); + const ariaLabel = this._dateAdapter.format(date, this._dateFormats.display.dateA11yLabel); + + weeks[weeks.length - 1].push({ + displayName: dateNames[i], + ariaLabel, + date, + selected: signal( + this._dateAdapter.compareDate( + date, + untracked(() => this._activeDate()), + ) === 0, + ), + }); + } + return weeks; + } +} diff --git a/src/components-examples/aria/grid/grid-common.css b/src/components-examples/aria/grid/grid-common.css index 90b84b899be3..67c1e65f27f6 100644 --- a/src/components-examples/aria/grid/grid-common.css +++ b/src/components-examples/aria/grid/grid-common.css @@ -1,23 +1,53 @@ -.example-grid-controls { +.example-controls { + display: flex; + flex-wrap: wrap; align-items: center; + gap: 16px; margin-bottom: 16px; } -.example-grid-controls-select { +.example-controls-select { padding: 8px; } -.example-grid-cell[data-active='true'] { +.example-cell[data-active='true'] { outline: 3px dashed var(--mat-sys-outline); outline-offset: 4px; } -.example-grid-cell[data-active='true']:focus-within, -[aria-activedescendant]:focus-within .example-grid-cell[data-active='true'] { +.example-grid:focus-within .example-cell[data-anchor='true'][data-active='false'] { + outline: 3px dashed var(--mat-sys-outline); + outline-offset: 3px; +} + +.example-cell[data-active='true']:focus-within, +[aria-activedescendant]:focus-within .example-cell[data-active='true'] { outline: 3px dashed var(--mat-sys-tertiary); outline-offset: 3px; } -.example-grid-cell[aria-disabled='true'] { +.example-cell.example-cell.example-cell:has([data-active-control='widget']) { + outline: 3px dashed var(--mat-sys-on-error-container); + outline-offset: 3px; +} + +.example-cell[aria-disabled='true'] { border: 1px dashed var(--mat-sys-outline-variant); } + +.example-cell:hover { + background: color-mix( + in srgb, + var(--mat-sys-on-surface) calc(var(--mat-sys-hover-state-layer-opacity) * 100%), + transparent + ); +} + +.example-cell[aria-selected='true'] { + background: color-mix( + in srgb, + var(--mat-sys-primary) calc(var(--mat-sys-pressed-state-layer-opacity) * 100%), + transparent + ); + color: var(--mat-sys-primary); +} diff --git a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.css b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.css index 9d620e264ec7..0db7bab4cffb 100644 --- a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.css +++ b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.css @@ -1,5 +1,6 @@ .example-grid-container { display: flex; + align-items: flex-start; } .example-grid { @@ -14,8 +15,12 @@ ); } -.example-grid-cell { +.example-cell { height: 50px; width: 50px; border: 1px solid; } + +.example-grid-shortcuts { + margin-left: 12px; +} diff --git a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html index ae284cf0d6a6..c56e1e022c87 100644 --- a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html +++ b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.html @@ -1,93 +1,57 @@ - - - - - - +
        + Disabled + Soft Disabled + Enable Selection + Multi + Range Selection -
        - - - - -
        - Disabled - - Skip Disabled - - Enable Selection -
        - - Row Wrap - - No Wrap - Loop - Continuous - - - - - Col Wrap - - No Wrap - Loop - Continuous - - - - - Focus Strategy - - Roving - Active Descendant - - -
        + + Row Wrap + + No Wrap + Loop + Continuous + + + + Col Wrap + + No Wrap + Loop + Continuous + + + + Focus Strategy + + Roving + Active Descendant + + + + Selection Strategy + + Explicit + Follow Focus + + +
        @for (row of gridData; track row) { @for (cell of row; track cell) {
        -
          -
        • - -
        • -
        • Home: first cell in the row
        • -
        • End: last cell in the row
        • -
        • Crtl + Home: very first cell
        • -
        • Ctrl + End: very last cell
        • -
        • Shift + Space: select a row
        • -
        • Ctrl + Space: select a col
        • -
        • Shift + Arrow: expand selection
        • -
        • Ctrl + A: select all
        • -
        • - Internal coords: ({{grid.pattern.gridBehavior.focusBehavior.activeCoords().row - - - - - +
          +
          + + Example Options + + Row Span + Col Span + Disabled + + +
          +
          + Navigation Keys +
            +
          • Arrow keys navigation
          • +
          • Home: first cell in the row
          • +
          • End: last cell in the row
          • +
          • Crtl + Home: very first cell
          • +
          • Ctrl + End: very last cell
          • +
          + @if (enableSelection.value) { + Selection Keys +
            + @if (this.selectionMode === 'explicit') { +
          • Enter/Space: select/deselect cell (explicit)
          • + } + @if (this.selectionMode === 'follow') { +
          • Selection change on navigation (follow focus)
          • + } + @if (multi.value && enableRangeSelection.value) { +
          • Shift + Arrow: expand selection
          • +
          • Ctrl + A: select all or deselect all (if all selected)
          • +
          • Shift + Space: select a row
          • +
          • Ctrl + Space: select a col
          • +
          • Shift + Home: range select from first cell in the row
          • +
          • Shift + End: range select to last cell in the row
          • +
          • Shift + Ctrl + Home: range select from very first cell
          • +
          • Shift + Ctrl + End: range select to very last cell
          • + } +
          - }}, {{grid.pattern.gridBehavior.focusBehavior.activeCoords().col}}) -
        • -
        + Mouse Selection +
          + @if (this.selectionMode === 'explicit') { +
        • Click: select/deselect cell (explicit)
        • + } + @if (this.selectionMode === 'follow') { +
        • Click: select cell (follow focus)
        • + @if (multi.value) { +
        • Ctrl + Click: add/remove a cell
        • + } + } + @if (multi.value && enableRangeSelection.value) { + @if (this.selectionMode === 'explicit') { +
        • Drag: add a new range selection (explicit)
        • + } + @if (this.selectionMode === 'follow') { +
        • Drag: start a new range selection (follow focus)
        • + } +
        • Shift + Drag: update the current range selection
        • +
        • Ctrl + Drag: add a new range selection
        • + } +
        + } + diff --git a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts index 882acb789ea2..59611bda697c 100644 --- a/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts +++ b/src/components-examples/aria/grid/grid-configurable/grid-configurable-example.ts @@ -5,12 +5,12 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ -import {Component} from '@angular/core'; +import {Component, model, afterRenderEffect, computed} from '@angular/core'; import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatSelectModule} from '@angular/material/select'; -import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; +import {Grid, GridRow, GridCell} from '@angular/aria/grid'; interface Cell { rowSpan?: number; @@ -30,7 +30,13 @@ function randomDisabled(): boolean { return disabledChanceTable[randomIndex]; } -function generateValidGrid(rowCount: number, colCount: number): Cell[][] { +function generateValidGrid( + rowCount: number, + colCount: number, + randomRowSpan: boolean = true, + randomColSpan: boolean = true, + randomDisable: boolean = true, +): Cell[][] { const grid: Cell[][] = []; const visitedCoords = new Set(); for (let r = 0; r < rowCount; r++) { @@ -40,14 +46,14 @@ function generateValidGrid(rowCount: number, colCount: number): Cell[][] { continue; } - const rowSpan = Math.min(randomSpan(), rowCount - r); - const maxColSpan = Math.min(randomSpan(), colCount - c); + const rowSpan = randomRowSpan ? Math.min(randomSpan(), rowCount - r) : 1; + const maxColSpan = randomColSpan ? Math.min(randomSpan(), colCount - c) : 1; let colSpan = 1; while (colSpan < maxColSpan) { if (visitedCoords.has(`${r},${c + colSpan}`)) break; colSpan += 1; } - const disabled = randomDisabled(); + const disabled = randomDisable ? randomDisabled() : false; row.push({ rowSpan, @@ -69,10 +75,8 @@ function generateValidGrid(rowCount: number, colCount: number): Cell[][] { /** @title Configurable Grid. */ @Component({ selector: 'grid-configurable-example', - exportAs: 'GridConfigurableExample', templateUrl: 'grid-configurable-example.html', styleUrls: ['../grid-common.css', 'grid-configurable-example.css'], - standalone: true, imports: [ FormsModule, ReactiveFormsModule, @@ -82,21 +86,42 @@ function generateValidGrid(rowCount: number, colCount: number): Cell[][] { Grid, GridRow, GridCell, - GridCellWidget, ], }) export class GridConfigurableExample { rowWrap: 'continuous' | 'loop' | 'nowrap' = 'loop'; colWrap: 'continuous' | 'loop' | 'nowrap' = 'continuous'; focusMode: 'roving' | 'activedescendant' = 'roving'; + selectionMode: 'explicit' | 'follow' = 'follow'; + exampleOptions = model(['rowSpan', 'colSpan', 'disable']); + + randomRowSpan = computed(() => this.exampleOptions().includes('rowSpan')); + randomColSpan = computed(() => this.exampleOptions().includes('colSpan')); + randomDisable = computed(() => this.exampleOptions().includes('disable')); disabled = new FormControl(false, {nonNullable: true}); - skipDisabled = new FormControl(true, {nonNullable: true}); + softDisabled = new FormControl(false, {nonNullable: true}); enableSelection = new FormControl(false, {nonNullable: true}); + multi = new FormControl(false, {nonNullable: true}); + enableRangeSelection = new FormControl(false, {nonNullable: true}); - gridData: Cell[][] = generateValidGrid(10, 10); + gridData: Cell[][] = generateValidGrid( + 10, + 10, + this.randomRowSpan(), + this.randomColSpan(), + this.randomDisable(), + ); - regenerateGrid() { - this.gridData = generateValidGrid(10, 10); + constructor() { + afterRenderEffect(() => { + this.gridData = generateValidGrid( + 10, + 10, + this.randomRowSpan(), + this.randomColSpan(), + this.randomDisable(), + ); + }); } } diff --git a/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.css b/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.css index 1501c190d051..fd4360b0882d 100644 --- a/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.css +++ b/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.css @@ -1,6 +1,5 @@ .example-pill-list-container { padding: 10px; - border: 1px solid; max-width: 800px; } @@ -21,7 +20,7 @@ .example-pill-label { border: 1px solid; border-right-width: 0; - border-radius: 1rem 0 0 1rem; + border-radius: 2rem 0 0 2rem; padding: 4px 4px 4px 12px; display: flex; align-items: center; @@ -32,7 +31,7 @@ .example-pill-action { border: 1px solid; - border-radius: 0 1rem 1rem 0; + border-radius: 0 2rem 2rem 0; padding: 4px 12px 4px 8px; display: flex; align-items: center; diff --git a/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.html b/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.html index 87cf65f0f218..798c623fba5e 100644 --- a/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.html +++ b/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.html @@ -6,10 +6,10 @@
        @for (item of sortedItems(); track item; let i = $index) {
        -
        +
        {{item.label}}
        -
        +
        diff --git a/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.ts b/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.ts index 0559ff99605f..5e7c45b336f0 100644 --- a/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.ts +++ b/src/components-examples/aria/grid/grid-pill-list/grid-pill-list-example.ts @@ -16,10 +16,8 @@ import {toSignal} from '@angular/core/rxjs-interop'; /** @title Grid Pill List. */ @Component({ selector: 'grid-pill-list-example', - exportAs: 'GridPillListExample', templateUrl: 'grid-pill-list-example.html', styleUrls: ['../grid-common.css', 'grid-pill-list-example.css'], - standalone: true, imports: [ Grid, GridRow, diff --git a/src/components-examples/aria/grid/grid-table/grid-chips.css b/src/components-examples/aria/grid/grid-table/grid-chips.css new file mode 100644 index 000000000000..46770ac72001 --- /dev/null +++ b/src/components-examples/aria/grid/grid-table/grid-chips.css @@ -0,0 +1,45 @@ +.example-grid-chips { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.example-grid-chips, +.example-chip-icon { + font-size: 0.825rem; +} + +.example-grid-chip { + border-radius: 8px; + display: flex; + align-items: center; + border: 1px solid; + padding: 4px; +} + +.example-grid-chip:hover, +.example-grid-chip:focus-within { + background: color-mix(in srgb, var(--mat-sys-secondary) 10%, transparent); +} + +.example-chip-label { + padding: 0 8px; +} + +.example-chip-remove-button { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 50%; + border: none; + background: color-mix(in srgb, var(--mat-sys-secondary) 80%, transparent); + color: var(--mat-sys-on-secondary); + height: 1rem; + width: 1rem; +} + +.example-chip-empty { + color: color-mix(in srgb, var(--mat-sys-on-background) 60%, transparent); +} diff --git a/src/components-examples/aria/grid/grid-table/grid-chips.html b/src/components-examples/aria/grid/grid-table/grid-chips.html new file mode 100644 index 000000000000..c3bd582a1d2f --- /dev/null +++ b/src/components-examples/aria/grid/grid-table/grid-chips.html @@ -0,0 +1,25 @@ +
        + @for (value of values(); track $index) { +
        +
        + {{value}} +
        +
        + +
        +
        + } + @if (values().length === 0) { +
        +
        No Tag
        +
        + } +
        diff --git a/src/components-examples/aria/grid/grid-table/grid-chips.ts b/src/components-examples/aria/grid/grid-table/grid-chips.ts new file mode 100644 index 000000000000..e0e421ba5d58 --- /dev/null +++ b/src/components-examples/aria/grid/grid-table/grid-chips.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ +import {Component, model, viewChild, input} from '@angular/core'; +import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; + +/** @title Grid Chips. */ +@Component({ + selector: 'grid-chips', + exportAs: 'gridChips', + templateUrl: 'grid-chips.html', + styleUrls: ['grid-chips.css'], + imports: [Grid, GridRow, GridCell, GridCellWidget], +}) +export class GridChips { + readonly firstCell = viewChild(GridCell); + readonly values = model([]); + + readonly tabindex = input(); + + removeItem(index: number) { + this.values.update(items => [...items.slice(0, index), ...items.slice(index + 1)]); + } + + focus(): void { + this.firstCell()?.element.focus(); + } +} diff --git a/src/components-examples/aria/grid/grid-table/grid-table-example.css b/src/components-examples/aria/grid/grid-table/grid-table-example.css new file mode 100644 index 000000000000..6ab620cd0b4a --- /dev/null +++ b/src/components-examples/aria/grid/grid-table/grid-table-example.css @@ -0,0 +1,73 @@ +.example-table { + border-spacing: 12px; +} + +.example-table td, +.example-table th { + height: 40px; +} + +.example-header-row th:not(:first-of-type) { + background: color-mix(in srgb, var(--mat-sys-secondary) 80%, transparent); + color: var(--mat-sys-on-secondary); +} + +.example-edit-icon { + color: color-mix(in srgb, var(--mat-sys-on-background) 60%, transparent); +} + +.example-row .example-edit-icon { + opacity: 0; + padding: 0 8px; +} + +.example-row:hover .example-edit-icon, +.example-row:focus-within .example-edit-icon { + opacity: 1; +} + +.example-cell-summary [role='button'] { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + outline: none; +} + +.example-cell-summary mat-form-field { + width: 100%; + height: 100%; +} + +.example-cell-checkbox { + width: 40px; +} + +.example-cell-assignee [role='button'] { + outline: none; +} + +.example-cell-tags { + display: flex; + gap: 10px; + align-items: baseline; + height: 100%; + max-width: 500px; +} + +.example-cell-tag-input { + border: none; + border-bottom: 1px dotted; + font-size: 0.825rem; + background-color: transparent; + max-width: 100px; + height: 24px; + padding: 0; + margin: 0; +} + +.example-hidden { + display: none; +} diff --git a/src/components-examples/aria/grid/grid-table/grid-table-example.html b/src/components-examples/aria/grid/grid-table/grid-table-example.html new file mode 100644 index 000000000000..83c38145fab8 --- /dev/null +++ b/src/components-examples/aria/grid/grid-table/grid-table-example.html @@ -0,0 +1,100 @@ + + + + + + + + + + + @for (task of tasks(); track task) { + + + + + + + } + +
        + + SummaryAssigneeTags
        + + +
        + {{task.summary()}} + + + + +
        +
        +
        + + + @for (e of employees; track e) { + {{e}} + } + + +
        +
        +
        +
        + +
        + +
        +
        diff --git a/src/components-examples/aria/grid/grid-table/grid-table-example.ts b/src/components-examples/aria/grid/grid-table/grid-table-example.ts new file mode 100644 index 000000000000..35337e74688f --- /dev/null +++ b/src/components-examples/aria/grid/grid-table/grid-table-example.ts @@ -0,0 +1,138 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ +import {Component, computed, Signal, signal, WritableSignal} from '@angular/core'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {MatCheckboxModule} from '@angular/material/checkbox'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatInputModule} from '@angular/material/input'; +import {MatSelectModule} from '@angular/material/select'; +import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; +import {GridChips} from './grid-chips'; + +interface TaskRow { + selected: WritableSignal; + summary: WritableSignal; + assignee: WritableSignal; + tags: WritableSignal; +} + +/** @title Table Grid. */ +@Component({ + selector: 'grid-table-example', + templateUrl: 'grid-table-example.html', + styleUrls: ['../grid-common.css', 'grid-table-example.css'], + imports: [ + FormsModule, + ReactiveFormsModule, + MatCheckboxModule, + MatFormFieldModule, + MatSelectModule, + MatInputModule, + Grid, + GridRow, + GridCell, + GridCellWidget, + GridChips, + ], +}) +export class GridTableExample { + readonly employees = [ + 'Sudo Sloth', + 'Copy-Pasta Capybara', + 'Rubber Duck', + 'Caffeinated Owl', + 'Patch Monkey', + ]; + + readonly allSelected: Signal = computed(() => this.tasks().every(t => t.selected())); + + readonly partiallySelected: Signal = computed( + () => this.tasks().some(t => t.selected()) && !this.allSelected(), + ); + + readonly tempInput: WritableSignal = signal(''); + + readonly tasks: WritableSignal = signal(this._createRows()); + + constructor() {} + + startEdit( + event: KeyboardEvent | FocusEvent | undefined, + task: TaskRow, + inputEl: HTMLInputElement, + ): void { + this.tempInput.set(task.summary()); + inputEl.focus(); + + if (!(event instanceof KeyboardEvent)) return; + + // Start editing with a alphanumeric character. + if (event.key.length === 1) { + this.tempInput.set(event.key); + } + } + + onClickEdit(widget: GridCellWidget, task: TaskRow, inputEl: HTMLInputElement) { + if (widget.isActivated()) return; + + widget.activate(); + setTimeout(() => this.startEdit(undefined, task, inputEl)); + } + + completeEdit(event: KeyboardEvent | FocusEvent | undefined, task: TaskRow): void { + if (!(event instanceof KeyboardEvent)) { + return; + } + if (event.key === 'Enter') { + task.summary.set(this.tempInput()); + } + } + + updateSelection(checked: boolean): void { + this.tasks().forEach(t => t.selected.set(checked)); + } + + addTag(event: KeyboardEvent | FocusEvent | undefined, task: TaskRow, inputEl: HTMLInputElement) { + if (event instanceof KeyboardEvent && event.key === 'Enter') { + const value = inputEl.value; + if (value.length > 0) { + task.tags.set([...task.tags(), value]); + } + } + inputEl.value = ''; + } + + private _createRows(): TaskRow[] { + return [ + { + selected: signal(false), + summary: signal('Repairing the coffee machine'), + assignee: signal('Caffeinated Owl'), + tags: signal(['P0']), + }, + { + selected: signal(false), + summary: signal('Burying technical debt in the backyard so no one finds it'), + assignee: signal(''), + tags: signal(['tech-debt', 'P3']), + }, + { + selected: signal(false), + summary: signal('Hibernating under the standing desk until the outage is resolved'), + assignee: signal(''), + tags: signal([]), + }, + { + selected: signal(false), + summary: signal('Hunting down the Uber Eats driver who got lost in the lobby'), + assignee: signal('Sudo Sloth'), + tags: signal(['lunch']), + }, + ]; + } +} diff --git a/src/components-examples/aria/grid/index.ts b/src/components-examples/aria/grid/index.ts index 713726afa907..d9fb3bd38aae 100644 --- a/src/components-examples/aria/grid/index.ts +++ b/src/components-examples/aria/grid/index.ts @@ -1,2 +1,4 @@ export {GridConfigurableExample} from './grid-configurable/grid-configurable-example'; export {GridPillListExample} from './grid-pill-list/grid-pill-list-example'; +export {GridCalendarExample} from './grid-calendar/grid-calendar-example'; +export {GridTableExample} from './grid-table/grid-table-example'; diff --git a/src/components-examples/aria/listbox/listbox-active-descendant/listbox-active-descendant-example.html b/src/components-examples/aria/listbox/listbox-active-descendant/listbox-active-descendant-example.html index 3380d0d60568..407824fa79b1 100644 --- a/src/components-examples/aria/listbox/listbox-active-descendant/listbox-active-descendant-example.html +++ b/src/components-examples/aria/listbox/listbox-active-descendant/listbox-active-descendant-example.html @@ -15,7 +15,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.html b/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.html index 8d467ca37354..e979b8fb8fd3 100644 --- a/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.html +++ b/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.html @@ -3,7 +3,7 @@ Multi Disabled Readonly - Skip Disabled + Soft Disabled Selection @@ -52,12 +52,12 @@
          {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.ts b/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.ts index 167f579884f6..23dcf34e6c4b 100644 --- a/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.ts +++ b/src/components-examples/aria/listbox/listbox-configurable/listbox-configurable-example.ts @@ -33,7 +33,7 @@ export class ListboxConfigurableExample { multi = new FormControl(true, {nonNullable: true}); disabled = new FormControl(false, {nonNullable: true}); readonly = new FormControl(false, {nonNullable: true}); - skipDisabled = new FormControl(true, {nonNullable: true}); + softDisabled = new FormControl(false, {nonNullable: true}); fruits = [ 'Apple', diff --git a/src/components-examples/aria/listbox/listbox-disabled-focusable/listbox-disabled-focusable-example.html b/src/components-examples/aria/listbox/listbox-disabled-focusable/listbox-disabled-focusable-example.html index 9fa9e9e4fe8c..a8a4dcf9a179 100644 --- a/src/components-examples/aria/listbox/listbox-disabled-focusable/listbox-disabled-focusable-example.html +++ b/src/components-examples/aria/listbox/listbox-disabled-focusable/listbox-disabled-focusable-example.html @@ -2,7 +2,7 @@
            @@ -16,7 +16,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-disabled-skipped/listbox-disabled-skipped-example.html b/src/components-examples/aria/listbox/listbox-disabled-skipped/listbox-disabled-skipped-example.html index cc3c97192a77..3976e66360cd 100644 --- a/src/components-examples/aria/listbox/listbox-disabled-skipped/listbox-disabled-skipped-example.html +++ b/src/components-examples/aria/listbox/listbox-disabled-skipped/listbox-disabled-skipped-example.html @@ -2,7 +2,7 @@
              @@ -16,7 +16,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-disabled/listbox-disabled-example.html b/src/components-examples/aria/listbox/listbox-disabled/listbox-disabled-example.html index d7b24b44f1a7..cf5f32184b0a 100644 --- a/src/components-examples/aria/listbox/listbox-disabled/listbox-disabled-example.html +++ b/src/components-examples/aria/listbox/listbox-disabled/listbox-disabled-example.html @@ -14,7 +14,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-horizontal/listbox-horizontal-example.html b/src/components-examples/aria/listbox/listbox-horizontal/listbox-horizontal-example.html index f67f0e5669d0..b58b0f4f1024 100644 --- a/src/components-examples/aria/listbox/listbox-horizontal/listbox-horizontal-example.html +++ b/src/components-examples/aria/listbox/listbox-horizontal/listbox-horizontal-example.html @@ -15,7 +15,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-multi-select-follow-focus/listbox-multi-select-follow-focus-example.html b/src/components-examples/aria/listbox/listbox-multi-select-follow-focus/listbox-multi-select-follow-focus-example.html index 5ee2efc48739..6d6d9ca6e133 100644 --- a/src/components-examples/aria/listbox/listbox-multi-select-follow-focus/listbox-multi-select-follow-focus-example.html +++ b/src/components-examples/aria/listbox/listbox-multi-select-follow-focus/listbox-multi-select-follow-focus-example.html @@ -16,7 +16,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-multi-select/listbox-multi-select-example.html b/src/components-examples/aria/listbox/listbox-multi-select/listbox-multi-select-example.html index 2268a7e97a10..7fba7b051aae 100644 --- a/src/components-examples/aria/listbox/listbox-multi-select/listbox-multi-select-example.html +++ b/src/components-examples/aria/listbox/listbox-multi-select/listbox-multi-select-example.html @@ -16,7 +16,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-readonly/listbox-readonly-example.html b/src/components-examples/aria/listbox/listbox-readonly/listbox-readonly-example.html index 13a76475f7e2..4a412f4fa767 100644 --- a/src/components-examples/aria/listbox/listbox-readonly/listbox-readonly-example.html +++ b/src/components-examples/aria/listbox/listbox-readonly/listbox-readonly-example.html @@ -15,7 +15,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-rtl-horizontal/listbox-rtl-horizontal-example.html b/src/components-examples/aria/listbox/listbox-rtl-horizontal/listbox-rtl-horizontal-example.html index 1fd90d8f30e7..1377a5e9aa8b 100644 --- a/src/components-examples/aria/listbox/listbox-rtl-horizontal/listbox-rtl-horizontal-example.html +++ b/src/components-examples/aria/listbox/listbox-rtl-horizontal/listbox-rtl-horizontal-example.html @@ -15,7 +15,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-single-select-follow-focus/listbox-single-select-follow-focus-example.html b/src/components-examples/aria/listbox/listbox-single-select-follow-focus/listbox-single-select-follow-focus-example.html index 69fcccf66db6..4d2d93d5ffd7 100644 --- a/src/components-examples/aria/listbox/listbox-single-select-follow-focus/listbox-single-select-follow-focus-example.html +++ b/src/components-examples/aria/listbox/listbox-single-select-follow-focus/listbox-single-select-follow-focus-example.html @@ -16,7 +16,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/listbox/listbox-single-select/listbox-single-select-example.html b/src/components-examples/aria/listbox/listbox-single-select/listbox-single-select-example.html index b26b3bad68fb..41694d4a096a 100644 --- a/src/components-examples/aria/listbox/listbox-single-select/listbox-single-select-example.html +++ b/src/components-examples/aria/listbox/listbox-single-select/listbox-single-select-example.html @@ -16,7 +16,7 @@ #option="ngOption" > {{ fruit }} diff --git a/src/components-examples/aria/menu/BUILD.bazel b/src/components-examples/aria/menu/BUILD.bazel index 3b006c241567..5827e26e1c7b 100644 --- a/src/components-examples/aria/menu/BUILD.bazel +++ b/src/components-examples/aria/menu/BUILD.bazel @@ -12,6 +12,7 @@ ng_project( deps = [ "//:node_modules/@angular/core", "//src/aria/menu", + "//src/cdk/a11y", ], ) diff --git a/src/components-examples/aria/menu/index.ts b/src/components-examples/aria/menu/index.ts index fd502b060d90..1aacab273cf2 100644 --- a/src/components-examples/aria/menu/index.ts +++ b/src/components-examples/aria/menu/index.ts @@ -1,4 +1,8 @@ export {MenuBarExample} from './menu-bar/menu-bar-example'; +export {MenuBarRTLExample} from './menu-bar-rtl/menu-bar-rtl-example'; +export {MenuBarDisabledExample} from './menu-bar-disabled/menu-bar-disabled-example'; export {MenuContextExample} from './menu-context/menu-context-example'; export {MenuTriggerExample} from './menu-trigger/menu-trigger-example'; +export {MenuTriggerDisabledExample} from './menu-trigger-disabled/menu-trigger-disabled-example'; export {MenuStandaloneExample} from './menu-standalone/menu-standalone-example'; +export {MenuStandaloneDisabledExample} from './menu-standalone-disabled/menu-standalone-disabled-example'; diff --git a/src/components-examples/aria/menu/menu-bar-disabled/menu-bar-disabled-example.html b/src/components-examples/aria/menu/menu-bar-disabled/menu-bar-disabled-example.html new file mode 100644 index 000000000000..5a97e99fa8cb --- /dev/null +++ b/src/components-examples/aria/menu/menu-bar-disabled/menu-bar-disabled-example.html @@ -0,0 +1,324 @@ +
              +
              File
              + +
              + +
              + article + New + ⌘N +
              + +
              + folder + Open + ⌘O +
              + +
              + file_copy + Make a copy +
              + + + +
              + person_add + Share + arrow_right +
              + +
              + +
              + person_add + Share with others +
              + +
              + public + Publish to web +
              +
              +
              + +
              + download + Download +
              + +
              + print + Print +
              + + + +
              + edit + Rename +
              + +
              + delete + Move to trash +
              +
              +
              + +
              Edit
              + +
              + +
              + undo + Undo + ⌘Z +
              + +
              + redo + Redo + ⌘Y +
              + + + +
              + content_cut + Cut + ⌘X +
              + +
              + content_copy + Copy + ⌘C +
              + +
              + content_paste + Paste + ⌘V +
              + + + +
              + find_replace + Find and replace + ā‡§āŒ˜H +
              +
              +
              + +
              View
              + +
              + +
              + check + Show print layout +
              + +
              + check + Show ruler +
              + + + +
              + Zoom in + ⌘+ +
              + +
              + Zoom out + ⌘- +
              + + + +
              + Full screen +
              +
              +
              + +
              Insert
              + +
              + +
              + image + Image + arrow_right +
              + +
              + +
              + upload + Upload from computer +
              + +
              + search + Search the web +
              + +
              + link + By URL +
              +
              +
              + +
              + table_chart + Table +
              + +
              + insert_chart + Chart + arrow_right +
              + +
              + +
              + bar_chart + Bar +
              + +
              + insert_chart + Column +
              + +
              + show_chart + Line +
              + +
              + pie_chart + Pie +
              +
              +
              + +
              + horizontal_rule + Horizontal line +
              +
              +
              + +
              Format
              + +
              + +
              + format_bold + Text + arrow_right +
              + +
              + +
              + format_bold + Bold + ⌘B +
              + +
              + format_italic + Italic + ⌘I +
              + +
              + format_underlined + Underline + ⌘U +
              + +
              + strikethrough_s + Strikethrough + ā‡§āŒ˜X +
              + + + +
              + Size + arrow_right +
              + +
              + +
              + Increase font size + ā‡§āŒ˜. +
              + +
              + Decrease font size + ā‡§āŒ˜, +
              +
              +
              +
              +
              + +
              + format_align_justify + Paragraph styles + arrow_right +
              + +
              + +
              Normal text
              +
              Heading 1
              +
              Heading 2
              +
              +
              + +
              + format_indent_increase + Align & indent + arrow_right +
              + +
              + +
              + format_align_left + Align left +
              + +
              + format_align_center + Align center +
              + +
              + format_align_right + Align right +
              + +
              + format_align_justify + Justify +
              +
              +
              +
              +
              +
              diff --git a/src/components-examples/aria/menu/menu-bar-disabled/menu-bar-disabled-example.ts b/src/components-examples/aria/menu/menu-bar-disabled/menu-bar-disabled-example.ts new file mode 100644 index 000000000000..9d8b7ce12e9e --- /dev/null +++ b/src/components-examples/aria/menu/menu-bar-disabled/menu-bar-disabled-example.ts @@ -0,0 +1,29 @@ +import {Component} from '@angular/core'; +import { + SimpleMenu, + SimpleMenuBar, + SimpleMenuBarItem, + SimpleMenuItem, + SimpleMenuItemIcon, + SimpleMenuItemShortcut, + SimpleMenuItemText, +} from '../simple-menu'; +import {MenuContent} from '@angular/aria/menu'; + +/** @title Disabled menu bar example. */ +@Component({ + selector: 'menu-bar-disabled-example', + templateUrl: 'menu-bar-disabled-example.html', + styleUrl: '../menu-example.css', + imports: [ + SimpleMenu, + SimpleMenuBar, + SimpleMenuBarItem, + SimpleMenuItem, + SimpleMenuItemIcon, + SimpleMenuItemText, + SimpleMenuItemShortcut, + MenuContent, + ], +}) +export class MenuBarDisabledExample {} diff --git a/src/components-examples/aria/menu/menu-bar-rtl/menu-bar-rtl-example.html b/src/components-examples/aria/menu/menu-bar-rtl/menu-bar-rtl-example.html new file mode 100644 index 000000000000..b80f45019a79 --- /dev/null +++ b/src/components-examples/aria/menu/menu-bar-rtl/menu-bar-rtl-example.html @@ -0,0 +1,329 @@ +
              +
              File
              + +
              + +
              + article + New + ⌘N +
              + +
              + folder + Open + ⌘O +
              + +
              + file_copy + Make a copy +
              + + + +
              + person_add + Share + arrow_left +
              + +
              + +
              + person_add + Share with others +
              + +
              + public + Publish to web +
              +
              +
              + +
              + download + Download +
              + +
              + print + Print +
              + + + +
              + edit + Rename +
              + +
              + delete + Move to trash +
              +
              +
              + +
              Edit
              + +
              + +
              + undo + Undo + ⌘Z +
              + +
              + redo + Redo + ⌘Y +
              + + + +
              + content_cut + Cut + ⌘X +
              + +
              + content_copy + Copy + ⌘C +
              + +
              + content_paste + Paste + ⌘V +
              + + + +
              + find_replace + Find and replace + ā‡§āŒ˜H +
              +
              +
              + +
              View
              + +
              + +
              + check + Show print layout +
              + +
              + check + Show ruler +
              + + + +
              + Zoom in + ⌘+ +
              + +
              + Zoom out + ⌘- +
              + + + +
              + Full screen +
              +
              +
              + +
              Insert
              + +
              + +
              + image + Image + arrow_left +
              + +
              + +
              + upload + Upload from computer +
              + +
              + search + Search the web +
              + +
              + link + By URL +
              +
              +
              + +
              + table_chart + Table +
              + +
              + insert_chart + Chart + arrow_left +
              + +
              + +
              + bar_chart + Bar +
              + +
              + insert_chart + Column +
              + +
              + show_chart + Line +
              + +
              + pie_chart + Pie +
              +
              +
              + +
              + horizontal_rule + Horizontal line +
              +
              +
              + +
              Format
              + +
              + +
              + format_bold + Text + arrow_left +
              + +
              + +
              + format_bold + Bold + ⌘B +
              + +
              + format_italic + Italic + ⌘I +
              + +
              + format_underlined + Underline + ⌘U +
              + +
              + strikethrough_s + Strikethrough + ā‡§āŒ˜X +
              + + + +
              + Size + arrow_left +
              + +
              + +
              + Increase font size + ā‡§āŒ˜. +
              + +
              + Decrease font size + ā‡§āŒ˜, +
              +
              +
              +
              +
              + +
              + format_align_justify + Paragraph styles + arrow_left +
              + +
              + +
              Normal text
              +
              Heading 1
              +
              Heading 2
              +
              +
              + +
              + format_indent_increase + Align & indent + arrow_left +
              + +
              + +
              + format_align_left + Align left +
              + +
              + format_align_center + Align center +
              + +
              + format_align_right + Align right +
              + +
              + format_align_justify + Justify +
              +
              +
              +
              +
              +
              diff --git a/src/components-examples/aria/menu/menu-bar-rtl/menu-bar-rtl-example.ts b/src/components-examples/aria/menu/menu-bar-rtl/menu-bar-rtl-example.ts new file mode 100644 index 000000000000..d516adbd1e11 --- /dev/null +++ b/src/components-examples/aria/menu/menu-bar-rtl/menu-bar-rtl-example.ts @@ -0,0 +1,31 @@ +import {Dir} from '@angular/cdk/bidi'; +import {Component} from '@angular/core'; +import { + SimpleMenu, + SimpleMenuBar, + SimpleMenuBarItem, + SimpleMenuItem, + SimpleMenuItemIcon, + SimpleMenuItemShortcut, + SimpleMenuItemText, +} from '../simple-menu'; +import {MenuContent} from '@angular/aria/menu'; + +/** @title Menu bar RTL example. */ +@Component({ + selector: 'menu-bar-rtl-example', + templateUrl: 'menu-bar-rtl-example.html', + styleUrl: '../menu-example.css', + imports: [ + Dir, + SimpleMenu, + SimpleMenuBar, + SimpleMenuBarItem, + SimpleMenuItem, + SimpleMenuItemIcon, + SimpleMenuItemText, + SimpleMenuItemShortcut, + MenuContent, + ], +}) +export class MenuBarRTLExample {} diff --git a/src/components-examples/aria/menu/menu-bar/menu-bar-example.html b/src/components-examples/aria/menu/menu-bar/menu-bar-example.html index 5b772ddf3e91..6d2f4406768b 100644 --- a/src/components-examples/aria/menu/menu-bar/menu-bar-example.html +++ b/src/components-examples/aria/menu/menu-bar/menu-bar-example.html @@ -1,300 +1,329 @@ -
              -
              File
              - -
              -
              - article - New - ⌘N -
              - -
              - folder - Open - ⌘O -
              - -
              - file_copy - Make a copy -
              - - - -
              - person_add - Share - arrow_right -
              - -
              -
              - person_add - Share with others -
              - -
              - public - Publish to web -
              -
              - -
              - download - Download -
              - -
              - print - Print -
              - - - -
              - edit - Rename -
              - -
              - delete - Move to trash -
              -
              +
              +
              File
              + +
              + +
              + article + New + ⌘N +
              -
              Edit
              - -
              -
              - undo - Undo - ⌘Z -
              - -
              - redo - Redo - ⌘Y -
              - - - -
              - content_cut - Cut - ⌘X -
              - -
              - content_copy - Copy - ⌘C -
              - -
              - content_paste - Paste - ⌘V -
              - - - -
              - find_replace - Find and replace - ā‡§āŒ˜H -
              -
              +
              + folder + Open + ⌘O +
              -
              View
              +
              + file_copy + Make a copy +
              -
              -
              - check - Show print layout -
              + + +
              + person_add + Share + arrow_right +
              -
              - check - Show ruler -
              +
              + +
              + person_add + Share with others +
              + +
              + public + Publish to web +
              +
              +
              - +
              + download + Download +
              -
              - Zoom in - ⌘+ -
              +
              + print + Print +
              -
              - Zoom out - ⌘- -
              + - +
              + edit + Rename +
              -
              - Full screen -
              +
              + delete + Move to trash +
              +
              -
              Insert
              +
              Edit
              -
              -
              - image - Image - arrow_right -
              +
              + +
              + undo + Undo + ⌘Z +
              -
              -
              - upload - Upload from computer +
              + redo + Redo + ⌘Y
              -
              - search - Search the web + + +
              + content_cut + Cut + ⌘X
              -
              - link - By URL +
              + content_copy + Copy + ⌘C
              -
              -
              - table_chart - Table -
              +
              + content_paste + Paste + ⌘V +
              -
              - insert_chart - Chart - arrow_right -
              + -
              -
              - bar_chart - Bar +
              + find_replace + Find and replace + ā‡§āŒ˜H
              + +
              -
              - insert_chart - Column -
              +
              View
              -
              - show_chart - Line +
              + +
              + check + Show print layout
              -
              - pie_chart - Pie +
              + check + Show ruler
              -
              - -
              - horizontal_rule - Horizontal line -
              -
              -
              Format
              + -
              -
              - format_bold - Text - arrow_right -
              +
              + Zoom in + ⌘+ +
              -
              -
              - format_bold - Bold - ⌘B +
              + Zoom out + ⌘-
              -
              - format_italic - Italic - ⌘I + + +
              + Full screen
              + +
              + +
              Insert
              -
              - format_underlined - Underline - ⌘U +
              + +
              + image + Image + arrow_right
              -
              - strikethrough_s - Strikethrough - ā‡§āŒ˜X +
              + +
              + upload + Upload from computer +
              + +
              + search + Search the web +
              + +
              + link + By URL +
              +
              - +
              + table_chart + Table +
              -
              - Size - arrow_right +
              + insert_chart + Chart + arrow_right
              -
              -
              - Increase font size - ā‡§āŒ˜. -
              +
              + +
              + bar_chart + Bar +
              + +
              + insert_chart + Column +
              + +
              + show_chart + Line +
              + +
              + pie_chart + Pie +
              +
              +
              -
              - Decrease font size - ā‡§āŒ˜, -
              +
              + horizontal_rule + Horizontal line
              -
              + +
              -
              - format_align_justify - Paragraph styles - arrow_right -
              +
              Format
              -
              -
              Normal text
              -
              Heading 1
              -
              Heading 2
              -
              +
              + +
              + format_bold + Text + arrow_right +
              -
              - format_indent_increase - Align & indent - arrow_right -
              +
              + +
              + format_bold + Bold + ⌘B +
              + +
              + format_italic + Italic + ⌘I +
              + +
              + format_underlined + Underline + ⌘U +
              + +
              + strikethrough_s + Strikethrough + ā‡§āŒ˜X +
              + + + +
              + Size + arrow_right +
              + +
              + +
              + Increase font size + ā‡§āŒ˜. +
              + +
              + Decrease font size + ā‡§āŒ˜, +
              +
              +
              +
              +
              -
              -
              - format_align_left - Align left +
              + format_align_justify + Paragraph styles + arrow_right
              -
              - format_align_center - Align center +
              + +
              Normal text
              +
              Heading 1
              +
              Heading 2
              +
              -
              - format_align_right - Align right +
              + format_indent_increase + Align & indent + arrow_right
              -
              - format_align_justify - Justify +
              + +
              + format_align_left + Align left +
              + +
              + format_align_center + Align center +
              + +
              + format_align_right + Align right +
              + +
              + format_align_justify + Justify +
              +
              -
              +
              diff --git a/src/components-examples/aria/menu/menu-bar/menu-bar-example.ts b/src/components-examples/aria/menu/menu-bar/menu-bar-example.ts index fd3aa5768f7d..2f8881d5b114 100644 --- a/src/components-examples/aria/menu/menu-bar/menu-bar-example.ts +++ b/src/components-examples/aria/menu/menu-bar/menu-bar-example.ts @@ -8,14 +8,13 @@ import { SimpleMenuItemShortcut, SimpleMenuItemText, } from '../simple-menu'; +import {MenuContent} from '@angular/aria/menu'; /** @title Menu bar example. */ @Component({ selector: 'menu-bar-example', - exportAs: 'MenuBarExample', templateUrl: 'menu-bar-example.html', styleUrl: '../menu-example.css', - standalone: true, imports: [ SimpleMenu, SimpleMenuBar, @@ -24,6 +23,7 @@ import { SimpleMenuItemIcon, SimpleMenuItemText, SimpleMenuItemShortcut, + MenuContent, ], }) export class MenuBarExample {} diff --git a/src/components-examples/aria/menu/menu-context/menu-context-example.html b/src/components-examples/aria/menu/menu-context/menu-context-example.html index 7505025ffd42..cf2ee2fea501 100644 --- a/src/components-examples/aria/menu/menu-context/menu-context-example.html +++ b/src/components-examples/aria/menu/menu-context/menu-context-example.html @@ -3,39 +3,43 @@
              -
              - content_cut - Cut - ⌘X -
              - -
              - content_copy - Copy - ⌘C -
              + +
              + content_cut + Cut + ⌘X +
              -
              - content_paste - Paste - arrow_right -
              +
              + content_copy + Copy + ⌘C +
              -
              -
              - Paste as plain text - āŒ˜ā‡§V +
              + content_paste + Paste + arrow_right
              -
              - Paste without formatting - āŒ˜ā‡§V +
              + +
              + Paste as plain text + āŒ˜ā‡§V +
              + +
              + Paste without formatting + āŒ˜ā‡§V +
              +
              -
              +
              diff --git a/src/components-examples/aria/menu/menu-context/menu-context-example.ts b/src/components-examples/aria/menu/menu-context/menu-context-example.ts index e265c393cf05..a484b5f3f5da 100644 --- a/src/components-examples/aria/menu/menu-context/menu-context-example.ts +++ b/src/components-examples/aria/menu/menu-context/menu-context-example.ts @@ -1,5 +1,5 @@ import {Component, viewChild} from '@angular/core'; -import {Menu} from '@angular/aria/menu'; +import {Menu, MenuContent} from '@angular/aria/menu'; import { SimpleMenu, SimpleMenuItem, @@ -11,16 +11,15 @@ import { /** @title Context menu example. */ @Component({ selector: 'menu-context-example', - exportAs: 'MenuContextExample', templateUrl: 'menu-context-example.html', styleUrl: '../menu-example.css', - standalone: true, imports: [ SimpleMenu, SimpleMenuItem, SimpleMenuItemText, SimpleMenuItemIcon, SimpleMenuItemShortcut, + MenuContent, ], }) export class MenuContextExample { @@ -38,7 +37,7 @@ export class MenuContextExample { open(event: MouseEvent) { const menu = this.menu(); - menu?.closeAll(); + menu?._pattern.closeAll(); if (menu) { event.preventDefault(); @@ -47,7 +46,7 @@ export class MenuContextExample { menu.element.style.top = `${event.clientY}px`; menu.element.style.left = `${event.clientX}px`; - setTimeout(() => menu.uiPattern.first()); + setTimeout(() => menu._pattern.first()); } } } diff --git a/src/components-examples/aria/menu/menu-example.css b/src/components-examples/aria/menu/menu-example.css index f5fb288f91ec..6aaf76c9d91a 100644 --- a/src/components-examples/aria/menu/menu-example.css +++ b/src/components-examples/aria/menu/menu-example.css @@ -22,6 +22,7 @@ } .example-menu[popover] { + left: auto; position: absolute; } @@ -42,6 +43,13 @@ border-radius: var(--mat-sys-corner-extra-small); } +.example-menu-item[aria-disabled='true'], +.example-menu-trigger[aria-disabled='true'], +.example-menu-bar-item[aria-disabled='true'] { + cursor: default; + opacity: 0.38; +} + .example-menu-heading { display: block; font-weight: bold; @@ -83,11 +91,6 @@ outline: 2px solid var(--mat-sys-primary); } -.example-menu-item[aria-disabled='true'] { - cursor: default; - opacity: 0.38; -} - .example-icon { opacity: 0.875; font-size: 1.25rem; diff --git a/src/components-examples/aria/menu/menu-standalone-disabled/menu-standalone-disabled-example.html b/src/components-examples/aria/menu/menu-standalone-disabled/menu-standalone-disabled-example.html new file mode 100644 index 000000000000..9f835ce855c4 --- /dev/null +++ b/src/components-examples/aria/menu/menu-standalone-disabled/menu-standalone-disabled-example.html @@ -0,0 +1,65 @@ +
              + + SECURITY + +
              +
              + lock_open + Change password +
              + +
              + security_key + Two-factor authentication +
              + +
              + refresh + Reset + arrow_right +
              + +
              + +
              + email + Email address +
              + +
              + phone + Phone number +
              + +
              + vpn_key + Password +
              +
              +
              +
              + + + + HELP + +
              +
              + help + Support +
              + +
              + feedback + Feedback +
              +
              + + + +
              + logout + Logout +
              +
              +
              diff --git a/src/components-examples/aria/menu/menu-standalone-disabled/menu-standalone-disabled-example.ts b/src/components-examples/aria/menu/menu-standalone-disabled/menu-standalone-disabled-example.ts new file mode 100644 index 000000000000..8de16e276329 --- /dev/null +++ b/src/components-examples/aria/menu/menu-standalone-disabled/menu-standalone-disabled-example.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; +import {Menu, MenuContent} from '@angular/aria/menu'; +import {SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText} from '../simple-menu'; + +/** + * @title Disabled standalone menu example. + */ +@Component({ + selector: 'menu-standalone-disabled-example', + templateUrl: 'menu-standalone-disabled-example.html', + styleUrl: '../menu-example.css', + imports: [Menu, MenuContent, SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText], +}) +export class MenuStandaloneDisabledExample {} diff --git a/src/components-examples/aria/menu/menu-standalone/menu-standalone-example.html b/src/components-examples/aria/menu/menu-standalone/menu-standalone-example.html index 6a3419830193..ad7fd866e21b 100644 --- a/src/components-examples/aria/menu/menu-standalone/menu-standalone-example.html +++ b/src/components-examples/aria/menu/menu-standalone/menu-standalone-example.html @@ -1,61 +1,65 @@
              - SECURITY + + SECURITY -
              -
              - lock_open - Change password -
              - -
              - security_key - Two-factor authentication -
              - -
              - refresh - Reset - arrow_right -
              +
              +
              + lock_open + Change password +
              -
              -
              - email - Email address +
              + security_key + Two-factor authentication
              -
              - phone - Phone number +
              + refresh + Reset + arrow_right
              -
              - vpn_key - Password +
              + +
              + email + Email address +
              + +
              + phone + Phone number +
              + +
              + vpn_key + Password +
              +
              -
              - + - HELP + HELP -
              -
              - help - Support -
              +
              +
              + help + Support +
              -
              - feedback - Feedback +
              + feedback + Feedback +
              -
              - + -
              - logout - Logout -
              +
              + logout + Logout +
              +
              diff --git a/src/components-examples/aria/menu/menu-standalone/menu-standalone-example.ts b/src/components-examples/aria/menu/menu-standalone/menu-standalone-example.ts index e2c12304b18a..e3cc7486db41 100644 --- a/src/components-examples/aria/menu/menu-standalone/menu-standalone-example.ts +++ b/src/components-examples/aria/menu/menu-standalone/menu-standalone-example.ts @@ -1,5 +1,5 @@ import {Component} from '@angular/core'; -import {Menu} from '@angular/aria/menu'; +import {Menu, MenuContent} from '@angular/aria/menu'; import {SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText} from '../simple-menu'; /** @@ -7,10 +7,8 @@ import {SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText} from */ @Component({ selector: 'menu-standalone-example', - exportAs: 'MenuStandaloneExample', templateUrl: 'menu-standalone-example.html', styleUrl: '../menu-example.css', - standalone: true, - imports: [Menu, SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText], + imports: [Menu, MenuContent, SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText], }) export class MenuStandaloneExample {} diff --git a/src/components-examples/aria/menu/menu-trigger-disabled/menu-trigger-disabled-example.html b/src/components-examples/aria/menu/menu-trigger-disabled/menu-trigger-disabled-example.html new file mode 100644 index 000000000000..571e8cf0dfad --- /dev/null +++ b/src/components-examples/aria/menu/menu-trigger-disabled/menu-trigger-disabled-example.html @@ -0,0 +1,67 @@ + + +
              + +
              + mark_email_read + Mark as read +
              + +
              + snooze + Snooze +
              + + + +
              + category + Categorize + arrow_right +
              + +
              + +
              + label_important + Mark as important +
              + +
              + star + Star +
              + +
              + label + Label +
              +
              +
              + + + +
              + archive + Archive +
              + +
              + report + Report spam +
              + +
              + delete + Delete +
              +
              +
              diff --git a/src/components-examples/aria/menu/menu-trigger-disabled/menu-trigger-disabled-example.ts b/src/components-examples/aria/menu/menu-trigger-disabled/menu-trigger-disabled-example.ts new file mode 100644 index 000000000000..e8eb4322e67b --- /dev/null +++ b/src/components-examples/aria/menu/menu-trigger-disabled/menu-trigger-disabled-example.ts @@ -0,0 +1,19 @@ +import {Component} from '@angular/core'; +import {MenuTrigger, MenuContent} from '@angular/aria/menu'; +import {SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText} from '../simple-menu'; + +/** @title Menu trigger example. */ +@Component({ + selector: 'menu-trigger-disabled-example', + templateUrl: 'menu-trigger-disabled-example.html', + styleUrl: '../menu-example.css', + imports: [ + MenuContent, + MenuTrigger, + SimpleMenu, + SimpleMenuItem, + SimpleMenuItemIcon, + SimpleMenuItemText, + ], +}) +export class MenuTriggerDisabledExample {} diff --git a/src/components-examples/aria/menu/menu-trigger/menu-trigger-example.html b/src/components-examples/aria/menu/menu-trigger/menu-trigger-example.html index 4c857c37e6ca..686b5d81b85e 100644 --- a/src/components-examples/aria/menu/menu-trigger/menu-trigger-example.html +++ b/src/components-examples/aria/menu/menu-trigger/menu-trigger-example.html @@ -1,63 +1,66 @@ -
              -
              - mark_email_read - Mark as read -
              - -
              - snooze - Snooze -
              - - - -
              - category - Categorize - arrow_right -
              - -
              -
              - label_important - Mark as important +
              + +
              + mark_email_read + Mark as read
              -
              - star - Star +
              + snooze + Snooze
              -
              - label - Label + + +
              + category + Categorize + arrow_right +
              + +
              + +
              + label_important + Mark as important +
              + +
              + star + Star +
              + +
              + label + Label +
              +
              -
              - + -
              - archive - Archive -
              +
              + archive + Archive +
              -
              - report - Report spam -
              +
              + report + Report spam +
              -
              - delete - Delete -
              +
              + delete + Delete +
              +
              diff --git a/src/components-examples/aria/menu/menu-trigger/menu-trigger-example.ts b/src/components-examples/aria/menu/menu-trigger/menu-trigger-example.ts index 0cf84e867441..49cf9aa69145 100644 --- a/src/components-examples/aria/menu/menu-trigger/menu-trigger-example.ts +++ b/src/components-examples/aria/menu/menu-trigger/menu-trigger-example.ts @@ -1,14 +1,19 @@ import {Component} from '@angular/core'; -import {MenuTrigger} from '@angular/aria/menu'; +import {MenuTrigger, MenuContent} from '@angular/aria/menu'; import {SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText} from '../simple-menu'; /** @title Menu trigger example. */ @Component({ selector: 'menu-trigger-example', - exportAs: 'MenuTriggerExample', templateUrl: 'menu-trigger-example.html', styleUrl: '../menu-example.css', - standalone: true, - imports: [SimpleMenu, SimpleMenuItem, SimpleMenuItemIcon, SimpleMenuItemText, MenuTrigger], + imports: [ + MenuContent, + MenuTrigger, + SimpleMenu, + SimpleMenuItem, + SimpleMenuItemIcon, + SimpleMenuItemText, + ], }) export class MenuTriggerExample {} diff --git a/src/components-examples/aria/menu/simple-menu.ts b/src/components-examples/aria/menu/simple-menu.ts index 17af13190dfc..87a71501ff71 100644 --- a/src/components-examples/aria/menu/simple-menu.ts +++ b/src/components-examples/aria/menu/simple-menu.ts @@ -2,8 +2,8 @@ import {Menu, MenuBar, MenuItem, MenuTrigger} from '@angular/aria/menu'; import {afterRenderEffect, Directive, effect, inject} from '@angular/core'; @Directive({ - selector: '[menu]', - hostDirectives: [{directive: Menu, inputs: ['parent']}], + selector: '[ng-menu]', + hostDirectives: [{directive: Menu}], host: { class: 'example-menu', popover: 'manual', @@ -15,7 +15,7 @@ export class SimpleMenu { constructor() { afterRenderEffect(() => { - this.menu.isVisible() ? this.menu.element.showPopover() : this.menu.element.hidePopover(); + this.menu.visible() ? this.menu.element.showPopover() : this.menu.element.hidePopover(); }); } @@ -28,6 +28,7 @@ export class SimpleMenu { const parentEl = parent.element; const parentRect = parentEl.getBoundingClientRect(); + const menuRect = this.menu.element.getBoundingClientRect(); const scrollX = window.scrollX; const scrollY = window.scrollY; @@ -36,28 +37,31 @@ export class SimpleMenu { const bottom = parentRect.y + scrollY + parentRect.height + 6; if (parent.parent instanceof MenuBar) { - this.menu.element.style.left = `${parentRect.left + scrollX}px`; + const rtlOffset = this.menu.textDirection() === 'rtl' ? menuRect.width - parentRect.width : 0; + this.menu.element.style.left = `${parentRect.left + scrollX - rtlOffset}px`; this.menu.element.style.top = `${bottom}px`; } else if (parent instanceof MenuTrigger) { this.menu.element.style.left = `${parentRect.left + scrollX}px`; this.menu.element.style.top = `${parentRect.bottom + scrollY + 2}px`; } else { - this.menu.element.style.left = `${parentRect.right + scrollX + 6}px`; + const rtlOffset = + this.menu.textDirection() === 'rtl' ? menuRect.width + parentRect.width + 12 : 0; + this.menu.element.style.left = `${parentRect.right + scrollX + 6 - rtlOffset}px`; this.menu.element.style.top = `${top}px`; } } } @Directive({ - selector: '[menu-bar]', - hostDirectives: [{directive: MenuBar}], + selector: '[ng-menu-bar]', + hostDirectives: [{directive: MenuBar, inputs: ['disabled']}], host: {class: 'example-menu-bar'}, }) export class SimpleMenuBar {} @Directive({ - selector: '[menu-bar-item]', - hostDirectives: [{directive: MenuItem, inputs: ['value', 'submenu']}], + selector: '[ng-menu-bar-item]', + hostDirectives: [{directive: MenuItem, inputs: ['value', 'submenu', 'disabled']}], host: {class: 'example-menu-bar-item'}, }) export class SimpleMenuBarItem { @@ -69,7 +73,7 @@ export class SimpleMenuBarItem { } @Directive({ - selector: '[menu-item]', + selector: '[ng-menu-item]', hostDirectives: [{directive: MenuItem, inputs: ['value', 'disabled', 'submenu']}], host: {class: 'example-menu-item'}, }) @@ -82,7 +86,7 @@ export class SimpleMenuItem { } @Directive({ - selector: '[menu-item-icon]', + selector: '[ng-menu-item-icon]', host: { 'aria-hidden': 'true', class: 'example-icon material-symbols-outlined', @@ -91,13 +95,13 @@ export class SimpleMenuItem { export class SimpleMenuItemIcon {} @Directive({ - selector: '[menu-item-text]', + selector: '[ng-menu-item-text]', host: {class: 'example-menu-item-text'}, }) export class SimpleMenuItemText {} @Directive({ - selector: '[menu-item-shortcut]', + selector: '[ng-menu-item-shortcut]', host: { 'aria-hidden': 'true', class: 'example-menu-item-shortcut', diff --git a/src/components-examples/aria/radio-group/BUILD.bazel b/src/components-examples/aria/radio-group/BUILD.bazel deleted file mode 100644 index fa62632483d9..000000000000 --- a/src/components-examples/aria/radio-group/BUILD.bazel +++ /dev/null @@ -1,29 +0,0 @@ -load("//tools:defaults.bzl", "ng_project") - -package(default_visibility = ["//visibility:public"]) - -ng_project( - name = "radio-group", - srcs = glob(["**/*.ts"]), - assets = glob([ - "**/*.html", - "**/*.css", - ]), - deps = [ - "//:node_modules/@angular/core", - "//:node_modules/@angular/forms", - "//src/aria/radio-group", - "//src/material/checkbox", - "//src/material/form-field", - "//src/material/select", - ], -) - -filegroup( - name = "source-files", - srcs = glob([ - "**/*.html", - "**/*.css", - "**/*.ts", - ]), -) diff --git a/src/components-examples/aria/radio-group/index.ts b/src/components-examples/aria/radio-group/index.ts deleted file mode 100644 index 1eac914e3216..000000000000 --- a/src/components-examples/aria/radio-group/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export {RadioGroupStandardExample} from './radio-group-standard/radio-group-standard-example'; -export {RadioGroupHorizontalExample} from './radio-group-horizontal/radio-group-horizontal-example'; -export {RadioGroupRtlHorizontalExample} from './radio-group-rtl-horizontal/radio-group-rtl-horizontal-example'; -export {RadioGroupActiveDescendantExample} from './radio-group-active-descendant/radio-group-active-descendant-example'; -export {RadioGroupDisabledFocusableExample} from './radio-group-disabled-focusable/radio-group-disabled-focusable-example'; -export {RadioGroupDisabledSkippedExample} from './radio-group-disabled-skipped/radio-group-disabled-skipped-example'; -export {RadioGroupReadonlyExample} from './radio-group-readonly/radio-group-readonly-example'; -export {RadioGroupDisabledExample} from './radio-group-disabled/radio-group-disabled-example'; -export {RadioGroupConfigurableExample} from './radio-group-configurable/radio-group-configurable-example'; diff --git a/src/components-examples/aria/radio-group/radio-common.css b/src/components-examples/aria/radio-group/radio-common.css deleted file mode 100644 index 35d90bdb095e..000000000000 --- a/src/components-examples/aria/radio-group/radio-common.css +++ /dev/null @@ -1,90 +0,0 @@ -.example-radio-controls { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 16px; - padding-bottom: 16px; -} - -.example-radio-group { - gap: 4px; - margin: 0; - padding: 8px; - max-height: 300px; - border: 1px solid var(--mat-sys-outline); - border-radius: var(--mat-sys-corner-extra-small); - display: flex; - list-style: none; - flex-direction: column; - overflow: scroll; -} - -.example-radio-group[aria-orientation='horizontal'] { - flex-direction: row; -} - -.example-radio-group[aria-disabled='true'] { - pointer-events: none; -} - -.example-radio-group label { - padding: 16px; - flex-shrink: 0; -} - -.example-radio-button { - gap: 16px; - padding: 16px; - display: flex; - cursor: pointer; - position: relative; - align-items: center; - border-radius: var(--mat-sys-corner-extra-small); -} - -/* Basic visual indicator for the radio button */ -.example-radio-indicator { - width: 16px; - height: 16px; - border-radius: 50%; - border: 2px solid var(--mat-sys-outline); - display: inline-block; - position: relative; -} - -.example-radio-button[aria-checked='true'] .example-radio-indicator { - border-color: var(--mat-sys-primary); -} - -.example-radio-button[aria-checked='true'] .example-radio-indicator::after { - content: ''; - display: block; - width: 8px; - height: 8px; - border-radius: 50%; - background-color: var(--mat-sys-primary); - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.example-radio-button[aria-disabled='true'][aria-checked='true'] .example-radio-indicator::after { - background-color: var(--mat-sys-outline); -} - -.example-radio-button[aria-disabled='true'] { - cursor: default; -} - -.example-radio-button[aria-disabled='true']::before { - content: ''; - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; - border-radius: var(--mat-sys-corner-extra-small); - background-color: var(--mat-sys-on-surface); - opacity: var(--mat-sys-focus-state-layer-opacity); -} diff --git a/src/components-examples/aria/radio-group/radio-group-active-descendant/radio-group-active-descendant-example.html b/src/components-examples/aria/radio-group/radio-group-active-descendant/radio-group-active-descendant-example.html deleted file mode 100644 index eb5bc78e5cbf..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-active-descendant/radio-group-active-descendant-example.html +++ /dev/null @@ -1,26 +0,0 @@ -
              -
                - - @for (fruit of fruits; track fruit) { -
              • - - {{ fruit }} -
              • - } -
              -
              diff --git a/src/components-examples/aria/radio-group/radio-group-active-descendant/radio-group-active-descendant-example.ts b/src/components-examples/aria/radio-group/radio-group-active-descendant/radio-group-active-descendant-example.ts deleted file mode 100644 index d111affcfe74..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-active-descendant/radio-group-active-descendant-example.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Component} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; - -/** @title Active descendant radio group. */ -@Component({ - selector: 'radio-group-active-descendant-example', - templateUrl: 'radio-group-active-descendant-example.html', - styleUrl: '../radio-common.css', - imports: [RadioGroup, RadioButton, FormsModule], -}) -export class RadioGroupActiveDescendantExample { - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; -} diff --git a/src/components-examples/aria/radio-group/radio-group-configurable/radio-group-configurable-example.html b/src/components-examples/aria/radio-group/radio-group-configurable/radio-group-configurable-example.html deleted file mode 100644 index cda2bef0de4a..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-configurable/radio-group-configurable-example.html +++ /dev/null @@ -1,56 +0,0 @@ -
              - Disabled - Readonly - Skip Disabled - - - Disabled Radio Options - - @for (fruit of fruits; track fruit) { - {{fruit}} - } - - - - - Orientation - - Vertical - Horizontal - - - - - Focus strategy - - Roving Tabindex - Active Descendant - - -
              - - -
                - @for (fruit of fruits; track fruit) { - @let optionDisabled = disabledOptions.includes(fruit); -
              • - - {{ fruit }} -
              • - } -
              - diff --git a/src/components-examples/aria/radio-group/radio-group-configurable/radio-group-configurable-example.ts b/src/components-examples/aria/radio-group/radio-group-configurable/radio-group-configurable-example.ts deleted file mode 100644 index 8c9db2e14332..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-configurable/radio-group-configurable-example.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {Component} from '@angular/core'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; -import {MatCheckboxModule} from '@angular/material/checkbox'; -import {MatFormFieldModule} from '@angular/material/form-field'; -import {MatSelectModule} from '@angular/material/select'; -import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; - -/** @title Configurable CDK Radio Group */ -@Component({ - selector: 'radio-group-configurable-example', - templateUrl: 'radio-group-configurable-example.html', - styleUrl: '../radio-common.css', - imports: [ - RadioGroup, - RadioButton, - MatCheckboxModule, - MatFormFieldModule, - MatSelectModule, - FormsModule, - ReactiveFormsModule, - ], -}) -export class RadioGroupConfigurableExample { - orientation: 'vertical' | 'horizontal' = 'vertical'; - disabled = new FormControl(false, {nonNullable: true}); - - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; - - // New controls - readonly = new FormControl(false, {nonNullable: true}); - skipDisabled = new FormControl(true, {nonNullable: true}); - focusMode: 'roving' | 'activedescendant' = 'roving'; - - // Control for which radio options are individually disabled - disabledOptions: string[] = ['Banana']; -} diff --git a/src/components-examples/aria/radio-group/radio-group-disabled-focusable/radio-group-disabled-focusable-example.html b/src/components-examples/aria/radio-group/radio-group-disabled-focusable/radio-group-disabled-focusable-example.html deleted file mode 100644 index f6764a413edf..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-disabled-focusable/radio-group-disabled-focusable-example.html +++ /dev/null @@ -1,27 +0,0 @@ -
              -
                - - @for (fruit of fruits; track fruit) { -
              • - - {{ fruit }} {{ disabledFruits.includes(fruit) ? '(Disabled)' : '' }} -
              • - } -
              -
              diff --git a/src/components-examples/aria/radio-group/radio-group-disabled-focusable/radio-group-disabled-focusable-example.ts b/src/components-examples/aria/radio-group/radio-group-disabled-focusable/radio-group-disabled-focusable-example.ts deleted file mode 100644 index d85354a2199c..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-disabled-focusable/radio-group-disabled-focusable-example.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Component} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; - -/** @title Radio group with disabled options that are focusable. */ -@Component({ - selector: 'radio-group-disabled-focusable-example', - templateUrl: 'radio-group-disabled-focusable-example.html', - styleUrl: '../radio-common.css', - imports: [RadioGroup, RadioButton, FormsModule], -}) -export class RadioGroupDisabledFocusableExample { - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; - disabledFruits = ['Banana', 'Kiwi']; -} diff --git a/src/components-examples/aria/radio-group/radio-group-disabled-skipped/radio-group-disabled-skipped-example.html b/src/components-examples/aria/radio-group/radio-group-disabled-skipped/radio-group-disabled-skipped-example.html deleted file mode 100644 index e0d01e2a092e..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-disabled-skipped/radio-group-disabled-skipped-example.html +++ /dev/null @@ -1,26 +0,0 @@ -
              -
                - - @for (fruit of fruits; track fruit) { -
              • - - {{ fruit }} {{ disabledFruits.includes(fruit) ? '(Disabled)' : '' }} -
              • - } -
              -
              diff --git a/src/components-examples/aria/radio-group/radio-group-disabled-skipped/radio-group-disabled-skipped-example.ts b/src/components-examples/aria/radio-group/radio-group-disabled-skipped/radio-group-disabled-skipped-example.ts deleted file mode 100644 index 9e74081d5788..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-disabled-skipped/radio-group-disabled-skipped-example.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Component} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; - -/** @title Radio group with disabled options that are skipped. */ -@Component({ - selector: 'radio-group-disabled-skipped-example', - templateUrl: 'radio-group-disabled-skipped-example.html', - styleUrl: '../radio-common.css', - imports: [RadioGroup, RadioButton, FormsModule], -}) -export class RadioGroupDisabledSkippedExample { - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; - disabledFruits = ['Banana', 'Kiwi']; -} diff --git a/src/components-examples/aria/radio-group/radio-group-disabled/radio-group-disabled-example.html b/src/components-examples/aria/radio-group/radio-group-disabled/radio-group-disabled-example.html deleted file mode 100644 index 767af665a645..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-disabled/radio-group-disabled-example.html +++ /dev/null @@ -1,25 +0,0 @@ -
              -
                - - @for (fruit of fruits; track fruit) { -
              • - - {{ fruit }} -
              • - } -
              -

              The entire radio group is disabled. Focus should not enter the group.

              -
              diff --git a/src/components-examples/aria/radio-group/radio-group-disabled/radio-group-disabled-example.ts b/src/components-examples/aria/radio-group/radio-group-disabled/radio-group-disabled-example.ts deleted file mode 100644 index 932769eb06a4..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-disabled/radio-group-disabled-example.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Component} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; - -/** @title Disabled radio group. */ -@Component({ - selector: 'radio-group-disabled-example', - templateUrl: 'radio-group-disabled-example.html', - styleUrl: '../radio-common.css', - imports: [RadioGroup, RadioButton, FormsModule], -}) -export class RadioGroupDisabledExample { - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; -} diff --git a/src/components-examples/aria/radio-group/radio-group-horizontal/radio-group-horizontal-example.html b/src/components-examples/aria/radio-group/radio-group-horizontal/radio-group-horizontal-example.html deleted file mode 100644 index 9ccf81a55f02..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-horizontal/radio-group-horizontal-example.html +++ /dev/null @@ -1,24 +0,0 @@ -
              -
                - - @for (fruit of fruits; track fruit) { -
              • - - {{ fruit }} -
              • - } -
              -
              diff --git a/src/components-examples/aria/radio-group/radio-group-horizontal/radio-group-horizontal-example.ts b/src/components-examples/aria/radio-group/radio-group-horizontal/radio-group-horizontal-example.ts deleted file mode 100644 index 081b62def973..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-horizontal/radio-group-horizontal-example.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Component} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; - -/** @title Horizontal radio group. */ -@Component({ - selector: 'radio-group-horizontal-example', - templateUrl: 'radio-group-horizontal-example.html', - styleUrl: '../radio-common.css', - imports: [RadioGroup, RadioButton, FormsModule], -}) -export class RadioGroupHorizontalExample { - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; -} diff --git a/src/components-examples/aria/radio-group/radio-group-readonly/radio-group-readonly-example.html b/src/components-examples/aria/radio-group/radio-group-readonly/radio-group-readonly-example.html deleted file mode 100644 index 8d6892260338..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-readonly/radio-group-readonly-example.html +++ /dev/null @@ -1,25 +0,0 @@ -
              -
                - - @for (fruit of fruits; track fruit) { -
              • - - {{ fruit }} -
              • - } -
              -

              The radio group is navigable, but selection cannot be changed.

              -
              diff --git a/src/components-examples/aria/radio-group/radio-group-readonly/radio-group-readonly-example.ts b/src/components-examples/aria/radio-group/radio-group-readonly/radio-group-readonly-example.ts deleted file mode 100644 index 3bb4d5cbad03..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-readonly/radio-group-readonly-example.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Component} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; - -/** @title Readonly radio group. */ -@Component({ - selector: 'radio-group-readonly-example', - templateUrl: 'radio-group-readonly-example.html', - styleUrl: '../radio-common.css', - imports: [RadioGroup, RadioButton, FormsModule], -}) -export class RadioGroupReadonlyExample { - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; -} diff --git a/src/components-examples/aria/radio-group/radio-group-rtl-horizontal/radio-group-rtl-horizontal-example.html b/src/components-examples/aria/radio-group/radio-group-rtl-horizontal/radio-group-rtl-horizontal-example.html deleted file mode 100644 index a6e8763958cb..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-rtl-horizontal/radio-group-rtl-horizontal-example.html +++ /dev/null @@ -1,21 +0,0 @@ -
              -
                - - @for (fruit of fruits; track fruit) { -
              • - - {{ fruit }} -
              • - } -
              -
              diff --git a/src/components-examples/aria/radio-group/radio-group-rtl-horizontal/radio-group-rtl-horizontal-example.ts b/src/components-examples/aria/radio-group/radio-group-rtl-horizontal/radio-group-rtl-horizontal-example.ts deleted file mode 100644 index 46a3bcc6fab1..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-rtl-horizontal/radio-group-rtl-horizontal-example.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {Component} from '@angular/core'; -import {Dir} from '@angular/cdk/bidi'; -import {FormsModule} from '@angular/forms'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; - -/** @title RTL horizontal radio group. */ -@Component({ - selector: 'radio-group-rtl-horizontal-example', - templateUrl: 'radio-group-rtl-horizontal-example.html', - styleUrl: '../radio-common.css', - imports: [RadioGroup, RadioButton, Dir, FormsModule], -}) -export class RadioGroupRtlHorizontalExample { - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; -} diff --git a/src/components-examples/aria/radio-group/radio-group-standard/radio-group-standard-example.html b/src/components-examples/aria/radio-group/radio-group-standard/radio-group-standard-example.html deleted file mode 100644 index ce6b57bde1ab..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-standard/radio-group-standard-example.html +++ /dev/null @@ -1,23 +0,0 @@ -
              -
                - - @for (fruit of fruits; track fruit) { -
              • - - {{ fruit }} -
              • - } -
              -
              diff --git a/src/components-examples/aria/radio-group/radio-group-standard/radio-group-standard-example.ts b/src/components-examples/aria/radio-group/radio-group-standard/radio-group-standard-example.ts deleted file mode 100644 index aa3f9564f31e..000000000000 --- a/src/components-examples/aria/radio-group/radio-group-standard/radio-group-standard-example.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {Component} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; - -/** @title Basic radio group. */ -@Component({ - selector: 'radio-group-standard-example', - templateUrl: 'radio-group-standard-example.html', - styleUrl: '../radio-common.css', - imports: [RadioGroup, RadioButton, FormsModule], -}) -export class RadioGroupStandardExample { - fruits = [ - 'Apple', - 'Apricot', - 'Banana', - 'Blackberry', - 'Blueberry', - 'Cantaloupe', - 'Cherry', - 'Clementine', - 'Cranberry', - 'Dates', - 'Figs', - 'Grapes', - 'Grapefruit', - 'Guava', - 'Kiwi', - 'Kumquat', - 'Lemon', - 'Lime', - 'Mandarin', - 'Mango', - 'Nectarine', - 'Orange', - 'Papaya', - 'Passion', - 'Peach', - 'Pear', - 'Pineapple', - 'Plum', - 'Pomegranate', - 'Raspberries', - 'Strawberry', - 'Tangerine', - 'Watermelon', - ]; -} diff --git a/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.html b/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.html index 97d542078fa8..89ba37a87145 100644 --- a/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.html +++ b/src/components-examples/aria/tabs/disabled-focusable/tabs-disabled-focusable-example.html @@ -1,5 +1,5 @@
              -
                +
                • tab 1
                • tab 2
                • tab 3
                • diff --git a/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.html b/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.html index 02900da93462..6235d52e00f9 100644 --- a/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.html +++ b/src/components-examples/aria/tabs/disabled-skipped/tabs-disabled-skipped-example.html @@ -1,5 +1,5 @@
                  -
                    +
                    • tab 1
                    • tab 2
                    • tab 3
                    • diff --git a/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.html b/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.html index c33f5e9481f0..427b2f366ac5 100644 --- a/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.html +++ b/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.html @@ -1,7 +1,7 @@
                      Wrap Disabled - Skip Disabled + Soft Disabled Orientation @@ -44,11 +44,11 @@ class="example-tablist" [wrap]="wrap.value" [disabled]="disabled.value" - [skipDisabled]="skipDisabled.value" + [softDisabled]="softDisabled.value" [orientation]="orientation" [focusMode]="focusMode" - [selectionMode]="selectionMode" [(selectedTab)]="tabSelection" + [selectionMode]="selectionMode" >
                    • tab 1
                    • tab 2
                    • diff --git a/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.ts b/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.ts index 44e274738a37..46e341450342 100644 --- a/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.ts +++ b/src/components-examples/aria/tabs/tabs-configurable/tabs-configurable-example.ts @@ -30,5 +30,5 @@ export class TabsConfigurableExample { wrap = new FormControl(true, {nonNullable: true}); disabled = new FormControl(false, {nonNullable: true}); - skipDisabled = new FormControl(true, {nonNullable: true}); + softDisabled = new FormControl(true, {nonNullable: true}); } diff --git a/src/components-examples/aria/toolbar/BUILD.bazel b/src/components-examples/aria/toolbar/BUILD.bazel index aac5f1eb7eed..a6dcbf508488 100644 --- a/src/components-examples/aria/toolbar/BUILD.bazel +++ b/src/components-examples/aria/toolbar/BUILD.bazel @@ -12,7 +12,8 @@ ng_project( deps = [ "//:node_modules/@angular/core", "//:node_modules/@angular/forms", - "//src/aria/radio-group", + "//src/aria/combobox", + "//src/aria/listbox", "//src/aria/toolbar", "//src/cdk/a11y", "//src/material/checkbox", diff --git a/src/components-examples/aria/toolbar/index.ts b/src/components-examples/aria/toolbar/index.ts index 42e92a97467b..89582a318a90 100644 --- a/src/components-examples/aria/toolbar/index.ts +++ b/src/components-examples/aria/toolbar/index.ts @@ -1,4 +1,5 @@ export {ToolbarBasicHorizontalExample} from './toolbar-basic-horizontal/toolbar-basic-horizontal-example'; export {ToolbarBasicVerticalExample} from './toolbar-basic-vertical/toolbar-basic-vertical-example'; export {ToolbarConfigurableExample} from './toolbar-configurable/toolbar-configurable-example'; -export {ToolbarSkipDisabledExample} from './toolbar-skip-disabled/toolbar-skip-disabled-example'; +export {ToolbarRtlExample} from './toolbar-rtl/toolbar-rtl-example'; +export {ToolbarHardDisabledExample} from './toolbar-hard-disabled/toolbar-hard-disabled-example'; diff --git a/src/components-examples/aria/toolbar/simple-toolbar.ts b/src/components-examples/aria/toolbar/simple-toolbar.ts new file mode 100644 index 000000000000..56ed29dbd183 --- /dev/null +++ b/src/components-examples/aria/toolbar/simple-toolbar.ts @@ -0,0 +1,142 @@ +import { + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, +} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {ToolbarWidget} from '@angular/aria/toolbar'; +import {Dir, Directionality} from '@angular/cdk/bidi'; +import { + afterRenderEffect, + Component, + Directive, + ElementRef, + inject, + signal, + viewChild, +} from '@angular/core'; + +@Directive({ + selector: 'button[toolbar-button]', + hostDirectives: [{directive: ToolbarWidget, inputs: ['value', 'disabled']}], + host: { + type: 'button', + class: 'example-button material-symbols-outlined', + '[aria-label]': 'widget.value()', + }, +}) +export class SimpleToolbarButton { + widget = inject(ToolbarWidget); +} + +@Directive({ + selector: 'button[toolbar-toggle-button]', + hostDirectives: [{directive: ToolbarWidget, inputs: ['value']}], + host: { + type: 'button', + class: 'example-button material-symbols-outlined', + '[aria-pressed]': 'widget.selected()', + '[aria-label]': 'widget.value()', + }, +}) +export class SimpleToolbarToggleButton { + widget = inject(ToolbarWidget); +} + +@Directive({ + selector: 'button[toolbar-radio-button]', + hostDirectives: [{directive: ToolbarWidget, inputs: ['value', 'disabled']}], + host: { + role: 'radio', + type: 'button', + class: 'example-button material-symbols-outlined', + '[aria-checked]': 'widget.selected()', + '[aria-label]': 'widget.value()', + }, +}) +export class SimpleToolbarRadioButton { + widget = inject(ToolbarWidget); +} + +@Component({ + selector: 'combobox', + imports: [ + Dir, + Combobox, + ComboboxInput, + ComboboxPopup, + ComboboxPopupContainer, + Listbox, + Option, + ToolbarWidget, + ], + styleUrl: 'toolbar-common.css', + host: {class: 'example-combobox-container'}, + template: ` +
                      +
                      + + arrow_drop_down +
                      + +
                      + +
                      + @for (option of options; track option) { +
                      + {{option}} + +
                      + } +
                      +
                      +
                      +
                      + `, +}) +export class SimpleCombobox { + dir = inject(Directionality).valueSignal; + popover = viewChild('popover'); + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); + + value = signal('Normal text'); + options = ['Normal text', 'Title', 'Subtitle', 'Heading 1', 'Heading 2', 'Heading 3']; + + constructor() { + afterRenderEffect(() => { + const popover = this.popover()!; + const combobox = this.combobox()!; + combobox.expanded() ? this.showPopover() : popover.nativeElement.hidePopover(); + + this.listbox()?.scrollActiveItemIntoView(); + }); + } + + showPopover() { + const popover = this.popover()!; + const combobox = this.combobox()!; + + const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); + const popoverEl = popover.nativeElement; + + if (comboboxRect) { + popoverEl.style.width = `${comboboxRect.width}px`; + popoverEl.style.top = `${comboboxRect.bottom + 4}px`; + popoverEl.style.left = `${comboboxRect.left - 1}px`; + } + + popover.nativeElement.showPopover(); + } +} diff --git a/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.html b/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.html index caa014e61494..94908c0c3209 100644 --- a/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.html +++ b/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.html @@ -1,43 +1,41 @@ -
                      -
                      - - - -
                        - @for (alignment of alignments; track alignment) { -
                      • - - {{ alignment.label }} -
                      • - } -
                      - +
                      +
                      +
                      + +
                      + + + +
                      + + + +
                      + + + + + + + +
                      + + + +
                      + + + +
                      + + + +
                      +
                      diff --git a/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.ts b/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.ts index 45af7aac691d..8ec2ff47c8c7 100644 --- a/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.ts +++ b/src/components-examples/aria/toolbar/toolbar-basic-horizontal/toolbar-basic-horizontal-example.ts @@ -1,28 +1,25 @@ import {Component} from '@angular/core'; -import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; -import {LiveAnnouncer} from '@angular/cdk/a11y'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; +import { + SimpleCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, +} from '../simple-toolbar'; /** @title Basic Horizontal Toolbar Example */ @Component({ selector: 'toolbar-basic-horizontal-example', templateUrl: 'toolbar-basic-horizontal-example.html', styleUrl: '../toolbar-common.css', - imports: [RadioButton, RadioGroup, Toolbar, ToolbarWidget], + imports: [ + Toolbar, + ToolbarWidget, + ToolbarWidgetGroup, + SimpleCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, + ], }) -export class ToolbarBasicHorizontalExample { - constructor(private _liveAnnouncer: LiveAnnouncer) {} - alignments = [ - {value: 'left', label: 'Left'}, - {value: 'center', label: 'Center'}, - {value: 'right', label: 'Right'}, - ]; - format(tool: string) { - console.log(`Tool activated: ${tool}`); - this._liveAnnouncer.announce(`${tool} applied`, 'polite'); - } - test(action: string) { - console.log(`Action triggered: ${action}`); - this._liveAnnouncer.announce(`${action} button activated`, 'polite'); - } -} +export class ToolbarBasicHorizontalExample {} diff --git a/src/components-examples/aria/toolbar/toolbar-basic-vertical/toolbar-basic-vertical-example.html b/src/components-examples/aria/toolbar/toolbar-basic-vertical/toolbar-basic-vertical-example.html index eec277e9de26..72d7096f48f6 100644 --- a/src/components-examples/aria/toolbar/toolbar-basic-vertical/toolbar-basic-vertical-example.html +++ b/src/components-examples/aria/toolbar/toolbar-basic-vertical/toolbar-basic-vertical-example.html @@ -1,43 +1,34 @@ -
                      -
                      - - - -
                        - @for (alignment of alignments; track alignment) { -
                      • - - {{ alignment.label }} -
                      • - } -
                      - +
                      +
                      +
                      + +
                      + + + +
                      + + + +
                      + + + +
                      + + + +
                      +
                      diff --git a/src/components-examples/aria/toolbar/toolbar-basic-vertical/toolbar-basic-vertical-example.ts b/src/components-examples/aria/toolbar/toolbar-basic-vertical/toolbar-basic-vertical-example.ts index fec7482882d1..62afddef2fc4 100644 --- a/src/components-examples/aria/toolbar/toolbar-basic-vertical/toolbar-basic-vertical-example.ts +++ b/src/components-examples/aria/toolbar/toolbar-basic-vertical/toolbar-basic-vertical-example.ts @@ -1,28 +1,23 @@ import {Component} from '@angular/core'; -import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; -import {LiveAnnouncer} from '@angular/cdk/a11y'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; +import { + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, +} from '../simple-toolbar'; /** @title Basic Vertical Toolbar Example */ @Component({ selector: 'toolbar-basic-vertical-example', templateUrl: 'toolbar-basic-vertical-example.html', styleUrl: '../toolbar-common.css', - imports: [RadioButton, RadioGroup, Toolbar, ToolbarWidget], + imports: [ + Toolbar, + ToolbarWidget, + ToolbarWidgetGroup, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, + ], }) -export class ToolbarBasicVerticalExample { - constructor(private _liveAnnouncer: LiveAnnouncer) {} - alignments = [ - {value: 'left', label: 'Left'}, - {value: 'center', label: 'Center'}, - {value: 'right', label: 'Right'}, - ]; - format(tool: string) { - console.log(`Tool activated: ${tool}`); - this._liveAnnouncer.announce(`${tool} applied`, 'polite'); - } - test(action: string) { - console.log(`Action triggered: ${action}`); - this._liveAnnouncer.announce(`${action} button activated`, 'polite'); - } -} +export class ToolbarBasicVerticalExample {} diff --git a/src/components-examples/aria/toolbar/toolbar-common.css b/src/components-examples/aria/toolbar/toolbar-common.css index 1e347bf97904..4be1ff833de1 100644 --- a/src/components-examples/aria/toolbar/toolbar-common.css +++ b/src/components-examples/aria/toolbar/toolbar-common.css @@ -1,7 +1,3 @@ -.example-container { - padding-bottom: 32px; -} - .example-heading { margin: 16px 0 4px; } @@ -15,120 +11,177 @@ } .example-toolbar { - display: flex; - flex-direction: column; - padding: 8px; - width: 50%; - border: 1px solid var(--mat-sys-outline); - border-radius: var(--mat-sys-corner-extra-small); gap: 16px; -} -.example-toolbar[aria-orientation='horizontal'] { + padding: 8px; + display: flex; + width: -webkit-fit-content; + width: -moz-fit-content; flex-direction: row; - width: 100%; + border-radius: var(--mat-sys-corner-small); + background-color: var(--mat-sys-surface); + border: 1px solid color-mix(in srgb, var(--mat-sys-outline) 50%, transparent); } -.example-radio-group { +.example-toolbar[aria-orientation='vertical'], +.example-toolbar[aria-orientation='vertical'] .example-group { + flex-direction: column; +} + +.example-group { gap: 4px; - margin: 0; - padding: 8px; - max-height: 300px; - border: 1px solid var(--mat-sys-outline); - border-radius: var(--mat-sys-corner-extra-small); display: flex; - list-style: none; - flex-direction: column; - overflow: scroll; } -.example-radio-group[aria-orientation='horizontal'] { - flex-direction: row; - width: 100%; +.example-button { + cursor: pointer; + opacity: 0.875; + font-size: 1.25rem; + padding: 6px 8px; + background-color: transparent; + border: 1px solid transparent; + border-radius: var(--mat-sys-corner-extra-small); } -.example-radio-group[aria-disabled='true'] { - background-color: var(--mat-sys-surface-dim); - pointer-events: none; +.example-button:focus, +.example-button:hover { + background: color-mix(in srgb, var(--mat-sys-outline) 10%, transparent); } -.example-radio-group label { - padding: 16px; - flex-shrink: 0; +.example-button:active { + background: color-mix(in srgb, var(--mat-sys-outline) 20%, transparent); } -.example-radio-button { - gap: 16px; - padding: 16px; +.example-button[aria-pressed='true'], +.example-button[aria-checked='true'] { + color: color-mix(in srgb, var(--mat-sys-primary) 90%, black); + background: color-mix(in srgb, var(--mat-sys-primary) 15%, transparent); +} + +.example-button:focus { + border-color: var(--mat-sys-primary); + outline: 2px solid color-mix(in srgb, var(--mat-sys-primary) 50%, transparent); +} + +.example-button[aria-disabled='true'] { + cursor: default; + opacity: 0.45; +} + +.example-separator { + width: 1px; + background: color-mix(in srgb, var(--mat-sys-outline) 50%, transparent); +} + +.example-toolbar[aria-orientation='vertical'] .example-separator { + height: 1px; + width: auto; +} + +.example-combobox-container { + border-radius: var(--mat-sys-corner-extra-small); + border: 1px solid color-mix(in srgb, var(--mat-sys-outline) 50%, transparent); +} + +.example-combobox-container:focus-within { + border-color: var(--mat-sys-primary); + outline: 2px solid color-mix(in srgb, var(--mat-sys-primary) 50%, transparent); +} + +.example-combobox { + height: 100%; + width: 10rem; + display: flex; + position: relative; + flex-direction: column; +} + +.example-combobox-input-container { display: flex; - cursor: pointer; position: relative; align-items: center; border-radius: var(--mat-sys-corner-extra-small); + height: 100%; } -/* Basic visual indicator for the radio button */ -.example-radio-indicator { - width: 16px; - height: 16px; - border-radius: 50%; - border: 2px solid var(--mat-sys-outline); - display: inline-block; - position: relative; +.example-combobox-input { + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + height: 100%; + width: 100%; + border: none; + outline: none; + font-size: 0.8rem; + padding: 0 0.5rem; + border-radius: var(--mat-sys-corner-extra-small); + background-color: transparent; } -.example-radio-button[aria-checked='true'] .example-radio-indicator { - border-color: var(--mat-sys-primary); +.example-combobox-input::-moz-selection { + background: transparent; } -.example-radio-button[aria-checked='true'] .example-radio-indicator::after { - content: ''; - display: block; - width: 8px; - height: 8px; - border-radius: 50%; - background-color: var(--mat-sys-primary); - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); +.example-combobox-input::-webkit-selection { + background: transparent; } -.example-radio-button[aria-disabled='true'][aria-checked='true'] .example-radio-indicator::after { - background-color: var(--mat-sys-outline); +.example-arrow-icon { + padding: 0 0.2rem; + position: absolute; + right: 0; + opacity: 0.7; + transition: transform 0.2s ease; + pointer-events: none; } -.example-radio-button[data-active='true'], -.example-radio-button[aria-disabled='false']:hover { - outline: 2px solid var(--mat-sys-outline); - background: var(--mat-sys-surface-container); +.example-combobox[dir='rtl'] .example-arrow-icon { + right: auto; + left: 0; } -.example-radio-button[aria-disabled='false']:focus-within { - outline: 2px solid var(--mat-sys-primary); - background: var(--mat-sys-surface-container); +.example-combobox-input[aria-expanded='true'] + .example-arrow-icon { + transform: rotate(180deg); } -.example-radio-button[data-active='true'][aria-disabled='true'], -.example-radio-button[aria-disabled='true']:focus-within { - outline: 2px solid var(--mat-sys-outline); +.example-popover { + margin: 0; + padding: 0; + border: 1px solid color-mix(in srgb, var(--mat-sys-outline) 50%, transparent); + border-radius: var(--mat-sys-corner-extra-small); + background-color: var(--mat-sys-surface); } -.example-radio-button[aria-disabled='true'] { - cursor: default; +.example-option { + cursor: pointer; + font-size: 0.8rem; + padding: 0.5rem; + display: flex; + overflow: hidden; + flex-shrink: 0; + align-items: center; + justify-content: space-between; } -.example-radio-button[aria-disabled='true'] span:not(.example-radio-indicator) { - opacity: 0.3; +.example-option:hover { + background: color-mix(in srgb, var(--mat-sys-outline) 10%, transparent); } -.example-radio-button[aria-disabled='true']::before { - content: ''; - position: absolute; - width: 100%; - height: 100%; - top: 0; - left: 0; +.example-option[data-active='true'] { border-radius: var(--mat-sys-corner-extra-small); - background-color: var(--mat-sys-on-surface); - opacity: var(--mat-sys-focus-state-layer-opacity); + outline: 1px solid var(--mat-sys-primary); + outline-offset: -1px; +} + +.example-option[aria-selected='true'] { + color: var(--mat-sys-primary); + background: color-mix(in srgb, var(--mat-sys-primary) 10%, transparent); +} + +.example-option-icon { + font-size: 1rem; +} + +.example-option[aria-selected='false'] .example-option-icon { + visibility: hidden; } diff --git a/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.html b/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.html index 1506f1b93a4a..813a2b5ea388 100644 --- a/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.html +++ b/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.html @@ -1,9 +1,9 @@

                      Toolbar Controls

                      - Skip Disabled - Wrap - Disabled + Soft Disabled + Wrap + Disabled Orientation @@ -12,26 +12,23 @@

                      Toolbar Controls

                      -

                      Radio Group Controls

                      + +

                      Button

                      - Disabled - Readonly - Disabled Radio Options - - @for (fruit of fruits; track fruit) { - {{fruit}} + Disabled Buttons + + @for (widget of widgets; track widget) { + {{widget}} } -
                      -

                      Button

                      -
                      + - Disabled Buttons - - @for (fruit of buttonFruits; track fruit) { - {{fruit}} + Disabled Groups + + @for (group of groups; track group) { + {{group}} } @@ -41,67 +38,73 @@

                      Button

                      - -
                        - @for (fruit of fruits; track fruit) { - @let optionDisabled = disabledOptions.includes(fruit); -
                      • - - {{ fruit }} -
                      • - } -
                      -
                        - @for (fruit of fruits; track fruit) { - @let optionDisabled = disabledOptions.includes(fruit); -
                      • - - {{ fruit }} -
                      • - } -
                      - - + +
                      + + + +
                      + + + +
                      + + @if (orientation === 'horizontal') { + + + } + + + +
                      - Cherry - - + + +
                      + + + +
                      - Date - + + + +
                      diff --git a/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.ts b/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.ts index abadbcb38af6..1306d9eea92e 100644 --- a/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.ts +++ b/src/components-examples/aria/toolbar/toolbar-configurable/toolbar-configurable-example.ts @@ -1,21 +1,29 @@ import {Component} from '@angular/core'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; import {MatCheckboxModule} from '@angular/material/checkbox'; import {MatFormFieldModule} from '@angular/material/form-field'; import {MatSelectModule} from '@angular/material/select'; -import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; +import { + SimpleCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, +} from '../simple-toolbar'; -/** @title Configurable CDK Radio Group */ +/** @title Configurable Aria Toolbar Example */ @Component({ selector: 'toolbar-configurable-example', templateUrl: 'toolbar-configurable-example.html', styleUrl: '../toolbar-common.css', imports: [ - RadioGroup, - RadioButton, Toolbar, ToolbarWidget, + ToolbarWidgetGroup, + SimpleCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, MatCheckboxModule, MatFormFieldModule, MatSelectModule, @@ -24,23 +32,32 @@ import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar'; ], }) export class ToolbarConfigurableExample { - skipDisabled = new FormControl(false, {nonNullable: true}); - wrap = new FormControl(true, {nonNullable: true}); - toolbarDisabled = new FormControl(false, {nonNullable: true}); + wrap = true; + softDisabled = true; + toolbarDisabled = false; orientation: 'vertical' | 'horizontal' = 'horizontal'; - fruits = ['Apple', 'Apricot', 'Banana']; - buttonFruits = ['Pear', 'Blueberry', 'Cherry', 'Date']; + widgets = [ + 'Undo', + 'Redo', + 'Bold', + 'Italic', + 'Underline', + 'Text style', + 'Align left', + 'Align center', + 'Align right', + 'Checklist', + 'Bullet list', + 'Numbered list', + ]; - // Radio group controls - disabled = new FormControl(false, {nonNullable: true}); - readonly = new FormControl(false, {nonNullable: true}); + groups = ['Alignment options', 'List options']; - // Control for which radio options are individually disabled - disabledOptions: string[] = ['Banana']; - disabledButtonOptions: string[] = ['Pear']; + disabledGroups: string[] = []; + disabledWidgets: string[] = []; - test(x: String) { - console.log(x); + isDisabled(value: string) { + return this.disabledWidgets.includes(value) || this.disabledGroups.includes(value); } } diff --git a/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.html b/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.html new file mode 100644 index 000000000000..6e94bd79fae4 --- /dev/null +++ b/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.html @@ -0,0 +1,52 @@ +
                      +
                      +
                      + + +
                      + + + +
                      + + + +
                      + + + + + + + +
                      + + + +
                      + + + +
                      + + + +
                      +
                      +
                      diff --git a/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.ts b/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.ts new file mode 100644 index 000000000000..2d1c27e4863c --- /dev/null +++ b/src/components-examples/aria/toolbar/toolbar-hard-disabled/toolbar-hard-disabled-example.ts @@ -0,0 +1,25 @@ +import {Component} from '@angular/core'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; +import { + SimpleCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, +} from '../simple-toolbar'; + +/** @title Hard Disabled Toolbar Example */ +@Component({ + selector: 'toolbar-hard-disabled-example', + templateUrl: 'toolbar-hard-disabled-example.html', + styleUrl: '../toolbar-common.css', + imports: [ + Toolbar, + ToolbarWidget, + ToolbarWidgetGroup, + SimpleCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, + ], +}) +export class ToolbarHardDisabledExample {} diff --git a/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.html b/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.html new file mode 100644 index 000000000000..519f5e2eed1a --- /dev/null +++ b/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.html @@ -0,0 +1,41 @@ +
                      +
                      +
                      + + +
                      + + + +
                      + + + +
                      + + + + + + + +
                      + + + +
                      + + + +
                      + + + +
                      +
                      +
                      diff --git a/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.ts b/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.ts new file mode 100644 index 000000000000..ddfca5a98528 --- /dev/null +++ b/src/components-examples/aria/toolbar/toolbar-rtl/toolbar-rtl-example.ts @@ -0,0 +1,27 @@ +import {Component} from '@angular/core'; +import {Dir} from '@angular/cdk/bidi'; +import {Toolbar, ToolbarWidget, ToolbarWidgetGroup} from '@angular/aria/toolbar'; +import { + SimpleCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, +} from '../simple-toolbar'; + +/** @title Basic RTL Toolbar Example */ +@Component({ + selector: 'toolbar-rtl-example', + templateUrl: 'toolbar-rtl-example.html', + styleUrl: '../toolbar-common.css', + imports: [ + Dir, + Toolbar, + ToolbarWidget, + ToolbarWidgetGroup, + SimpleCombobox, + SimpleToolbarButton, + SimpleToolbarRadioButton, + SimpleToolbarToggleButton, + ], +}) +export class ToolbarRtlExample {} diff --git a/src/components-examples/aria/toolbar/toolbar-skip-disabled/toolbar-skip-disabled-example.html b/src/components-examples/aria/toolbar/toolbar-skip-disabled/toolbar-skip-disabled-example.html deleted file mode 100644 index b67a71410005..000000000000 --- a/src/components-examples/aria/toolbar/toolbar-skip-disabled/toolbar-skip-disabled-example.html +++ /dev/null @@ -1,48 +0,0 @@ -
                      -
                      - - - -
                        - @for (alignment of alignments; track alignment) { -
                      • - - {{ alignment.label }} -
                      • - } -
                      - -
                      -
                      diff --git a/src/components-examples/aria/toolbar/toolbar-skip-disabled/toolbar-skip-disabled-example.ts b/src/components-examples/aria/toolbar/toolbar-skip-disabled/toolbar-skip-disabled-example.ts deleted file mode 100644 index 827b85bc5442..000000000000 --- a/src/components-examples/aria/toolbar/toolbar-skip-disabled/toolbar-skip-disabled-example.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {Component} from '@angular/core'; -import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar'; -import {RadioGroup, RadioButton} from '@angular/aria/radio-group'; -import {LiveAnnouncer} from '@angular/cdk/a11y'; - -/** @title Skip Disabled Toolbar Example */ -@Component({ - selector: 'toolbar-skip-disabled-example', - templateUrl: 'toolbar-skip-disabled-example.html', - styleUrl: '../toolbar-common.css', - imports: [RadioButton, RadioGroup, Toolbar, ToolbarWidget], -}) -export class ToolbarSkipDisabledExample { - constructor(private _liveAnnouncer: LiveAnnouncer) {} - alignments = [ - {value: 'left', label: 'Left'}, - {value: 'center', label: 'Center'}, - {value: 'right', label: 'Right'}, - ]; - - // Control for which radio options are individually disabled - disabledOptions: string[] = ['center']; - - format(tool: string) { - console.log(`Tool activated: ${tool}`); - this._liveAnnouncer.announce(`${tool} applied`, 'polite'); - } - test(action: string) { - console.log(`Action triggered: ${action}`); - this._liveAnnouncer.announce(`${action} button activated`, 'polite'); - } -} diff --git a/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.html b/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.html index 7a308eabebc9..9fbccf4943c4 100644 --- a/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.html +++ b/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.html @@ -2,7 +2,7 @@ Wrap Multi Disabled - Skip Disabled + Soft Disabled Nav Mode @@ -34,9 +34,9 @@ [selectionMode]="selectionMode" [focusMode]="focusMode" [wrap]="wrap.value" - [skipDisabled]="skipDisabled.value" + [softDisabled]="softDisabled.value" [nav]="nav.value" - [(value)]="selectedValues" + [(values)]="selectedValues" #tree="ngTree" > @for (node of nodes; track node.value) { -
                    • - - - {{ node.name }} - -
                    • + @if (nav.value) { +
                    • + + + + {{ node.name }} + + +
                    • - @if (node.children) { -
                        - - - -
                      - } + @if (node.children) { +
                        + + + +
                      + } + } @else { +
                    • + + + {{ node.name }} + +
                    • + + @if (node.children) { +
                        + + + +
                      + } + } }
                      diff --git a/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.ts b/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.ts index 1cb048a9c954..d193eb1cdffb 100644 --- a/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.ts +++ b/src/components-examples/aria/tree/tree-configurable/tree-configurable-example.ts @@ -40,7 +40,7 @@ export class TreeConfigurableExample { multi = new FormControl(false, {nonNullable: true}); disabled = new FormControl(false, {nonNullable: true}); wrap = new FormControl(true, {nonNullable: true}); - skipDisabled = new FormControl(true, {nonNullable: true}); + softDisabled = new FormControl(true, {nonNullable: true}); nav = new FormControl(false, {nonNullable: true}); selectedValues = model(['package.json']); diff --git a/src/components-examples/aria/tree/tree-data.ts b/src/components-examples/aria/tree/tree-data.ts index 8436794a43b8..a515bda22e41 100644 --- a/src/components-examples/aria/tree/tree-data.ts +++ b/src/components-examples/aria/tree/tree-data.ts @@ -10,6 +10,7 @@ export type TreeNode = { value: string; children?: TreeNode[]; disabled?: boolean; + expanded?: boolean; }; export const NODES: TreeNode[] = [ @@ -21,6 +22,7 @@ export const NODES: TreeNode[] = [ {name: 'favicon.ico', value: 'public/favicon.ico'}, {name: 'styles.css', value: 'public/styles.css'}, ], + expanded: false, }, { name: 'src', @@ -34,25 +36,33 @@ export const NODES: TreeNode[] = [ {name: 'app.module.ts', value: 'src/app/app.module.ts', disabled: true}, {name: 'app.css', value: 'src/app/app.css'}, ], + expanded: false, }, { name: 'assets', value: 'src/assets', children: [{name: 'logo.png', value: 'src/assets/logo.png'}], + expanded: false, }, { name: 'environments', value: 'src/environments', children: [ - {name: 'environment.prod.ts', value: 'src/environments/environment.prod.ts'}, + { + name: 'environment.prod.ts', + value: 'src/environments/environment.prod.ts', + expanded: false, + }, {name: 'environment.ts', value: 'src/environments/environment.ts'}, ], + expanded: false, }, {name: 'main.ts', value: 'src/main.ts'}, {name: 'polyfills.ts', value: 'src/polyfills.ts'}, {name: 'styles.css', value: 'src/styles.css', disabled: true}, {name: 'test.ts', value: 'src/test.ts'}, ], + expanded: false, }, {name: 'angular.json', value: 'angular.json'}, {name: 'package.json', value: 'package.json'}, diff --git a/src/components-examples/aria/tree/tree-disabled-focusable/tree-disabled-focusable-example.html b/src/components-examples/aria/tree/tree-disabled-focusable/tree-disabled-focusable-example.html index 25346aa88698..f899c831e099 100644 --- a/src/components-examples/aria/tree/tree-disabled-focusable/tree-disabled-focusable-example.html +++ b/src/components-examples/aria/tree/tree-disabled-focusable/tree-disabled-focusable-example.html @@ -1,4 +1,4 @@ -
                        +
                          +
                            + + + diff --git a/src/components-examples/material/list/list-action/list-action-example.ts b/src/components-examples/material/list/list-action/list-action-example.ts new file mode 100644 index 000000000000..c095b77e9d9a --- /dev/null +++ b/src/components-examples/material/list/list-action/list-action-example.ts @@ -0,0 +1,16 @@ +import {Component} from '@angular/core'; +import {MatListModule} from '@angular/material/list'; + +/** + * @title Action list + */ +@Component({ + selector: 'list-action-example', + templateUrl: 'list-action-example.html', + imports: [MatListModule], +}) +export class ListActionExample { + action(task: string) { + window.alert(task); + } +} diff --git a/src/components-examples/material/list/list-avatar/list-avatar-example.html b/src/components-examples/material/list/list-avatar/list-avatar-example.html new file mode 100644 index 000000000000..2e4b6dbfc7b3 --- /dev/null +++ b/src/components-examples/material/list/list-avatar/list-avatar-example.html @@ -0,0 +1,11 @@ + + + + Shiba Inu + + + + + Other Shiba Inu + + diff --git a/src/components-examples/material/list/list-avatar/list-avatar-example.ts b/src/components-examples/material/list/list-avatar/list-avatar-example.ts new file mode 100644 index 000000000000..d2081b9bbc6d --- /dev/null +++ b/src/components-examples/material/list/list-avatar/list-avatar-example.ts @@ -0,0 +1,12 @@ +import {Component} from '@angular/core'; +import {MatListModule} from '@angular/material/list'; + +/** + * @title Selection list with avatars + */ +@Component({ + selector: 'list-avatar-example', + templateUrl: 'list-avatar-example.html', + imports: [MatListModule], +}) +export class ListAvatarExample {} diff --git a/src/components-examples/material/list/list-navigation/list-navigation-example.html b/src/components-examples/material/list/list-navigation/list-navigation-example.html new file mode 100644 index 000000000000..f371e070827d --- /dev/null +++ b/src/components-examples/material/list/list-navigation/list-navigation-example.html @@ -0,0 +1,27 @@ + + @for (link of fragments; track link) { + {{link | titlecase}} + } + +

                            List with icons

                            + @for (link of fragments; track link) { + + folder + {{ link | titlecase}} +
                            + {{link}} +
                            +
                            + } +
                            diff --git a/src/components-examples/material/list/list-navigation/list-navigation-example.ts b/src/components-examples/material/list/list-navigation/list-navigation-example.ts new file mode 100644 index 000000000000..3a37f23f192d --- /dev/null +++ b/src/components-examples/material/list/list-navigation/list-navigation-example.ts @@ -0,0 +1,17 @@ +import {TitleCasePipe} from '@angular/common'; +import {Component} from '@angular/core'; +import {MatIconModule} from '@angular/material/icon'; +import {MatListModule} from '@angular/material/list'; + +/** + * @title Navigation list + */ +@Component({ + selector: 'list-navigation-example', + templateUrl: 'list-navigation-example.html', + imports: [MatListModule, MatIconModule, TitleCasePipe], +}) +export class ListNavigationExample { + fragments = ['inbox', 'outbox', 'drafts']; + activeLink: string | null = null; +} diff --git a/src/dev-app/BUILD.bazel b/src/dev-app/BUILD.bazel index 041ce095f9c9..3b10fb1d0a62 100644 --- a/src/dev-app/BUILD.bazel +++ b/src/dev-app/BUILD.bazel @@ -30,7 +30,6 @@ ng_project( "//src/dev-app/aria-grid", "//src/dev-app/aria-listbox", "//src/dev-app/aria-menu", - "//src/dev-app/aria-radio-group", "//src/dev-app/aria-tabs", "//src/dev-app/aria-toolbar", "//src/dev-app/aria-tree", diff --git a/src/dev-app/aria-combobox/combobox-demo.css b/src/dev-app/aria-combobox/combobox-demo.css index 8233ba47f19b..607c068d07ef 100644 --- a/src/dev-app/aria-combobox/combobox-demo.css +++ b/src/dev-app/aria-combobox/combobox-demo.css @@ -1,24 +1,22 @@ -.example-combobox-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); - gap: 20px; - justify-items: center; +.example-combobox-row { + display: flex; + gap: 20px; } .example-combobox-container { - display: flex; - flex-direction: column; - justify-content: flex-start; - - /* stylelint-disable material/no-prefixes */ - width: fit-content; -} - -.example-configurable-combobox-container { - padding-top: 40px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + min-width: 350px; + padding: 20px 0; } h2 { - font-size: 1.1rem; + font-size: 1.5rem; + padding-top: 20px; } +h3 { + font-size: 1rem; +} diff --git a/src/dev-app/aria-combobox/combobox-demo.html b/src/dev-app/aria-combobox/combobox-demo.html index 2a5bd48182a0..01048429e8d6 100644 --- a/src/dev-app/aria-combobox/combobox-demo.html +++ b/src/dev-app/aria-combobox/combobox-demo.html @@ -1,33 +1,72 @@
                            -
                            +

                            Listbox autocomplete examples

                            + +
                            -

                            Combobox with manual filtering

                            +

                            Combobox with manual filtering

                            -

                            Combobox with auto-select filtering

                            +

                            Combobox with auto-select filtering

                            -

                            Combobox with highlight filtering

                            +

                            Combobox with highlight filtering

                            -

                            Combobox with tree popup and manual filtering

                            +

                            Disabled combobox

                            + +
                            +
                            + +

                            Tree autocomplete examples

                            + +
                            +
                            +

                            Combobox with tree popup and manual filtering

                            -

                            Combobox with tree popup and auto-select filtering

                            +

                            Combobox with tree popup and auto-select filtering

                            -

                            Combobox with tree popup and highlight filtering

                            +

                            Combobox with tree popup and highlight filtering

                            + +

                            Select examples

                            + +
                            +
                            +

                            Readonly Combobox

                            + +
                            + +
                            +

                            Readonly Multiselect Combobox

                            + +
                            + +
                            +

                            Disabled Readonly Combobox

                            + +
                            +
                            + +

                            Combobox with dialog popup

                            + +
                            +
                            +

                            Combobox with dialog popup

                            + +
                            +
                            diff --git a/src/dev-app/aria-combobox/combobox-demo.ts b/src/dev-app/aria-combobox/combobox-demo.ts index a013d1dc1f5f..efb033b6a777 100644 --- a/src/dev-app/aria-combobox/combobox-demo.ts +++ b/src/dev-app/aria-combobox/combobox-demo.ts @@ -7,9 +7,14 @@ */ import { + ComboboxDialogExample, ComboboxAutoSelectExample, ComboboxHighlightExample, ComboboxManualExample, + ComboboxDisabledExample, + ComboboxReadonlyExample, + ComboboxReadonlyMultiselectExample, + ComboboxReadonlyDisabledExample, ComboboxTreeAutoSelectExample, ComboboxTreeHighlightExample, ComboboxTreeManualExample, @@ -20,9 +25,14 @@ import {ChangeDetectionStrategy, Component} from '@angular/core'; templateUrl: 'combobox-demo.html', styleUrl: 'combobox-demo.css', imports: [ + ComboboxDialogExample, ComboboxManualExample, ComboboxAutoSelectExample, ComboboxHighlightExample, + ComboboxDisabledExample, + ComboboxReadonlyExample, + ComboboxReadonlyMultiselectExample, + ComboboxReadonlyDisabledExample, ComboboxTreeManualExample, ComboboxTreeAutoSelectExample, ComboboxTreeHighlightExample, diff --git a/src/dev-app/aria-grid/grid-demo.css b/src/dev-app/aria-grid/grid-demo.css index b1c7a60608d0..1e8e47023ec1 100644 --- a/src/dev-app/aria-grid/grid-demo.css +++ b/src/dev-app/aria-grid/grid-demo.css @@ -1,17 +1,7 @@ -.demo-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr)); - gap: 20px; -} - .demo-grid-container { display: flex; flex-direction: column; - justify-content: flex-start; -} - -.demo-configurable-grid-container { - padding-top: 40px; + gap: 40px; } h2 { diff --git a/src/dev-app/aria-grid/grid-demo.html b/src/dev-app/aria-grid/grid-demo.html index f528a4a50e45..2055188cac14 100644 --- a/src/dev-app/aria-grid/grid-demo.html +++ b/src/dev-app/aria-grid/grid-demo.html @@ -1,11 +1,20 @@ -
                            -
                            +
                            +

                            Grid Pill List

                            -
                            -
                            -
                            + +
                            +

                            Grid Calendar

                            + +
                            + +
                            +

                            Grid Table

                            + +
                            + +

                            Configurable

                            diff --git a/src/dev-app/aria-grid/grid-demo.ts b/src/dev-app/aria-grid/grid-demo.ts index 1f415d8153aa..6bebfb9a10b4 100644 --- a/src/dev-app/aria-grid/grid-demo.ts +++ b/src/dev-app/aria-grid/grid-demo.ts @@ -7,11 +7,16 @@ */ import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; -import {GridConfigurableExample, GridPillListExample} from '@angular/components-examples/aria/grid'; +import { + GridConfigurableExample, + GridPillListExample, + GridCalendarExample, + GridTableExample, +} from '@angular/components-examples/aria/grid'; @Component({ templateUrl: 'grid-demo.html', - imports: [GridConfigurableExample, GridPillListExample], + imports: [GridConfigurableExample, GridPillListExample, GridCalendarExample, GridTableExample], styleUrl: 'grid-demo.css', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/dev-app/aria-menu/menu-demo.html b/src/dev-app/aria-menu/menu-demo.html index eb81a19febec..fd8e433f3a13 100644 --- a/src/dev-app/aria-menu/menu-demo.html +++ b/src/dev-app/aria-menu/menu-demo.html @@ -5,16 +5,36 @@

                            Menu Bar Example

                            +
                            +

                            Menu Bar RTL Example

                            + +
                            + +
                            +

                            Disabled Menu Bar Example

                            + +
                            +

                            Menu Trigger Example

                            +
                            +

                            Disabled Menu Trigger Example

                            + +
                            +

                            Standalone Menu Example

                            +
                            +

                            Disabled Standalone Menu Example

                            + +
                            +

                            Context Menu Example

                            diff --git a/src/dev-app/aria-menu/menu-demo.ts b/src/dev-app/aria-menu/menu-demo.ts index 7303be1bb923..b9ec74d10240 100644 --- a/src/dev-app/aria-menu/menu-demo.ts +++ b/src/dev-app/aria-menu/menu-demo.ts @@ -9,9 +9,13 @@ import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; import { MenuBarExample, + MenuBarRTLExample, + MenuBarDisabledExample, MenuContextExample, MenuTriggerExample, MenuStandaloneExample, + MenuStandaloneDisabledExample, + MenuTriggerDisabledExample, } from '@angular/components-examples/aria/menu'; @Component({ @@ -19,6 +23,15 @@ import { styleUrl: 'menu-demo.css', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, - imports: [MenuBarExample, MenuContextExample, MenuTriggerExample, MenuStandaloneExample], + imports: [ + MenuBarExample, + MenuBarRTLExample, + MenuBarDisabledExample, + MenuContextExample, + MenuTriggerExample, + MenuTriggerDisabledExample, + MenuStandaloneExample, + MenuStandaloneDisabledExample, + ], }) export class MenuDemo {} diff --git a/src/dev-app/aria-radio-group/BUILD.bazel b/src/dev-app/aria-radio-group/BUILD.bazel deleted file mode 100644 index f9824cfb7fab..000000000000 --- a/src/dev-app/aria-radio-group/BUILD.bazel +++ /dev/null @@ -1,16 +0,0 @@ -load("//tools:defaults.bzl", "ng_project") - -package(default_visibility = ["//visibility:public"]) - -ng_project( - name = "aria-radio-group", - srcs = glob(["**/*.ts"]), - assets = [ - "radio-group-demo.html", - ":radio-group-demo.css", - ], - deps = [ - "//:node_modules/@angular/core", - "//src/components-examples/aria/radio-group", - ], -) diff --git a/src/dev-app/aria-radio-group/radio-group-demo.css b/src/dev-app/aria-radio-group/radio-group-demo.css deleted file mode 100644 index 65db334e5c3f..000000000000 --- a/src/dev-app/aria-radio-group/radio-group-demo.css +++ /dev/null @@ -1,20 +0,0 @@ -.example-radio-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(500px, 1fr)); - gap: 20px; -} - -.example-radio-container { - width: 500px; - display: flex; - flex-direction: column; - justify-content: flex-start; -} - -.example-configurable-radio-container { - padding-top: 40px; -} - -h2 { - height: 36px; -} diff --git a/src/dev-app/aria-radio-group/radio-group-demo.html b/src/dev-app/aria-radio-group/radio-group-demo.html deleted file mode 100644 index 43adee741b9d..000000000000 --- a/src/dev-app/aria-radio-group/radio-group-demo.html +++ /dev/null @@ -1,48 +0,0 @@ -
                            -
                            -
                            -

                            Standard

                            - -
                            - -
                            -

                            Horizontal Orientation

                            - -
                            - -
                            -

                            RTL & Horizontal Orientation

                            - -
                            - -
                            -

                            Active Descendant

                            - -
                            - -
                            -

                            Disabled Radio Buttons are Focusable

                            - -
                            - -
                            -

                            Disabled Radio Buttons are Skipped

                            - -
                            - -
                            -

                            Readonly

                            - -
                            - -
                            -

                            Disabled

                            - -
                            -
                            - -
                            -

                            Configurable

                            - -
                            -
                            diff --git a/src/dev-app/aria-radio-group/radio-group-demo.ts b/src/dev-app/aria-radio-group/radio-group-demo.ts deleted file mode 100644 index 27dd3f929ed2..000000000000 --- a/src/dev-app/aria-radio-group/radio-group-demo.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.dev/license - */ - -import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core'; -import { - RadioGroupConfigurableExample, - RadioGroupStandardExample, - RadioGroupHorizontalExample, - RadioGroupRtlHorizontalExample, - RadioGroupActiveDescendantExample, - RadioGroupDisabledFocusableExample, - RadioGroupDisabledSkippedExample, - RadioGroupReadonlyExample, - RadioGroupDisabledExample, -} from '@angular/components-examples/aria/radio-group'; - -@Component({ - templateUrl: 'radio-group-demo.html', - imports: [ - RadioGroupStandardExample, - RadioGroupHorizontalExample, - RadioGroupRtlHorizontalExample, - RadioGroupActiveDescendantExample, - RadioGroupDisabledFocusableExample, - RadioGroupDisabledSkippedExample, - RadioGroupReadonlyExample, - RadioGroupDisabledExample, - RadioGroupConfigurableExample, - ], - styleUrl: './radio-group-demo.css', - encapsulation: ViewEncapsulation.None, - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class RadioGroupDemo {} diff --git a/src/dev-app/aria-toolbar/toolbar-demo.html b/src/dev-app/aria-toolbar/toolbar-demo.html index 09b493315371..3a309bf5e0ad 100644 --- a/src/dev-app/aria-toolbar/toolbar-demo.html +++ b/src/dev-app/aria-toolbar/toolbar-demo.html @@ -9,8 +9,12 @@

                            Toolbar Basic Vertical

                            -

                            Toolbar Skip Disabled

                            - +

                            Toolbar with Hard Disabled Items

                            + +
                            +
                            +

                            Toolbar RTL

                            +

                            Configurable CDK Toolbar

                            diff --git a/src/dev-app/aria-toolbar/toolbar-demo.ts b/src/dev-app/aria-toolbar/toolbar-demo.ts index 535db0baa8c7..7f133355b95f 100644 --- a/src/dev-app/aria-toolbar/toolbar-demo.ts +++ b/src/dev-app/aria-toolbar/toolbar-demo.ts @@ -11,7 +11,8 @@ import { ToolbarBasicHorizontalExample, ToolbarBasicVerticalExample, ToolbarConfigurableExample, - ToolbarSkipDisabledExample, + ToolbarRtlExample, + ToolbarHardDisabledExample, } from '@angular/components-examples/aria/toolbar'; @Component({ @@ -20,7 +21,8 @@ import { ToolbarBasicHorizontalExample, ToolbarBasicVerticalExample, ToolbarConfigurableExample, - ToolbarSkipDisabledExample, + ToolbarRtlExample, + ToolbarHardDisabledExample, ], styleUrl: './toolbar-demo.css', encapsulation: ViewEncapsulation.None, diff --git a/src/dev-app/common-classes.css b/src/dev-app/common-classes.css index 768f4e57708a..7da4d3c95c5b 100644 --- a/src/dev-app/common-classes.css +++ b/src/dev-app/common-classes.css @@ -68,7 +68,6 @@ [aria-disabled='true'] .example-selectable:focus-within, [aria-activedescendant] .example-selectable:focus-within { outline-color: transparent; - border-radius: 0; } [aria-disabled='true'] .example-selectable[aria-selected='true'], diff --git a/src/dev-app/dev-app/dev-app-layout.ts b/src/dev-app/dev-app/dev-app-layout.ts index a459ac767f14..a4db2af3a141 100644 --- a/src/dev-app/dev-app/dev-app-layout.ts +++ b/src/dev-app/dev-app/dev-app-layout.ts @@ -67,7 +67,6 @@ export class DevAppLayout { {name: 'Aria Grid', route: '/aria-grid'}, {name: 'Aria Listbox', route: '/aria-listbox'}, {name: 'Aria Menu', route: '/aria-menu'}, - {name: 'Aria Radio Group', route: '/aria-radio-group'}, {name: 'Aria Tabs', route: '/aria-tabs'}, {name: 'Aria Toolbar', route: '/aria-toolbar'}, {name: 'Aria Tree', route: '/aria-tree'}, diff --git a/src/dev-app/routes.ts b/src/dev-app/routes.ts index f24d0e51258b..e823b79b68a3 100644 --- a/src/dev-app/routes.ts +++ b/src/dev-app/routes.ts @@ -56,10 +56,6 @@ export const DEV_APP_ROUTES: Routes = [ path: 'aria-menu', loadComponent: () => import('./aria-menu/menu-demo').then(m => m.MenuDemo), }, - { - path: 'aria-radio-group', - loadComponent: () => import('./aria-radio-group/radio-group-demo').then(m => m.RadioGroupDemo), - }, { path: 'aria-tabs', loadComponent: () => import('./aria-tabs/tabs-demo').then(m => m.TabsDemo), diff --git a/src/dev-app/system/system-demo.ts b/src/dev-app/system/system-demo.ts index ba611cda5fbd..c3e0490359dd 100644 --- a/src/dev-app/system/system-demo.ts +++ b/src/dev-app/system/system-demo.ts @@ -1,4 +1,3 @@ -import {CommonModule} from '@angular/common'; import {ChangeDetectionStrategy, Component} from '@angular/core'; import {MatCardModule} from '@angular/material/card'; @@ -30,7 +29,7 @@ interface ColorGroup { selector: 'system-demo', templateUrl: 'system-demo.html', styleUrls: ['system-demo.css'], - imports: [CommonModule, MatCardModule], + imports: [MatCardModule], changeDetection: ChangeDetectionStrategy.OnPush, }) export class SystemDemo { diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts index 486c7a60728a..33594e257548 100644 --- a/src/material/autocomplete/autocomplete-trigger.ts +++ b/src/material/autocomplete/autocomplete-trigger.ts @@ -904,7 +904,8 @@ export class MatAutocompleteTrigger this._getConnectedElement(), ) .withFlexibleDimensions(false) - .withPush(false); + .withPush(false) + .withPopoverLocation('inline'); this._setStrategyPositions(strategy); this._positionStrategy = strategy; diff --git a/src/material/autocomplete/autocomplete.scss b/src/material/autocomplete/autocomplete.scss index b3d3be79f849..d2ee490bfa08 100644 --- a/src/material/autocomplete/autocomplete.scss +++ b/src/material/autocomplete/autocomplete.scss @@ -43,11 +43,12 @@ div.mat-mdc-autocomplete-panel { &.mat-mdc-autocomplete-visible { visibility: visible; } +} - &.mat-mdc-autocomplete-hidden { - visibility: hidden; - pointer-events: none; - } +div.mat-mdc-autocomplete-panel.mat-mdc-autocomplete-hidden, +.cdk-overlay-pane:has(> .mat-mdc-autocomplete-hidden) { + visibility: hidden; + pointer-events: none; } @keyframes _mat-autocomplete-enter { diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index c335f8e85b27..bcec42255607 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -62,6 +62,7 @@ import { } from './index'; describe('MatAutocomplete', () => { + const supportsPopover = 'showPopover' in document.body; let overlayContainerElement: HTMLElement; // Creates a test component fixture. @@ -77,6 +78,19 @@ describe('MatAutocomplete', () => { return TestBed.createComponent(component); } + function getOverlayHost(fixture: ComponentFixture): HTMLElement | null { + return supportsPopover + ? fixture.nativeElement.querySelector('.cdk-overlay-popover') + : overlayContainerElement.querySelector('.cdk-overlay-connected-position-bounding-box'); + } + + function getBackdrop(fixture: ComponentFixture): HTMLElement | null { + const selector = '.cdk-overlay-backdrop'; + return supportsPopover + ? getOverlayHost(fixture)?.querySelector(selector) || null + : overlayContainerElement.querySelector(selector); + } + describe('panel toggling', () => { let fixture: ComponentFixture; let input: HTMLInputElement; @@ -98,10 +112,10 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read open when input is focused.`) .toBe(true); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when input is focused.`) .toContain('Alabama'); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when input is focused.`) .toContain('California'); }); @@ -147,10 +161,10 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read open when opened programmatically.`) .toBe(true); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when opened programmatically.`) .toContain('Alabama'); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when opened programmatically.`) .toContain('California'); }); @@ -165,9 +179,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking outside the panel to set its state to closed.`) .toBe(false); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)) .withContext(`Expected clicking outside the panel to close the panel.`) - .toEqual(''); + .toBeFalsy(); })); it('should close the panel when the user clicks away via auxiliary button', waitForAsync(async () => { @@ -180,9 +194,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking outside the panel to set its state to closed.`) .toBe(false); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)) .withContext(`Expected clicking outside the panel to close the panel.`) - .toEqual(''); + .toBeFalsy(); })); it('should close the panel when the user taps away on a touch device', fakeAsync(() => { @@ -194,9 +208,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected tapping outside the panel to set its state to closed.`) .toBe(false); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)) .withContext(`Expected tapping outside the panel to close the panel.`) - .toEqual(''); + .toBeFalsy(); })); it('should close the panel when an option is clicked', waitForAsync(async () => { @@ -204,7 +218,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); await new Promise(r => setTimeout(r)); @@ -212,9 +226,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking an option to set the panel state to closed.`) .toBe(false); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)) .withContext(`Expected clicking an option to close the panel.`) - .toEqual(''); + .toBeFalsy(); })); it('should close the panel when a newly created option is clicked', waitForAsync(async () => { @@ -227,7 +241,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - let options = overlayContainerElement.querySelectorAll( + let options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[0].click(); @@ -240,7 +254,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; options[1].click(); fixture.detectChanges(); await new Promise(r => setTimeout(r)); @@ -248,9 +262,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected clicking a new option to set the panel state to closed.`) .toBe(false); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)) .withContext(`Expected clicking a new option to close the panel.`) - .toEqual(''); + .toBeFalsy(); })); it('should close the panel programmatically', fakeAsync(() => { @@ -264,9 +278,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected closing programmatically to set the panel state to closed.`) .toBe(false); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)) .withContext(`Expected closing programmatically to close the panel.`) - .toEqual(''); + .toBeFalsy(); })); it('should not throw when attempting to close the panel of a destroyed autocomplete', () => { @@ -283,7 +297,7 @@ describe('MatAutocomplete', () => { dispatchFakeEvent(input, 'focusin'); fixture.detectChanges(); - const panel = overlayContainerElement.querySelector( + const panel = getOverlayHost(fixture)!.querySelector( '.mat-mdc-autocomplete-panel', ) as HTMLElement; @@ -311,7 +325,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[1].click(); @@ -349,7 +363,7 @@ describe('MatAutocomplete', () => { flush(); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[1].click(); @@ -373,7 +387,7 @@ describe('MatAutocomplete', () => { flush(); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[1].click(); @@ -390,7 +404,7 @@ describe('MatAutocomplete', () => { tick(); fixture.detectChanges(); - expect(overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!.classList) + expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) .withContext('Expected panel to be visible.') .toContain('mat-mdc-autocomplete-visible'); @@ -399,7 +413,7 @@ describe('MatAutocomplete', () => { tick(); fixture.detectChanges(); - expect(overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!.classList) + expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) .withContext('Expected panel to be hidden.') .toContain('mat-mdc-autocomplete-hidden'); @@ -415,7 +429,7 @@ describe('MatAutocomplete', () => { tick(); fixture.detectChanges(); - expect(overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!.classList) + expect(getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!.classList) .withContext('Expected panel to be visible.') .toContain('mat-mdc-autocomplete-visible'); })); @@ -639,10 +653,7 @@ describe('MatAutocomplete', () => { rtlFixture.componentInstance.trigger.openPanel(); rtlFixture.detectChanges(); - const boundingBox = overlayContainerElement.querySelector( - '.cdk-overlay-connected-position-bounding-box', - )!; - expect(boundingBox.getAttribute('dir')).toEqual('rtl'); + expect(getOverlayHost(rtlFixture)?.getAttribute('dir')).toEqual('rtl'); }); it('should update the panel direction if it changes for the trigger', () => { @@ -653,10 +664,7 @@ describe('MatAutocomplete', () => { rtlFixture.componentInstance.trigger.openPanel(); rtlFixture.detectChanges(); - let boundingBox = overlayContainerElement.querySelector( - '.cdk-overlay-connected-position-bounding-box', - )!; - expect(boundingBox.getAttribute('dir')).toEqual('rtl'); + expect(getOverlayHost(rtlFixture)?.getAttribute('dir')).toEqual('rtl'); rtlFixture.componentInstance.trigger.closePanel(); rtlFixture.detectChanges(); @@ -665,10 +673,7 @@ describe('MatAutocomplete', () => { rtlFixture.componentInstance.trigger.openPanel(); rtlFixture.detectChanges(); - boundingBox = overlayContainerElement.querySelector( - '.cdk-overlay-connected-position-bounding-box', - )!; - expect(boundingBox.getAttribute('dir')).toEqual('ltr'); + expect(getOverlayHost(rtlFixture)?.getAttribute('dir')).toEqual('ltr'); }); it('should be able to set a custom value for the `autocomplete` attribute', () => { @@ -700,7 +705,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); // Select an option and reopen the panel. - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); await new Promise(r => setTimeout(r)); fixture.detectChanges(); @@ -731,7 +736,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); // Select an option and reopen the panel. - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); await new Promise(r => setTimeout(r)); fixture.detectChanges(); @@ -799,7 +804,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[1].click(); @@ -815,7 +820,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[1].click(); @@ -836,7 +841,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[1].click(); @@ -857,7 +862,7 @@ describe('MatAutocomplete', () => { fixture.changeDetectorRef.markForCheck(); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[1].click(); @@ -898,7 +903,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; const clickedOption = options[0]; @@ -965,7 +970,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[1].click(); @@ -1037,7 +1042,7 @@ describe('MatAutocomplete', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - const panel = overlayContainerElement.querySelector( + const panel = getOverlayHost(fixture)!.querySelector( '.mat-mdc-autocomplete-panel', )! as HTMLElement; expect(panel.classList).toContain('mat-warn'); @@ -1078,17 +1083,17 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to stay open when DOWN key is pressed.`) .toBe(true); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to keep displaying when DOWN key is pressed.`) .toContain('Alabama'); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to keep displaying when DOWN key is pressed.`) .toContain('California'); }); it('should set the active item to the first option when DOWN key is pressed', () => { const componentInstance = fixture.componentInstance; - const optionEls = overlayContainerElement.querySelectorAll( + const optionEls = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -1117,7 +1122,7 @@ describe('MatAutocomplete', () => { it('should set the active item to the last option when UP key is pressed', () => { const componentInstance = fixture.componentInstance; - const optionEls = overlayContainerElement.querySelectorAll( + const optionEls = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -1160,7 +1165,7 @@ describe('MatAutocomplete', () => { componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT); fixture.detectChanges(); - const optionEls = overlayContainerElement.querySelectorAll( + const optionEls = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -1272,9 +1277,9 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read closed after ENTER key.`) .toBe(false); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)) .withContext(`Expected panel to close after ENTER key.`) - .toEqual(''); + .toBeFalsy(); dispatchFakeEvent(input, 'focusin'); clearElement(input); @@ -1285,7 +1290,7 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read open when typing in input.`) .toBe(true); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when typing in input.`) .toContain('Alabama'); })); @@ -1511,7 +1516,7 @@ describe('MatAutocomplete', () => { input.focus(); flush(); - expect(overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')) + expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-autocomplete-panel')) .withContext('Expected panel to be rendered.') .toBeTruthy(); @@ -1519,7 +1524,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); tick(); - expect(overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')) + expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-autocomplete-panel')) .withContext('Expected panel to be removed.') .toBeFalsy(); })); @@ -1979,7 +1984,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; // Focus the option manually since the synthetic click may not do it. option.focus(); @@ -2015,7 +2020,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); const inputBottom = inputReference.getBoundingClientRect().bottom; - const panel = overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!; + const panel = getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!; const panelTop = panel.getBoundingClientRect().top; expect(Math.floor(inputBottom)) @@ -2048,7 +2053,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); const inputBottom = inputReference.getBoundingClientRect().bottom; - const panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + const panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; const panelTop = panel.getBoundingClientRect().top; expect(Math.floor(inputBottom)) @@ -2074,7 +2079,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); const inputTop = inputReference.getBoundingClientRect().top; - const panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + const panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; const panelBottom = panel.getBoundingClientRect().bottom; expect(Math.floor(inputTop)) @@ -2104,7 +2109,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - let panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + let panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; let initialPanelHeight = panel.getBoundingClientRect().height; fixture.componentInstance.trigger.closePanel(); @@ -2120,7 +2125,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; expect(panel.getBoundingClientRect().height).toBeGreaterThan(initialPanelHeight); })); @@ -2145,7 +2150,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); const inputTop = inputReference.getBoundingClientRect().top; - const panel = overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!; + const panel = getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!; const panelBottom = panel.getBoundingClientRect().bottom; expect(Math.floor(inputTop)) @@ -2175,7 +2180,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); fixture.detectChanges(); - let panel = overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!; + let panel = getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!; let inputRect = inputReference.getBoundingClientRect(); let panelRect = panel.getBoundingClientRect(); @@ -2226,7 +2231,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); const inputBottom = inputReference.getBoundingClientRect().bottom; - const panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + const panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; const panelTop = panel.getBoundingClientRect().top; expect(Math.floor(inputBottom)) @@ -2253,7 +2258,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); const inputTop = inputReference.getBoundingClientRect().top; - const panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + const panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; const panelBottom = panel.getBoundingClientRect().bottom; expect(Math.floor(inputTop)) @@ -2280,7 +2285,7 @@ describe('MatAutocomplete', () => { await openPanel(); let inputRect = inputReference.getBoundingClientRect(); - let panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + let panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; let panelRect = panel.getBoundingClientRect(); expect(Math.floor(inputRect.top)) @@ -2297,7 +2302,7 @@ describe('MatAutocomplete', () => { await openPanel(); inputRect = inputReference.getBoundingClientRect(); - panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; panelRect = panel.getBoundingClientRect(); expect(Math.floor(inputRect.bottom)) @@ -2320,7 +2325,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - let options = overlayContainerElement.querySelectorAll( + let options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[0].click(); @@ -2337,7 +2342,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; options[1].click(); fixture.detectChanges(); @@ -2354,7 +2359,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - let options = overlayContainerElement.querySelectorAll( + let options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[0].click(); @@ -2373,7 +2378,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; options[1].click(); fixture.detectChanges(); @@ -2389,7 +2394,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); fixture.detectChanges(); - expect(overlayContainerElement.querySelectorAll('mat-option')[0].classList) + expect(getOverlayHost(fixture)!.querySelectorAll('mat-option')[0].classList) .withContext('Expected first option to be highlighted.') .toContain('mat-mdc-option-active'); })); @@ -2407,7 +2412,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); fixture.detectChanges(); - expect(overlayContainerElement.querySelectorAll('mat-option')[2].classList) + expect(getOverlayHost(fixture)!.querySelectorAll('mat-option')[2].classList) .withContext('Expected third option to be highlighted.') .toContain('mat-mdc-option-active'); }), @@ -2424,7 +2429,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); fixture.detectChanges(); - const selectedOptions = overlayContainerElement.querySelectorAll( + const selectedOptions = getOverlayHost(fixture)!.querySelectorAll( 'mat-option.mat-mdc-option-active', ); expect(selectedOptions.length).withContext('expected no options to be active').toBe(0); @@ -2469,7 +2474,7 @@ describe('MatAutocomplete', () => { // Note: should not have a detectChanges call here // in order for the test to fail when it's supposed to. - expect(overlayContainerElement.querySelectorAll('mat-option')[0].classList) + expect(getOverlayHost(fixture)!.querySelectorAll('mat-option')[0].classList) .withContext('Expected first option to be highlighted.') .toContain('mat-mdc-option-active'); })); @@ -2487,7 +2492,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); fixture.detectChanges(); - expect(overlayContainerElement.querySelectorAll('mat-option')[0].classList) + expect(getOverlayHost(fixture)!.querySelectorAll('mat-option')[0].classList) .withContext('Expected first option to be highlighted.') .toContain('mat-mdc-option-active'); })); @@ -2509,7 +2514,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); @@ -2527,7 +2532,7 @@ describe('MatAutocomplete', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); await new Promise(r => setTimeout(r)); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); await new Promise(r => setTimeout(r)); }; @@ -2580,7 +2585,7 @@ describe('MatAutocomplete', () => { await flushPosition(); const inputBottom = inputReference.getBoundingClientRect().bottom; - const panel = overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!; + const panel = getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!; const panelTop = panel.getBoundingClientRect().top; expect(Math.floor(inputBottom)) @@ -2604,7 +2609,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); const input = fixture.nativeElement.querySelector('input'); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement; const optionInstance = fixture.componentInstance.options.first; const spy = jasmine.createSpy('selectionChange spy'); @@ -2644,7 +2649,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; const spy = jasmine.createSpy('optionSelected spy'); @@ -2677,7 +2682,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; const spy = jasmine.createSpy('optionSelected spy'); @@ -2837,7 +2842,7 @@ describe('MatAutocomplete', () => { trigger.openPanel(); fixture.detectChanges(); await new Promise(r => setTimeout(r)); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement).click(); await new Promise(r => setTimeout(r)); fixture.detectChanges(); @@ -2928,7 +2933,7 @@ describe('MatAutocomplete', () => { })); it('should emit panel close event when selecting an option', () => { - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)!.querySelector('mat-option') as HTMLElement; expect(closingActionSpy).not.toHaveBeenCalled(); option.click(); @@ -2987,7 +2992,7 @@ describe('MatAutocomplete', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - const panelClassList = overlayContainerElement.querySelector('.cdk-overlay-pane')!.classList; + const panelClassList = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!.classList; expect(panelClassList).toContain('default1'); }); @@ -3003,7 +3008,7 @@ describe('MatAutocomplete', () => { fixture.componentInstance.trigger.openPanel(); fixture.detectChanges(); - const panelClassList = overlayContainerElement.querySelector('.cdk-overlay-pane')!.classList; + const panelClassList = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!.classList; expect(panelClassList).toContain('default1'); expect(panelClassList).toContain('default2'); }); @@ -3018,7 +3023,7 @@ describe('MatAutocomplete', () => { tick(500); - expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeFalsy(); + expect(getBackdrop(fixture)).toBeFalsy(); })); it('should be able to add the backdrop using hasBackdrop option', fakeAsync(() => { @@ -3034,7 +3039,7 @@ describe('MatAutocomplete', () => { tick(500); - expect(overlayContainerElement.querySelector('.cdk-overlay-backdrop')).toBeTruthy(); + expect(getBackdrop(fixture)).toBeTruthy(); })); }); @@ -3052,8 +3057,8 @@ describe('MatAutocomplete', () => { tick(500); - const cdkPanelElement = overlayContainerElement.querySelector('.cdk-overlay-backdrop'); - expect(cdkPanelElement?.classList).toContain('my-custom-backdrop-class'); + const backdrop = getBackdrop(fixture); + expect(backdrop?.classList).toContain('my-custom-backdrop-class'); })); }); @@ -3067,7 +3072,7 @@ describe('MatAutocomplete', () => { typeInElement(input, 'd'); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; expect(options.length).toBe(1); @@ -3102,10 +3107,10 @@ describe('MatAutocomplete', () => { expect(fixture.componentInstance.trigger.panelOpen) .withContext(`Expected panel state to read open when input is focused.`) .toBe(true); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when input is focused.`) .toContain('One'); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.textContent) .withContext(`Expected panel to display when input is focused.`) .toContain('Two'); }); @@ -3155,7 +3160,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); const autocomplete = fixture.debugElement.nativeElement.querySelector('mat-autocomplete'); - const panel = overlayContainerElement.querySelector('.mat-mdc-autocomplete-panel')!; + const panel = getOverlayHost(fixture)!.querySelector('.mat-mdc-autocomplete-panel')!; expect(autocomplete.classList).not.toContain('class-one'); expect(autocomplete.classList).not.toContain('class-two'); @@ -3172,7 +3177,7 @@ describe('MatAutocomplete', () => { tick(); fixture.detectChanges(); - const classList = overlayContainerElement.querySelector( + const classList = getOverlayHost(fixture)!.querySelector( '.mat-mdc-autocomplete-panel', )!.classList; @@ -3478,7 +3483,7 @@ describe('MatAutocomplete', () => { expect(stateCtrl.value).toBeFalsy(); expect(input.value).toBe('Alabama'); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[2].click(); @@ -3498,7 +3503,9 @@ describe('MatAutocomplete', () => { widthFixture.componentInstance.trigger.openPanel(); widthFixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const overlayPane = getOverlayHost(widthFixture)!.querySelector( + '.cdk-overlay-pane', + ) as HTMLElement; // Firefox, Edge return a decimal value for width, so we need to parse and round it to verify expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(300); @@ -3526,7 +3533,9 @@ describe('MatAutocomplete', () => { widthFixture.componentInstance.trigger.openPanel(); widthFixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const overlayPane = getOverlayHost(widthFixture)!.querySelector( + '.cdk-overlay-pane', + ) as HTMLElement; const input = widthFixture.debugElement.query(By.css('input'))!.nativeElement; expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(300); @@ -3582,7 +3591,9 @@ describe('MatAutocomplete', () => { widthFixture.componentInstance.trigger.openPanel(); widthFixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const overlayPane = getOverlayHost(widthFixture)!.querySelector( + '.cdk-overlay-pane', + ) as HTMLElement; expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(300); @@ -3606,7 +3617,9 @@ describe('MatAutocomplete', () => { widthFixture.componentInstance.trigger.openPanel(); widthFixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const overlayPane = getOverlayHost(widthFixture)!.querySelector( + '.cdk-overlay-pane', + ) as HTMLElement; expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(300); }); @@ -3623,7 +3636,9 @@ describe('MatAutocomplete', () => { widthFixture.componentInstance.trigger.openPanel(); widthFixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const overlayPane = getOverlayHost(widthFixture)!.querySelector( + '.cdk-overlay-pane', + ) as HTMLElement; expect(overlayPane.style.width).toBe('auto'); }); @@ -3640,7 +3655,9 @@ describe('MatAutocomplete', () => { widthFixture.componentInstance.trigger.openPanel(); widthFixture.detectChanges(); - const overlayPane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const overlayPane = getOverlayHost(widthFixture)!.querySelector( + '.cdk-overlay-pane', + ) as HTMLElement; expect(Math.ceil(parseFloat(overlayPane.style.width as string))).toBe(400); }); @@ -3656,7 +3673,7 @@ describe('MatAutocomplete', () => { tick(); Promise.resolve().then(() => { - let panel = overlayContainerElement.querySelector( + let panel = getOverlayHost(fixture)!.querySelector( '.mat-mdc-autocomplete-panel', ) as HTMLElement; let visibleClass = 'mat-mdc-autocomplete-visible'; @@ -3675,7 +3692,9 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); fixture.detectChanges(); - let options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + let options = getOverlayHost(fixture)!.querySelectorAll( + 'mat-option', + ) as NodeListOf; let spy = fixture.componentInstance.optionSelected; options[1].click(); @@ -3700,7 +3719,7 @@ describe('MatAutocomplete', () => { await new Promise(r => setTimeout(r)); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; spyOn(input, 'focus').and.callFake(() => events.push('focus')); @@ -3727,7 +3746,9 @@ describe('MatAutocomplete', () => { tick(); fixture.detectChanges(); - let options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + let options = getOverlayHost(fixture)!.querySelectorAll( + 'mat-option', + ) as NodeListOf; let spy = fixture.componentInstance.optionSelected; options[3].click(); @@ -3803,7 +3824,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const overlayRect = overlayContainerElement + const overlayRect = getOverlayHost(fixture)! .querySelector('.cdk-overlay-pane')! .getBoundingClientRect(); const originRect = fixture.nativeElement.querySelector('.origin').getBoundingClientRect(); @@ -3832,7 +3853,7 @@ describe('MatAutocomplete', () => { fixture.detectChanges(); await new Promise(r => setTimeout(r)); - const overlayRect = overlayContainerElement + const overlayRect = getOverlayHost(fixture)! .querySelector('.cdk-overlay-pane')! .getBoundingClientRect(); const originRect = fixture.nativeElement.querySelector('.origin').getBoundingClientRect(); diff --git a/src/material/button-toggle/_m3-button-toggle.scss b/src/material/button-toggle/_m3-button-toggle.scss index d7190903d501..667d1ea56d2c 100644 --- a/src/material/button-toggle/_m3-button-toggle.scss +++ b/src/material/button-toggle/_m3-button-toggle.scss @@ -63,7 +63,7 @@ // Tokens that can be configured through Angular Material's density theming API. @function get-density-tokens($scale) { - $scale: theming.clamp-density(scale, -4); + $scale: theming.clamp-density($scale, -4); $index: ($scale * -1) + 1; @return ( diff --git a/src/material/button/testing/button-harness.ts b/src/material/button/testing/button-harness.ts index 49edebcb25ce..7f3ba9eacff9 100644 --- a/src/material/button/testing/button-harness.ts +++ b/src/material/button/testing/button-harness.ts @@ -60,8 +60,9 @@ export class MatButtonHarness extends ContentContainerComponentHarness { .addOption('buttonType', options.buttonType, (harness, buttonType) => HarnessPredicate.stringMatches(harness.getType(), buttonType), ) - .addOption('iconName', options.iconName, (harness, iconName) => { - return harness.hasHarness(MatIconHarness.with({name: iconName})); + .addOption('iconName', options.iconName, async (harness, iconName) => { + const result = await harness.locatorForOptional(MatIconHarness.with({name: iconName}))(); + return result !== null; }); } diff --git a/src/material/chips/chip.scss b/src/material/chips/chip.scss index acace3db6bb0..bbe6517b37cf 100644 --- a/src/material/chips/chip.scss +++ b/src/material/chips/chip.scss @@ -747,3 +747,9 @@ $fallbacks: m3-chip.get-tokens(); .mdc-evolution-chip__icon, .mat-mdc-chip-edit .mat-icon, .mat-mdc-chip-remove .mat-icon { min-height: fit-content; } + +// The `min-height: fit-content` above can stretch out image in Safari (see #32251). +// It also isn't necessary for image since their content doesn't affect the image container. +img.mdc-evolution-chip__icon { + min-height: 0; +} diff --git a/src/material/chips/chips.md b/src/material/chips/chips.md index 3464f0acddbd..6e52171d810a 100644 --- a/src/material/chips/chips.md +++ b/src/material/chips/chips.md @@ -65,6 +65,9 @@ Users can press delete to remove a chip. Pressing delete triggers the `removed` A `` can be combined with `` to enable free-form chip input with suggestions. +> _Please note: when using `MatChipsModule` together with `MatAutocompleteModule`, the order in which modules are imported matters._ +> _To ensure correct behavior (e.g., preventing adding typed text when autocomplete option is selected via keyboard), make sure to import `MatAutocompleteModule` before `MatChipsModule`._ + ### Icons @@ -141,7 +144,7 @@ The chips components support 3 user interaction patterns, each with its own cont `` and `` : These elements implement a grid accessibility pattern. Use them as part of a free form input that allows users to enter text to add chips. -Note : be sure to have the input element be a sibling of mat-chip-grid to ensure accessibility of the input element by accessibility devices such as Voice Control. It is also recommended to apply an appropriate `aria-label` to the input to optimize accessibility of the input. +> _Please note: be sure to have the input element be a sibling of `mat-chip-grid` to ensure accessibility of the input element by accessibility devices such as Voice Control. It is also recommended to apply an appropriate `aria-label` to the input to optimize accessibility of the input._ ```html diff --git a/src/material/dialog/dialog-ref.ts b/src/material/dialog/dialog-ref.ts index c247fad81cb1..21a048acdd7d 100644 --- a/src/material/dialog/dialog-ref.ts +++ b/src/material/dialog/dialog-ref.ts @@ -8,7 +8,7 @@ /** Possible states of the lifecycle of a dialog. */ import {FocusOrigin} from '@angular/cdk/a11y'; -import {merge, Observable, Subject} from 'rxjs'; +import {merge, Observable, ReplaySubject} from 'rxjs'; import {DialogRef} from '@angular/cdk/dialog'; import {DialogPosition, MatDialogConfig} from './dialog-config'; import {MatDialogContainer} from './dialog-container'; @@ -43,10 +43,10 @@ export class MatDialogRef { id: string; /** Subject for notifying the user that the dialog has finished opening. */ - private readonly _afterOpened = new Subject(); + private readonly _afterOpened = new ReplaySubject(1); /** Subject for notifying the user that the dialog has started closing. */ - private readonly _beforeClosed = new Subject(); + private readonly _beforeClosed = new ReplaySubject(1); /** Result to be passed to afterClosed. */ private _result: R | undefined; diff --git a/src/material/dialog/dialog.spec.ts b/src/material/dialog/dialog.spec.ts index d68cc19fe351..1b79514231fe 100644 --- a/src/material/dialog/dialog.spec.ts +++ b/src/material/dialog/dialog.spec.ts @@ -11,7 +11,7 @@ import { dispatchMouseEvent, patchElementFocus, } from '@angular/cdk/testing/private'; -import {Location} from '@angular/common'; +import {AsyncPipe, Location} from '@angular/common'; import {SpyLocation} from '@angular/common/testing'; import { ChangeDetectionStrategy, @@ -39,6 +39,7 @@ import { } from '@angular/core/testing'; import {By} from '@angular/platform-browser'; import {Subject} from 'rxjs'; +import {map} from 'rxjs/operators'; import {CLOSE_ANIMATION_DURATION, OPEN_ANIMATION_DURATION} from './dialog-container'; import { MAT_DIALOG_DATA, @@ -1240,6 +1241,14 @@ describe('MatDialog', () => { }), ); + it('should be able to use afterOpened in the template while animations are disabled', async () => { + const ref = dialog.open(DialogWithAfterOpenSubscription); + await new Promise(resolve => setTimeout(resolve, 10)); + + expect(ref.componentInstance.animations.animationsDisabled).toBe(true); + expect(overlayContainerElement.textContent).toContain('The dialog is now open!'); + }); + describe('hasBackdrop option', () => { it('should have a backdrop', () => { dialog.open(PizzaMsg, {hasBackdrop: true, viewContainerRef: testViewContainerRef}); @@ -2450,3 +2459,15 @@ class ModuleBoundDialogChildComponent { providers: [ModuleBoundDialogService], }) class ModuleBoundDialogModule {} + +@Component({ + template: `{{message | async}}`, + imports: [AsyncPipe], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +class DialogWithAfterOpenSubscription { + dialogRef = inject(MatDialogRef); + animations = inject(MATERIAL_ANIMATIONS); + + protected message = this.dialogRef.afterOpened().pipe(map(() => 'The dialog is now open!')); +} diff --git a/src/material/icon/BUILD.bazel b/src/material/icon/BUILD.bazel index bf4df42d2a6e..200496244769 100644 --- a/src/material/icon/BUILD.bazel +++ b/src/material/icon/BUILD.bazel @@ -68,7 +68,6 @@ ng_project( "icon-registry.ts", "index.ts", "public-api.ts", - "trusted-types.ts", ], assets = [ ":css", @@ -79,6 +78,7 @@ ng_project( "//:node_modules/@angular/platform-browser", "//:node_modules/rxjs", "//src:dev_mode_types", + "//src/cdk/private", "//src/material/core", ], ) diff --git a/src/material/icon/icon-registry.ts b/src/material/icon/icon-registry.ts index 2555b6342ddb..3d69ff4a7062 100644 --- a/src/material/icon/icon-registry.ts +++ b/src/material/icon/icon-registry.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import {TrustedHTML, trustedHTMLFromString} from '@angular/cdk/private'; import {HttpClient, HttpErrorResponse} from '@angular/common/http'; import { ErrorHandler, @@ -19,7 +20,6 @@ import { import {DomSanitizer, SafeHtml, SafeResourceUrl} from '@angular/platform-browser'; import {forkJoin, Observable, of as observableOf, throwError as observableThrow} from 'rxjs'; import {catchError, finalize, map, share, tap} from 'rxjs/operators'; -import {TrustedHTML, trustedHTMLFromString} from './trusted-types'; /** * Returns an exception to be thrown in the case when attempting to diff --git a/src/material/menu/testing/BUILD.bazel b/src/material/menu/testing/BUILD.bazel index 8db24891f557..bb8065b43cb3 100644 --- a/src/material/menu/testing/BUILD.bazel +++ b/src/material/menu/testing/BUILD.bazel @@ -11,6 +11,7 @@ ts_project( deps = [ "//src/cdk/coercion", "//src/cdk/testing", + "//src/material/icon/testing", ], ) @@ -31,6 +32,7 @@ ng_project( "//src/cdk/testing", "//src/cdk/testing/private", "//src/cdk/testing/testbed", + "//src/material/icon", "//src/material/menu", ], ) diff --git a/src/material/menu/testing/menu-harness-filters.ts b/src/material/menu/testing/menu-harness-filters.ts index b2bdccc45653..fc74bc6daa9a 100644 --- a/src/material/menu/testing/menu-harness-filters.ts +++ b/src/material/menu/testing/menu-harness-filters.ts @@ -12,6 +12,9 @@ import {BaseHarnessFilters} from '@angular/cdk/testing'; export interface MenuHarnessFilters extends BaseHarnessFilters { /** Only find instances whose trigger text matches the given value. */ triggerText?: string | RegExp; + + /** Only find instances where the trigger contains an icon whose name matches the given value. */ + triggerIconName?: string | RegExp; } /** A set of criteria that can be used to filter a list of `MatMenuItemHarness` instances. */ diff --git a/src/material/menu/testing/menu-harness.spec.ts b/src/material/menu/testing/menu-harness.spec.ts index 9db9280c9d47..84aa1721d16c 100644 --- a/src/material/menu/testing/menu-harness.spec.ts +++ b/src/material/menu/testing/menu-harness.spec.ts @@ -1,6 +1,7 @@ import {Component} from '@angular/core'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {HarnessLoader} from '@angular/cdk/testing'; +import {MatIconModule} from '@angular/material/icon'; import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed'; import {MatMenuModule} from '../menu-module'; import {MatMenuHarness} from './menu-harness'; @@ -18,7 +19,7 @@ describe('MatMenuHarness', () => { it('should load all menu harnesses', async () => { const menues = await loader.getAllHarnesses(MatMenuHarness); - expect(menues.length).toBe(2); + expect(menues.length).toBe(3); }); it('should load menu with exact text', async () => { @@ -33,6 +34,12 @@ describe('MatMenuHarness', () => { expect(await menus[0].getTriggerText()).toBe('Settings'); }); + it('should load menu by icon name', async () => { + const menus = await loader.getAllHarnesses(MatMenuHarness.with({triggerIconName: 'wrench'})); + expect(menus.length).toBe(1); + expect(await (await menus[0].host()).getAttribute('id')).toBe('with-icon'); + }); + it('should get disabled state', async () => { const [enabledMenu, disabledMenu] = await loader.getAllHarnesses(MatMenuHarness); expect(await enabledMenu.isDisabled()).toBe(false); @@ -146,39 +153,42 @@ describe('MatMenuHarness', () => { @Component({ template: ` - - - - - Profile - Account - + + + + + + Profile + Account + `, - imports: [MatMenuModule], + imports: [MatMenuModule, MatIconModule], }) class MenuHarnessTest {} @Component({ template: ` - + - - - - - + + + + + - - - + + + - - - + + + - - - + + + `, imports: [MatMenuModule], }) diff --git a/src/material/menu/testing/menu-harness.ts b/src/material/menu/testing/menu-harness.ts index 8bbe6215273f..55951213b65f 100644 --- a/src/material/menu/testing/menu-harness.ts +++ b/src/material/menu/testing/menu-harness.ts @@ -14,6 +14,7 @@ import { TestElement, } from '@angular/cdk/testing'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {MatIconHarness} from '@angular/material/icon/testing'; import {MenuHarnessFilters, MenuItemHarnessFilters} from './menu-harness-filters'; /** Harness for interacting with a mat-menu in tests. */ @@ -32,11 +33,16 @@ export class MatMenuHarness extends ContentContainerComponentHarness { this: ComponentHarnessConstructor, options: MenuHarnessFilters = {}, ): HarnessPredicate { - return new HarnessPredicate(this, options).addOption( - 'triggerText', - options.triggerText, - (harness, text) => HarnessPredicate.stringMatches(harness.getTriggerText(), text), - ); + return new HarnessPredicate(this, options) + .addOption('triggerText', options.triggerText, (harness, text) => + HarnessPredicate.stringMatches(harness.getTriggerText(), text), + ) + .addOption('triggerIconName', options.triggerIconName, async (harness, triggerIconName) => { + const result = await harness.locatorForOptional( + MatIconHarness.with({name: triggerIconName}), + )(); + return result !== null; + }); } /** Whether the menu is disabled. */ diff --git a/src/material/schematics/BUILD.bazel b/src/material/schematics/BUILD.bazel index e695d1279db2..279fed73f373 100644 --- a/src/material/schematics/BUILD.bazel +++ b/src/material/schematics/BUILD.bazel @@ -126,6 +126,7 @@ ts_project( "//:node_modules/@types/jasmine", "//:node_modules/@types/node", "//:node_modules/fs-extra", + "//:node_modules/parse5", "//src/cdk/schematics/testing", ], ) diff --git a/src/material/schematics/ng-update/BUILD.bazel b/src/material/schematics/ng-update/BUILD.bazel index 6ee349fda826..07f81612e6cc 100644 --- a/src/material/schematics/ng-update/BUILD.bazel +++ b/src/material/schematics/ng-update/BUILD.bazel @@ -1,5 +1,5 @@ -load("//tools:defaults.bzl", "jasmine_test", "ts_project") load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild") +load("//tools:defaults.bzl", "jasmine_test", "ts_project") ts_project( name = "ng_update_lib", @@ -59,6 +59,7 @@ ts_project( "//:node_modules/@angular-devkit/core", "//:node_modules/@angular-devkit/schematics", "//:node_modules/@bazel/runfiles", + "//:node_modules/parse5", "//src/cdk/schematics/testing", "//src/material/schematics:node_modules/@angular/cdk/schematics", "//src/material/schematics:paths", diff --git a/src/material/select/select.html b/src/material/select/select.html index 6eaecee7f5c3..ba6439f3fe45 100644 --- a/src/material/select/select.html +++ b/src/material/select/select.html @@ -40,6 +40,7 @@ [cdkConnectedOverlayPositions]="_positions" [cdkConnectedOverlayWidth]="_overlayWidth" [cdkConnectedOverlayFlexibleDimensions]="true" + cdkConnectedOverlayUsePopover="inline" (detach)="close()" (backdropClick)="close()" (overlayKeydown)="_handleOverlayKeydown($event)"> diff --git a/src/material/select/select.spec.ts b/src/material/select/select.spec.ts index 45a24a15d45f..e58ee1e5bee7 100644 --- a/src/material/select/select.spec.ts +++ b/src/material/select/select.spec.ts @@ -76,7 +76,7 @@ import { const DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL = 200; describe('MatSelect', () => { - let overlayContainerElement: HTMLElement; + const supportsPopover = 'showPopover' in document.body; let dir: WritableSignal; let scrolledSubject = new Subject(); @@ -94,10 +94,23 @@ describe('MatSelect', () => { }, ], }); - - overlayContainerElement = TestBed.inject(OverlayContainer).getContainerElement(); }); + function getOverlayHost(fixture: ComponentFixture): HTMLElement | null { + return supportsPopover + ? fixture.nativeElement.querySelector('.cdk-overlay-popover') + : TestBed.inject(OverlayContainer) + .getContainerElement() + .querySelector('.cdk-overlay-connected-position-bounding-box'); + } + + function getBackdrop(fixture: ComponentFixture): HTMLElement | null { + const parent = supportsPopover + ? fixture.nativeElement + : TestBed.inject(OverlayContainer).getContainerElement(); + return parent.querySelector('.cdk-overlay-backdrop'); + } + describe('core', () => { describe('accessibility', () => { describe('for select', () => { @@ -414,7 +427,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelectorAll('mat-option')[3] as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelectorAll('mat-option')[3] as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -769,7 +782,7 @@ describe('MatSelect', () => { multiFixture.componentInstance.select.open(); multiFixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(multiFixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -982,7 +995,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll('mat-option'); + const options = getOverlayHost(fixture)!.querySelectorAll('mat-option'); expect(host.getAttribute('aria-activedescendant')) .withContext('Expected aria-activedescendant to match the active option.') @@ -1004,7 +1017,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll('mat-option'); + const options = getOverlayHost(fixture)!.querySelectorAll('mat-option'); expect(host.getAttribute('aria-activedescendant')).toBe(options[0].id); @@ -1028,7 +1041,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll('mat-option'); + const options = getOverlayHost(fixture)!.querySelectorAll('mat-option'); expect(host.getAttribute('aria-activedescendant')).toBe(options[0].id); @@ -1055,7 +1068,7 @@ describe('MatSelect', () => { select.blur(); expect(document.activeElement).not.toBe(select, 'Expected trigger not to be focused.'); - const option = overlayContainerElement.querySelector('mat-option')! as HTMLElement; + const option = getOverlayHost(multiFixture)!.querySelector('mat-option')! as HTMLElement; option.click(); multiFixture.detectChanges(); @@ -1154,7 +1167,9 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - options = Array.from(overlayContainerElement.querySelectorAll('mat-option')); + options = Array.from( + getOverlayHost(fixture)!.querySelectorAll('mat-option'), + ); }); it('should set the role of mat-option to option', fakeAsync(() => { @@ -1202,7 +1217,9 @@ describe('MatSelect', () => { trigger.click(); multiFixture.detectChanges(); - options = Array.from(overlayContainerElement.querySelectorAll('mat-option')); + options = Array.from( + getOverlayHost(multiFixture)!.querySelectorAll('mat-option'), + ); expect( options.every( @@ -1340,7 +1357,9 @@ describe('MatSelect', () => { multiFixture.detectChanges(); flush(); - options = Array.from(overlayContainerElement.querySelectorAll('mat-option')); + options = Array.from( + getOverlayHost(multiFixture)!.querySelectorAll('mat-option'), + ); const pseudoCheckboxes = options .map(option => option.querySelector('.mat-pseudo-checkbox.mat-pseudo-checkbox-full')) .filter((x): x is HTMLElement => !!x); @@ -1366,7 +1385,7 @@ describe('MatSelect', () => { trigger = fixture.debugElement.query(By.css('.mat-mdc-select-trigger'))!.nativeElement; trigger.click(); fixture.detectChanges(); - groups = overlayContainerElement.querySelectorAll( + groups = getOverlayHost(fixture)!.querySelectorAll( 'mat-optgroup', ) as NodeListOf; }); @@ -1431,10 +1450,11 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); + const overlay = getOverlayHost(fixture)!; expect(fixture.componentInstance.select.panelOpen).toBe(true); - expect(overlayContainerElement.textContent).toContain('Steak'); - expect(overlayContainerElement.textContent).toContain('Pizza'); - expect(overlayContainerElement.textContent).toContain('Tacos'); + expect(overlay.textContent).toContain('Steak'); + expect(overlay.textContent).toContain('Pizza'); + expect(overlay.textContent).toContain('Tacos'); })); it('should close the panel when an item is clicked', fakeAsync(() => { @@ -1442,12 +1462,12 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); flush(); - expect(overlayContainerElement.textContent).toEqual(''); + expect(getOverlayHost(fixture)!).toBeFalsy(); expect(fixture.componentInstance.select.panelOpen).toBe(false); })); @@ -1456,15 +1476,11 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const backdrop = overlayContainerElement.querySelector( - '.cdk-overlay-backdrop', - ) as HTMLElement; - - backdrop.click(); + getBackdrop(fixture)!.click(); fixture.detectChanges(); flush(); - expect(overlayContainerElement.textContent).toEqual(''); + expect(getOverlayHost(fixture)!).toBeFalsy(); expect(fixture.componentInstance.select.panelOpen).toBe(false); })); @@ -1475,7 +1491,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const pane = getOverlayHost(fixture)?.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.style.width).toBe('200px'); })); @@ -1486,7 +1502,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const pane = getOverlayHost(fixture)?.querySelector('.cdk-overlay-pane') as HTMLElement; const initialWidth = parseInt(pane.style.width || '0'); expect(initialWidth).toBeGreaterThan(0); @@ -1509,7 +1525,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const pane = getOverlayHost(fixture)?.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.style.width).toBe('42px'); })); @@ -1522,7 +1538,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const pane = getOverlayHost(fixture)?.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.style.width).toBeFalsy(); })); @@ -1535,7 +1551,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const pane = getOverlayHost(fixture)?.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.style.width).toBeFalsy(); })); @@ -1590,7 +1606,7 @@ describe('MatSelect', () => { expect(fixture.componentInstance.select.panelOpen).toBe(true); - const panel = overlayContainerElement.querySelector('.mat-mdc-select-panel')!; + const panel = getOverlayHost(fixture)!.querySelector('.mat-mdc-select-panel')!; dispatchKeyboardEvent(panel, 'keydown', TAB); fixture.detectChanges(); flush(); @@ -1663,7 +1679,9 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - const panel = overlayContainerElement.querySelector('.mat-mdc-select-panel') as HTMLElement; + const panel = getOverlayHost(fixture)!.querySelector( + '.mat-mdc-select-panel', + ) as HTMLElement; expect(panel.classList).toContain('custom-one'); expect(panel.classList).toContain('custom-two'); @@ -1694,7 +1712,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelector('mat-option')!; + const option = getOverlayHost(fixture)!.querySelector('mat-option')!; dispatchFakeEvent(option, 'mousedown'); dispatchFakeEvent(option, 'mouseup'); @@ -1711,7 +1729,7 @@ describe('MatSelect', () => { trigger.click(); groupFixture.detectChanges(); - expect(document.querySelectorAll('.cdk-overlay-container mat-option').length) + expect(getOverlayHost(groupFixture)!.querySelectorAll('mat-option').length) .withContext('Expected at least one option to be rendered.') .toBeGreaterThan(0); }); @@ -1804,7 +1822,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - let option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + let option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); flush(); @@ -1813,7 +1831,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; expect(option.classList).toContain('mdc-list-item--selected'); expect(fixture.componentInstance.options.first.selected).toBe(true); @@ -1829,7 +1847,7 @@ describe('MatSelect', () => { const optionInstances = fixture.componentInstance.options.toArray(); const optionNodes: NodeListOf = - overlayContainerElement.querySelectorAll('mat-option'); + getOverlayHost(fixture)!.querySelectorAll('mat-option'); optionInstances[1].select(); fixture.detectChanges(); @@ -1844,7 +1862,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - let options = overlayContainerElement.querySelectorAll( + let options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -1856,7 +1874,9 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll( + 'mat-option', + ) as NodeListOf; expect(options[1].classList).not.toContain('mdc-list-item--selected'); expect(options[2].classList).not.toContain('mdc-list-item--selected'); @@ -1873,7 +1893,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - let options = overlayContainerElement.querySelectorAll( + let options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -1888,7 +1908,9 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll( + 'mat-option', + ) as NodeListOf; expect(options[0].classList).not.toContain( 'mdc-list-item--selected', @@ -1915,7 +1937,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - let firstOption = overlayContainerElement.querySelectorAll('mat-option')[0] as HTMLElement; + let firstOption = getOverlayHost(fixture)!.querySelectorAll('mat-option')[0] as HTMLElement; firstOption.click(); fixture.detectChanges(); @@ -1939,7 +1961,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); flush(); @@ -1974,7 +1996,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[8].click(); @@ -2018,7 +2040,7 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; options[2].click(); @@ -2037,7 +2059,7 @@ describe('MatSelect', () => { groupFixture.debugElement.query(By.css('.mat-mdc-select-trigger'))!.nativeElement.click(); groupFixture.detectChanges(); - const disabledGroup = overlayContainerElement.querySelectorAll('mat-optgroup')[1]; + const disabledGroup = getOverlayHost(groupFixture)!.querySelectorAll('mat-optgroup')[1]; const options = disabledGroup.querySelectorAll('mat-option'); (options[0] as HTMLElement).click(); @@ -2059,7 +2081,7 @@ describe('MatSelect', () => { const spy = jasmine.createSpy('option selection spy'); const subscription = fixture.componentInstance.select.optionSelectionChanges.subscribe(spy); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); flush(); @@ -2088,7 +2110,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); flush(); @@ -2106,7 +2128,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); flush(); @@ -2169,7 +2191,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; expect(options[1].classList) @@ -2193,7 +2215,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; expect(options[1].classList) @@ -2210,7 +2232,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); flush(); @@ -2240,7 +2262,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; expect(options[1].classList).not.toContain( @@ -2269,7 +2291,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; expect(options[1].classList).not.toContain( @@ -2292,10 +2314,7 @@ describe('MatSelect', () => { .withContext(`Expected the control to stay untouched when menu opened.`) .toEqual(false); - const backdrop = overlayContainerElement.querySelector( - '.cdk-overlay-backdrop', - ) as HTMLElement; - backdrop.click(); + getBackdrop(fixture)!.click(); dispatchFakeEvent(trigger, 'blur'); fixture.detectChanges(); flush(); @@ -2350,7 +2369,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); flush(); @@ -2417,9 +2436,9 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-select-panel')) .withContext(`Expected select panel to stay closed.`) - .toEqual(''); + .toBeFalsy(); expect(fixture.componentInstance.select.panelOpen) .withContext(`Expected select panelOpen property to stay false.`) .toBe(false); @@ -2434,7 +2453,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-select-panel')?.textContent) .withContext(`Expected select panel to open normally on re-enabled control`) .toContain('Steak'); expect(fixture.componentInstance.select.panelOpen) @@ -2478,7 +2497,7 @@ describe('MatSelect', () => { fixture.detectChanges(); host = fixture.debugElement.query(By.css('mat-select'))!.nativeElement; - panel = overlayContainerElement.querySelector('.mat-mdc-select-panel')! as HTMLElement; + panel = getOverlayHost(fixture)!.querySelector('.mat-mdc-select-panel')! as HTMLElement; })); it('should not scroll to options that are completely in the view', () => { @@ -2527,7 +2546,9 @@ describe('MatSelect', () => { flush(); host = groupFixture.debugElement.query(By.css('mat-select'))!.nativeElement; - panel = overlayContainerElement.querySelector('.mat-mdc-select-panel')! as HTMLElement; + panel = getOverlayHost(groupFixture)!.querySelector( + '.mat-mdc-select-panel', + )! as HTMLElement; for (let i = 0; i < 8; i++) { dispatchKeyboardEvent(host, 'keydown', DOWN_ARROW); @@ -2649,7 +2670,9 @@ describe('MatSelect', () => { flush(); host = groupFixture.debugElement.query(By.css('mat-select'))!.nativeElement; - panel = overlayContainerElement.querySelector('.mat-mdc-select-panel')! as HTMLElement; + panel = getOverlayHost(groupFixture)!.querySelector( + '.mat-mdc-select-panel', + )! as HTMLElement; for (let i = 0; i < 5; i++) { dispatchKeyboardEvent(host, 'keydown', DOWN_ARROW); @@ -2703,7 +2726,7 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement).click(); expect(fixture.componentInstance.changeListener).toHaveBeenCalled(); }); @@ -2712,7 +2735,7 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); option.click(); @@ -2750,9 +2773,9 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-select-panel')) .withContext(`Expected select panel to stay closed.`) - .toEqual(''); + .toBeFalsy(); expect(fixture.componentInstance.select.panelOpen) .withContext(`Expected select panelOpen property to stay false.`) .toBe(false); @@ -2771,7 +2794,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - expect(overlayContainerElement.textContent) + expect(getOverlayHost(fixture)?.querySelector('.mat-mdc-select-panel')?.textContent) .withContext(`Expected select panel to open normally on re-enabled control`) .toContain('Steak'); expect(fixture.componentInstance.select.panelOpen) @@ -2802,13 +2825,13 @@ describe('MatSelect', () => { .withContext(`Expected trigger to be populated by the control's initial value.`) .toContain('Pizza'); - const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const pane = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.style.width).toEqual('300px'); expect(fixture.componentInstance.select.panelOpen).toBe(true); - expect(overlayContainerElement.textContent).toContain('Steak'); - expect(overlayContainerElement.textContent).toContain('Pizza'); - expect(overlayContainerElement.textContent).toContain('Tacos'); + expect(pane.textContent).toContain('Steak'); + expect(pane.textContent).toContain('Pizza'); + expect(pane.textContent).toContain('Tacos'); })); }); @@ -2826,7 +2849,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; })); it('should set the option id', fakeAsync(() => { @@ -2837,10 +2860,7 @@ describe('MatSelect', () => { .toContain('mat-option'); expect(options[0].id).not.toEqual(options[1].id, `Expected option IDs to be unique.`); - const backdrop = overlayContainerElement.querySelector( - '.cdk-overlay-backdrop', - ) as HTMLElement; - backdrop.click(); + getBackdrop(fixture)!.click(); fixture.detectChanges(); flush(); @@ -2848,7 +2868,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; expect(options[0].id) .withContext(`Expected option ID to have the correct prefix.`) .toContain('mat-option'); @@ -2963,7 +2983,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const pane = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane') as HTMLElement; expect(pane.style.width).toBe('300px'); })); }); @@ -2979,7 +2999,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const pane = overlayContainerElement.querySelector('.cdk-overlay-pane') as HTMLElement; + const pane = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane') as HTMLElement; expect(parseInt(pane.style.width as string)).toBeGreaterThan(0); })); }); @@ -3000,7 +3020,7 @@ describe('MatSelect', () => { fixture.componentInstance.select.open(); fixture.detectChanges(); - const panel = overlayContainerElement.querySelector('.mat-mdc-select-panel')! as HTMLElement; + const panel = getOverlayHost(fixture)!.querySelector('.mat-mdc-select-panel')! as HTMLElement; expect(panel.classList).toContain('mat-warn'); }); }); @@ -3329,7 +3349,7 @@ describe('MatSelect', () => { expect(fixture.componentInstance.options.first.selected) .withContext('Expected first option to be selected') .toBe(true); - expect(overlayContainerElement.querySelectorAll('mat-option')[0].classList) + expect(getOverlayHost(fixture)!.querySelectorAll('mat-option')[0].classList) .withContext('Expected first option to be selected') .toContain('mdc-list-item--selected'); })); @@ -3409,7 +3429,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; options[0].click(); fixture.detectChanges(); flush(); @@ -3504,7 +3524,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - options = overlayContainerElement.querySelectorAll('mat-option') as NodeListOf; + options = getOverlayHost(fixture)!.querySelectorAll('mat-option') as NodeListOf; options[0].click(); fixture.detectChanges(); flush(); @@ -3590,7 +3610,7 @@ describe('MatSelect', () => { trigger = fixture.debugElement.query(By.css('.mat-mdc-select-trigger'))!.nativeElement; trigger.click(); fixture.detectChanges(); - options = Array.from(overlayContainerElement.querySelectorAll('mat-option')); + options = Array.from(getOverlayHost(fixture)!.querySelectorAll('mat-option')); }); it('should set the select value', fakeAsync(() => { @@ -3648,7 +3668,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -3660,7 +3680,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelectorAll('mat-option')[2] as HTMLElement).click(); + (getOverlayHost(fixture)!.querySelectorAll('mat-option')[2] as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -3684,7 +3704,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelectorAll('mat-option')[2]; + const option = getOverlayHost(fixture)!.querySelectorAll('mat-option')[2]; expect(option.classList).toContain('mdc-list-item--selected'); expect(fixture.componentInstance.select.value).toBe('sandwich-2'); @@ -3702,7 +3722,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -3732,7 +3752,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const option = overlayContainerElement.querySelectorAll('mat-option')[1]; + const option = getOverlayHost(fixture)!.querySelectorAll('mat-option')[1]; expect(option.classList).toContain('mdc-list-item--selected'); expect(fixture.componentInstance.select.value).toBe('pizza-1'); @@ -3750,7 +3770,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -3784,7 +3804,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -3806,7 +3826,7 @@ describe('MatSelect', () => { expect(document.activeElement).withContext('Expected trigger to be focused.').toBe(select); select.blur(); // Blur manually since the programmatic click might not do it. - (overlayContainerElement.querySelector('.cdk-overlay-backdrop') as HTMLElement).click(); + getBackdrop(fixture)!.click(); fixture.detectChanges(); flush(); @@ -3827,7 +3847,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -3931,7 +3951,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -3957,7 +3977,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -3967,7 +3987,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - (overlayContainerElement.querySelector('mat-option') as HTMLElement).click(); + (getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement).click(); fixture.detectChanges(); flush(); @@ -4031,7 +4051,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + const panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; const paneRect = panel.getBoundingClientRect(); const formFieldWrapperRect = formFieldWrapper.getBoundingClientRect(); @@ -4049,7 +4069,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const panel = overlayContainerElement.querySelector('.cdk-overlay-pane')!; + const panel = getOverlayHost(fixture)!.querySelector('.cdk-overlay-pane')!; const paneRect = panel.getBoundingClientRect(); const formFieldWrapperRect = formFieldWrapper.getBoundingClientRect(); @@ -4076,7 +4096,7 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4092,7 +4112,7 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - const option = overlayContainerElement.querySelector('mat-option') as HTMLElement; + const option = getOverlayHost(fixture)?.querySelector('mat-option') as HTMLElement; option.click(); fixture.detectChanges(); @@ -4110,7 +4130,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4132,7 +4152,7 @@ describe('MatSelect', () => { testInstance.control.setValue(['steak-0', 'eggs-5']); fixture.detectChanges(); - const optionNodes = overlayContainerElement.querySelectorAll( + const optionNodes = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4149,7 +4169,7 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4171,7 +4191,7 @@ describe('MatSelect', () => { expect(testInstance.select.panelOpen).toBe(true); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4187,7 +4207,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4206,7 +4226,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4230,7 +4250,7 @@ describe('MatSelect', () => { fixture.detectChanges(); flush(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4303,7 +4323,7 @@ describe('MatSelect', () => { expect(fixture.componentInstance.select._keyManager.activeItemIndex).toBe(0); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4325,7 +4345,7 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - const options = overlayContainerElement.querySelectorAll( + const options = getOverlayHost(fixture)!.querySelectorAll( 'mat-option', ) as NodeListOf; @@ -4479,7 +4499,9 @@ describe('MatSelect', () => { trigger.click(); fixture.detectChanges(); - const optionNodes = Array.from(overlayContainerElement.querySelectorAll('mat-option')); + const optionNodes = Array.from( + getOverlayHost(fixture)!.querySelectorAll('mat-option'), + ); const optionInstances = testInstance.options.toArray(); expect(optionNodes.map(node => node.classList.contains('mdc-list-item--selected'))).toEqual([ @@ -4508,7 +4530,7 @@ describe('MatSelect', () => { fixture.detectChanges(); const optionNodes = Array.from( - overlayContainerElement.querySelectorAll('mat-option'), + getOverlayHost(fixture)!.querySelectorAll('mat-option'), ); const optionInstances = testInstance.options.toArray(); diff --git a/src/material/select/select.ts b/src/material/select/select.ts index b05f7f8b6e3a..a80bed47b3c9 100644 --- a/src/material/select/select.ts +++ b/src/material/select/select.ts @@ -71,6 +71,7 @@ import { NgForm, Validators, } from '@angular/forms'; +import {_getEventTarget} from '@angular/cdk/platform'; import { _animationsDisabled, _countGroupLabelsBeforeOption, @@ -1461,10 +1462,12 @@ export class MatSelect * @docs-private */ setDescribedByIds(ids: string[]) { + const element = this._elementRef.nativeElement; + if (ids.length) { - this._elementRef.nativeElement.setAttribute('aria-describedby', ids.join(' ')); + element.setAttribute('aria-describedby', ids.join(' ')); } else { - this._elementRef.nativeElement.removeAttribute('aria-describedby'); + element.removeAttribute('aria-describedby'); } } @@ -1472,7 +1475,22 @@ export class MatSelect * Implemented as part of MatFormFieldControl. * @docs-private */ - onContainerClick() { + onContainerClick(event: MouseEvent) { + const target = _getEventTarget(event) as HTMLElement | null; + + // Since the overlay is inside the form field, this handler can fire for interactions + // with the container. Note that while it's redundant to select both for the popover + // and select panel, we need to do it because it tests the clicks can occur after + // the panel was detached from the popover. + if ( + target && + (target.tagName === 'MAT-OPTION' || + target.classList.contains('cdk-overlay-backdrop') || + target.closest('.mat-mdc-select-panel')) + ) { + return; + } + this.focus(); this.open(); } diff --git a/src/material/stepper/_m3-stepper.scss b/src/material/stepper/_m3-stepper.scss index 433352c141f1..3fa45be5b7d4 100644 --- a/src/material/stepper/_m3-stepper.scss +++ b/src/material/stepper/_m3-stepper.scss @@ -57,7 +57,7 @@ $prefix: (mat, stepper); // Tokens that can be configured through Angular Material's density theming API. @function get-density-tokens($scale) { - $scale: theming.clamp-density(scale, -4); + $scale: theming.clamp-density($scale, -4); $index: ($scale * -1) + 1; @return ( diff --git a/src/material/timepicker/timepicker-input.ts b/src/material/timepicker/timepicker-input.ts index a6fbe28430b9..656860228778 100644 --- a/src/material/timepicker/timepicker-input.ts +++ b/src/material/timepicker/timepicker-input.ts @@ -41,7 +41,7 @@ import {MAT_INPUT_VALUE_ACCESSOR} from '../input'; import {Subscription} from 'rxjs'; import {DOWN_ARROW, ESCAPE, hasModifierKey, UP_ARROW} from '@angular/cdk/keycodes'; import {validateAdapter} from './util'; -import {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform'; +import {_getEventTarget, _getFocusedElementPierceShadowDom} from '@angular/cdk/platform'; /** * Input that can be used to enter time and connect to a `mat-timepicker`. @@ -265,8 +265,15 @@ export class MatTimepickerInput } /** Handles clicks on the input or the containing form field. */ - private _handleClick = (): void => { - if (!this.disabled() && this.openOnClick()) { + private _handleClick = (event: MouseEvent): void => { + if (this.disabled() || !this.openOnClick()) { + return; + } + + const target = _getEventTarget(event) as Node | null; + const overlayHost = this.timepicker()._getOverlayHost(); + + if (!target || !overlayHost || !overlayHost.contains(target)) { this.timepicker().open(); } }; diff --git a/src/material/timepicker/timepicker.ts b/src/material/timepicker/timepicker.ts index 042e0524a321..eee9a66f51f7 100644 --- a/src/material/timepicker/timepicker.ts +++ b/src/material/timepicker/timepicker.ts @@ -312,6 +312,10 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent { this._overlayRef?.dispose(); } + _getOverlayHost() { + return this._overlayRef?.hostElement; + } + /** Selects a specific time value. */ protected _selectValue(option: MatOption) { this.close(); @@ -356,6 +360,7 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent { .withFlexibleDimensions(false) .withPush(false) .withTransformOriginOn('.mat-timepicker-panel') + .withPopoverLocation('inline') .withPositions([ { originX: 'start', diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index 7245a9bd7abf..a3c6f63751f0 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -508,7 +508,8 @@ export class MatTooltip implements OnDestroy, AfterViewInit { .withTransformOriginOn(`.${this._cssClassPrefix}-tooltip`) .withFlexibleDimensions(false) .withViewportMargin(this._viewportMargin) - .withScrollableContainers(scrollableAncestors); + .withScrollableContainers(scrollableAncestors) + .withPopoverLocation('global'); strategy.positionChanges.pipe(takeUntil(this._destroyed)).subscribe(change => { this._updateCurrentPositionClass(change.connectionPair); @@ -528,6 +529,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { panelClass: this._overlayPanelClass ? [...this._overlayPanelClass, panelClass] : panelClass, scrollStrategy: this._injector.get(MAT_TOOLTIP_SCROLL_STRATEGY)(), disableAnimations: this._animationsDisabled, + usePopover: true, }); this._updatePosition(this._overlayRef); diff --git a/test/browser-providers.js b/test/browser-providers.js index 8d8a4c4ca86b..6917083699b8 100644 --- a/test/browser-providers.js +++ b/test/browser-providers.js @@ -4,7 +4,7 @@ * - `browserstack`: Launches the browser within BrowserStack */ const browserConfig = { - 'Safari16': {unitTest: {target: 'browserstack'}}, + 'Safari26': {unitTest: {target: 'browserstack'}}, }; /** Exports all available custom Karma browsers. */ diff --git a/test/karma-browsers.json b/test/karma-browsers.json index b46b1a4b9883..3480a4f036b4 100644 --- a/test/karma-browsers.json +++ b/test/karma-browsers.json @@ -1,9 +1,9 @@ { - "BROWSERSTACK_SAFARI16": { + "BROWSERSTACK_SAFARI26": { "base": "BrowserStack", "browser": "Safari", - "browser_version": "16.0", + "browser_version": "26.0", "os": "OS X", - "os_version": "Ventura" + "os_version": "Tahoe" } } diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 059379e5401b..9ebeeaef3e01 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -78,6 +78,7 @@ def ng_package( }), visibility = visibility, rollup_runtime_deps = [ + "//:node_modules/@babel/core", "//:node_modules/@rollup/plugin-commonjs", "//:node_modules/@rollup/plugin-node-resolve", "//:node_modules/magic-string", diff --git a/tslint.json b/tslint.json index aa514c5ed925..b8c41104390a 100644 --- a/tslint.json +++ b/tslint.json @@ -40,6 +40,7 @@ {"name": ["Object", "assign"], "message": "Use the spread operator instead."}, {"name": ["*", "asObservable"], "message": "Cast to Observable type instead."}, {"name": ["*", "removeChild"], "message": "Use `remove` instead instead."}, + {"name": ["CommonModule"], "message": "Import the necessary symbols directly instead."}, {"name": ["*", "compileComponents"], "message": "`compileComponents` is not necessary."}, { "name": ["isDevMode"],