diff --git a/.agent/skills b/.agent/skills deleted file mode 120000 index 85ae949a9ab2..000000000000 --- a/.agent/skills +++ /dev/null @@ -1 +0,0 @@ -../.gemini/skills \ No newline at end of file diff --git a/.gemini/skills/adev-writing-guide/SKILL.md b/.agent/skills/adev-writing-guide/SKILL.md similarity index 97% rename from .gemini/skills/adev-writing-guide/SKILL.md rename to .agent/skills/adev-writing-guide/SKILL.md index 4855ec6b95de..250afcbfad1a 100644 --- a/.gemini/skills/adev-writing-guide/SKILL.md +++ b/.agent/skills/adev-writing-guide/SKILL.md @@ -1,6 +1,6 @@ --- name: adev-writing-guide -description: Comprehensive writing guide for Angular documentation (adev). Covers Google Technical Writing standards, Angular-specific markdown extensions, code blocks, and components. Use when authoring or reviewing content in adev/src/content. +description: Comprehensive writing guide for Angular documentation (adev). Covers Google Technical Writing standards, Angular-specific markdown extensions, code blocks, and components. You MUST use this skill any time you plan to create, edit, or review documentation files in `adev/` or `adev/src/content`. --- # Angular Documentation (adev) Writing Guide diff --git a/.agent/skills/pr_review/SKILL.md b/.agent/skills/pr_review/SKILL.md new file mode 100644 index 000000000000..f407ad4fe904 --- /dev/null +++ b/.agent/skills/pr_review/SKILL.md @@ -0,0 +1,134 @@ +--- +name: PR Review +description: Guidelines and tools for reviewing pull requests in the Angular repository. +--- + +# PR Review Guidelines + +When reviewing a pull request for the `angular` repository, follow these essential guidelines to ensure high-quality contributions: + +1. **Context & Ecosystem**: + - Keep in mind that this is the core Angular framework. Changes here can impact millions of developers. + - Be mindful of backwards compatibility. Breaking changes require strict approval processes and deprecation periods. + +2. **Key Focus Areas**: + - **Comprehensive Reviews**: You **MUST always** perform a deep, comprehensive review of the _entire_ pull request. If the user asks you to look into a specific issue, file, or area of concern, you must investigate that specific area _in addition to_ reviewing the rest of the PR's substantive changes. Do not terminate your review after addressing only the user's focal point. + - **Package-Specific Guidelines**: Check if there are specific guidelines for the package being modified in the `reference/` directory (e.g., `reference/router.md`). Always prioritize these rules for their respective packages. + - **Commit Messages**: Evaluate the quality of commit messages. They should explain the _why_ behind the change, not just the _what_. Someone should be able to look at the commit history years from now and clearly understand the context and reasoning for the change. + - **Code Cleanliness**: Ensure the code is readable, maintainable, and follows Angular's project standards. + - **Performance**: Look out for code that might negatively impact runtime performance or bundle size, particularly in hot paths like change detection or rendering. + - **Testing**: Ensure all new logic has comprehensive tests, including edge cases. **Do NOT run tests locally** as part of your review process. CI handles this automatically, and running tests locally is redundant and inefficient. + - **API Design**: Ensure new public APIs are well-designed, consistent with existing APIs, and properly documented. + - **Payload Size**: Pay attention to the impact of changes on the final client payload size. + +3. **Execution Workflow**: + Determine the appropriate review method. If the user explicitly asks for a `remote` or `local` review in their request, that takes precedence (e.g. "leave comments on the PR" implies `remote`). Otherwise, use the GitHub MCP or available scripts to determine if the review should be `local` or `remote`. + + **Common Review Practices (Applies to both Local and Remote)** + - **Preparation & Checklist**: + - First, create a task list (e.g., in `task.md`) that you can easily reference containing **all** the review requirements from the "Key Focus Areas" section (Commit Messages, Performance, Testing, etc.), along with any specific review notes or requests from the user. + - Before doing an in-depth review, expand this list into more detailed items of what you plan to explore and verify in the PR. + - As you conduct the review, check off items in this list, adding your assessment or findings underneath each item. + - At the end of your review, refer back to the checklist to ensure every single requirement was completely verified. + - **Fetch PR Metadata Safely**: When you need to read the PR description or context, do NOT use `gh pr view ` by itself, as its default GraphQL query may fail due to lack of `read:org` and `read:discussion` token scopes. Instead, use `read_url_content` on the PR URL or use `gh pr view --json title,body,state,author`. + - **Check Existing Comments First**: Before formulating feedback, use the GitHub MCP or available scripts to fetch existing comments on the PR. Review this feedback to avoid duplicate comments, and incorporate its insights into your own review process. + - **Constructive Feedback**: Provide clear, actionable, and polite feedback. Explain the _why_ behind your suggestions or edits. Do **NOT** leave inline comments purely to praise, agree with, or acknowledge a correct implementation detail, as this clutters the review. If you want to praise the PR, do so in the single general PR comment. + + **A. Local Code Review (If the PR is owned by the author requesting the review)** + - **Checkout**: Check out the PR branch locally (if it doesn't already exist, fetch it). If checking out the branch fails due to a worktree claim (e.g. "fatal: '' is already used by worktree at ''"), do the review in that directory. + - **Review & Edit**: Execute the review directly on the code. Instead of adding inline PR comments for suggestions, format the codebase or apply the edits directly to the files. + - **Feedback**: Summarize the review findings and the concrete changes you made in a message to the user, referencing the completed items from your checklist. + - **Do NOT Commit or Push**: Leave the changes uncommitted in the working directory so the user can easily review the pending edits locally. Let the user know the changes are ready for their review, but do not ask for approval to push. + - **Resolve Comments**: Once the user confirms the changes are good and should be committed/pushed, respond to the existing comments as 'resolved' using the GitHub MCP or available scripts. + + **B. Remote Code Review (For all other PRs)** + - **Batching Comments (MCP Server - Preferred)**: If you have the GitHub MCP Server configured, you **MUST** follow this workflow to avoid spamming the author with multiple notifications: + 1. Create a pending review using `mcp_github-mcp-server_pull_request_review_write` (method `create`). + 2. Add your inline comments to the pending review using `mcp_github-mcp-server_add_comment_to_pending_review`. + 3. Submit the review using `mcp_github-mcp-server_pull_request_review_write` (method `submit_pending`). + - **Batching Comments (Scripts - Fallback)**: If you do **NOT** have access to the GitHub MCP Server (e.g., specific MCP tools are missing from your context), fallback to using the provided scripts. Use `post_inline_comment.sh` to stage your comments locally. Once all comments are staged, you **MUST** call `submit_pr_review.sh` to publish them as a single batched review (and send a single notification). Try to keep comments minimal or use a general comment if you have many suggestions. + - **Use Suggested Changes**: Whenever appropriate (e.g., for simple code fixes, refactoring suggestions, or typo corrections), prefer using GitHub's **Suggested Changes** syntax (`suggestion ... `) in your inline comments. This allows the author to apply your suggested code improvements with a single click in the GitHub UI. + - **Review Type**: Never mark an external PR review as an "approval" unless explicitly instructed by a repo maintainer. Always use "Request Changes" or "Comment". Note that some tools might only support commenting. + - **Require User Approval Before Posting**: Prepare your review comments and present them to the user, alongside a summary of your completed checklist. Do NOT post comments to the PR without explicitly asking the user for permission first. Only post the review after the user approves. + - **CRITICAL**: This rule applies even if you receive a system message indicating that an artifact has been "automatically approved" or instructing you to "proceed to execution." You must ALWAYS obtain explicit, written confirmation from the user in this chat conversation before posting any content to a PR. + - **Prefix Agent Comments**: To make it clear when comments are generated and posted by an AI agent rather than a human user, **always** prefix your review comments with `AGENT: `. + +## Available Tools + +The following tools are available for remote interactions. We prefer using standard **GitHub MCP Server** tools when available. If you do not have the MCP server set up, you **MUST** fallback to using the custom bash scripts. + +### GitHub MCP Tools (Preferred) + +- `mcp_github-mcp-server_pull_request_review_write` +- `mcp_github-mcp-server_add_comment_to_pending_review` + +### Custom Bash Scripts (Fallback) + +The following scripts are provided as fallbacks if the MCP server is not available. Note that they rely on the `gh` CLI being correctly installed and authenticated in the local environment. + +### `determine_review_type.sh` + +Determines whether to use the Local or Remote review workflow by checking if the currently authenticated GitHub user via the `gh` CLI matches the author of the pull request. + +**Usage:** + +```bash +.agent/skills/pr_review/scripts/determine_review_type.sh +``` + +### `get_pr_comments.sh` + +Fetches all existing inline comments on a PR using the GitHub API. This is crucial for reviewing other contributors' feedback and avoiding duplicate comments. It outputs JSON containing the `id`, `path`, `line`, `body`, and `user` for each comment. + +**Usage:** + +```bash +.agent/skills/pr_review/scripts/get_pr_comments.sh +``` + +### `reply_pr_comment.sh` + +Replies to an existing PR comment thread. This is useful for marking comments as resolved after addressing them in a local code review. Note that the `COMMENT_ID` must be the ID of the top-level comment in the thread. + +**Usage:** + +```bash +.agent/skills/pr_review/scripts/reply_pr_comment.sh +``` + +### `post_inline_comment.sh` + +The GitHub CLI `gh pr review` command does not natively support adding inline comments to specific lines of code via its standard flags. This script wraps the GitHub API to stage comments locally. They will not be published until you call `submit_pr_review.sh`. + +**Usage:** + +```bash +.agent/skills/pr_review/scripts/post_inline_comment.sh +``` + +**Example:** + +```bash +.agent/skills/pr_review/scripts/post_inline_comment.sh 12345 "packages/core/src/render3/instructions/element.ts" 42 "AGENT: Consider the performance implications here." +``` + +### `submit_pr_review.sh` + +Submits all locally staged inline comments as a single batched review via the GitHub Pull Request Reviews API. + +**Usage:** + +```bash +.agent/skills/pr_review/scripts/submit_pr_review.sh [BODY] +``` + +**Options:** + +- `EVENT_TYPE`: Must be `COMMENT`, `APPROVE`, or `REQUEST_CHANGES`. Never use `APPROVE` for external PRs. +- `BODY`: (Optional) A general summary comment for the review. + +**Example:** + +```bash +.agent/skills/pr_review/scripts/submit_pr_review.sh 12345 COMMENT "AGENT: I have left a few inline suggestions for your consideration." +``` diff --git a/.agent/skills/pr_review/reference/router.md b/.agent/skills/pr_review/reference/router.md new file mode 100644 index 000000000000..2d0569af9247 --- /dev/null +++ b/.agent/skills/pr_review/reference/router.md @@ -0,0 +1,7 @@ +# Router PR Review Guidelines + +When reviewing pull requests that modify the Angular Router (`packages/router`), pay special attention to the following: + +- **Timing Sensitivity**: The router is extremely sensitive to timing changes. Any changes that alter the asynchronous timing of navigations, resolvers, or guards are almost always breaking changes and must be scrutinized carefully. +- **Testing Practices**: Tests should usually use the `RouterTestingHarness`. Many existing tests are older and do not use this harness. Do not blindly follow the shape of existing tests when writing or reviewing new ones; encourage the use of modern testing utilities. +- **Feature Justification**: Changes to router core code should be well-justified. Consider whether the change is proven to be a core developer ask, such as resolving a highly upvoted GitHub issue or addressing a critical bug. diff --git a/.agent/skills/pr_review/scripts/determine_review_type.sh b/.agent/skills/pr_review/scripts/determine_review_type.sh new file mode 100755 index 000000000000..d773b40f6e42 --- /dev/null +++ b/.agent/skills/pr_review/scripts/determine_review_type.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +# determine_review_type.sh +# Determines if the PR should be reviewed locally or remotely based on author. + +if [ -z "$1" ]; then + echo "Usage: determine_review_type.sh " + exit 1 +fi + +PR_NUMBER=$1 + +# Get current authenticated user +CURRENT_USER=$(gh api user -q .login 2>/dev/null) +if [ $? -ne 0 ]; then + echo "Error: Could not determine current GitHub user. Are you logged in to gh?" + exit 1 +fi + +# Get PR author +PR_AUTHOR=$(gh pr view "$PR_NUMBER" --json author -q .author.login 2>/dev/null) +if [ $? -ne 0 ]; then + echo "Error: Could not retrieve PR information for $PR_NUMBER." + exit 1 +fi + +if [ "$CURRENT_USER" = "$PR_AUTHOR" ]; then + echo "local" +else + echo "remote" +fi diff --git a/.agent/skills/pr_review/scripts/get_pr_comments.sh b/.agent/skills/pr_review/scripts/get_pr_comments.sh new file mode 100755 index 000000000000..e6c3fbeece46 --- /dev/null +++ b/.agent/skills/pr_review/scripts/get_pr_comments.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +# get_pr_comments.sh +# Fetches existing inline comments on a PR to avoid duplicate reviews. +# Usage: ./get_pr_comments.sh + +if [ "$#" -lt 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +PR_NUMBER="$1" + +# Ensure gh cli is installed +if ! command -v gh &> /dev/null; then + echo "Error: gh CLI could not be found. Please install and authenticate." + exit 1 +fi + +# Get the current repository (e.g., angular/angular) +REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner) + +# Fetch comments +gh api \ + --paginate \ + -H "Accept: application/vnd.github+json" \ + "/repos/${REPO}/pulls/${PR_NUMBER}/comments" \ + --jq '.[] | {id: .id, path: .path, line: .line, body: .body, user: .user.login}' diff --git a/.agent/skills/pr_review/scripts/post_inline_comment.sh b/.agent/skills/pr_review/scripts/post_inline_comment.sh new file mode 100755 index 000000000000..61956cb1f390 --- /dev/null +++ b/.agent/skills/pr_review/scripts/post_inline_comment.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +# post_inline_comment.sh +# Adds an inline comment to a specific line in a PR via the GitHub API. +# Usage: ./post_inline_comment.sh + +if [ "$#" -lt 4 ]; then + echo "Usage: $0 " + exit 1 +fi + +PR_NUMBER="$1" +FILE_PATH="$2" +LINE="$3" +BODY="$4" + +# Ensure gh cli is installed +if ! command -v gh &> /dev/null; then + echo "Error: gh CLI could not be found. Please install and authenticate." + exit 1 +fi + +# Get the current repository (e.g., angular/angular) +REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner) + +echo "Staging inline comment for PR #${PR_NUMBER} on ${FILE_PATH}:${LINE}..." + +COMMENT_FILE="/tmp/angular_pr_${PR_NUMBER}_comments.json" +if [ ! -f "$COMMENT_FILE" ]; then + echo "[]" > "$COMMENT_FILE" +fi + +# Append the new comment to the JSON array +jq --arg path "${FILE_PATH}" --argjson line "${LINE}" --arg body "${BODY}" \ + '. += [{"path": $path, "line": $line, "body": $body}]' "$COMMENT_FILE" > "${COMMENT_FILE}.tmp" && mv "${COMMENT_FILE}.tmp" "$COMMENT_FILE" + +echo "Comment successfully staged locally. Remember to call submit_pr_review.sh when finished to publish all comments as a single review!" diff --git a/.agent/skills/pr_review/scripts/reply_pr_comment.sh b/.agent/skills/pr_review/scripts/reply_pr_comment.sh new file mode 100755 index 000000000000..bd4577c37aca --- /dev/null +++ b/.agent/skills/pr_review/scripts/reply_pr_comment.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +# reply_pr_comment.sh +# Replies to an existing PR comment thread. Note: COMMENT_ID must be the ID of the top-level comment in the thread you are replying to. + +if [ "$#" -lt 3 ]; then + echo "Usage: reply_pr_comment.sh " + exit 1 +fi + +PR_NUMBER="$1" +COMMENT_ID="$2" +BODY="$3" + +# Ensure gh cli is installed +if ! command -v gh &> /dev/null; then + echo "Error: gh CLI could not be found. Please install and authenticate." + exit 1 +fi + +# Get the current repository (e.g., angular/angular) +REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner) + +# Reply to the thread using the provided comment ID +gh api \ + --silent \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + "/repos/${REPO}/pulls/${PR_NUMBER}/comments/${COMMENT_ID}/replies" \ + -f body="$BODY" diff --git a/.agent/skills/pr_review/scripts/submit_pr_review.sh b/.agent/skills/pr_review/scripts/submit_pr_review.sh new file mode 100755 index 000000000000..9dc28dad757c --- /dev/null +++ b/.agent/skills/pr_review/scripts/submit_pr_review.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +# submit_pr_review.sh +# Submits a batched PR review using comments previously staged by post_inline_comment.sh +# Usage: ./submit_pr_review.sh [BODY] +# EVENT_TYPE must be COMMENT, APPROVE, or REQUEST_CHANGES + +if [ "$#" -lt 2 ]; then + echo "Usage: $0 [BODY]" + echo "EVENT_TYPE must be COMMENT, APPROVE, or REQUEST_CHANGES" + exit 1 +fi + +PR_NUMBER="$1" +EVENT="$2" +BODY="${3:-}" +COMMENT_FILE="/tmp/angular_pr_${PR_NUMBER}_comments.json" + +if ! command -v gh &> /dev/null; then + echo "Error: gh CLI could not be found. Please install and authenticate." + exit 1 +fi + +REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner) + +# Check if there are staged comments +COMMENTS="[]" +if [ -f "$COMMENT_FILE" ]; then + COMMENTS=$(cat "$COMMENT_FILE") +fi + +echo "Submitting review for PR #${PR_NUMBER}..." + +# Create the payload +PAYLOAD_FILE="/tmp/angular_pr_${PR_NUMBER}_payload.json" +jq -n --arg event "$EVENT" --arg body "$BODY" --argjson comments "$COMMENTS" \ + '{event: $event, body: $body, comments: $comments}' > "$PAYLOAD_FILE" + +# Post the review using the GitHub Pull Request Reviews API +gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${REPO}/pulls/${PR_NUMBER}/reviews" \ + --input "$PAYLOAD_FILE" + +echo "Review submitted successfully!" +rm -f "$COMMENT_FILE" +rm -f "$PAYLOAD_FILE" diff --git a/.gemini/skills/reference-compiler-cli/SKILL.md b/.agent/skills/reference-compiler-cli/SKILL.md similarity index 100% rename from .gemini/skills/reference-compiler-cli/SKILL.md rename to .agent/skills/reference-compiler-cli/SKILL.md diff --git a/.gemini/skills/reference-core/SKILL.md b/.agent/skills/reference-core/SKILL.md similarity index 100% rename from .gemini/skills/reference-core/SKILL.md rename to .agent/skills/reference-core/SKILL.md diff --git a/.gemini/skills/reference-signal-forms/SKILL.md b/.agent/skills/reference-signal-forms/SKILL.md similarity index 100% rename from .gemini/skills/reference-signal-forms/SKILL.md rename to .agent/skills/reference-signal-forms/SKILL.md diff --git a/.gemini/skills/reference-signal-forms/references/integration.md b/.agent/skills/reference-signal-forms/references/integration.md similarity index 100% rename from .gemini/skills/reference-signal-forms/references/integration.md rename to .agent/skills/reference-signal-forms/references/integration.md diff --git a/.agent/workflows/fix-flaky-tests.md b/.agent/workflows/fix-flaky-tests.md new file mode 100644 index 000000000000..a747b6e9466d --- /dev/null +++ b/.agent/workflows/fix-flaky-tests.md @@ -0,0 +1,56 @@ +--- +description: Find and fix flaky tests in the repository +--- + +Investigate flaky tests in the repo and propose fixes to improve stability. +High-level process: + +1. Run tests in the repo to look for flakes. + - Consider using Bazel's `--runs_per_test` flag to easily find + flakes. + - Be cognizant of not exhausting all the resources on the current + machine, run a subset of tests at a time such as + `bazel test //packages/core/...`. +2. Once you find some flakes, focus on one at a time. +3. Create a new branch named `flakes/${relevantNameFromTest}`. +4. Reproduce the flake to the best of your ability. + - Consider using `--test_env JASMINE_RANDOM_SEED=1234` to + replicate the broken test ordering. +5. Debug the test to understand the failure mode. + - Consider temporarily disabling / skipping other tests with `xit` + and `fit` to narrow down where the flake might be coming from if + multiple tests are influencing each other. + - Consider temporarily ignoring Firefox tests with + `--test_tag_filters -firefox` if the flake does not appear to be + browser specific. + - Consider using `--test_sharding_strategy disabled` to run the + test in a single shard. + - Try to understand why the test was _flaky_, not just why it + _failed_. Understanding the inconsistency is important to + finding the correct fix. +6. Attempt a fix and validate with `--runs_per_test`. + - Iterate on the fix until you have something which appears to + work. + - If you find yourself stuck and not making meaningful progress, + note down what you've learned/where you're struggling, commit + what you have, look for another flake to fix, and continue. At + the end, surface to the user what you failed to fix. + - Don't try to make significant changes to Angular's runtime + behavior, focus just on making the test pass/fail consistently. +7. Commit the change with relevant details in the commit message and + move on to the next test. + - Be sure to include your theory of why the test was flaky and + how this fix eliminates or reduces that flakiness. +8. Iterate as many times as the user requests you to (default 5 + branches if not otherwise specified). +9. Once you can't find any flaky tests or have iterated as many times + as requested, stop and inform the user what you found and fixed. + +Additional notes: + +- Multiple fixes including the same/related files can go in the same + commit or multiple commits on the same branch. +- Distinct test fixes should go in different branches, make a new one + for each investigation. +- You may push these branches to `origin`, but do not create PRs for + them. diff --git a/.bazelrc b/.bazelrc index 415b7ec60058..9a8dbfd1cceb 100644 --- a/.bazelrc +++ b/.bazelrc @@ -159,16 +159,6 @@ common --incompatible_allow_tags_propagation # ) build --nosandbox_default_allow_network -################################## -# Saucelabs tests settings # -# Turn on these settings with # -# --config=saucelabs # -################################## - -# For saucelabs tests we don't want to enable flaky test attempts. Karma has its own integrated -# retry mechanism and we do not want to retry unnecessarily if Karma already tried multiple times. -test:saucelabs --flaky_test_attempts=1 - ################ # Flag Aliases # ################ diff --git a/.bazelversion b/.bazelversion index f9c71a52e2fd..df5119ec64e6 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -8.5.1 +8.7.0 diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 8d1c3c310286..000000000000 --- a/.clang-format +++ /dev/null @@ -1,3 +0,0 @@ -Language: JavaScript -BasedOnStyle: Google -ColumnLimit: 100 diff --git a/.gemini/config.yaml b/.gemini/config.yaml index 28eab0db4d88..c37f5ba91c21 100644 --- a/.gemini/config.yaml +++ b/.gemini/config.yaml @@ -7,4 +7,5 @@ code_review: help: false summary: false code_review: false -ignore_patterns: [] +ignore_patterns: + - pnpm-lock.yaml diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 8283170d78e5..d4b2cde773ea 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -43,3 +43,5 @@ c702e8af0b2144d97b93171dc2806ed1a0346762 6d3f5752f204a5a30f3c09fbf6d4a510a4522bcb 3fa7b2b136696464e0a91b5ec25c6adf272d4d6b ad65f44877f22caadbc8b977b5b720c3c86fcc45 +# commit that changed formatting +698b0288bee60b8c5926148b79b5b93f454098db \ No newline at end of file diff --git a/.github/actions/deploy-docs-site/BUILD.bazel b/.github/actions/deploy-docs-site/BUILD.bazel index 2c87772b1f38..c66019e37d3c 100644 --- a/.github/actions/deploy-docs-site/BUILD.bazel +++ b/.github/actions/deploy-docs-site/BUILD.bazel @@ -11,7 +11,6 @@ esbuild_checked_in( config = "esbuild.conf.js", entry_point = ":lib/main.mts", external = [ - "undici", "pnpapi", ], metafile = False, @@ -36,7 +35,5 @@ ts_project( "//:node_modules/@actions/github", "//:node_modules/@angular/ng-dev", "//:node_modules/@types/node", - "//:node_modules/@types/tmp", - "//:node_modules/tmp", ], ) diff --git a/.github/actions/deploy-docs-site/lib/credential.mts b/.github/actions/deploy-docs-site/lib/credential.mts index deb654d3041a..5654b146a366 100644 --- a/.github/actions/deploy-docs-site/lib/credential.mts +++ b/.github/actions/deploy-docs-site/lib/credential.mts @@ -1,15 +1,17 @@ -import {fileSync} from 'tmp'; -import {writeSync} from 'node:fs'; +import {writeFileSync, mkdtempSync} from 'node:fs'; +import {join} from 'node:path'; +import {tmpdir} from 'node:os'; import {getInput, setSecret} from '@actions/core'; let credentialFilePath: undefined | string; export function getCredentialFilePath(): string { if (credentialFilePath === undefined) { - const tmpFile = fileSync({postfix: '.json'}); - writeSync(tmpFile.fd, getInput('serviceKey', {required: true})); - setSecret(tmpFile.name); - credentialFilePath = tmpFile.name; + const tmpDir = mkdtempSync(join(tmpdir(), 'credential-')); + const filePath = join(tmpDir, 'credential.json'); + writeFileSync(filePath, getInput('serviceKey', {required: true})); + setSecret(filePath); + credentialFilePath = filePath; } return credentialFilePath; } diff --git a/.github/actions/deploy-docs-site/lib/deployments.mts b/.github/actions/deploy-docs-site/lib/deployments.mts index f1c974ab8926..c8ad8241b7a1 100644 --- a/.github/actions/deploy-docs-site/lib/deployments.mts +++ b/.github/actions/deploy-docs-site/lib/deployments.mts @@ -1,6 +1,9 @@ -import {fetchLongTermSupportBranchesFromNpm, ActiveReleaseTrains} from '@angular/ng-dev'; -import {ReleaseConfig} from '@angular/ng-dev'; -import {AuthenticatedGitClient} from '@angular/ng-dev'; +import { + fetchLongTermSupportBranchesFromNpm, + ActiveReleaseTrains, + AuthenticatedGitClient, + ReleaseConfig, +} from '@angular/ng-dev'; export interface Deployment { branch: string; diff --git a/.github/actions/deploy-docs-site/main.js b/.github/actions/deploy-docs-site/main.js index 99ebd156a9df..e2f4a611ffd9 100644 --- a/.github/actions/deploy-docs-site/main.js +++ b/.github/actions/deploy-docs-site/main.js @@ -270,6 +270,18382 @@ var require_tunnel2 = __commonJS({ } }); +// +var require_symbols = __commonJS({ + ""(exports, module) { + module.exports = { + kClose: Symbol("close"), + kDestroy: Symbol("destroy"), + kDispatch: Symbol("dispatch"), + kUrl: Symbol("url"), + kWriting: Symbol("writing"), + kResuming: Symbol("resuming"), + kQueue: Symbol("queue"), + kConnect: Symbol("connect"), + kConnecting: Symbol("connecting"), + kKeepAliveDefaultTimeout: Symbol("default keep alive timeout"), + kKeepAliveMaxTimeout: Symbol("max keep alive timeout"), + kKeepAliveTimeoutThreshold: Symbol("keep alive timeout threshold"), + kKeepAliveTimeoutValue: Symbol("keep alive timeout"), + kKeepAlive: Symbol("keep alive"), + kHeadersTimeout: Symbol("headers timeout"), + kBodyTimeout: Symbol("body timeout"), + kServerName: Symbol("server name"), + kLocalAddress: Symbol("local address"), + kHost: Symbol("host"), + kNoRef: Symbol("no ref"), + kBodyUsed: Symbol("used"), + kBody: Symbol("abstracted request body"), + kRunning: Symbol("running"), + kBlocking: Symbol("blocking"), + kPending: Symbol("pending"), + kSize: Symbol("size"), + kBusy: Symbol("busy"), + kQueued: Symbol("queued"), + kFree: Symbol("free"), + kConnected: Symbol("connected"), + kClosed: Symbol("closed"), + kNeedDrain: Symbol("need drain"), + kReset: Symbol("reset"), + kDestroyed: Symbol.for("nodejs.stream.destroyed"), + kResume: Symbol("resume"), + kOnError: Symbol("on error"), + kMaxHeadersSize: Symbol("max headers size"), + kRunningIdx: Symbol("running index"), + kPendingIdx: Symbol("pending index"), + kError: Symbol("error"), + kClients: Symbol("clients"), + kClient: Symbol("client"), + kParser: Symbol("parser"), + kOnDestroyed: Symbol("destroy callbacks"), + kPipelining: Symbol("pipelining"), + kSocket: Symbol("socket"), + kHostHeader: Symbol("host header"), + kConnector: Symbol("connector"), + kStrictContentLength: Symbol("strict content length"), + kMaxRedirections: Symbol("maxRedirections"), + kMaxRequests: Symbol("maxRequestsPerClient"), + kProxy: Symbol("proxy agent options"), + kCounter: Symbol("socket request counter"), + kInterceptors: Symbol("dispatch interceptors"), + kMaxResponseSize: Symbol("max response size"), + kHTTP2Session: Symbol("http2Session"), + kHTTP2SessionState: Symbol("http2Session state"), + kRetryHandlerDefaultRetry: Symbol("retry agent default retry"), + kConstruct: Symbol("constructable"), + kListeners: Symbol("listeners"), + kHTTPContext: Symbol("http context"), + kMaxConcurrentStreams: Symbol("max concurrent streams"), + kNoProxyAgent: Symbol("no proxy agent"), + kHttpProxyAgent: Symbol("http proxy agent"), + kHttpsProxyAgent: Symbol("https proxy agent") + }; + } +}); + +// +var require_errors = __commonJS({ + ""(exports, module) { + "use strict"; + var kUndiciError = Symbol.for("undici.error.UND_ERR"); + var UndiciError = class extends Error { + constructor(message) { + super(message); + this.name = "UndiciError"; + this.code = "UND_ERR"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kUndiciError] === true; + } + [kUndiciError] = true; + }; + var kConnectTimeoutError = Symbol.for("undici.error.UND_ERR_CONNECT_TIMEOUT"); + var ConnectTimeoutError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ConnectTimeoutError"; + this.message = message || "Connect Timeout Error"; + this.code = "UND_ERR_CONNECT_TIMEOUT"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kConnectTimeoutError] === true; + } + [kConnectTimeoutError] = true; + }; + var kHeadersTimeoutError = Symbol.for("undici.error.UND_ERR_HEADERS_TIMEOUT"); + var HeadersTimeoutError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "HeadersTimeoutError"; + this.message = message || "Headers Timeout Error"; + this.code = "UND_ERR_HEADERS_TIMEOUT"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kHeadersTimeoutError] === true; + } + [kHeadersTimeoutError] = true; + }; + var kHeadersOverflowError = Symbol.for("undici.error.UND_ERR_HEADERS_OVERFLOW"); + var HeadersOverflowError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "HeadersOverflowError"; + this.message = message || "Headers Overflow Error"; + this.code = "UND_ERR_HEADERS_OVERFLOW"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kHeadersOverflowError] === true; + } + [kHeadersOverflowError] = true; + }; + var kBodyTimeoutError = Symbol.for("undici.error.UND_ERR_BODY_TIMEOUT"); + var BodyTimeoutError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "BodyTimeoutError"; + this.message = message || "Body Timeout Error"; + this.code = "UND_ERR_BODY_TIMEOUT"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kBodyTimeoutError] === true; + } + [kBodyTimeoutError] = true; + }; + var kResponseStatusCodeError = Symbol.for("undici.error.UND_ERR_RESPONSE_STATUS_CODE"); + var ResponseStatusCodeError = class extends UndiciError { + constructor(message, statusCode, headers, body) { + super(message); + this.name = "ResponseStatusCodeError"; + this.message = message || "Response Status Code Error"; + this.code = "UND_ERR_RESPONSE_STATUS_CODE"; + this.body = body; + this.status = statusCode; + this.statusCode = statusCode; + this.headers = headers; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kResponseStatusCodeError] === true; + } + [kResponseStatusCodeError] = true; + }; + var kInvalidArgumentError = Symbol.for("undici.error.UND_ERR_INVALID_ARG"); + var InvalidArgumentError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "InvalidArgumentError"; + this.message = message || "Invalid Argument Error"; + this.code = "UND_ERR_INVALID_ARG"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kInvalidArgumentError] === true; + } + [kInvalidArgumentError] = true; + }; + var kInvalidReturnValueError = Symbol.for("undici.error.UND_ERR_INVALID_RETURN_VALUE"); + var InvalidReturnValueError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "InvalidReturnValueError"; + this.message = message || "Invalid Return Value Error"; + this.code = "UND_ERR_INVALID_RETURN_VALUE"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kInvalidReturnValueError] === true; + } + [kInvalidReturnValueError] = true; + }; + var kAbortError = Symbol.for("undici.error.UND_ERR_ABORT"); + var AbortError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "AbortError"; + this.message = message || "The operation was aborted"; + this.code = "UND_ERR_ABORT"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kAbortError] === true; + } + [kAbortError] = true; + }; + var kRequestAbortedError = Symbol.for("undici.error.UND_ERR_ABORTED"); + var RequestAbortedError = class extends AbortError { + constructor(message) { + super(message); + this.name = "AbortError"; + this.message = message || "Request aborted"; + this.code = "UND_ERR_ABORTED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kRequestAbortedError] === true; + } + [kRequestAbortedError] = true; + }; + var kInformationalError = Symbol.for("undici.error.UND_ERR_INFO"); + var InformationalError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "InformationalError"; + this.message = message || "Request information"; + this.code = "UND_ERR_INFO"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kInformationalError] === true; + } + [kInformationalError] = true; + }; + var kRequestContentLengthMismatchError = Symbol.for("undici.error.UND_ERR_REQ_CONTENT_LENGTH_MISMATCH"); + var RequestContentLengthMismatchError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "RequestContentLengthMismatchError"; + this.message = message || "Request body length does not match content-length header"; + this.code = "UND_ERR_REQ_CONTENT_LENGTH_MISMATCH"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kRequestContentLengthMismatchError] === true; + } + [kRequestContentLengthMismatchError] = true; + }; + var kResponseContentLengthMismatchError = Symbol.for("undici.error.UND_ERR_RES_CONTENT_LENGTH_MISMATCH"); + var ResponseContentLengthMismatchError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ResponseContentLengthMismatchError"; + this.message = message || "Response body length does not match content-length header"; + this.code = "UND_ERR_RES_CONTENT_LENGTH_MISMATCH"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kResponseContentLengthMismatchError] === true; + } + [kResponseContentLengthMismatchError] = true; + }; + var kClientDestroyedError = Symbol.for("undici.error.UND_ERR_DESTROYED"); + var ClientDestroyedError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ClientDestroyedError"; + this.message = message || "The client is destroyed"; + this.code = "UND_ERR_DESTROYED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kClientDestroyedError] === true; + } + [kClientDestroyedError] = true; + }; + var kClientClosedError = Symbol.for("undici.error.UND_ERR_CLOSED"); + var ClientClosedError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ClientClosedError"; + this.message = message || "The client is closed"; + this.code = "UND_ERR_CLOSED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kClientClosedError] === true; + } + [kClientClosedError] = true; + }; + var kSocketError = Symbol.for("undici.error.UND_ERR_SOCKET"); + var SocketError = class extends UndiciError { + constructor(message, socket) { + super(message); + this.name = "SocketError"; + this.message = message || "Socket error"; + this.code = "UND_ERR_SOCKET"; + this.socket = socket; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kSocketError] === true; + } + [kSocketError] = true; + }; + var kNotSupportedError = Symbol.for("undici.error.UND_ERR_NOT_SUPPORTED"); + var NotSupportedError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "NotSupportedError"; + this.message = message || "Not supported error"; + this.code = "UND_ERR_NOT_SUPPORTED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kNotSupportedError] === true; + } + [kNotSupportedError] = true; + }; + var kBalancedPoolMissingUpstreamError = Symbol.for("undici.error.UND_ERR_BPL_MISSING_UPSTREAM"); + var BalancedPoolMissingUpstreamError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "MissingUpstreamError"; + this.message = message || "No upstream has been added to the BalancedPool"; + this.code = "UND_ERR_BPL_MISSING_UPSTREAM"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kBalancedPoolMissingUpstreamError] === true; + } + [kBalancedPoolMissingUpstreamError] = true; + }; + var kHTTPParserError = Symbol.for("undici.error.UND_ERR_HTTP_PARSER"); + var HTTPParserError = class extends Error { + constructor(message, code, data) { + super(message); + this.name = "HTTPParserError"; + this.code = code ? `HPE_${code}` : void 0; + this.data = data ? data.toString() : void 0; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kHTTPParserError] === true; + } + [kHTTPParserError] = true; + }; + var kResponseExceededMaxSizeError = Symbol.for("undici.error.UND_ERR_RES_EXCEEDED_MAX_SIZE"); + var ResponseExceededMaxSizeError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "ResponseExceededMaxSizeError"; + this.message = message || "Response content exceeded max size"; + this.code = "UND_ERR_RES_EXCEEDED_MAX_SIZE"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kResponseExceededMaxSizeError] === true; + } + [kResponseExceededMaxSizeError] = true; + }; + var kRequestRetryError = Symbol.for("undici.error.UND_ERR_REQ_RETRY"); + var RequestRetryError = class extends UndiciError { + constructor(message, code, { headers, data }) { + super(message); + this.name = "RequestRetryError"; + this.message = message || "Request retry error"; + this.code = "UND_ERR_REQ_RETRY"; + this.statusCode = code; + this.data = data; + this.headers = headers; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kRequestRetryError] === true; + } + [kRequestRetryError] = true; + }; + var kResponseError = Symbol.for("undici.error.UND_ERR_RESPONSE"); + var ResponseError = class extends UndiciError { + constructor(message, code, { headers, data }) { + super(message); + this.name = "ResponseError"; + this.message = message || "Response error"; + this.code = "UND_ERR_RESPONSE"; + this.statusCode = code; + this.data = data; + this.headers = headers; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kResponseError] === true; + } + [kResponseError] = true; + }; + var kSecureProxyConnectionError = Symbol.for("undici.error.UND_ERR_PRX_TLS"); + var SecureProxyConnectionError = class extends UndiciError { + constructor(cause, message, options) { + super(message, { cause, ...options ?? {} }); + this.name = "SecureProxyConnectionError"; + this.message = message || "Secure Proxy Connection failed"; + this.code = "UND_ERR_PRX_TLS"; + this.cause = cause; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kSecureProxyConnectionError] === true; + } + [kSecureProxyConnectionError] = true; + }; + var kMessageSizeExceededError = Symbol.for("undici.error.UND_ERR_WS_MESSAGE_SIZE_EXCEEDED"); + var MessageSizeExceededError = class extends UndiciError { + constructor(message) { + super(message); + this.name = "MessageSizeExceededError"; + this.message = message || "Max decompressed message size exceeded"; + this.code = "UND_ERR_WS_MESSAGE_SIZE_EXCEEDED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kMessageSizeExceededError] === true; + } + get [kMessageSizeExceededError]() { + return true; + } + }; + module.exports = { + AbortError, + HTTPParserError, + UndiciError, + HeadersTimeoutError, + HeadersOverflowError, + BodyTimeoutError, + RequestContentLengthMismatchError, + ConnectTimeoutError, + ResponseStatusCodeError, + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError, + ClientDestroyedError, + ClientClosedError, + InformationalError, + SocketError, + NotSupportedError, + ResponseContentLengthMismatchError, + BalancedPoolMissingUpstreamError, + ResponseExceededMaxSizeError, + RequestRetryError, + ResponseError, + SecureProxyConnectionError, + MessageSizeExceededError + }; + } +}); + +// +var require_constants = __commonJS({ + ""(exports, module) { + "use strict"; + var headerNameLowerCasedRecord = {}; + var wellknownHeaderNames = [ + "Accept", + "Accept-Encoding", + "Accept-Language", + "Accept-Ranges", + "Access-Control-Allow-Credentials", + "Access-Control-Allow-Headers", + "Access-Control-Allow-Methods", + "Access-Control-Allow-Origin", + "Access-Control-Expose-Headers", + "Access-Control-Max-Age", + "Access-Control-Request-Headers", + "Access-Control-Request-Method", + "Age", + "Allow", + "Alt-Svc", + "Alt-Used", + "Authorization", + "Cache-Control", + "Clear-Site-Data", + "Connection", + "Content-Disposition", + "Content-Encoding", + "Content-Language", + "Content-Length", + "Content-Location", + "Content-Range", + "Content-Security-Policy", + "Content-Security-Policy-Report-Only", + "Content-Type", + "Cookie", + "Cross-Origin-Embedder-Policy", + "Cross-Origin-Opener-Policy", + "Cross-Origin-Resource-Policy", + "Date", + "Device-Memory", + "Downlink", + "ECT", + "ETag", + "Expect", + "Expect-CT", + "Expires", + "Forwarded", + "From", + "Host", + "If-Match", + "If-Modified-Since", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", + "Keep-Alive", + "Last-Modified", + "Link", + "Location", + "Max-Forwards", + "Origin", + "Permissions-Policy", + "Pragma", + "Proxy-Authenticate", + "Proxy-Authorization", + "RTT", + "Range", + "Referer", + "Referrer-Policy", + "Refresh", + "Retry-After", + "Sec-WebSocket-Accept", + "Sec-WebSocket-Extensions", + "Sec-WebSocket-Key", + "Sec-WebSocket-Protocol", + "Sec-WebSocket-Version", + "Server", + "Server-Timing", + "Service-Worker-Allowed", + "Service-Worker-Navigation-Preload", + "Set-Cookie", + "SourceMap", + "Strict-Transport-Security", + "Supports-Loading-Mode", + "TE", + "Timing-Allow-Origin", + "Trailer", + "Transfer-Encoding", + "Upgrade", + "Upgrade-Insecure-Requests", + "User-Agent", + "Vary", + "Via", + "WWW-Authenticate", + "X-Content-Type-Options", + "X-DNS-Prefetch-Control", + "X-Frame-Options", + "X-Permitted-Cross-Domain-Policies", + "X-Powered-By", + "X-Requested-With", + "X-XSS-Protection" + ]; + for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = wellknownHeaderNames[i]; + const lowerCasedKey = key.toLowerCase(); + headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = lowerCasedKey; + } + Object.setPrototypeOf(headerNameLowerCasedRecord, null); + module.exports = { + wellknownHeaderNames, + headerNameLowerCasedRecord + }; + } +}); + +// +var require_tree = __commonJS({ + ""(exports, module) { + "use strict"; + var { + wellknownHeaderNames, + headerNameLowerCasedRecord + } = require_constants(); + var TstNode = class _TstNode { + /** @type {any} */ + value = null; + /** @type {null | TstNode} */ + left = null; + /** @type {null | TstNode} */ + middle = null; + /** @type {null | TstNode} */ + right = null; + /** @type {number} */ + code; + /** + * @param {string} key + * @param {any} value + * @param {number} index + */ + constructor(key, value, index) { + if (index === void 0 || index >= key.length) { + throw new TypeError("Unreachable"); + } + const code = this.code = key.charCodeAt(index); + if (code > 127) { + throw new TypeError("key must be ascii string"); + } + if (key.length !== ++index) { + this.middle = new _TstNode(key, value, index); + } else { + this.value = value; + } + } + /** + * @param {string} key + * @param {any} value + */ + add(key, value) { + const length = key.length; + if (length === 0) { + throw new TypeError("Unreachable"); + } + let index = 0; + let node = this; + while (true) { + const code = key.charCodeAt(index); + if (code > 127) { + throw new TypeError("key must be ascii string"); + } + if (node.code === code) { + if (length === ++index) { + node.value = value; + break; + } else if (node.middle !== null) { + node = node.middle; + } else { + node.middle = new _TstNode(key, value, index); + break; + } + } else if (node.code < code) { + if (node.left !== null) { + node = node.left; + } else { + node.left = new _TstNode(key, value, index); + break; + } + } else if (node.right !== null) { + node = node.right; + } else { + node.right = new _TstNode(key, value, index); + break; + } + } + } + /** + * @param {Uint8Array} key + * @return {TstNode | null} + */ + search(key) { + const keylength = key.length; + let index = 0; + let node = this; + while (node !== null && index < keylength) { + let code = key[index]; + if (code <= 90 && code >= 65) { + code |= 32; + } + while (node !== null) { + if (code === node.code) { + if (keylength === ++index) { + return node; + } + node = node.middle; + break; + } + node = node.code < code ? node.left : node.right; + } + } + return null; + } + }; + var TernarySearchTree = class { + /** @type {TstNode | null} */ + node = null; + /** + * @param {string} key + * @param {any} value + * */ + insert(key, value) { + if (this.node === null) { + this.node = new TstNode(key, value, 0); + } else { + this.node.add(key, value); + } + } + /** + * @param {Uint8Array} key + * @return {any} + */ + lookup(key) { + return this.node?.search(key)?.value ?? null; + } + }; + var tree = new TernarySearchTree(); + for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = headerNameLowerCasedRecord[wellknownHeaderNames[i]]; + tree.insert(key, key); + } + module.exports = { + TernarySearchTree, + tree + }; + } +}); + +// +var require_util = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var { kDestroyed, kBodyUsed, kListeners, kBody } = require_symbols(); + var { IncomingMessage } = __require("node:http"); + var stream = __require("node:stream"); + var net = __require("node:net"); + var { Blob: Blob2 } = __require("node:buffer"); + var nodeUtil = __require("node:util"); + var { stringify } = __require("node:querystring"); + var { EventEmitter: EE } = __require("node:events"); + var { InvalidArgumentError } = require_errors(); + var { headerNameLowerCasedRecord } = require_constants(); + var { tree } = require_tree(); + var [nodeMajor, nodeMinor] = process.versions.node.split(".").map((v) => Number(v)); + var BodyAsyncIterable = class { + constructor(body) { + this[kBody] = body; + this[kBodyUsed] = false; + } + async *[Symbol.asyncIterator]() { + assert2(!this[kBodyUsed], "disturbed"); + this[kBodyUsed] = true; + yield* this[kBody]; + } + }; + function wrapRequestBody(body) { + if (isStream(body)) { + if (bodyLength(body) === 0) { + body.on("data", function() { + assert2(false); + }); + } + if (typeof body.readableDidRead !== "boolean") { + body[kBodyUsed] = false; + EE.prototype.on.call(body, "data", function() { + this[kBodyUsed] = true; + }); + } + return body; + } else if (body && typeof body.pipeTo === "function") { + return new BodyAsyncIterable(body); + } else if (body && typeof body !== "string" && !ArrayBuffer.isView(body) && isIterable(body)) { + return new BodyAsyncIterable(body); + } else { + return body; + } + } + function nop() { + } + function isStream(obj) { + return obj && typeof obj === "object" && typeof obj.pipe === "function" && typeof obj.on === "function"; + } + function isBlobLike(object) { + if (object === null) { + return false; + } else if (object instanceof Blob2) { + return true; + } else if (typeof object !== "object") { + return false; + } else { + const sTag = object[Symbol.toStringTag]; + return (sTag === "Blob" || sTag === "File") && ("stream" in object && typeof object.stream === "function" || "arrayBuffer" in object && typeof object.arrayBuffer === "function"); + } + } + function buildURL(url, queryParams) { + if (url.includes("?") || url.includes("#")) { + throw new Error('Query params cannot be passed when url already contains "?" or "#".'); + } + const stringified = stringify(queryParams); + if (stringified) { + url += "?" + stringified; + } + return url; + } + function isValidPort(port) { + const value = parseInt(port, 10); + return value === Number(port) && value >= 0 && value <= 65535; + } + function isHttpOrHttpsPrefixed(value) { + return value != null && value[0] === "h" && value[1] === "t" && value[2] === "t" && value[3] === "p" && (value[4] === ":" || value[4] === "s" && value[5] === ":"); + } + function parseURL(url) { + if (typeof url === "string") { + url = new URL(url); + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { + throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`."); + } + return url; + } + if (!url || typeof url !== "object") { + throw new InvalidArgumentError("Invalid URL: The URL argument must be a non-null object."); + } + if (!(url instanceof URL)) { + if (url.port != null && url.port !== "" && isValidPort(url.port) === false) { + throw new InvalidArgumentError("Invalid URL: port must be a valid integer or a string representation of an integer."); + } + if (url.path != null && typeof url.path !== "string") { + throw new InvalidArgumentError("Invalid URL path: the path must be a string or null/undefined."); + } + if (url.pathname != null && typeof url.pathname !== "string") { + throw new InvalidArgumentError("Invalid URL pathname: the pathname must be a string or null/undefined."); + } + if (url.hostname != null && typeof url.hostname !== "string") { + throw new InvalidArgumentError("Invalid URL hostname: the hostname must be a string or null/undefined."); + } + if (url.origin != null && typeof url.origin !== "string") { + throw new InvalidArgumentError("Invalid URL origin: the origin must be a string or null/undefined."); + } + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { + throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`."); + } + const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80; + let origin = url.origin != null ? url.origin : `${url.protocol || ""}//${url.hostname || ""}:${port}`; + let path = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`; + if (origin[origin.length - 1] === "/") { + origin = origin.slice(0, origin.length - 1); + } + if (path && path[0] !== "/") { + path = `/${path}`; + } + return new URL(`${origin}${path}`); + } + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { + throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`."); + } + return url; + } + function parseOrigin(url) { + url = parseURL(url); + if (url.pathname !== "/" || url.search || url.hash) { + throw new InvalidArgumentError("invalid url"); + } + return url; + } + function getHostname(host) { + if (host[0] === "[") { + const idx2 = host.indexOf("]"); + assert2(idx2 !== -1); + return host.substring(1, idx2); + } + const idx = host.indexOf(":"); + if (idx === -1) + return host; + return host.substring(0, idx); + } + function getServerName(host) { + if (!host) { + return null; + } + assert2(typeof host === "string"); + const servername = getHostname(host); + if (net.isIP(servername)) { + return ""; + } + return servername; + } + function deepClone(obj) { + return JSON.parse(JSON.stringify(obj)); + } + function isAsyncIterable(obj) { + return !!(obj != null && typeof obj[Symbol.asyncIterator] === "function"); + } + function isIterable(obj) { + return !!(obj != null && (typeof obj[Symbol.iterator] === "function" || typeof obj[Symbol.asyncIterator] === "function")); + } + function bodyLength(body) { + if (body == null) { + return 0; + } else if (isStream(body)) { + const state = body._readableState; + return state && state.objectMode === false && state.ended === true && Number.isFinite(state.length) ? state.length : null; + } else if (isBlobLike(body)) { + return body.size != null ? body.size : null; + } else if (isBuffer(body)) { + return body.byteLength; + } + return null; + } + function isDestroyed(body) { + return body && !!(body.destroyed || body[kDestroyed] || stream.isDestroyed?.(body)); + } + function destroy(stream2, err) { + if (stream2 == null || !isStream(stream2) || isDestroyed(stream2)) { + return; + } + if (typeof stream2.destroy === "function") { + if (Object.getPrototypeOf(stream2).constructor === IncomingMessage) { + stream2.socket = null; + } + stream2.destroy(err); + } else if (err) { + queueMicrotask(() => { + stream2.emit("error", err); + }); + } + if (stream2.destroyed !== true) { + stream2[kDestroyed] = true; + } + } + var KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/; + function parseKeepAliveTimeout(val) { + const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR); + return m ? parseInt(m[1], 10) * 1e3 : null; + } + function headerNameToString(value) { + return typeof value === "string" ? headerNameLowerCasedRecord[value] ?? value.toLowerCase() : tree.lookup(value) ?? value.toString("latin1").toLowerCase(); + } + function bufferToLowerCasedHeaderName(value) { + return tree.lookup(value) ?? value.toString("latin1").toLowerCase(); + } + function parseHeaders(headers, obj) { + if (obj === void 0) + obj = {}; + for (let i = 0; i < headers.length; i += 2) { + const key = headerNameToString(headers[i]); + let val = obj[key]; + if (val) { + if (typeof val === "string") { + val = [val]; + obj[key] = val; + } + val.push(headers[i + 1].toString("utf8")); + } else { + const headersValue = headers[i + 1]; + if (typeof headersValue === "string") { + obj[key] = headersValue; + } else { + obj[key] = Array.isArray(headersValue) ? headersValue.map((x) => x.toString("utf8")) : headersValue.toString("utf8"); + } + } + } + if ("content-length" in obj && "content-disposition" in obj) { + obj["content-disposition"] = Buffer.from(obj["content-disposition"]).toString("latin1"); + } + return obj; + } + function parseRawHeaders(headers) { + const len = headers.length; + const ret = new Array(len); + let hasContentLength = false; + let contentDispositionIdx = -1; + let key; + let val; + let kLen = 0; + for (let n = 0; n < headers.length; n += 2) { + key = headers[n]; + val = headers[n + 1]; + typeof key !== "string" && (key = key.toString()); + typeof val !== "string" && (val = val.toString("utf8")); + kLen = key.length; + if (kLen === 14 && key[7] === "-" && (key === "content-length" || key.toLowerCase() === "content-length")) { + hasContentLength = true; + } else if (kLen === 19 && key[7] === "-" && (key === "content-disposition" || key.toLowerCase() === "content-disposition")) { + contentDispositionIdx = n + 1; + } + ret[n] = key; + ret[n + 1] = val; + } + if (hasContentLength && contentDispositionIdx !== -1) { + ret[contentDispositionIdx] = Buffer.from(ret[contentDispositionIdx]).toString("latin1"); + } + return ret; + } + function isBuffer(buffer) { + return buffer instanceof Uint8Array || Buffer.isBuffer(buffer); + } + function validateHandler(handler3, method, upgrade) { + if (!handler3 || typeof handler3 !== "object") { + throw new InvalidArgumentError("handler must be an object"); + } + if (typeof handler3.onConnect !== "function") { + throw new InvalidArgumentError("invalid onConnect method"); + } + if (typeof handler3.onError !== "function") { + throw new InvalidArgumentError("invalid onError method"); + } + if (typeof handler3.onBodySent !== "function" && handler3.onBodySent !== void 0) { + throw new InvalidArgumentError("invalid onBodySent method"); + } + if (upgrade || method === "CONNECT") { + if (typeof handler3.onUpgrade !== "function") { + throw new InvalidArgumentError("invalid onUpgrade method"); + } + } else { + if (typeof handler3.onHeaders !== "function") { + throw new InvalidArgumentError("invalid onHeaders method"); + } + if (typeof handler3.onData !== "function") { + throw new InvalidArgumentError("invalid onData method"); + } + if (typeof handler3.onComplete !== "function") { + throw new InvalidArgumentError("invalid onComplete method"); + } + } + } + function isDisturbed(body) { + return !!(body && (stream.isDisturbed(body) || body[kBodyUsed])); + } + function isErrored(body) { + return !!(body && stream.isErrored(body)); + } + function isReadable(body) { + return !!(body && stream.isReadable(body)); + } + function getSocketInfo(socket) { + return { + localAddress: socket.localAddress, + localPort: socket.localPort, + remoteAddress: socket.remoteAddress, + remotePort: socket.remotePort, + remoteFamily: socket.remoteFamily, + timeout: socket.timeout, + bytesWritten: socket.bytesWritten, + bytesRead: socket.bytesRead + }; + } + function ReadableStreamFrom(iterable) { + let iterator3; + return new ReadableStream( + { + async start() { + iterator3 = iterable[Symbol.asyncIterator](); + }, + async pull(controller) { + const { done, value } = await iterator3.next(); + if (done) { + queueMicrotask(() => { + controller.close(); + controller.byobRequest?.respond(0); + }); + } else { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value); + if (buf.byteLength) { + controller.enqueue(new Uint8Array(buf)); + } + } + return controller.desiredSize > 0; + }, + async cancel(reason) { + await iterator3.return(); + }, + type: "bytes" + } + ); + } + function isFormDataLike(object) { + return object && typeof object === "object" && typeof object.append === "function" && typeof object.delete === "function" && typeof object.get === "function" && typeof object.getAll === "function" && typeof object.has === "function" && typeof object.set === "function" && object[Symbol.toStringTag] === "FormData"; + } + function addAbortListener(signal, listener) { + if ("addEventListener" in signal) { + signal.addEventListener("abort", listener, { once: true }); + return () => signal.removeEventListener("abort", listener); + } + signal.addListener("abort", listener); + return () => signal.removeListener("abort", listener); + } + var hasToWellFormed = typeof String.prototype.toWellFormed === "function"; + var hasIsWellFormed = typeof String.prototype.isWellFormed === "function"; + function toUSVString(val) { + return hasToWellFormed ? `${val}`.toWellFormed() : nodeUtil.toUSVString(val); + } + function isUSVString(val) { + return hasIsWellFormed ? `${val}`.isWellFormed() : toUSVString(val) === `${val}`; + } + function isTokenCharCode(c) { + switch (c) { + case 34: + case 40: + case 41: + case 44: + case 47: + case 58: + case 59: + case 60: + case 61: + case 62: + case 63: + case 64: + case 91: + case 92: + case 93: + case 123: + case 125: + return false; + default: + return c >= 33 && c <= 126; + } + } + function isValidHTTPToken(characters) { + if (characters.length === 0) { + return false; + } + for (let i = 0; i < characters.length; ++i) { + if (!isTokenCharCode(characters.charCodeAt(i))) { + return false; + } + } + return true; + } + var headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; + function isValidHeaderValue(characters) { + return !headerCharRegex.test(characters); + } + function parseRangeHeader(range) { + if (range == null || range === "") + return { start: 0, end: null, size: null }; + const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null; + return m ? { + start: parseInt(m[1]), + end: m[2] ? parseInt(m[2]) : null, + size: m[3] ? parseInt(m[3]) : null + } : null; + } + function addListener(obj, name, listener) { + const listeners = obj[kListeners] ??= []; + listeners.push([name, listener]); + obj.on(name, listener); + return obj; + } + function removeAllListeners(obj) { + for (const [name, listener] of obj[kListeners] ?? []) { + obj.removeListener(name, listener); + } + obj[kListeners] = null; + } + function errorRequest(client, request3, err) { + try { + request3.onError(err); + assert2(request3.aborted); + } catch (err2) { + client.emit("error", err2); + } + } + var kEnumerableProperty = /* @__PURE__ */ Object.create(null); + kEnumerableProperty.enumerable = true; + var normalizedMethodRecordsBase = { + delete: "DELETE", + DELETE: "DELETE", + get: "GET", + GET: "GET", + head: "HEAD", + HEAD: "HEAD", + options: "OPTIONS", + OPTIONS: "OPTIONS", + post: "POST", + POST: "POST", + put: "PUT", + PUT: "PUT" + }; + var normalizedMethodRecords = { + ...normalizedMethodRecordsBase, + patch: "patch", + PATCH: "PATCH" + }; + Object.setPrototypeOf(normalizedMethodRecordsBase, null); + Object.setPrototypeOf(normalizedMethodRecords, null); + module.exports = { + kEnumerableProperty, + nop, + isDisturbed, + isErrored, + isReadable, + toUSVString, + isUSVString, + isBlobLike, + parseOrigin, + parseURL, + getServerName, + isStream, + isIterable, + isAsyncIterable, + isDestroyed, + headerNameToString, + bufferToLowerCasedHeaderName, + addListener, + removeAllListeners, + errorRequest, + parseRawHeaders, + parseHeaders, + parseKeepAliveTimeout, + destroy, + bodyLength, + deepClone, + ReadableStreamFrom, + isBuffer, + validateHandler, + getSocketInfo, + isFormDataLike, + buildURL, + addAbortListener, + isValidHTTPToken, + isValidHeaderValue, + isTokenCharCode, + parseRangeHeader, + normalizedMethodRecordsBase, + normalizedMethodRecords, + isValidPort, + isHttpOrHttpsPrefixed, + nodeMajor, + nodeMinor, + safeHTTPMethods: ["GET", "HEAD", "OPTIONS", "TRACE"], + wrapRequestBody + }; + } +}); + +// +var require_diagnostics = __commonJS({ + ""(exports, module) { + "use strict"; + var diagnosticsChannel = __require("node:diagnostics_channel"); + var util = __require("node:util"); + var undiciDebugLog = util.debuglog("undici"); + var fetchDebuglog = util.debuglog("fetch"); + var websocketDebuglog = util.debuglog("websocket"); + var isClientSet = false; + var channels = { + // Client + beforeConnect: diagnosticsChannel.channel("undici:client:beforeConnect"), + connected: diagnosticsChannel.channel("undici:client:connected"), + connectError: diagnosticsChannel.channel("undici:client:connectError"), + sendHeaders: diagnosticsChannel.channel("undici:client:sendHeaders"), + // Request + create: diagnosticsChannel.channel("undici:request:create"), + bodySent: diagnosticsChannel.channel("undici:request:bodySent"), + headers: diagnosticsChannel.channel("undici:request:headers"), + trailers: diagnosticsChannel.channel("undici:request:trailers"), + error: diagnosticsChannel.channel("undici:request:error"), + // WebSocket + open: diagnosticsChannel.channel("undici:websocket:open"), + close: diagnosticsChannel.channel("undici:websocket:close"), + socketError: diagnosticsChannel.channel("undici:websocket:socket_error"), + ping: diagnosticsChannel.channel("undici:websocket:ping"), + pong: diagnosticsChannel.channel("undici:websocket:pong") + }; + if (undiciDebugLog.enabled || fetchDebuglog.enabled) { + const debuglog = fetchDebuglog.enabled ? fetchDebuglog : undiciDebugLog; + diagnosticsChannel.channel("undici:client:beforeConnect").subscribe((evt) => { + const { + connectParams: { version, protocol, port, host } + } = evt; + debuglog( + "connecting to %s using %s%s", + `${host}${port ? `:${port}` : ""}`, + protocol, + version + ); + }); + diagnosticsChannel.channel("undici:client:connected").subscribe((evt) => { + const { + connectParams: { version, protocol, port, host } + } = evt; + debuglog( + "connected to %s using %s%s", + `${host}${port ? `:${port}` : ""}`, + protocol, + version + ); + }); + diagnosticsChannel.channel("undici:client:connectError").subscribe((evt) => { + const { + connectParams: { version, protocol, port, host }, + error: error2 + } = evt; + debuglog( + "connection to %s using %s%s errored - %s", + `${host}${port ? `:${port}` : ""}`, + protocol, + version, + error2.message + ); + }); + diagnosticsChannel.channel("undici:client:sendHeaders").subscribe((evt) => { + const { + request: { method, path, origin } + } = evt; + debuglog("sending request to %s %s/%s", method, origin, path); + }); + diagnosticsChannel.channel("undici:request:headers").subscribe((evt) => { + const { + request: { method, path, origin }, + response: { statusCode } + } = evt; + debuglog( + "received response to %s %s/%s - HTTP %d", + method, + origin, + path, + statusCode + ); + }); + diagnosticsChannel.channel("undici:request:trailers").subscribe((evt) => { + const { + request: { method, path, origin } + } = evt; + debuglog("trailers received from %s %s/%s", method, origin, path); + }); + diagnosticsChannel.channel("undici:request:error").subscribe((evt) => { + const { + request: { method, path, origin }, + error: error2 + } = evt; + debuglog( + "request to %s %s/%s errored - %s", + method, + origin, + path, + error2.message + ); + }); + isClientSet = true; + } + if (websocketDebuglog.enabled) { + if (!isClientSet) { + const debuglog = undiciDebugLog.enabled ? undiciDebugLog : websocketDebuglog; + diagnosticsChannel.channel("undici:client:beforeConnect").subscribe((evt) => { + const { + connectParams: { version, protocol, port, host } + } = evt; + debuglog( + "connecting to %s%s using %s%s", + host, + port ? `:${port}` : "", + protocol, + version + ); + }); + diagnosticsChannel.channel("undici:client:connected").subscribe((evt) => { + const { + connectParams: { version, protocol, port, host } + } = evt; + debuglog( + "connected to %s%s using %s%s", + host, + port ? `:${port}` : "", + protocol, + version + ); + }); + diagnosticsChannel.channel("undici:client:connectError").subscribe((evt) => { + const { + connectParams: { version, protocol, port, host }, + error: error2 + } = evt; + debuglog( + "connection to %s%s using %s%s errored - %s", + host, + port ? `:${port}` : "", + protocol, + version, + error2.message + ); + }); + diagnosticsChannel.channel("undici:client:sendHeaders").subscribe((evt) => { + const { + request: { method, path, origin } + } = evt; + debuglog("sending request to %s %s/%s", method, origin, path); + }); + } + diagnosticsChannel.channel("undici:websocket:open").subscribe((evt) => { + const { + address: { address, port } + } = evt; + websocketDebuglog("connection opened %s%s", address, port ? `:${port}` : ""); + }); + diagnosticsChannel.channel("undici:websocket:close").subscribe((evt) => { + const { websocket, code, reason } = evt; + websocketDebuglog( + "closed connection to %s - %s %s", + websocket.url, + code, + reason + ); + }); + diagnosticsChannel.channel("undici:websocket:socket_error").subscribe((err) => { + websocketDebuglog("connection errored - %s", err.message); + }); + diagnosticsChannel.channel("undici:websocket:ping").subscribe((evt) => { + websocketDebuglog("ping received"); + }); + diagnosticsChannel.channel("undici:websocket:pong").subscribe((evt) => { + websocketDebuglog("pong received"); + }); + } + module.exports = { + channels + }; + } +}); + +// +var require_request = __commonJS({ + ""(exports, module) { + "use strict"; + var { + InvalidArgumentError, + NotSupportedError + } = require_errors(); + var assert2 = __require("node:assert"); + var { + isValidHTTPToken, + isValidHeaderValue, + isStream, + destroy, + isBuffer, + isFormDataLike, + isIterable, + isBlobLike, + buildURL, + validateHandler, + getServerName, + normalizedMethodRecords + } = require_util(); + var { channels } = require_diagnostics(); + var { headerNameLowerCasedRecord } = require_constants(); + var invalidPathRegex = /[^\u0021-\u00ff]/; + var kHandler = Symbol("handler"); + var Request = class { + constructor(origin, { + path, + method, + body, + headers, + query: query2, + idempotent, + blocking, + upgrade, + headersTimeout, + bodyTimeout, + reset, + throwOnError, + expectContinue, + servername + }, handler3) { + if (typeof path !== "string") { + throw new InvalidArgumentError("path must be a string"); + } else if (path[0] !== "/" && !(path.startsWith("http://") || path.startsWith("https://")) && method !== "CONNECT") { + throw new InvalidArgumentError("path must be an absolute URL or start with a slash"); + } else if (invalidPathRegex.test(path)) { + throw new InvalidArgumentError("invalid request path"); + } + if (typeof method !== "string") { + throw new InvalidArgumentError("method must be a string"); + } else if (normalizedMethodRecords[method] === void 0 && !isValidHTTPToken(method)) { + throw new InvalidArgumentError("invalid request method"); + } + if (upgrade && typeof upgrade !== "string") { + throw new InvalidArgumentError("upgrade must be a string"); + } + if (upgrade && !isValidHeaderValue(upgrade)) { + throw new InvalidArgumentError("invalid upgrade header"); + } + if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) { + throw new InvalidArgumentError("invalid headersTimeout"); + } + if (bodyTimeout != null && (!Number.isFinite(bodyTimeout) || bodyTimeout < 0)) { + throw new InvalidArgumentError("invalid bodyTimeout"); + } + if (reset != null && typeof reset !== "boolean") { + throw new InvalidArgumentError("invalid reset"); + } + if (expectContinue != null && typeof expectContinue !== "boolean") { + throw new InvalidArgumentError("invalid expectContinue"); + } + this.headersTimeout = headersTimeout; + this.bodyTimeout = bodyTimeout; + this.throwOnError = throwOnError === true; + this.method = method; + this.abort = null; + if (body == null) { + this.body = null; + } else if (isStream(body)) { + this.body = body; + const rState = this.body._readableState; + if (!rState || !rState.autoDestroy) { + this.endHandler = function autoDestroy() { + destroy(this); + }; + this.body.on("end", this.endHandler); + } + this.errorHandler = (err) => { + if (this.abort) { + this.abort(err); + } else { + this.error = err; + } + }; + this.body.on("error", this.errorHandler); + } else if (isBuffer(body)) { + this.body = body.byteLength ? body : null; + } else if (ArrayBuffer.isView(body)) { + this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null; + } else if (body instanceof ArrayBuffer) { + this.body = body.byteLength ? Buffer.from(body) : null; + } else if (typeof body === "string") { + this.body = body.length ? Buffer.from(body) : null; + } else if (isFormDataLike(body) || isIterable(body) || isBlobLike(body)) { + this.body = body; + } else { + throw new InvalidArgumentError("body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable"); + } + this.completed = false; + this.aborted = false; + this.upgrade = upgrade || null; + this.path = query2 ? buildURL(path, query2) : path; + this.origin = origin; + this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent; + this.blocking = blocking == null ? false : blocking; + this.reset = reset == null ? null : reset; + this.host = null; + this.contentLength = null; + this.contentType = null; + this.headers = []; + this.expectContinue = expectContinue != null ? expectContinue : false; + if (Array.isArray(headers)) { + if (headers.length % 2 !== 0) { + throw new InvalidArgumentError("headers array must be even"); + } + for (let i = 0; i < headers.length; i += 2) { + processHeader(this, headers[i], headers[i + 1]); + } + } else if (headers && typeof headers === "object") { + if (headers[Symbol.iterator]) { + for (const header of headers) { + if (!Array.isArray(header) || header.length !== 2) { + throw new InvalidArgumentError("headers must be in key-value pair format"); + } + processHeader(this, header[0], header[1]); + } + } else { + const keys = Object.keys(headers); + for (let i = 0; i < keys.length; ++i) { + processHeader(this, keys[i], headers[keys[i]]); + } + } + } else if (headers != null) { + throw new InvalidArgumentError("headers must be an object or an array"); + } + validateHandler(handler3, method, upgrade); + this.servername = servername || getServerName(this.host); + this[kHandler] = handler3; + if (channels.create.hasSubscribers) { + channels.create.publish({ request: this }); + } + } + onBodySent(chunk) { + if (this[kHandler].onBodySent) { + try { + return this[kHandler].onBodySent(chunk); + } catch (err) { + this.abort(err); + } + } + } + onRequestSent() { + if (channels.bodySent.hasSubscribers) { + channels.bodySent.publish({ request: this }); + } + if (this[kHandler].onRequestSent) { + try { + return this[kHandler].onRequestSent(); + } catch (err) { + this.abort(err); + } + } + } + onConnect(abort) { + assert2(!this.aborted); + assert2(!this.completed); + if (this.error) { + abort(this.error); + } else { + this.abort = abort; + return this[kHandler].onConnect(abort); + } + } + onResponseStarted() { + return this[kHandler].onResponseStarted?.(); + } + onHeaders(statusCode, headers, resume, statusText) { + assert2(!this.aborted); + assert2(!this.completed); + if (channels.headers.hasSubscribers) { + channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }); + } + try { + return this[kHandler].onHeaders(statusCode, headers, resume, statusText); + } catch (err) { + this.abort(err); + } + } + onData(chunk) { + assert2(!this.aborted); + assert2(!this.completed); + try { + return this[kHandler].onData(chunk); + } catch (err) { + this.abort(err); + return false; + } + } + onUpgrade(statusCode, headers, socket) { + assert2(!this.aborted); + assert2(!this.completed); + return this[kHandler].onUpgrade(statusCode, headers, socket); + } + onComplete(trailers) { + this.onFinally(); + assert2(!this.aborted); + this.completed = true; + if (channels.trailers.hasSubscribers) { + channels.trailers.publish({ request: this, trailers }); + } + try { + return this[kHandler].onComplete(trailers); + } catch (err) { + this.onError(err); + } + } + onError(error2) { + this.onFinally(); + if (channels.error.hasSubscribers) { + channels.error.publish({ request: this, error: error2 }); + } + if (this.aborted) { + return; + } + this.aborted = true; + return this[kHandler].onError(error2); + } + onFinally() { + if (this.errorHandler) { + this.body.off("error", this.errorHandler); + this.errorHandler = null; + } + if (this.endHandler) { + this.body.off("end", this.endHandler); + this.endHandler = null; + } + } + addHeader(key, value) { + processHeader(this, key, value); + return this; + } + }; + function processHeader(request3, key, val) { + if (val && (typeof val === "object" && !Array.isArray(val))) { + throw new InvalidArgumentError(`invalid ${key} header`); + } else if (val === void 0) { + return; + } + let headerName = headerNameLowerCasedRecord[key]; + if (headerName === void 0) { + headerName = key.toLowerCase(); + if (headerNameLowerCasedRecord[headerName] === void 0 && !isValidHTTPToken(headerName)) { + throw new InvalidArgumentError("invalid header key"); + } + } + if (Array.isArray(val)) { + const arr = []; + for (let i = 0; i < val.length; i++) { + if (typeof val[i] === "string") { + if (!isValidHeaderValue(val[i])) { + throw new InvalidArgumentError(`invalid ${key} header`); + } + arr.push(val[i]); + } else if (val[i] === null) { + arr.push(""); + } else if (typeof val[i] === "object") { + throw new InvalidArgumentError(`invalid ${key} header`); + } else { + arr.push(`${val[i]}`); + } + } + val = arr; + } else if (typeof val === "string") { + if (!isValidHeaderValue(val)) { + throw new InvalidArgumentError(`invalid ${key} header`); + } + } else if (val === null) { + val = ""; + } else { + val = `${val}`; + } + if (headerName === "host") { + if (request3.host !== null) { + throw new InvalidArgumentError("duplicate host header"); + } + if (typeof val !== "string") { + throw new InvalidArgumentError("invalid host header"); + } + request3.host = val; + } else if (headerName === "content-length") { + if (request3.contentLength !== null) { + throw new InvalidArgumentError("duplicate content-length header"); + } + request3.contentLength = parseInt(val, 10); + if (!Number.isFinite(request3.contentLength)) { + throw new InvalidArgumentError("invalid content-length header"); + } + } else if (request3.contentType === null && headerName === "content-type") { + request3.contentType = val; + request3.headers.push(key, val); + } else if (headerName === "transfer-encoding" || headerName === "keep-alive" || headerName === "upgrade") { + throw new InvalidArgumentError(`invalid ${headerName} header`); + } else if (headerName === "connection") { + const value = typeof val === "string" ? val.toLowerCase() : null; + if (value !== "close" && value !== "keep-alive") { + throw new InvalidArgumentError("invalid connection header"); + } + if (value === "close") { + request3.reset = true; + } + } else if (headerName === "expect") { + throw new NotSupportedError("expect header not supported"); + } else { + request3.headers.push(key, val); + } + } + module.exports = Request; + } +}); + +// +var require_dispatcher = __commonJS({ + ""(exports, module) { + "use strict"; + var EventEmitter = __require("node:events"); + var Dispatcher = class extends EventEmitter { + dispatch() { + throw new Error("not implemented"); + } + close() { + throw new Error("not implemented"); + } + destroy() { + throw new Error("not implemented"); + } + compose(...args) { + const interceptors = Array.isArray(args[0]) ? args[0] : args; + let dispatch = this.dispatch.bind(this); + for (const interceptor of interceptors) { + if (interceptor == null) { + continue; + } + if (typeof interceptor !== "function") { + throw new TypeError(`invalid interceptor, expected function received ${typeof interceptor}`); + } + dispatch = interceptor(dispatch); + if (dispatch == null || typeof dispatch !== "function" || dispatch.length !== 2) { + throw new TypeError("invalid interceptor"); + } + } + return new ComposedDispatcher(this, dispatch); + } + }; + var ComposedDispatcher = class extends Dispatcher { + #dispatcher = null; + #dispatch = null; + constructor(dispatcher, dispatch) { + super(); + this.#dispatcher = dispatcher; + this.#dispatch = dispatch; + } + dispatch(...args) { + this.#dispatch(...args); + } + close(...args) { + return this.#dispatcher.close(...args); + } + destroy(...args) { + return this.#dispatcher.destroy(...args); + } + }; + module.exports = Dispatcher; + } +}); + +// +var require_dispatcher_base = __commonJS({ + ""(exports, module) { + "use strict"; + var Dispatcher = require_dispatcher(); + var { + ClientDestroyedError, + ClientClosedError, + InvalidArgumentError + } = require_errors(); + var { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = require_symbols(); + var kOnDestroyed = Symbol("onDestroyed"); + var kOnClosed = Symbol("onClosed"); + var kInterceptedDispatch = Symbol("Intercepted Dispatch"); + var kWebSocketOptions = Symbol("webSocketOptions"); + var DispatcherBase = class extends Dispatcher { + constructor(opts) { + super(); + this[kDestroyed] = false; + this[kOnDestroyed] = null; + this[kClosed] = false; + this[kOnClosed] = []; + this[kWebSocketOptions] = opts?.webSocket ?? {}; + } + get webSocketOptions() { + return { + maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024 + }; + } + get destroyed() { + return this[kDestroyed]; + } + get closed() { + return this[kClosed]; + } + get interceptors() { + return this[kInterceptors]; + } + set interceptors(newInterceptors) { + if (newInterceptors) { + for (let i = newInterceptors.length - 1; i >= 0; i--) { + const interceptor = this[kInterceptors][i]; + if (typeof interceptor !== "function") { + throw new InvalidArgumentError("interceptor must be an function"); + } + } + } + this[kInterceptors] = newInterceptors; + } + close(callback) { + if (callback === void 0) { + return new Promise((resolve5, reject) => { + this.close((err, data) => { + return err ? reject(err) : resolve5(data); + }); + }); + } + if (typeof callback !== "function") { + throw new InvalidArgumentError("invalid callback"); + } + if (this[kDestroyed]) { + queueMicrotask(() => callback(new ClientDestroyedError(), null)); + return; + } + if (this[kClosed]) { + if (this[kOnClosed]) { + this[kOnClosed].push(callback); + } else { + queueMicrotask(() => callback(null, null)); + } + return; + } + this[kClosed] = true; + this[kOnClosed].push(callback); + const onClosed = () => { + const callbacks = this[kOnClosed]; + this[kOnClosed] = null; + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](null, null); + } + }; + this[kClose]().then(() => this.destroy()).then(() => { + queueMicrotask(onClosed); + }); + } + destroy(err, callback) { + if (typeof err === "function") { + callback = err; + err = null; + } + if (callback === void 0) { + return new Promise((resolve5, reject) => { + this.destroy(err, (err2, data) => { + return err2 ? ( + /* istanbul ignore next: should never error */ + reject(err2) + ) : resolve5(data); + }); + }); + } + if (typeof callback !== "function") { + throw new InvalidArgumentError("invalid callback"); + } + if (this[kDestroyed]) { + if (this[kOnDestroyed]) { + this[kOnDestroyed].push(callback); + } else { + queueMicrotask(() => callback(null, null)); + } + return; + } + if (!err) { + err = new ClientDestroyedError(); + } + this[kDestroyed] = true; + this[kOnDestroyed] = this[kOnDestroyed] || []; + this[kOnDestroyed].push(callback); + const onDestroyed = () => { + const callbacks = this[kOnDestroyed]; + this[kOnDestroyed] = null; + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](null, null); + } + }; + this[kDestroy](err).then(() => { + queueMicrotask(onDestroyed); + }); + } + [kInterceptedDispatch](opts, handler3) { + if (!this[kInterceptors] || this[kInterceptors].length === 0) { + this[kInterceptedDispatch] = this[kDispatch]; + return this[kDispatch](opts, handler3); + } + let dispatch = this[kDispatch].bind(this); + for (let i = this[kInterceptors].length - 1; i >= 0; i--) { + dispatch = this[kInterceptors][i](dispatch); + } + this[kInterceptedDispatch] = dispatch; + return dispatch(opts, handler3); + } + dispatch(opts, handler3) { + if (!handler3 || typeof handler3 !== "object") { + throw new InvalidArgumentError("handler must be an object"); + } + try { + if (!opts || typeof opts !== "object") { + throw new InvalidArgumentError("opts must be an object."); + } + if (this[kDestroyed] || this[kOnDestroyed]) { + throw new ClientDestroyedError(); + } + if (this[kClosed]) { + throw new ClientClosedError(); + } + return this[kInterceptedDispatch](opts, handler3); + } catch (err) { + if (typeof handler3.onError !== "function") { + throw new InvalidArgumentError("invalid onError method"); + } + handler3.onError(err); + return false; + } + } + }; + module.exports = DispatcherBase; + } +}); + +// +var require_timers = __commonJS({ + ""(exports, module) { + "use strict"; + var fastNow = 0; + var RESOLUTION_MS = 1e3; + var TICK_MS = (RESOLUTION_MS >> 1) - 1; + var fastNowTimeout; + var kFastTimer = Symbol("kFastTimer"); + var fastTimers = []; + var NOT_IN_LIST = -2; + var TO_BE_CLEARED = -1; + var PENDING = 0; + var ACTIVE = 1; + function onTick() { + fastNow += TICK_MS; + let idx = 0; + let len = fastTimers.length; + while (idx < len) { + const timer = fastTimers[idx]; + if (timer._state === PENDING) { + timer._idleStart = fastNow - TICK_MS; + timer._state = ACTIVE; + } else if (timer._state === ACTIVE && fastNow >= timer._idleStart + timer._idleTimeout) { + timer._state = TO_BE_CLEARED; + timer._idleStart = -1; + timer._onTimeout(timer._timerArg); + } + if (timer._state === TO_BE_CLEARED) { + timer._state = NOT_IN_LIST; + if (--len !== 0) { + fastTimers[idx] = fastTimers[len]; + } + } else { + ++idx; + } + } + fastTimers.length = len; + if (fastTimers.length !== 0) { + refreshTimeout(); + } + } + function refreshTimeout() { + if (fastNowTimeout) { + fastNowTimeout.refresh(); + } else { + clearTimeout(fastNowTimeout); + fastNowTimeout = setTimeout(onTick, TICK_MS); + if (fastNowTimeout.unref) { + fastNowTimeout.unref(); + } + } + } + var FastTimer = class { + [kFastTimer] = true; + /** + * The state of the timer, which can be one of the following: + * - NOT_IN_LIST (-2) + * - TO_BE_CLEARED (-1) + * - PENDING (0) + * - ACTIVE (1) + * + * @type {-2|-1|0|1} + * @private + */ + _state = NOT_IN_LIST; + /** + * The number of milliseconds to wait before calling the callback. + * + * @type {number} + * @private + */ + _idleTimeout = -1; + /** + * The time in milliseconds when the timer was started. This value is used to + * calculate when the timer should expire. + * + * @type {number} + * @default -1 + * @private + */ + _idleStart = -1; + /** + * The function to be executed when the timer expires. + * @type {Function} + * @private + */ + _onTimeout; + /** + * The argument to be passed to the callback when the timer expires. + * + * @type {*} + * @private + */ + _timerArg; + /** + * @constructor + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should wait + * before the specified function or code is executed. + * @param {*} arg + */ + constructor(callback, delay, arg) { + this._onTimeout = callback; + this._idleTimeout = delay; + this._timerArg = arg; + this.refresh(); + } + /** + * Sets the timer's start time to the current time, and reschedules the timer + * to call its callback at the previously specified duration adjusted to the + * current time. + * Using this on a timer that has already called its callback will reactivate + * the timer. + * + * @returns {void} + */ + refresh() { + if (this._state === NOT_IN_LIST) { + fastTimers.push(this); + } + if (!fastNowTimeout || fastTimers.length === 1) { + refreshTimeout(); + } + this._state = PENDING; + } + /** + * The `clear` method cancels the timer, preventing it from executing. + * + * @returns {void} + * @private + */ + clear() { + this._state = TO_BE_CLEARED; + this._idleStart = -1; + } + }; + module.exports = { + /** + * The setTimeout() method sets a timer which executes a function once the + * timer expires. + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should + * wait before the specified function or code is executed. + * @param {*} [arg] An optional argument to be passed to the callback function + * when the timer expires. + * @returns {NodeJS.Timeout|FastTimer} + */ + setTimeout(callback, delay, arg) { + return delay <= RESOLUTION_MS ? setTimeout(callback, delay, arg) : new FastTimer(callback, delay, arg); + }, + /** + * The clearTimeout method cancels an instantiated Timer previously created + * by calling setTimeout. + * + * @param {NodeJS.Timeout|FastTimer} timeout + */ + clearTimeout(timeout) { + if (timeout[kFastTimer]) { + timeout.clear(); + } else { + clearTimeout(timeout); + } + }, + /** + * The setFastTimeout() method sets a fastTimer which executes a function once + * the timer expires. + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should + * wait before the specified function or code is executed. + * @param {*} [arg] An optional argument to be passed to the callback function + * when the timer expires. + * @returns {FastTimer} + */ + setFastTimeout(callback, delay, arg) { + return new FastTimer(callback, delay, arg); + }, + /** + * The clearTimeout method cancels an instantiated FastTimer previously + * created by calling setFastTimeout. + * + * @param {FastTimer} timeout + */ + clearFastTimeout(timeout) { + timeout.clear(); + }, + /** + * The now method returns the value of the internal fast timer clock. + * + * @returns {number} + */ + now() { + return fastNow; + }, + /** + * Trigger the onTick function to process the fastTimers array. + * Exported for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + * @param {number} [delay=0] The delay in milliseconds to add to the now value. + */ + tick(delay = 0) { + fastNow += delay - RESOLUTION_MS + 1; + onTick(); + onTick(); + }, + /** + * Reset FastTimers. + * Exported for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + */ + reset() { + fastNow = 0; + fastTimers.length = 0; + clearTimeout(fastNowTimeout); + fastNowTimeout = null; + }, + /** + * Exporting for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + */ + kFastTimer + }; + } +}); + +// +var require_connect = __commonJS({ + ""(exports, module) { + "use strict"; + var net = __require("node:net"); + var assert2 = __require("node:assert"); + var util = require_util(); + var { InvalidArgumentError, ConnectTimeoutError } = require_errors(); + var timers = require_timers(); + function noop4() { + } + var tls; + var SessionCache; + if (global.FinalizationRegistry && !(process.env.NODE_V8_COVERAGE || process.env.UNDICI_NO_FG)) { + SessionCache = class WeakSessionCache { + constructor(maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions; + this._sessionCache = /* @__PURE__ */ new Map(); + this._sessionRegistry = new global.FinalizationRegistry((key) => { + if (this._sessionCache.size < this._maxCachedSessions) { + return; + } + const ref = this._sessionCache.get(key); + if (ref !== void 0 && ref.deref() === void 0) { + this._sessionCache.delete(key); + } + }); + } + get(sessionKey) { + const ref = this._sessionCache.get(sessionKey); + return ref ? ref.deref() : null; + } + set(sessionKey, session) { + if (this._maxCachedSessions === 0) { + return; + } + this._sessionCache.set(sessionKey, new WeakRef(session)); + this._sessionRegistry.register(session, sessionKey); + } + }; + } else { + SessionCache = class SimpleSessionCache { + constructor(maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions; + this._sessionCache = /* @__PURE__ */ new Map(); + } + get(sessionKey) { + return this._sessionCache.get(sessionKey); + } + set(sessionKey, session) { + if (this._maxCachedSessions === 0) { + return; + } + if (this._sessionCache.size >= this._maxCachedSessions) { + const { value: oldestKey } = this._sessionCache.keys().next(); + this._sessionCache.delete(oldestKey); + } + this._sessionCache.set(sessionKey, session); + } + }; + } + function buildConnector({ allowH2, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) { + if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { + throw new InvalidArgumentError("maxCachedSessions must be a positive integer or zero"); + } + const options = { path: socketPath, ...opts }; + const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions); + timeout = timeout == null ? 1e4 : timeout; + allowH2 = allowH2 != null ? allowH2 : false; + return function connect({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { + let socket; + if (protocol === "https:") { + if (!tls) { + tls = __require("node:tls"); + } + servername = servername || options.servername || util.getServerName(host) || null; + const sessionKey = servername || hostname; + assert2(sessionKey); + const session = customSession || sessionCache.get(sessionKey) || null; + port = port || 443; + socket = tls.connect({ + highWaterMark: 16384, + // TLS in node can't have bigger HWM anyway... + ...options, + servername, + session, + localAddress, + // TODO(HTTP/2): Add support for h2c + ALPNProtocols: allowH2 ? ["http/1.1", "h2"] : ["http/1.1"], + socket: httpSocket, + // upgrade socket connection + port, + host: hostname + }); + socket.on("session", function(session2) { + sessionCache.set(sessionKey, session2); + }); + } else { + assert2(!httpSocket, "httpSocket can only be sent on TLS update"); + port = port || 80; + socket = net.connect({ + highWaterMark: 64 * 1024, + // Same as nodejs fs streams. + ...options, + localAddress, + port, + host: hostname + }); + } + if (options.keepAlive == null || options.keepAlive) { + const keepAliveInitialDelay = options.keepAliveInitialDelay === void 0 ? 6e4 : options.keepAliveInitialDelay; + socket.setKeepAlive(true, keepAliveInitialDelay); + } + const clearConnectTimeout = setupConnectTimeout(new WeakRef(socket), { timeout, hostname, port }); + socket.setNoDelay(true).once(protocol === "https:" ? "secureConnect" : "connect", function() { + queueMicrotask(clearConnectTimeout); + if (callback) { + const cb = callback; + callback = null; + cb(null, this); + } + }).on("error", function(err) { + queueMicrotask(clearConnectTimeout); + if (callback) { + const cb = callback; + callback = null; + cb(err); + } + }); + return socket; + }; + } + var setupConnectTimeout = process.platform === "win32" ? (socketWeakRef, opts) => { + if (!opts.timeout) { + return noop4; + } + let s1 = null; + let s2 = null; + const fastTimer = timers.setFastTimeout(() => { + s1 = setImmediate(() => { + s2 = setImmediate(() => onConnectTimeout(socketWeakRef.deref(), opts)); + }); + }, opts.timeout); + return () => { + timers.clearFastTimeout(fastTimer); + clearImmediate(s1); + clearImmediate(s2); + }; + } : (socketWeakRef, opts) => { + if (!opts.timeout) { + return noop4; + } + let s1 = null; + const fastTimer = timers.setFastTimeout(() => { + s1 = setImmediate(() => { + onConnectTimeout(socketWeakRef.deref(), opts); + }); + }, opts.timeout); + return () => { + timers.clearFastTimeout(fastTimer); + clearImmediate(s1); + }; + }; + function onConnectTimeout(socket, opts) { + if (socket == null) { + return; + } + let message = "Connect Timeout Error"; + if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) { + message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(", ")},`; + } else { + message += ` (attempted address: ${opts.hostname}:${opts.port},`; + } + message += ` timeout: ${opts.timeout}ms)`; + util.destroy(socket, new ConnectTimeoutError(message)); + } + module.exports = buildConnector; + } +}); + +// +var require_utils = __commonJS({ + ""(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.enumToMap = void 0; + function enumToMap(obj) { + const res = {}; + Object.keys(obj).forEach((key) => { + const value = obj[key]; + if (typeof value === "number") { + res[key] = value; + } + }); + return res; + } + exports.enumToMap = enumToMap; + } +}); + +// +var require_constants2 = __commonJS({ + ""(exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.SPECIAL_HEADERS = exports.HEADER_STATE = exports.MINOR = exports.MAJOR = exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS = exports.TOKEN = exports.STRICT_TOKEN = exports.HEX = exports.URL_CHAR = exports.STRICT_URL_CHAR = exports.USERINFO_CHARS = exports.MARK = exports.ALPHANUM = exports.NUM = exports.HEX_MAP = exports.NUM_MAP = exports.ALPHA = exports.FINISH = exports.H_METHOD_MAP = exports.METHOD_MAP = exports.METHODS_RTSP = exports.METHODS_ICE = exports.METHODS_HTTP = exports.METHODS = exports.LENIENT_FLAGS = exports.FLAGS = exports.TYPE = exports.ERROR = void 0; + var utils_1 = require_utils(); + var ERROR; + (function(ERROR2) { + ERROR2[ERROR2["OK"] = 0] = "OK"; + ERROR2[ERROR2["INTERNAL"] = 1] = "INTERNAL"; + ERROR2[ERROR2["STRICT"] = 2] = "STRICT"; + ERROR2[ERROR2["LF_EXPECTED"] = 3] = "LF_EXPECTED"; + ERROR2[ERROR2["UNEXPECTED_CONTENT_LENGTH"] = 4] = "UNEXPECTED_CONTENT_LENGTH"; + ERROR2[ERROR2["CLOSED_CONNECTION"] = 5] = "CLOSED_CONNECTION"; + ERROR2[ERROR2["INVALID_METHOD"] = 6] = "INVALID_METHOD"; + ERROR2[ERROR2["INVALID_URL"] = 7] = "INVALID_URL"; + ERROR2[ERROR2["INVALID_CONSTANT"] = 8] = "INVALID_CONSTANT"; + ERROR2[ERROR2["INVALID_VERSION"] = 9] = "INVALID_VERSION"; + ERROR2[ERROR2["INVALID_HEADER_TOKEN"] = 10] = "INVALID_HEADER_TOKEN"; + ERROR2[ERROR2["INVALID_CONTENT_LENGTH"] = 11] = "INVALID_CONTENT_LENGTH"; + ERROR2[ERROR2["INVALID_CHUNK_SIZE"] = 12] = "INVALID_CHUNK_SIZE"; + ERROR2[ERROR2["INVALID_STATUS"] = 13] = "INVALID_STATUS"; + ERROR2[ERROR2["INVALID_EOF_STATE"] = 14] = "INVALID_EOF_STATE"; + ERROR2[ERROR2["INVALID_TRANSFER_ENCODING"] = 15] = "INVALID_TRANSFER_ENCODING"; + ERROR2[ERROR2["CB_MESSAGE_BEGIN"] = 16] = "CB_MESSAGE_BEGIN"; + ERROR2[ERROR2["CB_HEADERS_COMPLETE"] = 17] = "CB_HEADERS_COMPLETE"; + ERROR2[ERROR2["CB_MESSAGE_COMPLETE"] = 18] = "CB_MESSAGE_COMPLETE"; + ERROR2[ERROR2["CB_CHUNK_HEADER"] = 19] = "CB_CHUNK_HEADER"; + ERROR2[ERROR2["CB_CHUNK_COMPLETE"] = 20] = "CB_CHUNK_COMPLETE"; + ERROR2[ERROR2["PAUSED"] = 21] = "PAUSED"; + ERROR2[ERROR2["PAUSED_UPGRADE"] = 22] = "PAUSED_UPGRADE"; + ERROR2[ERROR2["PAUSED_H2_UPGRADE"] = 23] = "PAUSED_H2_UPGRADE"; + ERROR2[ERROR2["USER"] = 24] = "USER"; + })(ERROR = exports.ERROR || (exports.ERROR = {})); + var TYPE; + (function(TYPE2) { + TYPE2[TYPE2["BOTH"] = 0] = "BOTH"; + TYPE2[TYPE2["REQUEST"] = 1] = "REQUEST"; + TYPE2[TYPE2["RESPONSE"] = 2] = "RESPONSE"; + })(TYPE = exports.TYPE || (exports.TYPE = {})); + var FLAGS; + (function(FLAGS2) { + FLAGS2[FLAGS2["CONNECTION_KEEP_ALIVE"] = 1] = "CONNECTION_KEEP_ALIVE"; + FLAGS2[FLAGS2["CONNECTION_CLOSE"] = 2] = "CONNECTION_CLOSE"; + FLAGS2[FLAGS2["CONNECTION_UPGRADE"] = 4] = "CONNECTION_UPGRADE"; + FLAGS2[FLAGS2["CHUNKED"] = 8] = "CHUNKED"; + FLAGS2[FLAGS2["UPGRADE"] = 16] = "UPGRADE"; + FLAGS2[FLAGS2["CONTENT_LENGTH"] = 32] = "CONTENT_LENGTH"; + FLAGS2[FLAGS2["SKIPBODY"] = 64] = "SKIPBODY"; + FLAGS2[FLAGS2["TRAILING"] = 128] = "TRAILING"; + FLAGS2[FLAGS2["TRANSFER_ENCODING"] = 512] = "TRANSFER_ENCODING"; + })(FLAGS = exports.FLAGS || (exports.FLAGS = {})); + var LENIENT_FLAGS; + (function(LENIENT_FLAGS2) { + LENIENT_FLAGS2[LENIENT_FLAGS2["HEADERS"] = 1] = "HEADERS"; + LENIENT_FLAGS2[LENIENT_FLAGS2["CHUNKED_LENGTH"] = 2] = "CHUNKED_LENGTH"; + LENIENT_FLAGS2[LENIENT_FLAGS2["KEEP_ALIVE"] = 4] = "KEEP_ALIVE"; + })(LENIENT_FLAGS = exports.LENIENT_FLAGS || (exports.LENIENT_FLAGS = {})); + var METHODS; + (function(METHODS2) { + METHODS2[METHODS2["DELETE"] = 0] = "DELETE"; + METHODS2[METHODS2["GET"] = 1] = "GET"; + METHODS2[METHODS2["HEAD"] = 2] = "HEAD"; + METHODS2[METHODS2["POST"] = 3] = "POST"; + METHODS2[METHODS2["PUT"] = 4] = "PUT"; + METHODS2[METHODS2["CONNECT"] = 5] = "CONNECT"; + METHODS2[METHODS2["OPTIONS"] = 6] = "OPTIONS"; + METHODS2[METHODS2["TRACE"] = 7] = "TRACE"; + METHODS2[METHODS2["COPY"] = 8] = "COPY"; + METHODS2[METHODS2["LOCK"] = 9] = "LOCK"; + METHODS2[METHODS2["MKCOL"] = 10] = "MKCOL"; + METHODS2[METHODS2["MOVE"] = 11] = "MOVE"; + METHODS2[METHODS2["PROPFIND"] = 12] = "PROPFIND"; + METHODS2[METHODS2["PROPPATCH"] = 13] = "PROPPATCH"; + METHODS2[METHODS2["SEARCH"] = 14] = "SEARCH"; + METHODS2[METHODS2["UNLOCK"] = 15] = "UNLOCK"; + METHODS2[METHODS2["BIND"] = 16] = "BIND"; + METHODS2[METHODS2["REBIND"] = 17] = "REBIND"; + METHODS2[METHODS2["UNBIND"] = 18] = "UNBIND"; + METHODS2[METHODS2["ACL"] = 19] = "ACL"; + METHODS2[METHODS2["REPORT"] = 20] = "REPORT"; + METHODS2[METHODS2["MKACTIVITY"] = 21] = "MKACTIVITY"; + METHODS2[METHODS2["CHECKOUT"] = 22] = "CHECKOUT"; + METHODS2[METHODS2["MERGE"] = 23] = "MERGE"; + METHODS2[METHODS2["M-SEARCH"] = 24] = "M-SEARCH"; + METHODS2[METHODS2["NOTIFY"] = 25] = "NOTIFY"; + METHODS2[METHODS2["SUBSCRIBE"] = 26] = "SUBSCRIBE"; + METHODS2[METHODS2["UNSUBSCRIBE"] = 27] = "UNSUBSCRIBE"; + METHODS2[METHODS2["PATCH"] = 28] = "PATCH"; + METHODS2[METHODS2["PURGE"] = 29] = "PURGE"; + METHODS2[METHODS2["MKCALENDAR"] = 30] = "MKCALENDAR"; + METHODS2[METHODS2["LINK"] = 31] = "LINK"; + METHODS2[METHODS2["UNLINK"] = 32] = "UNLINK"; + METHODS2[METHODS2["SOURCE"] = 33] = "SOURCE"; + METHODS2[METHODS2["PRI"] = 34] = "PRI"; + METHODS2[METHODS2["DESCRIBE"] = 35] = "DESCRIBE"; + METHODS2[METHODS2["ANNOUNCE"] = 36] = "ANNOUNCE"; + METHODS2[METHODS2["SETUP"] = 37] = "SETUP"; + METHODS2[METHODS2["PLAY"] = 38] = "PLAY"; + METHODS2[METHODS2["PAUSE"] = 39] = "PAUSE"; + METHODS2[METHODS2["TEARDOWN"] = 40] = "TEARDOWN"; + METHODS2[METHODS2["GET_PARAMETER"] = 41] = "GET_PARAMETER"; + METHODS2[METHODS2["SET_PARAMETER"] = 42] = "SET_PARAMETER"; + METHODS2[METHODS2["REDIRECT"] = 43] = "REDIRECT"; + METHODS2[METHODS2["RECORD"] = 44] = "RECORD"; + METHODS2[METHODS2["FLUSH"] = 45] = "FLUSH"; + })(METHODS = exports.METHODS || (exports.METHODS = {})); + exports.METHODS_HTTP = [ + METHODS.DELETE, + METHODS.GET, + METHODS.HEAD, + METHODS.POST, + METHODS.PUT, + METHODS.CONNECT, + METHODS.OPTIONS, + METHODS.TRACE, + METHODS.COPY, + METHODS.LOCK, + METHODS.MKCOL, + METHODS.MOVE, + METHODS.PROPFIND, + METHODS.PROPPATCH, + METHODS.SEARCH, + METHODS.UNLOCK, + METHODS.BIND, + METHODS.REBIND, + METHODS.UNBIND, + METHODS.ACL, + METHODS.REPORT, + METHODS.MKACTIVITY, + METHODS.CHECKOUT, + METHODS.MERGE, + METHODS["M-SEARCH"], + METHODS.NOTIFY, + METHODS.SUBSCRIBE, + METHODS.UNSUBSCRIBE, + METHODS.PATCH, + METHODS.PURGE, + METHODS.MKCALENDAR, + METHODS.LINK, + METHODS.UNLINK, + METHODS.PRI, + // TODO(indutny): should we allow it with HTTP? + METHODS.SOURCE + ]; + exports.METHODS_ICE = [ + METHODS.SOURCE + ]; + exports.METHODS_RTSP = [ + METHODS.OPTIONS, + METHODS.DESCRIBE, + METHODS.ANNOUNCE, + METHODS.SETUP, + METHODS.PLAY, + METHODS.PAUSE, + METHODS.TEARDOWN, + METHODS.GET_PARAMETER, + METHODS.SET_PARAMETER, + METHODS.REDIRECT, + METHODS.RECORD, + METHODS.FLUSH, + // For AirPlay + METHODS.GET, + METHODS.POST + ]; + exports.METHOD_MAP = utils_1.enumToMap(METHODS); + exports.H_METHOD_MAP = {}; + Object.keys(exports.METHOD_MAP).forEach((key) => { + if (/^H/.test(key)) { + exports.H_METHOD_MAP[key] = exports.METHOD_MAP[key]; + } + }); + var FINISH; + (function(FINISH2) { + FINISH2[FINISH2["SAFE"] = 0] = "SAFE"; + FINISH2[FINISH2["SAFE_WITH_CB"] = 1] = "SAFE_WITH_CB"; + FINISH2[FINISH2["UNSAFE"] = 2] = "UNSAFE"; + })(FINISH = exports.FINISH || (exports.FINISH = {})); + exports.ALPHA = []; + for (let i = "A".charCodeAt(0); i <= "Z".charCodeAt(0); i++) { + exports.ALPHA.push(String.fromCharCode(i)); + exports.ALPHA.push(String.fromCharCode(i + 32)); + } + exports.NUM_MAP = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9 + }; + exports.HEX_MAP = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + A: 10, + B: 11, + C: 12, + D: 13, + E: 14, + F: 15, + a: 10, + b: 11, + c: 12, + d: 13, + e: 14, + f: 15 + }; + exports.NUM = [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9" + ]; + exports.ALPHANUM = exports.ALPHA.concat(exports.NUM); + exports.MARK = ["-", "_", ".", "!", "~", "*", "'", "(", ")"]; + exports.USERINFO_CHARS = exports.ALPHANUM.concat(exports.MARK).concat(["%", ";", ":", "&", "=", "+", "$", ","]); + exports.STRICT_URL_CHAR = [ + "!", + '"', + "$", + "%", + "&", + "'", + "(", + ")", + "*", + "+", + ",", + "-", + ".", + "/", + ":", + ";", + "<", + "=", + ">", + "@", + "[", + "\\", + "]", + "^", + "_", + "`", + "{", + "|", + "}", + "~" + ].concat(exports.ALPHANUM); + exports.URL_CHAR = exports.STRICT_URL_CHAR.concat([" ", "\f"]); + for (let i = 128; i <= 255; i++) { + exports.URL_CHAR.push(i); + } + exports.HEX = exports.NUM.concat(["a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F"]); + exports.STRICT_TOKEN = [ + "!", + "#", + "$", + "%", + "&", + "'", + "*", + "+", + "-", + ".", + "^", + "_", + "`", + "|", + "~" + ].concat(exports.ALPHANUM); + exports.TOKEN = exports.STRICT_TOKEN.concat([" "]); + exports.HEADER_CHARS = [" "]; + for (let i = 32; i <= 255; i++) { + if (i !== 127) { + exports.HEADER_CHARS.push(i); + } + } + exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS.filter((c) => c !== 44); + exports.MAJOR = exports.NUM_MAP; + exports.MINOR = exports.MAJOR; + var HEADER_STATE; + (function(HEADER_STATE2) { + HEADER_STATE2[HEADER_STATE2["GENERAL"] = 0] = "GENERAL"; + HEADER_STATE2[HEADER_STATE2["CONNECTION"] = 1] = "CONNECTION"; + HEADER_STATE2[HEADER_STATE2["CONTENT_LENGTH"] = 2] = "CONTENT_LENGTH"; + HEADER_STATE2[HEADER_STATE2["TRANSFER_ENCODING"] = 3] = "TRANSFER_ENCODING"; + HEADER_STATE2[HEADER_STATE2["UPGRADE"] = 4] = "UPGRADE"; + HEADER_STATE2[HEADER_STATE2["CONNECTION_KEEP_ALIVE"] = 5] = "CONNECTION_KEEP_ALIVE"; + HEADER_STATE2[HEADER_STATE2["CONNECTION_CLOSE"] = 6] = "CONNECTION_CLOSE"; + HEADER_STATE2[HEADER_STATE2["CONNECTION_UPGRADE"] = 7] = "CONNECTION_UPGRADE"; + HEADER_STATE2[HEADER_STATE2["TRANSFER_ENCODING_CHUNKED"] = 8] = "TRANSFER_ENCODING_CHUNKED"; + })(HEADER_STATE = exports.HEADER_STATE || (exports.HEADER_STATE = {})); + exports.SPECIAL_HEADERS = { + "connection": HEADER_STATE.CONNECTION, + "content-length": HEADER_STATE.CONTENT_LENGTH, + "proxy-connection": HEADER_STATE.CONNECTION, + "transfer-encoding": HEADER_STATE.TRANSFER_ENCODING, + "upgrade": HEADER_STATE.UPGRADE + }; + } +}); + +// +var require_llhttp_wasm = __commonJS({ + ""(exports, module) { + "use strict"; + var { Buffer: Buffer2 } = __require("node:buffer"); + module.exports = Buffer2.from("AGFzbQEAAAABJwdgAX8Bf2ADf39/AX9gAX8AYAJ/fwBgBH9/f38Bf2AAAGADf39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQAEA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAAy0sBQYAAAIAAAAAAAACAQIAAgICAAADAAAAAAMDAwMBAQEBAQEBAQEAAAIAAAAEBQFwARISBQMBAAIGCAF/AUGA1AQLB9EFIgZtZW1vcnkCAAtfaW5pdGlhbGl6ZQAIGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBAAtsbGh0dHBfaW5pdAAJGGxsaHR0cF9zaG91bGRfa2VlcF9hbGl2ZQAvDGxsaHR0cF9hbGxvYwALBm1hbGxvYwAxC2xsaHR0cF9mcmVlAAwEZnJlZQAMD2xsaHR0cF9nZXRfdHlwZQANFWxsaHR0cF9nZXRfaHR0cF9tYWpvcgAOFWxsaHR0cF9nZXRfaHR0cF9taW5vcgAPEWxsaHR0cF9nZXRfbWV0aG9kABAWbGxodHRwX2dldF9zdGF0dXNfY29kZQAREmxsaHR0cF9nZXRfdXBncmFkZQASDGxsaHR0cF9yZXNldAATDmxsaHR0cF9leGVjdXRlABQUbGxodHRwX3NldHRpbmdzX2luaXQAFQ1sbGh0dHBfZmluaXNoABYMbGxodHRwX3BhdXNlABcNbGxodHRwX3Jlc3VtZQAYG2xsaHR0cF9yZXN1bWVfYWZ0ZXJfdXBncmFkZQAZEGxsaHR0cF9nZXRfZXJybm8AGhdsbGh0dHBfZ2V0X2Vycm9yX3JlYXNvbgAbF2xsaHR0cF9zZXRfZXJyb3JfcmVhc29uABwUbGxodHRwX2dldF9lcnJvcl9wb3MAHRFsbGh0dHBfZXJybm9fbmFtZQAeEmxsaHR0cF9tZXRob2RfbmFtZQAfEmxsaHR0cF9zdGF0dXNfbmFtZQAgGmxsaHR0cF9zZXRfbGVu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PgtBhwEMPQtBiAEMPAtBiQEMOwtBigEMOgtBiwEMOQtBjAEMOAtBjQEMNwtBjgEMNgtBjwEMNQtBkAEMNAtBkQEMMwtBkgEMMgtBkwEMMQtBlAEMMAtBlQEMLwtBlgEMLgtBlwEMLQtBmAEMLAtBmQEMKwtBmgEMKgtBmwEMKQtBnAEMKAtBnQEMJwtBngEMJgtBnwEMJQtBoAEMJAtBoQEMIwtBogEMIgtBowEMIQtBpAEMIAtBpQEMHwtBpgEMHgtBpwEMHQtBqAEMHAtBqQEMGwtBqgEMGgtBqwEMGQtBrAEMGAtBrQEMFwtBrgEMFgtBAQwVC0GvAQwUC0GwAQwTC0GxAQwSC0GzAQwRC0GyAQwQC0G0AQwPC0G1AQwOC0G2AQwNC0G3AQwMC0G4AQwLC0G5AQwKC0G6AQwJC0G7AQwIC0HGAQwHC0G8AQwGC0G9AQwFC0G+AQwEC0G/AQwDC0HAAQwCC0HCAQwBC0HBAQshAwNAAkACQAJAAkACQAJAAkACQAJAIAICfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAgJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC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iQEhAwykAgtBASABLQAAQd8ARw1nGiABQQFqIQEMXgtBigEhAyABIARGDaICIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgNAIAEtAAAgAEGMzwBqLQAARw1kIABBAUYN+gEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMogILQYsBIQMgASAERg2hAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGOzwBqLQAARw1kIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyiAgsgAkEANgIAIAZBAWohAUECDGULQYwBIQMgASAERg2gAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHwzwBqLQAARw1jIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyhAgsgAkEANgIAIAZBAWohAUEfDGQLQY0BIQMgASAERg2fAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHyzwBqLQAARw1iIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAygAgsgAkEANgIAIAZBAWohAUEJDGMLIAEgBEYEQEGOASEDDJ8CCwJAAkAgAS0AAEHJAGsOBwBiYmJiYgFiCyABQQFqIQFB+AAhAwyGAgsgAUEBaiEBQfkAIQMMhQILQY8BIQMgASAERg2dAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGRzwBqLQAARw1gIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyeAgsgAkEANgIAIAZBAWohAUEYDGELQZABIQMgASAERg2cAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGXzwBqLQAARw1fIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAydAgsgAkEANgIAIAZBAWohAUEXDGALQZEBIQMgASAERg2bAiACKAIAIgAgBCABa2ohBSABIABrQQZqIQYCQANAIAEtAAAgAEGazwBqLQAARw1eIABBBkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAycAgsgAkEANgIAIAZBAWohAUEV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JSIARQ0RIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMtAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0gIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMswELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0iIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMsgELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0OIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMsQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0dIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMsAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0fIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMrwELIABBP0cNASABQQFqCyEBQQUhAwyUAQtBACEDIAJBADYCHCACIAE2AhQgAkH9EjYCECACQQc2AgwMrAELIAJBADYCHCACIAE2AhQgAkHcCDYCECACQQc2AgxBACEDDKsBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNByACQeUANgIcIAIgATYCFCACIAA2AgxBACEDDKoBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNFiACQdMANgIcIAIgATYCFCACIAA2AgxBACEDDKkBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNGCACQdIANgIcIAIgATYCFCACIAA2AgxBACEDDKgBCyACQQA2AhwgAiABNgIUIAJBxgo2AhAgAkEHNgIMQQAhAwynAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQMgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwymAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRIgAkHTADYCHCACIAE2AhQgAiAANgIMQQAhAwylAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRQgAkHSADYCHCACIAE2AhQgAiAANgIMQQAhAwykAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQAgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwyjAQtB1QAhAwyJAQsgAEEVRwRAIAJBADYCHCACIAE2AhQgAkG5DTYCECACQRo2AgxB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AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB5ABrDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0HhJw8LQaQhDwtByywPC0H+MQ8LQcAkDwtBqyQPC0GNKA8LQeImDwtBgDAPC0G5Lw8LQdckDwtB7x8PC0HhHw8LQfofDwtB8iAPC0GoLw8LQa4yDwtBiDAPC0HsJw8LQYIiDwtBjh0PC0HQLg8LQcojDwtBxTIPC0HfHA8LQdIcDwtBxCAPC0HXIA8LQaIfDwtB7S4PC0GrMA8LQdQlDwtBzC4PC0H6Lg8LQfwrDwtB0jAPC0HxHQ8LQbsgDwtB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CABB3AgLii1JbnZhbGlkIGNoYXIgaW4gdXJsIHF1ZXJ5AFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fYm9keQBDb250ZW50LUxlbmd0aCBvdmVyZmxvdwBDaHVuayBzaXplIG92ZXJmbG93AFJlc3BvbnNlIG92ZXJmbG93AEludmFsaWQgbWV0aG9kIGZvciBIVFRQL3gueCByZXF1ZXN0AEludmFsaWQgbWV0aG9kIGZvciBSVFNQL3gueCByZXF1ZXN0AEV4cGVjdGVkIFNPVVJDRSBtZXRob2QgZm9yIElDRS94LnggcmVxdWVzdABJbnZhbGlkIGNoYXIgaW4gdXJsIGZyYWdtZW50IHN0YXJ0AEV4cGVjdGVkIGRvdABTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3N0YXR1cwBJbnZhbGlkIHJlc3BvbnNlIHN0YXR1cwBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zAFVzZXIgY2FsbGJhY2sgZXJyb3IAYG9uX3Jlc2V0YCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfaGVhZGVyYCBjYWxsYmFjayBlcnJvcgBgb25fbWVzc2FnZV9iZWdpbmAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3N0YXR1c19jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3ZlcnNpb25fY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl91cmxfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2hlYWRlcl92YWx1ZV9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXRob2RfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfZmllbGRfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fbmFtZWAgY2FsbGJhY2sgZXJyb3IAVW5leHBlY3RlZCBjaGFyIGluIHVybCBzZXJ2ZXIASW52YWxpZCBoZWFkZXIgdmFsdWUgY2hhcgBJbnZhbGlkIGhlYWRlciBmaWVsZCBjaGFyAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fdmVyc2lvbgBJbnZhbGlkIG1pbm9yIHZlcnNpb24ASW52YWxpZCBtYWpvciB2ZXJzaW9uAEV4cGVjdGVkIHNwYWNlIGFmdGVyIHZlcnNpb24ARXhwZWN0ZWQgQ1JMRiBhZnRlciB2ZXJzaW9uAEludmFsaWQgSFRUUCB2ZXJzaW9uAEludmFsaWQgaGVhZGVyIHRva2VuAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fdXJsAEludmFsaWQgY2hhcmFjdGVycyBpbiB1cmwAVW5leHBlY3RlZCBzdGFydCBjaGFyIGluIHVybABEb3VibGUgQCBpbiB1cmwARW1wdHkgQ29udGVudC1MZW5ndGgASW52YWxpZCBjaGFyYWN0ZXIgaW4gQ29udGVudC1MZW5ndGgARHVwbGljYXRlIENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhciBpbiB1cmwgcGF0aABDb250ZW50LUxlbmd0aCBjYW4ndCBiZSBwcmVzZW50IHdpdGggVHJhbnNmZXItRW5jb2RpbmcASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgc2l6ZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2hlYWRlcl92YWx1ZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHZhbHVlAE1pc3NpbmcgZXhwZWN0ZWQgTEYgYWZ0ZXIgaGVhZGVyIHZhbHVlAEludmFsaWQgYFRyYW5zZmVyLUVuY29kaW5nYCBoZWFkZXIgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZSB2YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHF1b3RlZCB2YWx1ZQBQYXVzZWQgYnkgb25faGVhZGVyc19jb21wbGV0ZQBJbnZhbGlkIEVPRiBzdGF0ZQBvbl9yZXNldCBwYXVzZQBvbl9jaHVua19oZWFkZXIgcGF1c2UAb25fbWVzc2FnZV9iZWdpbiBwYXVzZQBvbl9jaHVua19leHRlbnNpb25fdmFsdWUgcGF1c2UAb25fc3RhdHVzX2NvbXBsZXRlIHBhdXNlAG9uX3ZlcnNpb25fY29tcGxldGUgcGF1c2UAb25fdXJsX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2NvbXBsZXRlIHBhdXNlAG9uX2hlYWRlcl92YWx1ZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXNzYWdlX2NvbXBsZXRlIHBhdXNlAG9uX21ldGhvZF9jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfZmllbGRfY29tcGxldGUgcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX25hbWUgcGF1c2UAVW5leHBlY3RlZCBzcGFjZSBhZnRlciBzdGFydCBsaW5lAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fY2h1bmtfZXh0ZW5zaW9uX25hbWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBuYW1lAFBhdXNlIG9uIENPTk5FQ1QvVXBncmFkZQBQYXVzZSBvbiBQUkkvVXBncmFkZQBFeHBlY3RlZCBIVFRQLzIgQ29ubmVjdGlvbiBQcmVmYWNlAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fbWV0aG9kAEV4cGVjdGVkIHNwYWNlIGFmdGVyIG1ldGhvZABTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2hlYWRlcl9maWVsZABQYXVzZWQASW52YWxpZCB3b3JkIGVuY291bnRlcmVkAEludmFsaWQgbWV0aG9kIGVuY291bnRlcmVkAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2NoZW1hAFJlcXVlc3QgaGFzIGludmFsaWQgYFRyYW5zZmVyLUVuY29kaW5nYABTV0lUQ0hfUFJPWFkAVVNFX1BST1hZAE1LQUNUSVZJVFkAVU5QUk9DRVNTQUJMRV9FTlRJVFkAQ09QWQBNT1ZFRF9QRVJNQU5FTlRMWQBUT09fRUFSTFkATk9USUZZAEZBSUxFRF9ERVBFTkRFTkNZAEJBRF9HQVRFV0FZAFBMQVkAUFVUAENIRUNLT1VUAEdBVEVXQVlfVElNRU9VVABSRVFVRVNUX1RJTUVPVVQATkVUV09SS19DT05ORUNUX1RJTUVPVVQAQ09OTkVDVElPTl9USU1FT1VUAExPR0lOX1RJTUVPVVQATkVUV09SS19SRUFEX1RJTUVPVVQAUE9TVABNSVNESVJFQ1RFRF9SRVFVRVNUAENMSUVOVF9DTE9TRURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX0xPQURfQkFMQU5DRURfUkVRVUVTVABCQURfUkVRVUVTVABIVFRQX1JFUVVFU1RfU0VOVF9UT19IVFRQU19QT1JUAFJFUE9SVABJTV9BX1RFQVBPVABSRVNFVF9DT05URU5UAE5PX0NPTlRFTlQAUEFSVElBTF9DT05URU5UAEhQRV9JTlZBTElEX0NPTlNUQU5UAEhQRV9DQl9SRVNFVABHRVQASFBFX1NUUklDVABDT05GTElDVABURU1QT1JBUllfUkVESVJFQ1QAUEVSTUFORU5UX1JFRElSRUNUAENPTk5FQ1QATVVMVElfU1RBVFVTAEhQRV9JTlZBTElEX1NUQVRVUwBUT09fTUFOWV9SRVFVRVNUUwBFQVJMWV9ISU5UUwBVTkFWQUlMQUJMRV9GT1JfTEVHQUxfUkVBU09OUwBPUFRJT05TAFNXSVRDSElOR19QUk9UT0NPTFMAVkFSSUFOVF9BTFNPX05FR09USUFURVMATVVMVElQTEVfQ0hPSUNFUwBJTlRFUk5BTF9TRVJWRVJfRVJST1IAV0VCX1NFUlZFUl9VTktOT1dOX0VSUk9SAFJBSUxHVU5fRVJST1IASURFTlRJVFlfUFJPVklERVJfQVVUSEVOVElDQVRJT05fRVJST1IAU1NMX0NFUlRJRklDQVRFX0VSUk9SAElOVkFMSURfWF9GT1JXQVJERURfRk9SAFNFVF9QQVJBTUVURVIAR0VUX1BBUkFNRVRFUgBIUEVfVVNFUgBTRUVfT1RIRVIASFBFX0NCX0NIVU5LX0hFQURFUgBNS0NBTEVOREFSAFNFVFVQAFdFQl9TRVJWRVJfSVNfRE9XTgBURUFSRE9XTgBIUEVfQ0xPU0VEX0NPTk5FQ1RJT04ASEVVUklTVElDX0VYUElSQVRJT04ARElTQ09OTkVDVEVEX09QRVJBVElPTgBOT05fQVVUSE9SSVRBVElWRV9JTkZPUk1BVElPTgBIUEVfSU5WQUxJRF9WRVJTSU9OAEhQRV9DQl9NRVNTQUdFX0JFR0lOAFNJVEVfSVNfRlJPWkVOAEhQRV9JTlZBTElEX0hFQURFUl9UT0tFTgBJTlZBTElEX1RPS0VOAEZPUkJJRERFTgBFTkhBTkNFX1lPVVJfQ0FMTQBIUEVfSU5WQUxJRF9VUkwAQkxPQ0tFRF9CWV9QQVJFTlRBTF9DT05UUk9MAE1LQ09MAEFDTABIUEVfSU5URVJOQUwAUkVRVUVTVF9IRUFERVJfRklFTERTX1RPT19MQVJHRV9VTk9GRklDSUFMAEhQRV9PSwBVTkxJTksAVU5MT0NLAFBSSQBSRVRSWV9XSVRIAEhQRV9JTlZBTElEX0NPTlRFTlRfTEVOR1RIAEhQRV9VTkVYUEVDVEVEX0NPTlRFTlRfTEVOR1RIAEZMVVNIAFBST1BQQVRDSABNLVNFQVJDSABVUklfVE9PX0xPTkcAUFJPQ0VTU0lORwBNSVNDRUxMQU5FT1VTX1BFUlNJU1RFTlRfV0FSTklORwBNSVNDRUxMQU5FT1VTX1dBUk5JTkcASFBFX0lOVkFMSURfVFJBTlNGRVJfRU5DT0RJTkcARXhwZWN0ZWQgQ1JMRgBIUEVfSU5WQUxJRF9DSFVOS19TSVpFAE1PVkUAQ09OVElOVUUASFBFX0NCX1NUQVRVU19DT01QTEVURQBIUEVfQ0JfSEVBREVSU19DT01QTEVURQBIUEVfQ0JfVkVSU0lPTl9DT01QTEVURQBIUEVfQ0JfVVJMX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19DT01QTEVURQBIUEVfQ0JfSEVBREVSX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fVkFMVUVfQ09NUExFVEUASFBFX0NCX0NIVU5LX0VYVEVOU0lPTl9OQU1FX0NPTVBMRVRFAEhQRV9DQl9NRVNTQUdFX0NPTVBMRVRFAEhQRV9DQl9NRVRIT0RfQ09NUExFVEUASFBFX0NCX0hFQURFUl9GSUVMRF9DT01QTEVURQBERUxFVEUASFBFX0lOVkFMSURfRU9GX1NUQVRFAElOVkFMSURfU1NMX0NFUlRJRklDQVRFAFBBVVNFAE5PX1JFU1BPTlNFAFVOU1VQUE9SVEVEX01FRElBX1RZUEUAR09ORQBOT1RfQUNDRVBUQUJMRQBTRVJWSUNFX1VOQVZBSUxBQkxFAFJBTkdFX05PVF9TQVRJU0ZJQUJMRQBPUklHSU5fSVNfVU5SRUFDSEFCTEUAUkVTUE9OU0VfSVNfU1RBTEUAUFVSR0UATUVSR0UAUkVRVUVTVF9IRUFERVJfRklFTERTX1RPT19MQVJHRQBSRVFVRVNUX0hFQURFUl9UT09fTEFSR0UAUEFZTE9BRF9UT09fTEFSR0UASU5TVUZGSUNJRU5UX1NUT1JBR0UASFBFX1BBVVNFRF9VUEdSQURFAEhQRV9QQVVTRURfSDJfVVBHUkFERQBTT1VSQ0UAQU5OT1VOQ0UAVFJBQ0UASFBFX1VORVhQRUNURURfU1BBQ0UAREVTQ1JJQkUAVU5TVUJTQ1JJQkUAUkVDT1JEAEhQRV9JTlZBTElEX01FVEhPRABOT1RfRk9VTkQAUFJPUEZJTkQAVU5CSU5EAFJFQklORABVTkFVVEhPUklaRUQATUVUSE9EX05PVF9BTExPV0VEAEhUVFBfVkVSU0lPTl9OT1RfU1VQUE9SVEVEAEFMUkVBRFlfUkVQT1JURUQAQUNDRVBURUQATk9UX0lNUExFTUVOVEVEAExPT1BfREVURUNURUQASFBFX0NSX0VYUEVDVEVEAEhQRV9MRl9FWFBFQ1RFRABDUkVBVEVEAElNX1VTRUQASFBFX1BBVVNFRABUSU1FT1VUX09DQ1VSRUQAUEFZTUVOVF9SRVFVSVJFRABQUkVDT05ESVRJT05fUkVRVUlSRUQAUFJPWFlfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATkVUV09SS19BVVRIRU5USUNBVElPTl9SRVFVSVJFRABMRU5HVEhfUkVRVUlSRUQAU1NMX0NFUlRJRklDQVRFX1JFUVVJUkVEAFVQR1JBREVfUkVRVUlSRUQAUEFHRV9FWFBJUkVEAFBSRUNPTkRJVElPTl9GQUlMRUQARVhQRUNUQVRJT05fRkFJTEVEAFJFVkFMSURBVElPTl9GQUlMRUQAU1NMX0hBTkRTSEFLRV9GQUlMRUQATE9DS0VEAFRSQU5TRk9STUFUSU9OX0FQUExJRUQATk9UX01PRElGSUVEAE5PVF9FWFRFTkRFRABCQU5EV0lEVEhfTElNSVRfRVhDRUVERUQAU0lURV9JU19PVkVSTE9BREVEAEhFQUQARXhwZWN0ZWQgSFRUUC8AAF4TAAAmEwAAMBAAAPAXAACdEwAAFRIAADkXAADwEgAAChAAAHUSAACtEgAAghMAAE8UAAB/EAAAoBUAACMUAACJEgAAixQAAE0VAADUEQAAzxQAABAYAADJFgAA3BYAAMERAADgFwAAuxQAAHQUAAB8FQAA5RQAAAgXAAAfEAAAZRUAAKMUAAAoFQAAAhUAAJkVAAAsEAAAixkAAE8PAADUDgAAahAAAM4QAAACFwAAiQ4AAG4TAAAcEwAAZhQAAFYXAADBEwAAzRMAAGwTAABoFwAAZhcAAF8XAAAiEwAAzg8AAGkOAADYDgAAYxYAAMsTAACqDgAAKBcAACYXAADFEwAAXRYAAOgRAABnEwAAZRMAAPIWAABzEwAAHRcAAPkWAADzEQAAzw4AAM4VAAAMEgAAsxEAAKURAABhEAAAMhcAALsTAEH5NQsBAQBBkDYL4AEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB/TcLAQEAQZE4C14CAwICAgICAAACAgACAgACAgICAgICAgICAAQAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAEH9OQsBAQBBkToLXgIAAgICAgIAAAICAAICAAICAgICAgICAgIAAwAEAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAQfA7Cw1sb3NlZWVwLWFsaXZlAEGJPAsBAQBBoDwL4AEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBBiT4LAQEAQaA+C+cBAQEBAQEBAQEBAQEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFjaHVua2VkAEGwwAALXwEBAAEBAQEBAAABAQABAQABAQEBAQEBAQEBAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAEGQwgALIWVjdGlvbmVudC1sZW5ndGhvbnJveHktY29ubmVjdGlvbgBBwMIACy1yYW5zZmVyLWVuY29kaW5ncGdyYWRlDQoNCg0KU00NCg0KVFRQL0NFL1RTUC8AQfnCAAsFAQIAAQMAQZDDAAvgAQQBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAEH5xAALBQECAAEDAEGQxQAL4AEEAQEFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+cYACwQBAAABAEGRxwAL3wEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAEH6yAALBAEAAAIAQZDJAAtfAwQAAAQEBAQEBAQEBAQEBQQEBAQEBAQEBAQEBAAEAAYHBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQAQfrKAAsEAQAAAQBBkMsACwEBAEGqywALQQIAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAEH6zAALBAEAAAEAQZDNAAsBAQBBms0ACwYCAAAAAAIAQbHNAAs6AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBB8M4AC5YBTk9VTkNFRUNLT1VUTkVDVEVURUNSSUJFTFVTSEVURUFEU0VBUkNIUkdFQ1RJVklUWUxFTkRBUlZFT1RJRllQVElPTlNDSFNFQVlTVEFUQ0hHRU9SRElSRUNUT1JUUkNIUEFSQU1FVEVSVVJDRUJTQ1JJQkVBUkRPV05BQ0VJTkROS0NLVUJTQ1JJQkVIVFRQL0FEVFAv", "base64"); + } +}); + +// +var require_llhttp_simd_wasm = __commonJS({ + ""(exports, module) { + "use strict"; + var { Buffer: Buffer2 } = __require("node:buffer"); + module.exports = Buffer2.from("AGFzbQEAAAABJwdgAX8Bf2ADf39/AX9gAX8AYAJ/fwBgBH9/f38Bf2AAAGADf39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQAEA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAAy0sBQYAAAIAAAAAAAACAQIAAgICAAADAAAAAAMDAwMBAQEBAQEBAQEAAAIAAAAEBQFwARISBQMBAAIGCAF/AUGA1AQLB9EFIgZtZW1vcnkCAAtfaW5pdGlhbGl6ZQAIGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBAAtsbGh0dHBfaW5pdAAJGGxsaHR0cF9zaG91bGRfa2VlcF9hbGl2ZQAvDGxsaHR0cF9hbGxvYwALBm1hbGxvYwAxC2xsaHR0cF9mcmVlAAwEZnJlZQAMD2xsaHR0cF9nZXRfdHlwZQANFWxsaHR0cF9nZXRfaHR0cF9tYWpvcgAOFWxsaHR0cF9nZXRfaHR0cF9taW5vcgAPEWxsaHR0cF9nZXRfbWV0aG9kABAWbGxodHRwX2dldF9zdGF0dXNfY29kZQAREmxsaHR0cF9nZXRfdXBncmFkZQASDGxsaHR0cF9yZXNldAATDmxsaHR0cF9leGVjdXRlABQUbGxodHRwX3NldHRpbmdzX2luaXQAFQ1sbGh0dHBfZmluaXNoABYMbGxodHRwX3BhdXNlABcNbGxodHRwX3Jlc3VtZQAYG2xsaHR0cF9yZXN1bWVfYWZ0ZXJfdXBncmFkZQAZEGxsaHR0cF9nZXRfZXJybm8AGhdsbGh0dHBfZ2V0X2Vycm9yX3JlYXNvbgAbF2xsaHR0cF9zZXRfZXJyb3JfcmVhc29uABwUbGxodHRwX2dldF9lcnJvcl9wb3MAHRFsbGh0dHBfZXJybm9fbmFtZQAeEmxsaHR0cF9tZXRob2RfbmFtZQAfEmxsaHR0cF9zdGF0dXNfbmFtZQAgGmxsaHR0cF9zZXRfbGVu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PgtBhwEMPQtBiAEMPAtBiQEMOwtBigEMOgtBiwEMOQtBjAEMOAtBjQEMNwtBjgEMNgtBjwEMNQtBkAEMNAtBkQEMMwtBkgEMMgtBkwEMMQtBlAEMMAtBlQEMLwtBlgEMLgtBlwEMLQtBmAEMLAtBmQEMKwtBmgEMKgtBmwEMKQtBnAEMKAtBnQEMJwtBngEMJgtBnwEMJQtBoAEMJAtBoQEMIwtBogEMIgtBowEMIQtBpAEMIAtBpQEMHwtBpgEMHgtBpwEMHQtBqAEMHAtBqQEMGwtBqgEMGgtBqwEMGQtBrAEMGAtBrQEMFwtBrgEMFgtBAQwVC0GvAQwUC0GwAQwTC0GxAQwSC0GzAQwRC0GyAQwQC0G0AQwPC0G1AQwOC0G2AQwNC0G3AQwMC0G4AQwLC0G5AQwKC0G6AQwJC0G7AQwIC0HGAQwHC0G8AQwGC0G9AQwFC0G+AQwEC0G/AQwDC0HAAQwCC0HCAQwBC0HBAQshAwNAAkACQAJAAkACQAJAAkACQAJAIAICfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAgJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC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iQEhAwykAgtBASABLQAAQd8ARw1nGiABQQFqIQEMXgtBigEhAyABIARGDaICIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgNAIAEtAAAgAEGMzwBqLQAARw1kIABBAUYN+gEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMogILQYsBIQMgASAERg2hAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGOzwBqLQAARw1kIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyiAgsgAkEANgIAIAZBAWohAUECDGULQYwBIQMgASAERg2gAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHwzwBqLQAARw1jIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyhAgsgAkEANgIAIAZBAWohAUEfDGQLQY0BIQMgASAERg2fAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHyzwBqLQAARw1iIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAygAgsgAkEANgIAIAZBAWohAUEJDGMLIAEgBEYEQEGOASEDDJ8CCwJAAkAgAS0AAEHJAGsOBwBiYmJiYgFiCyABQQFqIQFB+AAhAwyGAgsgAUEBaiEBQfkAIQMMhQILQY8BIQMgASAERg2dAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGRzwBqLQAARw1gIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyeAgsgAkEANgIAIAZBAWohAUEYDGELQZABIQMgASAERg2cAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGXzwBqLQAARw1fIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAydAgsgAkEANgIAIAZBAWohAUEXDGALQZEBIQMgASAERg2bAiACKAIAIgAgBCABa2ohBSABIABrQQZqIQYCQANAIAEtAAAgAEGazwBqLQAARw1eIABBBkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAycAgsgAkEANgIAIAZBAWohAUEV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JSIARQ0RIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMtAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0gIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMswELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0iIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMsgELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0OIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMsQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0dIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMsAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0fIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMrwELIABBP0cNASABQQFqCyEBQQUhAwyUAQtBACEDIAJBADYCHCACIAE2AhQgAkH9EjYCECACQQc2AgwMrAELIAJBADYCHCACIAE2AhQgAkHcCDYCECACQQc2AgxBACEDDKsBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNByACQeUANgIcIAIgATYCFCACIAA2AgxBACEDDKoBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNFiACQdMANgIcIAIgATYCFCACIAA2AgxBACEDDKkBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNGCACQdIANgIcIAIgATYCFCACIAA2AgxBACEDDKgBCyACQQA2AhwgAiABNgIUIAJBxgo2AhAgAkEHNgIMQQAhAwynAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQMgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwymAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRIgAkHTADYCHCACIAE2AhQgAiAANgIMQQAhAwylAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRQgAkHSADYCHCACIAE2AhQgAiAANgIMQQAhAwykAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQAgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwyjAQtB1QAhAwyJAQsgAEEVRwRAIAJBADYCHCACIAE2AhQgAkG5DTYCECACQRo2AgxB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AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB5ABrDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0HhJw8LQaQhDwtByywPC0H+MQ8LQcAkDwtBqyQPC0GNKA8LQeImDwtBgDAPC0G5Lw8LQdckDwtB7x8PC0HhHw8LQfofDwtB8iAPC0GoLw8LQa4yDwtBiDAPC0HsJw8LQYIiDwtBjh0PC0HQLg8LQcojDwtBxTIPC0HfHA8LQdIcDwtBxCAPC0HXIA8LQaIfDwtB7S4PC0GrMA8LQdQlDwtBzC4PC0H6Lg8LQfwrDwtB0jAPC0HxHQ8LQbsgDwtB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AAADAEGUCAsFBAAAAAUAQaQICwkGAAAABwAAAAgAQdwIC4otSW52YWxpZCBjaGFyIGluIHVybCBxdWVyeQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2JvZHkAQ29udGVudC1MZW5ndGggb3ZlcmZsb3cAQ2h1bmsgc2l6ZSBvdmVyZmxvdwBSZXNwb25zZSBvdmVyZmxvdwBJbnZhbGlkIG1ldGhvZCBmb3IgSFRUUC94LnggcmVxdWVzdABJbnZhbGlkIG1ldGhvZCBmb3IgUlRTUC94LnggcmVxdWVzdABFeHBlY3RlZCBTT1VSQ0UgbWV0aG9kIGZvciBJQ0UveC54IHJlcXVlc3QASW52YWxpZCBjaGFyIGluIHVybCBmcmFnbWVudCBzdGFydABFeHBlY3RlZCBkb3QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9zdGF0dXMASW52YWxpZCByZXNwb25zZSBzdGF0dXMASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucwBVc2VyIGNhbGxiYWNrIGVycm9yAGBvbl9yZXNldGAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2hlYWRlcmAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfYmVnaW5gIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fdmFsdWVgIGNhbGxiYWNrIGVycm9yAGBvbl9zdGF0dXNfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl92ZXJzaW9uX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdXJsX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWV0aG9kX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX25hbWVgIGNhbGxiYWNrIGVycm9yAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2VydmVyAEludmFsaWQgaGVhZGVyIHZhbHVlIGNoYXIASW52YWxpZCBoZWFkZXIgZmllbGQgY2hhcgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3ZlcnNpb24ASW52YWxpZCBtaW5vciB2ZXJzaW9uAEludmFsaWQgbWFqb3IgdmVyc2lvbgBFeHBlY3RlZCBzcGFjZSBhZnRlciB2ZXJzaW9uAEV4cGVjdGVkIENSTEYgYWZ0ZXIgdmVyc2lvbgBJbnZhbGlkIEhUVFAgdmVyc2lvbgBJbnZhbGlkIGhlYWRlciB0b2tlbgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3VybABJbnZhbGlkIGNoYXJhY3RlcnMgaW4gdXJsAFVuZXhwZWN0ZWQgc3RhcnQgY2hhciBpbiB1cmwARG91YmxlIEAgaW4gdXJsAEVtcHR5IENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhcmFjdGVyIGluIENvbnRlbnQtTGVuZ3RoAER1cGxpY2F0ZSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXIgaW4gdXJsIHBhdGgAQ29udGVudC1MZW5ndGggY2FuJ3QgYmUgcHJlc2VudCB3aXRoIFRyYW5zZmVyLUVuY29kaW5nAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIHNpemUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfdmFsdWUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyB2YWx1ZQBNaXNzaW5nIGV4cGVjdGVkIExGIGFmdGVyIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AgaGVhZGVyIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGUgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZWQgdmFsdWUAUGF1c2VkIGJ5IG9uX2hlYWRlcnNfY29tcGxldGUASW52YWxpZCBFT0Ygc3RhdGUAb25fcmVzZXQgcGF1c2UAb25fY2h1bmtfaGVhZGVyIHBhdXNlAG9uX21lc3NhZ2VfYmVnaW4gcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlIHBhdXNlAG9uX3N0YXR1c19jb21wbGV0ZSBwYXVzZQBvbl92ZXJzaW9uX2NvbXBsZXRlIHBhdXNlAG9uX3VybF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGUgcGF1c2UAb25fbWVzc2FnZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXRob2RfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lIHBhdXNlAFVuZXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgc3RhcnQgbGluZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgbmFtZQBQYXVzZSBvbiBDT05ORUNUL1VwZ3JhZGUAUGF1c2Ugb24gUFJJL1VwZ3JhZGUARXhwZWN0ZWQgSFRUUC8yIENvbm5lY3Rpb24gUHJlZmFjZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX21ldGhvZABFeHBlY3RlZCBzcGFjZSBhZnRlciBtZXRob2QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfZmllbGQAUGF1c2VkAEludmFsaWQgd29yZCBlbmNvdW50ZXJlZABJbnZhbGlkIG1ldGhvZCBlbmNvdW50ZXJlZABVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNjaGVtYQBSZXF1ZXN0IGhhcyBpbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AAU1dJVENIX1BST1hZAFVTRV9QUk9YWQBNS0FDVElWSVRZAFVOUFJPQ0VTU0FCTEVfRU5USVRZAENPUFkATU9WRURfUEVSTUFORU5UTFkAVE9PX0VBUkxZAE5PVElGWQBGQUlMRURfREVQRU5ERU5DWQBCQURfR0FURVdBWQBQTEFZAFBVVABDSEVDS09VVABHQVRFV0FZX1RJTUVPVVQAUkVRVUVTVF9USU1FT1VUAE5FVFdPUktfQ09OTkVDVF9USU1FT1VUAENPTk5FQ1RJT05fVElNRU9VVABMT0dJTl9USU1FT1VUAE5FVFdPUktfUkVBRF9USU1FT1VUAFBPU1QATUlTRElSRUNURURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9MT0FEX0JBTEFOQ0VEX1JFUVVFU1QAQkFEX1JFUVVFU1QASFRUUF9SRVFVRVNUX1NFTlRfVE9fSFRUUFNfUE9SVABSRVBPUlQASU1fQV9URUFQT1QAUkVTRVRfQ09OVEVOVABOT19DT05URU5UAFBBUlRJQUxfQ09OVEVOVABIUEVfSU5WQUxJRF9DT05TVEFOVABIUEVfQ0JfUkVTRVQAR0VUAEhQRV9TVFJJQ1QAQ09ORkxJQ1QAVEVNUE9SQVJZX1JFRElSRUNUAFBFUk1BTkVOVF9SRURJUkVDVABDT05ORUNUAE1VTFRJX1NUQVRVUwBIUEVfSU5WQUxJRF9TVEFUVVMAVE9PX01BTllfUkVRVUVTVFMARUFSTFlfSElOVFMAVU5BVkFJTEFCTEVfRk9SX0xFR0FMX1JFQVNPTlMAT1BUSU9OUwBTV0lUQ0hJTkdfUFJPVE9DT0xTAFZBUklBTlRfQUxTT19ORUdPVElBVEVTAE1VTFRJUExFX0NIT0lDRVMASU5URVJOQUxfU0VSVkVSX0VSUk9SAFdFQl9TRVJWRVJfVU5LTk9XTl9FUlJPUgBSQUlMR1VOX0VSUk9SAElERU5USVRZX1BST1ZJREVSX0FVVEhFTlRJQ0FUSU9OX0VSUk9SAFNTTF9DRVJUSUZJQ0FURV9FUlJPUgBJTlZBTElEX1hfRk9SV0FSREVEX0ZPUgBTRVRfUEFSQU1FVEVSAEdFVF9QQVJBTUVURVIASFBFX1VTRVIAU0VFX09USEVSAEhQRV9DQl9DSFVOS19IRUFERVIATUtDQUxFTkRBUgBTRVRVUABXRUJfU0VSVkVSX0lTX0RPV04AVEVBUkRPV04ASFBFX0NMT1NFRF9DT05ORUNUSU9OAEhFVVJJU1RJQ19FWFBJUkFUSU9OAERJU0NPTk5FQ1RFRF9PUEVSQVRJT04ATk9OX0FVVEhPUklUQVRJVkVfSU5GT1JNQVRJT04ASFBFX0lOVkFMSURfVkVSU0lPTgBIUEVfQ0JfTUVTU0FHRV9CRUdJTgBTSVRFX0lTX0ZST1pFTgBIUEVfSU5WQUxJRF9IRUFERVJfVE9LRU4ASU5WQUxJRF9UT0tFTgBGT1JCSURERU4ARU5IQU5DRV9ZT1VSX0NBTE0ASFBFX0lOVkFMSURfVVJMAEJMT0NLRURfQllfUEFSRU5UQUxfQ09OVFJPTABNS0NPTABBQ0wASFBFX0lOVEVSTkFMAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0VfVU5PRkZJQ0lBTABIUEVfT0sAVU5MSU5LAFVOTE9DSwBQUkkAUkVUUllfV0lUSABIUEVfSU5WQUxJRF9DT05URU5UX0xFTkdUSABIUEVfVU5FWFBFQ1RFRF9DT05URU5UX0xFTkdUSABGTFVTSABQUk9QUEFUQ0gATS1TRUFSQ0gAVVJJX1RPT19MT05HAFBST0NFU1NJTkcATUlTQ0VMTEFORU9VU19QRVJTSVNURU5UX1dBUk5JTkcATUlTQ0VMTEFORU9VU19XQVJOSU5HAEhQRV9JTlZBTElEX1RSQU5TRkVSX0VOQ09ESU5HAEV4cGVjdGVkIENSTEYASFBFX0lOVkFMSURfQ0hVTktfU0laRQBNT1ZFAENPTlRJTlVFAEhQRV9DQl9TVEFUVVNfQ09NUExFVEUASFBFX0NCX0hFQURFUlNfQ09NUExFVEUASFBFX0NCX1ZFUlNJT05fQ09NUExFVEUASFBFX0NCX1VSTF9DT01QTEVURQBIUEVfQ0JfQ0hVTktfQ09NUExFVEUASFBFX0NCX0hFQURFUl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fTkFNRV9DT01QTEVURQBIUEVfQ0JfTUVTU0FHRV9DT01QTEVURQBIUEVfQ0JfTUVUSE9EX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfRklFTERfQ09NUExFVEUAREVMRVRFAEhQRV9JTlZBTElEX0VPRl9TVEFURQBJTlZBTElEX1NTTF9DRVJUSUZJQ0FURQBQQVVTRQBOT19SRVNQT05TRQBVTlNVUFBPUlRFRF9NRURJQV9UWVBFAEdPTkUATk9UX0FDQ0VQVEFCTEUAU0VSVklDRV9VTkFWQUlMQUJMRQBSQU5HRV9OT1RfU0FUSVNGSUFCTEUAT1JJR0lOX0lTX1VOUkVBQ0hBQkxFAFJFU1BPTlNFX0lTX1NUQUxFAFBVUkdFAE1FUkdFAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0UAUkVRVUVTVF9IRUFERVJfVE9PX0xBUkdFAFBBWUxPQURfVE9PX0xBUkdFAElOU1VGRklDSUVOVF9TVE9SQUdFAEhQRV9QQVVTRURfVVBHUkFERQBIUEVfUEFVU0VEX0gyX1VQR1JBREUAU09VUkNFAEFOTk9VTkNFAFRSQUNFAEhQRV9VTkVYUEVDVEVEX1NQQUNFAERFU0NSSUJFAFVOU1VCU0NSSUJFAFJFQ09SRABIUEVfSU5WQUxJRF9NRVRIT0QATk9UX0ZPVU5EAFBST1BGSU5EAFVOQklORABSRUJJTkQAVU5BVVRIT1JJWkVEAE1FVEhPRF9OT1RfQUxMT1dFRABIVFRQX1ZFUlNJT05fTk9UX1NVUFBPUlRFRABBTFJFQURZX1JFUE9SVEVEAEFDQ0VQVEVEAE5PVF9JTVBMRU1FTlRFRABMT09QX0RFVEVDVEVEAEhQRV9DUl9FWFBFQ1RFRABIUEVfTEZfRVhQRUNURUQAQ1JFQVRFRABJTV9VU0VEAEhQRV9QQVVTRUQAVElNRU9VVF9PQ0NVUkVEAFBBWU1FTlRfUkVRVUlSRUQAUFJFQ09ORElUSU9OX1JFUVVJUkVEAFBST1hZX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAE5FVFdPUktfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATEVOR1RIX1JFUVVJUkVEAFNTTF9DRVJUSUZJQ0FURV9SRVFVSVJFRABVUEdSQURFX1JFUVVJUkVEAFBBR0VfRVhQSVJFRABQUkVDT05ESVRJT05fRkFJTEVEAEVYUEVDVEFUSU9OX0ZBSUxFRABSRVZBTElEQVRJT05fRkFJTEVEAFNTTF9IQU5EU0hBS0VfRkFJTEVEAExPQ0tFRABUUkFOU0ZPUk1BVElPTl9BUFBMSUVEAE5PVF9NT0RJRklFRABOT1RfRVhURU5ERUQAQkFORFdJRFRIX0xJTUlUX0VYQ0VFREVEAFNJVEVfSVNfT1ZFUkxPQURFRABIRUFEAEV4cGVjdGVkIEhUVFAvAABeEwAAJhMAADAQAADwFwAAnRMAABUSAAA5FwAA8BIAAAoQAAB1EgAArRIAAIITAABPFAAAfxAAAKAVAAAjFAAAiRIAAIsUAABNFQAA1BEAAM8UAAAQGAAAyRYAANwWAADBEQAA4BcAALsUAAB0FAAAfBUAAOUUAAAIFwAAHxAAAGUVAACjFAAAKBUAAAIVAACZFQAALBAAAIsZAABPDwAA1A4AAGoQAADOEAAAAhcAAIkOAABuEwAAHBMAAGYUAABWFwAAwRMAAM0TAABsEwAAaBcAAGYXAABfFwAAIhMAAM4PAABpDgAA2A4AAGMWAADLEwAAqg4AACgXAAAmFwAAxRMAAF0WAADoEQAAZxMAAGUTAADyFgAAcxMAAB0XAAD5FgAA8xEAAM8OAADOFQAADBIAALMRAAClEQAAYRAAADIXAAC7EwBB+TULAQEAQZA2C+ABAQECAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQf03CwEBAEGROAteAgMCAgICAgAAAgIAAgIAAgICAgICAgICAgAEAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAIAAgBB/TkLAQEAQZE6C14CAAICAgICAAACAgACAgACAgICAgICAgICAAMABAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAEHwOwsNbG9zZWVlcC1hbGl2ZQBBiTwLAQEAQaA8C+ABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQYk+CwEBAEGgPgvnAQEBAQEBAQEBAQEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBY2h1bmtlZABBsMAAC18BAQABAQEBAQAAAQEAAQEAAQEBAQEBAQEBAQAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQBBkMIACyFlY3Rpb25lbnQtbGVuZ3Rob25yb3h5LWNvbm5lY3Rpb24AQcDCAAstcmFuc2Zlci1lbmNvZGluZ3BncmFkZQ0KDQoNClNNDQoNClRUUC9DRS9UU1AvAEH5wgALBQECAAEDAEGQwwAL4AEEAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+cQACwUBAgABAwBBkMUAC+ABBAEBBQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQfnGAAsEAQAAAQBBkccAC98BAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+sgACwQBAAACAEGQyQALXwMEAAAEBAQEBAQEBAQEBAUEBAQEBAQEBAQEBAQABAAGBwQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEAEH6ygALBAEAAAEAQZDLAAsBAQBBqssAC0ECAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBB+swACwQBAAABAEGQzQALAQEAQZrNAAsGAgAAAAACAEGxzQALOgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQfDOAAuWAU5PVU5DRUVDS09VVE5FQ1RFVEVDUklCRUxVU0hFVEVBRFNFQVJDSFJHRUNUSVZJVFlMRU5EQVJWRU9USUZZUFRJT05TQ0hTRUFZU1RBVENIR0VPUkRJUkVDVE9SVFJDSFBBUkFNRVRFUlVSQ0VCU0NSSUJFQVJET1dOQUNFSU5ETktDS1VCU0NSSUJFSFRUUC9BRFRQLw==", "base64"); + } +}); + +// +var require_constants3 = __commonJS({ + ""(exports, module) { + "use strict"; + var corsSafeListedMethods = ( + /** @type {const} */ + ["GET", "HEAD", "POST"] + ); + var corsSafeListedMethodsSet = new Set(corsSafeListedMethods); + var nullBodyStatus = ( + /** @type {const} */ + [101, 204, 205, 304] + ); + var redirectStatus = ( + /** @type {const} */ + [301, 302, 303, 307, 308] + ); + var redirectStatusSet = new Set(redirectStatus); + var badPorts = ( + /** @type {const} */ + [ + "1", + "7", + "9", + "11", + "13", + "15", + "17", + "19", + "20", + "21", + "22", + "23", + "25", + "37", + "42", + "43", + "53", + "69", + "77", + "79", + "87", + "95", + "101", + "102", + "103", + "104", + "109", + "110", + "111", + "113", + "115", + "117", + "119", + "123", + "135", + "137", + "139", + "143", + "161", + "179", + "389", + "427", + "465", + "512", + "513", + "514", + "515", + "526", + "530", + "531", + "532", + "540", + "548", + "554", + "556", + "563", + "587", + "601", + "636", + "989", + "990", + "993", + "995", + "1719", + "1720", + "1723", + "2049", + "3659", + "4045", + "4190", + "5060", + "5061", + "6000", + "6566", + "6665", + "6666", + "6667", + "6668", + "6669", + "6679", + "6697", + "10080" + ] + ); + var badPortsSet = new Set(badPorts); + var referrerPolicy = ( + /** @type {const} */ + [ + "", + "no-referrer", + "no-referrer-when-downgrade", + "same-origin", + "origin", + "strict-origin", + "origin-when-cross-origin", + "strict-origin-when-cross-origin", + "unsafe-url" + ] + ); + var referrerPolicySet = new Set(referrerPolicy); + var requestRedirect = ( + /** @type {const} */ + ["follow", "manual", "error"] + ); + var safeMethods = ( + /** @type {const} */ + ["GET", "HEAD", "OPTIONS", "TRACE"] + ); + var safeMethodsSet = new Set(safeMethods); + var requestMode = ( + /** @type {const} */ + ["navigate", "same-origin", "no-cors", "cors"] + ); + var requestCredentials = ( + /** @type {const} */ + ["omit", "same-origin", "include"] + ); + var requestCache = ( + /** @type {const} */ + [ + "default", + "no-store", + "reload", + "no-cache", + "force-cache", + "only-if-cached" + ] + ); + var requestBodyHeader = ( + /** @type {const} */ + [ + "content-encoding", + "content-language", + "content-location", + "content-type", + // See https://github.com/nodejs/undici/issues/2021 + // 'Content-Length' is a forbidden header name, which is typically + // removed in the Headers implementation. However, undici doesn't + // filter out headers, so we add it here. + "content-length" + ] + ); + var requestDuplex = ( + /** @type {const} */ + [ + "half" + ] + ); + var forbiddenMethods = ( + /** @type {const} */ + ["CONNECT", "TRACE", "TRACK"] + ); + var forbiddenMethodsSet = new Set(forbiddenMethods); + var subresource = ( + /** @type {const} */ + [ + "audio", + "audioworklet", + "font", + "image", + "manifest", + "paintworklet", + "script", + "style", + "track", + "video", + "xslt", + "" + ] + ); + var subresourceSet = new Set(subresource); + module.exports = { + subresource, + forbiddenMethods, + requestBodyHeader, + referrerPolicy, + requestRedirect, + requestMode, + requestCredentials, + requestCache, + redirectStatus, + corsSafeListedMethods, + nullBodyStatus, + safeMethods, + badPorts, + requestDuplex, + subresourceSet, + badPortsSet, + redirectStatusSet, + corsSafeListedMethodsSet, + safeMethodsSet, + forbiddenMethodsSet, + referrerPolicySet + }; + } +}); + +// +var require_global = __commonJS({ + ""(exports, module) { + "use strict"; + var globalOrigin = Symbol.for("undici.globalOrigin.1"); + function getGlobalOrigin() { + return globalThis[globalOrigin]; + } + function setGlobalOrigin(newOrigin) { + if (newOrigin === void 0) { + Object.defineProperty(globalThis, globalOrigin, { + value: void 0, + writable: true, + enumerable: false, + configurable: false + }); + return; + } + const parsedURL = new URL(newOrigin); + if (parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") { + throw new TypeError(`Only http & https urls are allowed, received ${parsedURL.protocol}`); + } + Object.defineProperty(globalThis, globalOrigin, { + value: parsedURL, + writable: true, + enumerable: false, + configurable: false + }); + } + module.exports = { + getGlobalOrigin, + setGlobalOrigin + }; + } +}); + +// +var require_data_url = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var encoder = new TextEncoder(); + var HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+\-.^_|~A-Za-z0-9]+$/; + var HTTP_WHITESPACE_REGEX = /[\u000A\u000D\u0009\u0020]/; + var ASCII_WHITESPACE_REPLACE_REGEX = /[\u0009\u000A\u000C\u000D\u0020]/g; + var HTTP_QUOTED_STRING_TOKENS = /^[\u0009\u0020-\u007E\u0080-\u00FF]+$/; + function dataURLProcessor(dataURL) { + assert2(dataURL.protocol === "data:"); + let input = URLSerializer(dataURL, true); + input = input.slice(5); + const position = { position: 0 }; + let mimeType = collectASequenceOfCodePointsFast( + ",", + input, + position + ); + const mimeTypeLength = mimeType.length; + mimeType = removeASCIIWhitespace(mimeType, true, true); + if (position.position >= input.length) { + return "failure"; + } + position.position++; + const encodedBody = input.slice(mimeTypeLength + 1); + let body = stringPercentDecode(encodedBody); + if (/;(\u0020){0,}base64$/i.test(mimeType)) { + const stringBody = isomorphicDecode(body); + body = forgivingBase64(stringBody); + if (body === "failure") { + return "failure"; + } + mimeType = mimeType.slice(0, -6); + mimeType = mimeType.replace(/(\u0020)+$/, ""); + mimeType = mimeType.slice(0, -1); + } + if (mimeType.startsWith(";")) { + mimeType = "text/plain" + mimeType; + } + let mimeTypeRecord = parseMIMEType(mimeType); + if (mimeTypeRecord === "failure") { + mimeTypeRecord = parseMIMEType("text/plain;charset=US-ASCII"); + } + return { mimeType: mimeTypeRecord, body }; + } + function URLSerializer(url, excludeFragment = false) { + if (!excludeFragment) { + return url.href; + } + const href = url.href; + const hashLength = url.hash.length; + const serialized = hashLength === 0 ? href : href.substring(0, href.length - hashLength); + if (!hashLength && href.endsWith("#")) { + return serialized.slice(0, -1); + } + return serialized; + } + function collectASequenceOfCodePoints(condition, input, position) { + let result = ""; + while (position.position < input.length && condition(input[position.position])) { + result += input[position.position]; + position.position++; + } + return result; + } + function collectASequenceOfCodePointsFast(char, input, position) { + const idx = input.indexOf(char, position.position); + const start = position.position; + if (idx === -1) { + position.position = input.length; + return input.slice(start); + } + position.position = idx; + return input.slice(start, position.position); + } + function stringPercentDecode(input) { + const bytes = encoder.encode(input); + return percentDecode(bytes); + } + function isHexCharByte(byte) { + return byte >= 48 && byte <= 57 || byte >= 65 && byte <= 70 || byte >= 97 && byte <= 102; + } + function hexByteToNumber(byte) { + return ( + // 0-9 + byte >= 48 && byte <= 57 ? byte - 48 : (byte & 223) - 55 + ); + } + function percentDecode(input) { + const length = input.length; + const output = new Uint8Array(length); + let j = 0; + for (let i = 0; i < length; ++i) { + const byte = input[i]; + if (byte !== 37) { + output[j++] = byte; + } else if (byte === 37 && !(isHexCharByte(input[i + 1]) && isHexCharByte(input[i + 2]))) { + output[j++] = 37; + } else { + output[j++] = hexByteToNumber(input[i + 1]) << 4 | hexByteToNumber(input[i + 2]); + i += 2; + } + } + return length === j ? output : output.subarray(0, j); + } + function parseMIMEType(input) { + input = removeHTTPWhitespace(input, true, true); + const position = { position: 0 }; + const type = collectASequenceOfCodePointsFast( + "/", + input, + position + ); + if (type.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(type)) { + return "failure"; + } + if (position.position > input.length) { + return "failure"; + } + position.position++; + let subtype = collectASequenceOfCodePointsFast( + ";", + input, + position + ); + subtype = removeHTTPWhitespace(subtype, false, true); + if (subtype.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(subtype)) { + return "failure"; + } + const typeLowercase = type.toLowerCase(); + const subtypeLowercase = subtype.toLowerCase(); + const mimeType = { + type: typeLowercase, + subtype: subtypeLowercase, + /** @type {Map} */ + parameters: /* @__PURE__ */ new Map(), + // https://mimesniff.spec.whatwg.org/#mime-type-essence + essence: `${typeLowercase}/${subtypeLowercase}` + }; + while (position.position < input.length) { + position.position++; + collectASequenceOfCodePoints( + // https://fetch.spec.whatwg.org/#http-whitespace + (char) => HTTP_WHITESPACE_REGEX.test(char), + input, + position + ); + let parameterName = collectASequenceOfCodePoints( + (char) => char !== ";" && char !== "=", + input, + position + ); + parameterName = parameterName.toLowerCase(); + if (position.position < input.length) { + if (input[position.position] === ";") { + continue; + } + position.position++; + } + if (position.position > input.length) { + break; + } + let parameterValue = null; + if (input[position.position] === '"') { + parameterValue = collectAnHTTPQuotedString(input, position, true); + collectASequenceOfCodePointsFast( + ";", + input, + position + ); + } else { + parameterValue = collectASequenceOfCodePointsFast( + ";", + input, + position + ); + parameterValue = removeHTTPWhitespace(parameterValue, false, true); + if (parameterValue.length === 0) { + continue; + } + } + if (parameterName.length !== 0 && HTTP_TOKEN_CODEPOINTS.test(parameterName) && (parameterValue.length === 0 || HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) && !mimeType.parameters.has(parameterName)) { + mimeType.parameters.set(parameterName, parameterValue); + } + } + return mimeType; + } + function forgivingBase64(data) { + data = data.replace(ASCII_WHITESPACE_REPLACE_REGEX, ""); + let dataLength = data.length; + if (dataLength % 4 === 0) { + if (data.charCodeAt(dataLength - 1) === 61) { + --dataLength; + if (data.charCodeAt(dataLength - 1) === 61) { + --dataLength; + } + } + } + if (dataLength % 4 === 1) { + return "failure"; + } + if (/[^+/0-9A-Za-z]/.test(data.length === dataLength ? data : data.substring(0, dataLength))) { + return "failure"; + } + const buffer = Buffer.from(data, "base64"); + return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength); + } + function collectAnHTTPQuotedString(input, position, extractValue) { + const positionStart = position.position; + let value = ""; + assert2(input[position.position] === '"'); + position.position++; + while (true) { + value += collectASequenceOfCodePoints( + (char) => char !== '"' && char !== "\\", + input, + position + ); + if (position.position >= input.length) { + break; + } + const quoteOrBackslash = input[position.position]; + position.position++; + if (quoteOrBackslash === "\\") { + if (position.position >= input.length) { + value += "\\"; + break; + } + value += input[position.position]; + position.position++; + } else { + assert2(quoteOrBackslash === '"'); + break; + } + } + if (extractValue) { + return value; + } + return input.slice(positionStart, position.position); + } + function serializeAMimeType(mimeType) { + assert2(mimeType !== "failure"); + const { parameters, essence } = mimeType; + let serialization = essence; + for (let [name, value] of parameters.entries()) { + serialization += ";"; + serialization += name; + serialization += "="; + if (!HTTP_TOKEN_CODEPOINTS.test(value)) { + value = value.replace(/(\\|")/g, "\\$1"); + value = '"' + value; + value += '"'; + } + serialization += value; + } + return serialization; + } + function isHTTPWhiteSpace(char) { + return char === 13 || char === 10 || char === 9 || char === 32; + } + function removeHTTPWhitespace(str, leading = true, trailing = true) { + return removeChars(str, leading, trailing, isHTTPWhiteSpace); + } + function isASCIIWhitespace(char) { + return char === 13 || char === 10 || char === 9 || char === 12 || char === 32; + } + function removeASCIIWhitespace(str, leading = true, trailing = true) { + return removeChars(str, leading, trailing, isASCIIWhitespace); + } + function removeChars(str, leading, trailing, predicate) { + let lead = 0; + let trail = str.length - 1; + if (leading) { + while (lead < str.length && predicate(str.charCodeAt(lead))) + lead++; + } + if (trailing) { + while (trail > 0 && predicate(str.charCodeAt(trail))) + trail--; + } + return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1); + } + function isomorphicDecode(input) { + const length = input.length; + if ((2 << 15) - 1 > length) { + return String.fromCharCode.apply(null, input); + } + let result = ""; + let i = 0; + let addition = (2 << 15) - 1; + while (i < length) { + if (i + addition > length) { + addition = length - i; + } + result += String.fromCharCode.apply(null, input.subarray(i, i += addition)); + } + return result; + } + function minimizeSupportedMimeType(mimeType) { + switch (mimeType.essence) { + case "application/ecmascript": + case "application/javascript": + case "application/x-ecmascript": + case "application/x-javascript": + case "text/ecmascript": + case "text/javascript": + case "text/javascript1.0": + case "text/javascript1.1": + case "text/javascript1.2": + case "text/javascript1.3": + case "text/javascript1.4": + case "text/javascript1.5": + case "text/jscript": + case "text/livescript": + case "text/x-ecmascript": + case "text/x-javascript": + return "text/javascript"; + case "application/json": + case "text/json": + return "application/json"; + case "image/svg+xml": + return "image/svg+xml"; + case "text/xml": + case "application/xml": + return "application/xml"; + } + if (mimeType.subtype.endsWith("+json")) { + return "application/json"; + } + if (mimeType.subtype.endsWith("+xml")) { + return "application/xml"; + } + return ""; + } + module.exports = { + dataURLProcessor, + URLSerializer, + collectASequenceOfCodePoints, + collectASequenceOfCodePointsFast, + stringPercentDecode, + parseMIMEType, + collectAnHTTPQuotedString, + serializeAMimeType, + removeChars, + removeHTTPWhitespace, + minimizeSupportedMimeType, + HTTP_TOKEN_CODEPOINTS, + isomorphicDecode + }; + } +}); + +// +var require_webidl = __commonJS({ + ""(exports, module) { + "use strict"; + var { types: types2, inspect: inspect2 } = __require("node:util"); + var { markAsUncloneable } = __require("node:worker_threads"); + var { toUSVString } = require_util(); + var webidl = {}; + webidl.converters = {}; + webidl.util = {}; + webidl.errors = {}; + webidl.errors.exception = function(message) { + return new TypeError(`${message.header}: ${message.message}`); + }; + webidl.errors.conversionFailed = function(context3) { + const plural = context3.types.length === 1 ? "" : " one of"; + const message = `${context3.argument} could not be converted to${plural}: ${context3.types.join(", ")}.`; + return webidl.errors.exception({ + header: context3.prefix, + message + }); + }; + webidl.errors.invalidArgument = function(context3) { + return webidl.errors.exception({ + header: context3.prefix, + message: `"${context3.value}" is an invalid ${context3.type}.` + }); + }; + webidl.brandCheck = function(V, I, opts) { + if (opts?.strict !== false) { + if (!(V instanceof I)) { + const err = new TypeError("Illegal invocation"); + err.code = "ERR_INVALID_THIS"; + throw err; + } + } else { + if (V?.[Symbol.toStringTag] !== I.prototype[Symbol.toStringTag]) { + const err = new TypeError("Illegal invocation"); + err.code = "ERR_INVALID_THIS"; + throw err; + } + } + }; + webidl.argumentLengthCheck = function({ length }, min, ctx) { + if (length < min) { + throw webidl.errors.exception({ + message: `${min} argument${min !== 1 ? "s" : ""} required, but${length ? " only" : ""} ${length} found.`, + header: ctx + }); + } + }; + webidl.illegalConstructor = function() { + throw webidl.errors.exception({ + header: "TypeError", + message: "Illegal constructor" + }); + }; + webidl.util.Type = function(V) { + switch (typeof V) { + case "undefined": + return "Undefined"; + case "boolean": + return "Boolean"; + case "string": + return "String"; + case "symbol": + return "Symbol"; + case "number": + return "Number"; + case "bigint": + return "BigInt"; + case "function": + case "object": { + if (V === null) { + return "Null"; + } + return "Object"; + } + } + }; + webidl.util.markAsUncloneable = markAsUncloneable || (() => { + }); + webidl.util.ConvertToInt = function(V, bitLength, signedness, opts) { + let upperBound; + let lowerBound; + if (bitLength === 64) { + upperBound = Math.pow(2, 53) - 1; + if (signedness === "unsigned") { + lowerBound = 0; + } else { + lowerBound = Math.pow(-2, 53) + 1; + } + } else if (signedness === "unsigned") { + lowerBound = 0; + upperBound = Math.pow(2, bitLength) - 1; + } else { + lowerBound = Math.pow(-2, bitLength) - 1; + upperBound = Math.pow(2, bitLength - 1) - 1; + } + let x = Number(V); + if (x === 0) { + x = 0; + } + if (opts?.enforceRange === true) { + if (Number.isNaN(x) || x === Number.POSITIVE_INFINITY || x === Number.NEGATIVE_INFINITY) { + throw webidl.errors.exception({ + header: "Integer conversion", + message: `Could not convert ${webidl.util.Stringify(V)} to an integer.` + }); + } + x = webidl.util.IntegerPart(x); + if (x < lowerBound || x > upperBound) { + throw webidl.errors.exception({ + header: "Integer conversion", + message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.` + }); + } + return x; + } + if (!Number.isNaN(x) && opts?.clamp === true) { + x = Math.min(Math.max(x, lowerBound), upperBound); + if (Math.floor(x) % 2 === 0) { + x = Math.floor(x); + } else { + x = Math.ceil(x); + } + return x; + } + if (Number.isNaN(x) || x === 0 && Object.is(0, x) || x === Number.POSITIVE_INFINITY || x === Number.NEGATIVE_INFINITY) { + return 0; + } + x = webidl.util.IntegerPart(x); + x = x % Math.pow(2, bitLength); + if (signedness === "signed" && x >= Math.pow(2, bitLength) - 1) { + return x - Math.pow(2, bitLength); + } + return x; + }; + webidl.util.IntegerPart = function(n) { + const r = Math.floor(Math.abs(n)); + if (n < 0) { + return -1 * r; + } + return r; + }; + webidl.util.Stringify = function(V) { + const type = webidl.util.Type(V); + switch (type) { + case "Symbol": + return `Symbol(${V.description})`; + case "Object": + return inspect2(V); + case "String": + return `"${V}"`; + default: + return `${V}`; + } + }; + webidl.sequenceConverter = function(converter) { + return (V, prefix, argument, Iterable) => { + if (webidl.util.Type(V) !== "Object") { + throw webidl.errors.exception({ + header: prefix, + message: `${argument} (${webidl.util.Stringify(V)}) is not iterable.` + }); + } + const method = typeof Iterable === "function" ? Iterable() : V?.[Symbol.iterator]?.(); + const seq = []; + let index = 0; + if (method === void 0 || typeof method.next !== "function") { + throw webidl.errors.exception({ + header: prefix, + message: `${argument} is not iterable.` + }); + } + while (true) { + const { done, value } = method.next(); + if (done) { + break; + } + seq.push(converter(value, prefix, `${argument}[${index++}]`)); + } + return seq; + }; + }; + webidl.recordConverter = function(keyConverter, valueConverter) { + return (O, prefix, argument) => { + if (webidl.util.Type(O) !== "Object") { + throw webidl.errors.exception({ + header: prefix, + message: `${argument} ("${webidl.util.Type(O)}") is not an Object.` + }); + } + const result = {}; + if (!types2.isProxy(O)) { + const keys2 = [...Object.getOwnPropertyNames(O), ...Object.getOwnPropertySymbols(O)]; + for (const key of keys2) { + const typedKey = keyConverter(key, prefix, argument); + const typedValue = valueConverter(O[key], prefix, argument); + result[typedKey] = typedValue; + } + return result; + } + const keys = Reflect.ownKeys(O); + for (const key of keys) { + const desc = Reflect.getOwnPropertyDescriptor(O, key); + if (desc?.enumerable) { + const typedKey = keyConverter(key, prefix, argument); + const typedValue = valueConverter(O[key], prefix, argument); + result[typedKey] = typedValue; + } + } + return result; + }; + }; + webidl.interfaceConverter = function(i) { + return (V, prefix, argument, opts) => { + if (opts?.strict !== false && !(V instanceof i)) { + throw webidl.errors.exception({ + header: prefix, + message: `Expected ${argument} ("${webidl.util.Stringify(V)}") to be an instance of ${i.name}.` + }); + } + return V; + }; + }; + webidl.dictionaryConverter = function(converters) { + return (dictionary, prefix, argument) => { + const type = webidl.util.Type(dictionary); + const dict = {}; + if (type === "Null" || type === "Undefined") { + return dict; + } else if (type !== "Object") { + throw webidl.errors.exception({ + header: prefix, + message: `Expected ${dictionary} to be one of: Null, Undefined, Object.` + }); + } + for (const options of converters) { + const { key, defaultValue, required, converter } = options; + if (required === true) { + if (!Object.hasOwn(dictionary, key)) { + throw webidl.errors.exception({ + header: prefix, + message: `Missing required key "${key}".` + }); + } + } + let value = dictionary[key]; + const hasDefault = Object.hasOwn(options, "defaultValue"); + if (hasDefault && value !== null) { + value ??= defaultValue(); + } + if (required || hasDefault || value !== void 0) { + value = converter(value, prefix, `${argument}.${key}`); + if (options.allowedValues && !options.allowedValues.includes(value)) { + throw webidl.errors.exception({ + header: prefix, + message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(", ")}.` + }); + } + dict[key] = value; + } + } + return dict; + }; + }; + webidl.nullableConverter = function(converter) { + return (V, prefix, argument) => { + if (V === null) { + return V; + } + return converter(V, prefix, argument); + }; + }; + webidl.converters.DOMString = function(V, prefix, argument, opts) { + if (V === null && opts?.legacyNullToEmptyString) { + return ""; + } + if (typeof V === "symbol") { + throw webidl.errors.exception({ + header: prefix, + message: `${argument} is a symbol, which cannot be converted to a DOMString.` + }); + } + return String(V); + }; + webidl.converters.ByteString = function(V, prefix, argument) { + const x = webidl.converters.DOMString(V, prefix, argument); + for (let index = 0; index < x.length; index++) { + if (x.charCodeAt(index) > 255) { + throw new TypeError( + `Cannot convert argument to a ByteString because the character at index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.` + ); + } + } + return x; + }; + webidl.converters.USVString = toUSVString; + webidl.converters.boolean = function(V) { + const x = Boolean(V); + return x; + }; + webidl.converters.any = function(V) { + return V; + }; + webidl.converters["long long"] = function(V, prefix, argument) { + const x = webidl.util.ConvertToInt(V, 64, "signed", void 0, prefix, argument); + return x; + }; + webidl.converters["unsigned long long"] = function(V, prefix, argument) { + const x = webidl.util.ConvertToInt(V, 64, "unsigned", void 0, prefix, argument); + return x; + }; + webidl.converters["unsigned long"] = function(V, prefix, argument) { + const x = webidl.util.ConvertToInt(V, 32, "unsigned", void 0, prefix, argument); + return x; + }; + webidl.converters["unsigned short"] = function(V, prefix, argument, opts) { + const x = webidl.util.ConvertToInt(V, 16, "unsigned", opts, prefix, argument); + return x; + }; + webidl.converters.ArrayBuffer = function(V, prefix, argument, opts) { + if (webidl.util.Type(V) !== "Object" || !types2.isAnyArrayBuffer(V)) { + throw webidl.errors.conversionFailed({ + prefix, + argument: `${argument} ("${webidl.util.Stringify(V)}")`, + types: ["ArrayBuffer"] + }); + } + if (opts?.allowShared === false && types2.isSharedArrayBuffer(V)) { + throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "SharedArrayBuffer is not allowed." + }); + } + if (V.resizable || V.growable) { + throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "Received a resizable ArrayBuffer." + }); + } + return V; + }; + webidl.converters.TypedArray = function(V, T, prefix, name, opts) { + if (webidl.util.Type(V) !== "Object" || !types2.isTypedArray(V) || V.constructor.name !== T.name) { + throw webidl.errors.conversionFailed({ + prefix, + argument: `${name} ("${webidl.util.Stringify(V)}")`, + types: [T.name] + }); + } + if (opts?.allowShared === false && types2.isSharedArrayBuffer(V.buffer)) { + throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "SharedArrayBuffer is not allowed." + }); + } + if (V.buffer.resizable || V.buffer.growable) { + throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "Received a resizable ArrayBuffer." + }); + } + return V; + }; + webidl.converters.DataView = function(V, prefix, name, opts) { + if (webidl.util.Type(V) !== "Object" || !types2.isDataView(V)) { + throw webidl.errors.exception({ + header: prefix, + message: `${name} is not a DataView.` + }); + } + if (opts?.allowShared === false && types2.isSharedArrayBuffer(V.buffer)) { + throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "SharedArrayBuffer is not allowed." + }); + } + if (V.buffer.resizable || V.buffer.growable) { + throw webidl.errors.exception({ + header: "ArrayBuffer", + message: "Received a resizable ArrayBuffer." + }); + } + return V; + }; + webidl.converters.BufferSource = function(V, prefix, name, opts) { + if (types2.isAnyArrayBuffer(V)) { + return webidl.converters.ArrayBuffer(V, prefix, name, { ...opts, allowShared: false }); + } + if (types2.isTypedArray(V)) { + return webidl.converters.TypedArray(V, V.constructor, prefix, name, { ...opts, allowShared: false }); + } + if (types2.isDataView(V)) { + return webidl.converters.DataView(V, prefix, name, { ...opts, allowShared: false }); + } + throw webidl.errors.conversionFailed({ + prefix, + argument: `${name} ("${webidl.util.Stringify(V)}")`, + types: ["BufferSource"] + }); + }; + webidl.converters["sequence"] = webidl.sequenceConverter( + webidl.converters.ByteString + ); + webidl.converters["sequence>"] = webidl.sequenceConverter( + webidl.converters["sequence"] + ); + webidl.converters["record"] = webidl.recordConverter( + webidl.converters.ByteString, + webidl.converters.ByteString + ); + module.exports = { + webidl + }; + } +}); + +// +var require_util2 = __commonJS({ + ""(exports, module) { + "use strict"; + var { Transform } = __require("node:stream"); + var zlib = __require("node:zlib"); + var { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require_constants3(); + var { getGlobalOrigin } = require_global(); + var { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require_data_url(); + var { performance: performance2 } = __require("node:perf_hooks"); + var { isBlobLike, ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require_util(); + var assert2 = __require("node:assert"); + var { isUint8Array } = __require("node:util/types"); + var { webidl } = require_webidl(); + var supportedHashes = []; + var crypto; + try { + crypto = __require("node:crypto"); + const possibleRelevantHashes = ["sha256", "sha384", "sha512"]; + supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash)); + } catch { + } + function responseURL(response) { + const urlList = response.urlList; + const length = urlList.length; + return length === 0 ? null : urlList[length - 1].toString(); + } + function responseLocationURL(response, requestFragment) { + if (!redirectStatusSet.has(response.status)) { + return null; + } + let location = response.headersList.get("location", true); + if (location !== null && isValidHeaderValue(location)) { + if (!isValidEncodedURL(location)) { + location = normalizeBinaryStringToUtf8(location); + } + location = new URL(location, responseURL(response)); + } + if (location && !location.hash) { + location.hash = requestFragment; + } + return location; + } + function isValidEncodedURL(url) { + for (let i = 0; i < url.length; ++i) { + const code = url.charCodeAt(i); + if (code > 126 || // Non-US-ASCII + DEL + code < 32) { + return false; + } + } + return true; + } + function normalizeBinaryStringToUtf8(value) { + return Buffer.from(value, "binary").toString("utf8"); + } + function requestCurrentURL(request3) { + return request3.urlList[request3.urlList.length - 1]; + } + function requestBadPort(request3) { + const url = requestCurrentURL(request3); + if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) { + return "blocked"; + } + return "allowed"; + } + function isErrorLike(object) { + return object instanceof Error || (object?.constructor?.name === "Error" || object?.constructor?.name === "DOMException"); + } + function isValidReasonPhrase(statusText) { + for (let i = 0; i < statusText.length; ++i) { + const c = statusText.charCodeAt(i); + if (!(c === 9 || // HTAB + c >= 32 && c <= 126 || // SP / VCHAR + c >= 128 && c <= 255)) { + return false; + } + } + return true; + } + var isValidHeaderName = isValidHTTPToken; + function isValidHeaderValue(potentialValue) { + return (potentialValue[0] === " " || potentialValue[0] === " " || potentialValue[potentialValue.length - 1] === " " || potentialValue[potentialValue.length - 1] === " " || potentialValue.includes("\n") || potentialValue.includes("\r") || potentialValue.includes("\0")) === false; + } + function setRequestReferrerPolicyOnRedirect(request3, actualResponse) { + const { headersList } = actualResponse; + const policyHeader = (headersList.get("referrer-policy", true) ?? "").split(","); + let policy = ""; + if (policyHeader.length > 0) { + for (let i = policyHeader.length; i !== 0; i--) { + const token = policyHeader[i - 1].trim(); + if (referrerPolicyTokens.has(token)) { + policy = token; + break; + } + } + } + if (policy !== "") { + request3.referrerPolicy = policy; + } + } + function crossOriginResourcePolicyCheck() { + return "allowed"; + } + function corsCheck() { + return "success"; + } + function TAOCheck() { + return "success"; + } + function appendFetchMetadata(httpRequest) { + let header = null; + header = httpRequest.mode; + httpRequest.headersList.set("sec-fetch-mode", header, true); + } + function appendRequestOriginHeader(request3) { + let serializedOrigin = request3.origin; + if (serializedOrigin === "client" || serializedOrigin === void 0) { + return; + } + if (request3.responseTainting === "cors" || request3.mode === "websocket") { + request3.headersList.append("origin", serializedOrigin, true); + } else if (request3.method !== "GET" && request3.method !== "HEAD") { + switch (request3.referrerPolicy) { + case "no-referrer": + serializedOrigin = null; + break; + case "no-referrer-when-downgrade": + case "strict-origin": + case "strict-origin-when-cross-origin": + if (request3.origin && urlHasHttpsScheme(request3.origin) && !urlHasHttpsScheme(requestCurrentURL(request3))) { + serializedOrigin = null; + } + break; + case "same-origin": + if (!sameOrigin(request3, requestCurrentURL(request3))) { + serializedOrigin = null; + } + break; + default: + } + request3.headersList.append("origin", serializedOrigin, true); + } + } + function coarsenTime(timestamp, crossOriginIsolatedCapability) { + return timestamp; + } + function clampAndCoarsenConnectionTimingInfo(connectionTimingInfo, defaultStartTime, crossOriginIsolatedCapability) { + if (!connectionTimingInfo?.startTime || connectionTimingInfo.startTime < defaultStartTime) { + return { + domainLookupStartTime: defaultStartTime, + domainLookupEndTime: defaultStartTime, + connectionStartTime: defaultStartTime, + connectionEndTime: defaultStartTime, + secureConnectionStartTime: defaultStartTime, + ALPNNegotiatedProtocol: connectionTimingInfo?.ALPNNegotiatedProtocol + }; + } + return { + domainLookupStartTime: coarsenTime(connectionTimingInfo.domainLookupStartTime, crossOriginIsolatedCapability), + domainLookupEndTime: coarsenTime(connectionTimingInfo.domainLookupEndTime, crossOriginIsolatedCapability), + connectionStartTime: coarsenTime(connectionTimingInfo.connectionStartTime, crossOriginIsolatedCapability), + connectionEndTime: coarsenTime(connectionTimingInfo.connectionEndTime, crossOriginIsolatedCapability), + secureConnectionStartTime: coarsenTime(connectionTimingInfo.secureConnectionStartTime, crossOriginIsolatedCapability), + ALPNNegotiatedProtocol: connectionTimingInfo.ALPNNegotiatedProtocol + }; + } + function coarsenedSharedCurrentTime(crossOriginIsolatedCapability) { + return coarsenTime(performance2.now(), crossOriginIsolatedCapability); + } + function createOpaqueTimingInfo(timingInfo) { + return { + startTime: timingInfo.startTime ?? 0, + redirectStartTime: 0, + redirectEndTime: 0, + postRedirectStartTime: timingInfo.startTime ?? 0, + finalServiceWorkerStartTime: 0, + finalNetworkResponseStartTime: 0, + finalNetworkRequestStartTime: 0, + endTime: 0, + encodedBodySize: 0, + decodedBodySize: 0, + finalConnectionTimingInfo: null + }; + } + function makePolicyContainer() { + return { + referrerPolicy: "strict-origin-when-cross-origin" + }; + } + function clonePolicyContainer(policyContainer) { + return { + referrerPolicy: policyContainer.referrerPolicy + }; + } + function determineRequestsReferrer(request3) { + const policy = request3.referrerPolicy; + assert2(policy); + let referrerSource = null; + if (request3.referrer === "client") { + const globalOrigin = getGlobalOrigin(); + if (!globalOrigin || globalOrigin.origin === "null") { + return "no-referrer"; + } + referrerSource = new URL(globalOrigin); + } else if (request3.referrer instanceof URL) { + referrerSource = request3.referrer; + } + let referrerURL = stripURLForReferrer(referrerSource); + const referrerOrigin = stripURLForReferrer(referrerSource, true); + if (referrerURL.toString().length > 4096) { + referrerURL = referrerOrigin; + } + const areSameOrigin = sameOrigin(request3, referrerURL); + const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(request3.url); + switch (policy) { + case "origin": + return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true); + case "unsafe-url": + return referrerURL; + case "same-origin": + return areSameOrigin ? referrerOrigin : "no-referrer"; + case "origin-when-cross-origin": + return areSameOrigin ? referrerURL : referrerOrigin; + case "strict-origin-when-cross-origin": { + const currentURL = requestCurrentURL(request3); + if (sameOrigin(referrerURL, currentURL)) { + return referrerURL; + } + if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) { + return "no-referrer"; + } + return referrerOrigin; + } + case "strict-origin": + case "no-referrer-when-downgrade": + default: + return isNonPotentiallyTrustWorthy ? "no-referrer" : referrerOrigin; + } + } + function stripURLForReferrer(url, originOnly) { + assert2(url instanceof URL); + url = new URL(url); + if (url.protocol === "file:" || url.protocol === "about:" || url.protocol === "blank:") { + return "no-referrer"; + } + url.username = ""; + url.password = ""; + url.hash = ""; + if (originOnly) { + url.pathname = ""; + url.search = ""; + } + return url; + } + function isURLPotentiallyTrustworthy(url) { + if (!(url instanceof URL)) { + return false; + } + if (url.href === "about:blank" || url.href === "about:srcdoc") { + return true; + } + if (url.protocol === "data:") + return true; + if (url.protocol === "file:") + return true; + return isOriginPotentiallyTrustworthy(url.origin); + function isOriginPotentiallyTrustworthy(origin) { + if (origin == null || origin === "null") + return false; + const originAsURL = new URL(origin); + if (originAsURL.protocol === "https:" || originAsURL.protocol === "wss:") { + return true; + } + if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) || (originAsURL.hostname === "localhost" || originAsURL.hostname.includes("localhost.")) || originAsURL.hostname.endsWith(".localhost")) { + return true; + } + return false; + } + } + function bytesMatch(bytes, metadataList) { + if (crypto === void 0) { + return true; + } + const parsedMetadata = parseMetadata(metadataList); + if (parsedMetadata === "no metadata") { + return true; + } + if (parsedMetadata.length === 0) { + return true; + } + const strongest = getStrongestMetadata(parsedMetadata); + const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest); + for (const item of metadata) { + const algorithm = item.algo; + const expectedValue = item.hash; + let actualValue = crypto.createHash(algorithm).update(bytes).digest("base64"); + if (actualValue[actualValue.length - 1] === "=") { + if (actualValue[actualValue.length - 2] === "=") { + actualValue = actualValue.slice(0, -2); + } else { + actualValue = actualValue.slice(0, -1); + } + } + if (compareBase64Mixed(actualValue, expectedValue)) { + return true; + } + } + return false; + } + var parseHashWithOptions = /(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i; + function parseMetadata(metadata) { + const result = []; + let empty = true; + for (const token of metadata.split(" ")) { + empty = false; + const parsedToken = parseHashWithOptions.exec(token); + if (parsedToken === null || parsedToken.groups === void 0 || parsedToken.groups.algo === void 0) { + continue; + } + const algorithm = parsedToken.groups.algo.toLowerCase(); + if (supportedHashes.includes(algorithm)) { + result.push(parsedToken.groups); + } + } + if (empty === true) { + return "no metadata"; + } + return result; + } + function getStrongestMetadata(metadataList) { + let algorithm = metadataList[0].algo; + if (algorithm[3] === "5") { + return algorithm; + } + for (let i = 1; i < metadataList.length; ++i) { + const metadata = metadataList[i]; + if (metadata.algo[3] === "5") { + algorithm = "sha512"; + break; + } else if (algorithm[3] === "3") { + continue; + } else if (metadata.algo[3] === "3") { + algorithm = "sha384"; + } + } + return algorithm; + } + function filterMetadataListByAlgorithm(metadataList, algorithm) { + if (metadataList.length === 1) { + return metadataList; + } + let pos = 0; + for (let i = 0; i < metadataList.length; ++i) { + if (metadataList[i].algo === algorithm) { + metadataList[pos++] = metadataList[i]; + } + } + metadataList.length = pos; + return metadataList; + } + function compareBase64Mixed(actualValue, expectedValue) { + if (actualValue.length !== expectedValue.length) { + return false; + } + for (let i = 0; i < actualValue.length; ++i) { + if (actualValue[i] !== expectedValue[i]) { + if (actualValue[i] === "+" && expectedValue[i] === "-" || actualValue[i] === "/" && expectedValue[i] === "_") { + continue; + } + return false; + } + } + return true; + } + function tryUpgradeRequestToAPotentiallyTrustworthyURL(request3) { + } + function sameOrigin(A, B) { + if (A.origin === B.origin && A.origin === "null") { + return true; + } + if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) { + return true; + } + return false; + } + function createDeferredPromise() { + let res; + let rej; + const promise = new Promise((resolve5, reject) => { + res = resolve5; + rej = reject; + }); + return { promise, resolve: res, reject: rej }; + } + function isAborted(fetchParams) { + return fetchParams.controller.state === "aborted"; + } + function isCancelled(fetchParams) { + return fetchParams.controller.state === "aborted" || fetchParams.controller.state === "terminated"; + } + function normalizeMethod(method) { + return normalizedMethodRecordsBase[method.toLowerCase()] ?? method; + } + function serializeJavascriptValueToJSONString(value) { + const result = JSON.stringify(value); + if (result === void 0) { + throw new TypeError("Value is not JSON serializable"); + } + assert2(typeof result === "string"); + return result; + } + var esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())); + function createIterator(name, kInternalIterator, keyIndex = 0, valueIndex = 1) { + class FastIterableIterator { + /** @type {any} */ + #target; + /** @type {'key' | 'value' | 'key+value'} */ + #kind; + /** @type {number} */ + #index; + /** + * @see https://webidl.spec.whatwg.org/#dfn-default-iterator-object + * @param {unknown} target + * @param {'key' | 'value' | 'key+value'} kind + */ + constructor(target, kind) { + this.#target = target; + this.#kind = kind; + this.#index = 0; + } + next() { + if (typeof this !== "object" || this === null || !(#target in this)) { + throw new TypeError( + `'next' called on an object that does not implement interface ${name} Iterator.` + ); + } + const index = this.#index; + const values = this.#target[kInternalIterator]; + const len = values.length; + if (index >= len) { + return { + value: void 0, + done: true + }; + } + const { [keyIndex]: key, [valueIndex]: value } = values[index]; + this.#index = index + 1; + let result; + switch (this.#kind) { + case "key": + result = key; + break; + case "value": + result = value; + break; + case "key+value": + result = [key, value]; + break; + } + return { + value: result, + done: false + }; + } + } + delete FastIterableIterator.prototype.constructor; + Object.setPrototypeOf(FastIterableIterator.prototype, esIteratorPrototype); + Object.defineProperties(FastIterableIterator.prototype, { + [Symbol.toStringTag]: { + writable: false, + enumerable: false, + configurable: true, + value: `${name} Iterator` + }, + next: { writable: true, enumerable: true, configurable: true } + }); + return function(target, kind) { + return new FastIterableIterator(target, kind); + }; + } + function iteratorMixin(name, object, kInternalIterator, keyIndex = 0, valueIndex = 1) { + const makeIterator = createIterator(name, kInternalIterator, keyIndex, valueIndex); + const properties = { + keys: { + writable: true, + enumerable: true, + configurable: true, + value: function keys() { + webidl.brandCheck(this, object); + return makeIterator(this, "key"); + } + }, + values: { + writable: true, + enumerable: true, + configurable: true, + value: function values() { + webidl.brandCheck(this, object); + return makeIterator(this, "value"); + } + }, + entries: { + writable: true, + enumerable: true, + configurable: true, + value: function entries() { + webidl.brandCheck(this, object); + return makeIterator(this, "key+value"); + } + }, + forEach: { + writable: true, + enumerable: true, + configurable: true, + value: function forEach(callbackfn, thisArg = globalThis) { + webidl.brandCheck(this, object); + webidl.argumentLengthCheck(arguments, 1, `${name}.forEach`); + if (typeof callbackfn !== "function") { + throw new TypeError( + `Failed to execute 'forEach' on '${name}': parameter 1 is not of type 'Function'.` + ); + } + for (const { 0: key, 1: value } of makeIterator(this, "key+value")) { + callbackfn.call(thisArg, value, key, this); + } + } + } + }; + return Object.defineProperties(object.prototype, { + ...properties, + [Symbol.iterator]: { + writable: true, + enumerable: false, + configurable: true, + value: properties.entries.value + } + }); + } + async function fullyReadBody(body, processBody, processBodyError) { + const successSteps = processBody; + const errorSteps = processBodyError; + let reader; + try { + reader = body.stream.getReader(); + } catch (e) { + errorSteps(e); + return; + } + try { + successSteps(await readAllBytes(reader)); + } catch (e) { + errorSteps(e); + } + } + function isReadableStreamLike(stream) { + return stream instanceof ReadableStream || stream[Symbol.toStringTag] === "ReadableStream" && typeof stream.tee === "function"; + } + function readableStreamClose(controller) { + try { + controller.close(); + controller.byobRequest?.respond(0); + } catch (err) { + if (!err.message.includes("Controller is already closed") && !err.message.includes("ReadableStream is already closed")) { + throw err; + } + } + } + var invalidIsomorphicEncodeValueRegex = /[^\x00-\xFF]/; + function isomorphicEncode(input) { + assert2(!invalidIsomorphicEncodeValueRegex.test(input)); + return input; + } + async function readAllBytes(reader) { + const bytes = []; + let byteLength = 0; + while (true) { + const { done, value: chunk } = await reader.read(); + if (done) { + return Buffer.concat(bytes, byteLength); + } + if (!isUint8Array(chunk)) { + throw new TypeError("Received non-Uint8Array chunk"); + } + bytes.push(chunk); + byteLength += chunk.length; + } + } + function urlIsLocal(url) { + assert2("protocol" in url); + const protocol = url.protocol; + return protocol === "about:" || protocol === "blob:" || protocol === "data:"; + } + function urlHasHttpsScheme(url) { + return typeof url === "string" && url[5] === ":" && url[0] === "h" && url[1] === "t" && url[2] === "t" && url[3] === "p" && url[4] === "s" || url.protocol === "https:"; + } + function urlIsHttpHttpsScheme(url) { + assert2("protocol" in url); + const protocol = url.protocol; + return protocol === "http:" || protocol === "https:"; + } + function simpleRangeHeaderValue(value, allowWhitespace) { + const data = value; + if (!data.startsWith("bytes")) { + return "failure"; + } + const position = { position: 5 }; + if (allowWhitespace) { + collectASequenceOfCodePoints( + (char) => char === " " || char === " ", + data, + position + ); + } + if (data.charCodeAt(position.position) !== 61) { + return "failure"; + } + position.position++; + if (allowWhitespace) { + collectASequenceOfCodePoints( + (char) => char === " " || char === " ", + data, + position + ); + } + const rangeStart = collectASequenceOfCodePoints( + (char) => { + const code = char.charCodeAt(0); + return code >= 48 && code <= 57; + }, + data, + position + ); + const rangeStartValue = rangeStart.length ? Number(rangeStart) : null; + if (allowWhitespace) { + collectASequenceOfCodePoints( + (char) => char === " " || char === " ", + data, + position + ); + } + if (data.charCodeAt(position.position) !== 45) { + return "failure"; + } + position.position++; + if (allowWhitespace) { + collectASequenceOfCodePoints( + (char) => char === " " || char === " ", + data, + position + ); + } + const rangeEnd = collectASequenceOfCodePoints( + (char) => { + const code = char.charCodeAt(0); + return code >= 48 && code <= 57; + }, + data, + position + ); + const rangeEndValue = rangeEnd.length ? Number(rangeEnd) : null; + if (position.position < data.length) { + return "failure"; + } + if (rangeEndValue === null && rangeStartValue === null) { + return "failure"; + } + if (rangeStartValue > rangeEndValue) { + return "failure"; + } + return { rangeStartValue, rangeEndValue }; + } + function buildContentRange(rangeStart, rangeEnd, fullLength) { + let contentRange = "bytes "; + contentRange += isomorphicEncode(`${rangeStart}`); + contentRange += "-"; + contentRange += isomorphicEncode(`${rangeEnd}`); + contentRange += "/"; + contentRange += isomorphicEncode(`${fullLength}`); + return contentRange; + } + var InflateStream = class extends Transform { + #zlibOptions; + /** @param {zlib.ZlibOptions} [zlibOptions] */ + constructor(zlibOptions) { + super(); + this.#zlibOptions = zlibOptions; + } + _transform(chunk, encoding, callback) { + if (!this._inflateStream) { + if (chunk.length === 0) { + callback(); + return; + } + this._inflateStream = (chunk[0] & 15) === 8 ? zlib.createInflate(this.#zlibOptions) : zlib.createInflateRaw(this.#zlibOptions); + this._inflateStream.on("data", this.push.bind(this)); + this._inflateStream.on("end", () => this.push(null)); + this._inflateStream.on("error", (err) => this.destroy(err)); + } + this._inflateStream.write(chunk, encoding, callback); + } + _final(callback) { + if (this._inflateStream) { + this._inflateStream.end(); + this._inflateStream = null; + } + callback(); + } + }; + function createInflate(zlibOptions) { + return new InflateStream(zlibOptions); + } + function extractMimeType(headers) { + let charset = null; + let essence = null; + let mimeType = null; + const values = getDecodeSplit("content-type", headers); + if (values === null) { + return "failure"; + } + for (const value of values) { + const temporaryMimeType = parseMIMEType(value); + if (temporaryMimeType === "failure" || temporaryMimeType.essence === "*/*") { + continue; + } + mimeType = temporaryMimeType; + if (mimeType.essence !== essence) { + charset = null; + if (mimeType.parameters.has("charset")) { + charset = mimeType.parameters.get("charset"); + } + essence = mimeType.essence; + } else if (!mimeType.parameters.has("charset") && charset !== null) { + mimeType.parameters.set("charset", charset); + } + } + if (mimeType == null) { + return "failure"; + } + return mimeType; + } + function gettingDecodingSplitting(value) { + const input = value; + const position = { position: 0 }; + const values = []; + let temporaryValue = ""; + while (position.position < input.length) { + temporaryValue += collectASequenceOfCodePoints( + (char) => char !== '"' && char !== ",", + input, + position + ); + if (position.position < input.length) { + if (input.charCodeAt(position.position) === 34) { + temporaryValue += collectAnHTTPQuotedString( + input, + position + ); + if (position.position < input.length) { + continue; + } + } else { + assert2(input.charCodeAt(position.position) === 44); + position.position++; + } + } + temporaryValue = removeChars(temporaryValue, true, true, (char) => char === 9 || char === 32); + values.push(temporaryValue); + temporaryValue = ""; + } + return values; + } + function getDecodeSplit(name, list) { + const value = list.get(name, true); + if (value === null) { + return null; + } + return gettingDecodingSplitting(value); + } + var textDecoder = new TextDecoder(); + function utf8DecodeBytes(buffer) { + if (buffer.length === 0) { + return ""; + } + if (buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191) { + buffer = buffer.subarray(3); + } + const output = textDecoder.decode(buffer); + return output; + } + var EnvironmentSettingsObjectBase = class { + get baseUrl() { + return getGlobalOrigin(); + } + get origin() { + return this.baseUrl?.origin; + } + policyContainer = makePolicyContainer(); + }; + var EnvironmentSettingsObject = class { + settingsObject = new EnvironmentSettingsObjectBase(); + }; + var environmentSettingsObject = new EnvironmentSettingsObject(); + module.exports = { + isAborted, + isCancelled, + isValidEncodedURL, + createDeferredPromise, + ReadableStreamFrom, + tryUpgradeRequestToAPotentiallyTrustworthyURL, + clampAndCoarsenConnectionTimingInfo, + coarsenedSharedCurrentTime, + determineRequestsReferrer, + makePolicyContainer, + clonePolicyContainer, + appendFetchMetadata, + appendRequestOriginHeader, + TAOCheck, + corsCheck, + crossOriginResourcePolicyCheck, + createOpaqueTimingInfo, + setRequestReferrerPolicyOnRedirect, + isValidHTTPToken, + requestBadPort, + requestCurrentURL, + responseURL, + responseLocationURL, + isBlobLike, + isURLPotentiallyTrustworthy, + isValidReasonPhrase, + sameOrigin, + normalizeMethod, + serializeJavascriptValueToJSONString, + iteratorMixin, + createIterator, + isValidHeaderName, + isValidHeaderValue, + isErrorLike, + fullyReadBody, + bytesMatch, + isReadableStreamLike, + readableStreamClose, + isomorphicEncode, + urlIsLocal, + urlHasHttpsScheme, + urlIsHttpHttpsScheme, + readAllBytes, + simpleRangeHeaderValue, + buildContentRange, + parseMetadata, + createInflate, + extractMimeType, + getDecodeSplit, + utf8DecodeBytes, + environmentSettingsObject + }; + } +}); + +// +var require_symbols2 = __commonJS({ + ""(exports, module) { + "use strict"; + module.exports = { + kUrl: Symbol("url"), + kHeaders: Symbol("headers"), + kSignal: Symbol("signal"), + kState: Symbol("state"), + kDispatcher: Symbol("dispatcher") + }; + } +}); + +// +var require_file = __commonJS({ + ""(exports, module) { + "use strict"; + var { Blob: Blob2, File } = __require("node:buffer"); + var { kState } = require_symbols2(); + var { webidl } = require_webidl(); + var FileLike = class _FileLike { + constructor(blobLike, fileName, options = {}) { + const n = fileName; + const t = options.type; + const d = options.lastModified ?? Date.now(); + this[kState] = { + blobLike, + name: n, + type: t, + lastModified: d + }; + } + stream(...args) { + webidl.brandCheck(this, _FileLike); + return this[kState].blobLike.stream(...args); + } + arrayBuffer(...args) { + webidl.brandCheck(this, _FileLike); + return this[kState].blobLike.arrayBuffer(...args); + } + slice(...args) { + webidl.brandCheck(this, _FileLike); + return this[kState].blobLike.slice(...args); + } + text(...args) { + webidl.brandCheck(this, _FileLike); + return this[kState].blobLike.text(...args); + } + get size() { + webidl.brandCheck(this, _FileLike); + return this[kState].blobLike.size; + } + get type() { + webidl.brandCheck(this, _FileLike); + return this[kState].blobLike.type; + } + get name() { + webidl.brandCheck(this, _FileLike); + return this[kState].name; + } + get lastModified() { + webidl.brandCheck(this, _FileLike); + return this[kState].lastModified; + } + get [Symbol.toStringTag]() { + return "File"; + } + }; + webidl.converters.Blob = webidl.interfaceConverter(Blob2); + function isFileLike(object) { + return object instanceof File || object && (typeof object.stream === "function" || typeof object.arrayBuffer === "function") && object[Symbol.toStringTag] === "File"; + } + module.exports = { FileLike, isFileLike }; + } +}); + +// +var require_formdata = __commonJS({ + ""(exports, module) { + "use strict"; + var { isBlobLike, iteratorMixin } = require_util2(); + var { kState } = require_symbols2(); + var { kEnumerableProperty } = require_util(); + var { FileLike, isFileLike } = require_file(); + var { webidl } = require_webidl(); + var { File: NativeFile } = __require("node:buffer"); + var nodeUtil = __require("node:util"); + var File = globalThis.File ?? NativeFile; + var FormData = class _FormData { + constructor(form) { + webidl.util.markAsUncloneable(this); + if (form !== void 0) { + throw webidl.errors.conversionFailed({ + prefix: "FormData constructor", + argument: "Argument 1", + types: ["undefined"] + }); + } + this[kState] = []; + } + append(name, value, filename = void 0) { + webidl.brandCheck(this, _FormData); + const prefix = "FormData.append"; + webidl.argumentLengthCheck(arguments, 2, prefix); + if (arguments.length === 3 && !isBlobLike(value)) { + throw new TypeError( + "Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'" + ); + } + name = webidl.converters.USVString(name, prefix, "name"); + value = isBlobLike(value) ? webidl.converters.Blob(value, prefix, "value", { strict: false }) : webidl.converters.USVString(value, prefix, "value"); + filename = arguments.length === 3 ? webidl.converters.USVString(filename, prefix, "filename") : void 0; + const entry = makeEntry(name, value, filename); + this[kState].push(entry); + } + delete(name) { + webidl.brandCheck(this, _FormData); + const prefix = "FormData.delete"; + webidl.argumentLengthCheck(arguments, 1, prefix); + name = webidl.converters.USVString(name, prefix, "name"); + this[kState] = this[kState].filter((entry) => entry.name !== name); + } + get(name) { + webidl.brandCheck(this, _FormData); + const prefix = "FormData.get"; + webidl.argumentLengthCheck(arguments, 1, prefix); + name = webidl.converters.USVString(name, prefix, "name"); + const idx = this[kState].findIndex((entry) => entry.name === name); + if (idx === -1) { + return null; + } + return this[kState][idx].value; + } + getAll(name) { + webidl.brandCheck(this, _FormData); + const prefix = "FormData.getAll"; + webidl.argumentLengthCheck(arguments, 1, prefix); + name = webidl.converters.USVString(name, prefix, "name"); + return this[kState].filter((entry) => entry.name === name).map((entry) => entry.value); + } + has(name) { + webidl.brandCheck(this, _FormData); + const prefix = "FormData.has"; + webidl.argumentLengthCheck(arguments, 1, prefix); + name = webidl.converters.USVString(name, prefix, "name"); + return this[kState].findIndex((entry) => entry.name === name) !== -1; + } + set(name, value, filename = void 0) { + webidl.brandCheck(this, _FormData); + const prefix = "FormData.set"; + webidl.argumentLengthCheck(arguments, 2, prefix); + if (arguments.length === 3 && !isBlobLike(value)) { + throw new TypeError( + "Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'" + ); + } + name = webidl.converters.USVString(name, prefix, "name"); + value = isBlobLike(value) ? webidl.converters.Blob(value, prefix, "name", { strict: false }) : webidl.converters.USVString(value, prefix, "name"); + filename = arguments.length === 3 ? webidl.converters.USVString(filename, prefix, "name") : void 0; + const entry = makeEntry(name, value, filename); + const idx = this[kState].findIndex((entry2) => entry2.name === name); + if (idx !== -1) { + this[kState] = [ + ...this[kState].slice(0, idx), + entry, + ...this[kState].slice(idx + 1).filter((entry2) => entry2.name !== name) + ]; + } else { + this[kState].push(entry); + } + } + [nodeUtil.inspect.custom](depth, options) { + const state = this[kState].reduce((a, b) => { + if (a[b.name]) { + if (Array.isArray(a[b.name])) { + a[b.name].push(b.value); + } else { + a[b.name] = [a[b.name], b.value]; + } + } else { + a[b.name] = b.value; + } + return a; + }, { __proto__: null }); + options.depth ??= depth; + options.colors ??= true; + const output = nodeUtil.formatWithOptions(options, state); + return `FormData ${output.slice(output.indexOf("]") + 2)}`; + } + }; + iteratorMixin("FormData", FormData, kState, "name", "value"); + Object.defineProperties(FormData.prototype, { + append: kEnumerableProperty, + delete: kEnumerableProperty, + get: kEnumerableProperty, + getAll: kEnumerableProperty, + has: kEnumerableProperty, + set: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "FormData", + configurable: true + } + }); + function makeEntry(name, value, filename) { + if (typeof value === "string") { + } else { + if (!isFileLike(value)) { + value = value instanceof Blob ? new File([value], "blob", { type: value.type }) : new FileLike(value, "blob", { type: value.type }); + } + if (filename !== void 0) { + const options = { + type: value.type, + lastModified: value.lastModified + }; + value = value instanceof NativeFile ? new File([value], filename, options) : new FileLike(value, filename, options); + } + } + return { name, value }; + } + module.exports = { FormData, makeEntry }; + } +}); + +// +var require_formdata_parser = __commonJS({ + ""(exports, module) { + "use strict"; + var { isUSVString, bufferToLowerCasedHeaderName } = require_util(); + var { utf8DecodeBytes } = require_util2(); + var { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require_data_url(); + var { isFileLike } = require_file(); + var { makeEntry } = require_formdata(); + var assert2 = __require("node:assert"); + var { File: NodeFile } = __require("node:buffer"); + var File = globalThis.File ?? NodeFile; + var formDataNameBuffer = Buffer.from('form-data; name="'); + var filenameBuffer = Buffer.from("; filename"); + var dd = Buffer.from("--"); + var ddcrlf = Buffer.from("--\r\n"); + function isAsciiString(chars) { + for (let i = 0; i < chars.length; ++i) { + if ((chars.charCodeAt(i) & ~127) !== 0) { + return false; + } + } + return true; + } + function validateBoundary(boundary) { + const length = boundary.length; + if (length < 27 || length > 70) { + return false; + } + for (let i = 0; i < length; ++i) { + const cp2 = boundary.charCodeAt(i); + if (!(cp2 >= 48 && cp2 <= 57 || cp2 >= 65 && cp2 <= 90 || cp2 >= 97 && cp2 <= 122 || cp2 === 39 || cp2 === 45 || cp2 === 95)) { + return false; + } + } + return true; + } + function multipartFormDataParser(input, mimeType) { + assert2(mimeType !== "failure" && mimeType.essence === "multipart/form-data"); + const boundaryString = mimeType.parameters.get("boundary"); + if (boundaryString === void 0) { + return "failure"; + } + const boundary = Buffer.from(`--${boundaryString}`, "utf8"); + const entryList = []; + const position = { position: 0 }; + while (input[position.position] === 13 && input[position.position + 1] === 10) { + position.position += 2; + } + let trailing = input.length; + while (input[trailing - 1] === 10 && input[trailing - 2] === 13) { + trailing -= 2; + } + if (trailing !== input.length) { + input = input.subarray(0, trailing); + } + while (true) { + if (input.subarray(position.position, position.position + boundary.length).equals(boundary)) { + position.position += boundary.length; + } else { + return "failure"; + } + if (position.position === input.length - 2 && bufferStartsWith(input, dd, position) || position.position === input.length - 4 && bufferStartsWith(input, ddcrlf, position)) { + return entryList; + } + if (input[position.position] !== 13 || input[position.position + 1] !== 10) { + return "failure"; + } + position.position += 2; + const result = parseMultipartFormDataHeaders(input, position); + if (result === "failure") { + return "failure"; + } + let { name, filename, contentType, encoding } = result; + position.position += 2; + let body; + { + const boundaryIndex = input.indexOf(boundary.subarray(2), position.position); + if (boundaryIndex === -1) { + return "failure"; + } + body = input.subarray(position.position, boundaryIndex - 4); + position.position += body.length; + if (encoding === "base64") { + body = Buffer.from(body.toString(), "base64"); + } + } + if (input[position.position] !== 13 || input[position.position + 1] !== 10) { + return "failure"; + } else { + position.position += 2; + } + let value; + if (filename !== null) { + contentType ??= "text/plain"; + if (!isAsciiString(contentType)) { + contentType = ""; + } + value = new File([body], filename, { type: contentType }); + } else { + value = utf8DecodeBytes(Buffer.from(body)); + } + assert2(isUSVString(name)); + assert2(typeof value === "string" && isUSVString(value) || isFileLike(value)); + entryList.push(makeEntry(name, value, filename)); + } + } + function parseMultipartFormDataHeaders(input, position) { + let name = null; + let filename = null; + let contentType = null; + let encoding = null; + while (true) { + if (input[position.position] === 13 && input[position.position + 1] === 10) { + if (name === null) { + return "failure"; + } + return { name, filename, contentType, encoding }; + } + let headerName = collectASequenceOfBytes( + (char) => char !== 10 && char !== 13 && char !== 58, + input, + position + ); + headerName = removeChars(headerName, true, true, (char) => char === 9 || char === 32); + if (!HTTP_TOKEN_CODEPOINTS.test(headerName.toString())) { + return "failure"; + } + if (input[position.position] !== 58) { + return "failure"; + } + position.position++; + collectASequenceOfBytes( + (char) => char === 32 || char === 9, + input, + position + ); + switch (bufferToLowerCasedHeaderName(headerName)) { + case "content-disposition": { + name = filename = null; + if (!bufferStartsWith(input, formDataNameBuffer, position)) { + return "failure"; + } + position.position += 17; + name = parseMultipartFormDataName(input, position); + if (name === null) { + return "failure"; + } + if (bufferStartsWith(input, filenameBuffer, position)) { + let check = position.position + filenameBuffer.length; + if (input[check] === 42) { + position.position += 1; + check += 1; + } + if (input[check] !== 61 || input[check + 1] !== 34) { + return "failure"; + } + position.position += 12; + filename = parseMultipartFormDataName(input, position); + if (filename === null) { + return "failure"; + } + } + break; + } + case "content-type": { + let headerValue = collectASequenceOfBytes( + (char) => char !== 10 && char !== 13, + input, + position + ); + headerValue = removeChars(headerValue, false, true, (char) => char === 9 || char === 32); + contentType = isomorphicDecode(headerValue); + break; + } + case "content-transfer-encoding": { + let headerValue = collectASequenceOfBytes( + (char) => char !== 10 && char !== 13, + input, + position + ); + headerValue = removeChars(headerValue, false, true, (char) => char === 9 || char === 32); + encoding = isomorphicDecode(headerValue); + break; + } + default: { + collectASequenceOfBytes( + (char) => char !== 10 && char !== 13, + input, + position + ); + } + } + if (input[position.position] !== 13 && input[position.position + 1] !== 10) { + return "failure"; + } else { + position.position += 2; + } + } + } + function parseMultipartFormDataName(input, position) { + assert2(input[position.position - 1] === 34); + let name = collectASequenceOfBytes( + (char) => char !== 10 && char !== 13 && char !== 34, + input, + position + ); + if (input[position.position] !== 34) { + return null; + } else { + position.position++; + } + name = new TextDecoder().decode(name).replace(/%0A/ig, "\n").replace(/%0D/ig, "\r").replace(/%22/g, '"'); + return name; + } + function collectASequenceOfBytes(condition, input, position) { + let start = position.position; + while (start < input.length && condition(input[start])) { + ++start; + } + return input.subarray(position.position, position.position = start); + } + function removeChars(buf, leading, trailing, predicate) { + let lead = 0; + let trail = buf.length - 1; + if (leading) { + while (lead < buf.length && predicate(buf[lead])) + lead++; + } + if (trailing) { + while (trail > 0 && predicate(buf[trail])) + trail--; + } + return lead === 0 && trail === buf.length - 1 ? buf : buf.subarray(lead, trail + 1); + } + function bufferStartsWith(buffer, start, position) { + if (buffer.length < start.length) { + return false; + } + for (let i = 0; i < start.length; i++) { + if (start[i] !== buffer[position.position + i]) { + return false; + } + } + return true; + } + module.exports = { + multipartFormDataParser, + validateBoundary + }; + } +}); + +// +var require_body = __commonJS({ + ""(exports, module) { + "use strict"; + var util = require_util(); + var { + ReadableStreamFrom, + isBlobLike, + isReadableStreamLike, + readableStreamClose, + createDeferredPromise, + fullyReadBody, + extractMimeType, + utf8DecodeBytes + } = require_util2(); + var { FormData } = require_formdata(); + var { kState } = require_symbols2(); + var { webidl } = require_webidl(); + var { Blob: Blob2 } = __require("node:buffer"); + var assert2 = __require("node:assert"); + var { isErrored, isDisturbed } = __require("node:stream"); + var { isArrayBuffer } = __require("node:util/types"); + var { serializeAMimeType } = require_data_url(); + var { multipartFormDataParser } = require_formdata_parser(); + var random; + try { + const crypto = __require("node:crypto"); + random = (max) => crypto.randomInt(0, max); + } catch { + random = (max) => Math.floor(Math.random(max)); + } + var textEncoder = new TextEncoder(); + function noop4() { + } + var hasFinalizationRegistry = globalThis.FinalizationRegistry && process.version.indexOf("v18") !== 0; + var streamRegistry; + if (hasFinalizationRegistry) { + streamRegistry = new FinalizationRegistry((weakRef) => { + const stream = weakRef.deref(); + if (stream && !stream.locked && !isDisturbed(stream) && !isErrored(stream)) { + stream.cancel("Response object has been garbage collected").catch(noop4); + } + }); + } + function extractBody(object, keepalive = false) { + let stream = null; + if (object instanceof ReadableStream) { + stream = object; + } else if (isBlobLike(object)) { + stream = object.stream(); + } else { + stream = new ReadableStream({ + async pull(controller) { + const buffer = typeof source === "string" ? textEncoder.encode(source) : source; + if (buffer.byteLength) { + controller.enqueue(buffer); + } + queueMicrotask(() => readableStreamClose(controller)); + }, + start() { + }, + type: "bytes" + }); + } + assert2(isReadableStreamLike(stream)); + let action = null; + let source = null; + let length = null; + let type = null; + if (typeof object === "string") { + source = object; + type = "text/plain;charset=UTF-8"; + } else if (object instanceof URLSearchParams) { + source = object.toString(); + type = "application/x-www-form-urlencoded;charset=UTF-8"; + } else if (isArrayBuffer(object)) { + source = new Uint8Array(object.slice()); + } else if (ArrayBuffer.isView(object)) { + source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)); + } else if (util.isFormDataLike(object)) { + const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, "0")}`; + const prefix = `--${boundary}\r +Content-Disposition: form-data`; + const escape = (str) => str.replace(/\n/g, "%0A").replace(/\r/g, "%0D").replace(/"/g, "%22"); + const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, "\r\n"); + const blobParts = []; + const rn = new Uint8Array([13, 10]); + length = 0; + let hasUnknownSizeValue = false; + for (const [name, value] of object) { + if (typeof value === "string") { + const chunk2 = textEncoder.encode(prefix + `; name="${escape(normalizeLinefeeds(name))}"\r +\r +${normalizeLinefeeds(value)}\r +`); + blobParts.push(chunk2); + length += chunk2.byteLength; + } else { + const chunk2 = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` + (value.name ? `; filename="${escape(value.name)}"` : "") + `\r +Content-Type: ${value.type || "application/octet-stream"}\r +\r +`); + blobParts.push(chunk2, value, rn); + if (typeof value.size === "number") { + length += chunk2.byteLength + value.size + rn.byteLength; + } else { + hasUnknownSizeValue = true; + } + } + } + const chunk = textEncoder.encode(`--${boundary}--\r +`); + blobParts.push(chunk); + length += chunk.byteLength; + if (hasUnknownSizeValue) { + length = null; + } + source = object; + action = async function* () { + for (const part of blobParts) { + if (part.stream) { + yield* part.stream(); + } else { + yield part; + } + } + }; + type = `multipart/form-data; boundary=${boundary}`; + } else if (isBlobLike(object)) { + source = object; + length = object.size; + if (object.type) { + type = object.type; + } + } else if (typeof object[Symbol.asyncIterator] === "function") { + if (keepalive) { + throw new TypeError("keepalive"); + } + if (util.isDisturbed(object) || object.locked) { + throw new TypeError( + "Response body object should not be disturbed or locked" + ); + } + stream = object instanceof ReadableStream ? object : ReadableStreamFrom(object); + } + if (typeof source === "string" || util.isBuffer(source)) { + length = Buffer.byteLength(source); + } + if (action != null) { + let iterator3; + stream = new ReadableStream({ + async start() { + iterator3 = action(object)[Symbol.asyncIterator](); + }, + async pull(controller) { + const { value, done } = await iterator3.next(); + if (done) { + queueMicrotask(() => { + controller.close(); + controller.byobRequest?.respond(0); + }); + } else { + if (!isErrored(stream)) { + const buffer = new Uint8Array(value); + if (buffer.byteLength) { + controller.enqueue(buffer); + } + } + } + return controller.desiredSize > 0; + }, + async cancel(reason) { + await iterator3.return(); + }, + type: "bytes" + }); + } + const body = { stream, source, length }; + return [body, type]; + } + function safelyExtractBody(object, keepalive = false) { + if (object instanceof ReadableStream) { + assert2(!util.isDisturbed(object), "The body has already been consumed."); + assert2(!object.locked, "The stream is locked."); + } + return extractBody(object, keepalive); + } + function cloneBody(instance, body) { + const [out1, out2] = body.stream.tee(); + body.stream = out1; + return { + stream: out2, + length: body.length, + source: body.source + }; + } + function throwIfAborted(state) { + if (state.aborted) { + throw new DOMException("The operation was aborted.", "AbortError"); + } + } + function bodyMixinMethods(instance) { + const methods = { + blob() { + return consumeBody(this, (bytes) => { + let mimeType = bodyMimeType(this); + if (mimeType === null) { + mimeType = ""; + } else if (mimeType) { + mimeType = serializeAMimeType(mimeType); + } + return new Blob2([bytes], { type: mimeType }); + }, instance); + }, + arrayBuffer() { + return consumeBody(this, (bytes) => { + return new Uint8Array(bytes).buffer; + }, instance); + }, + text() { + return consumeBody(this, utf8DecodeBytes, instance); + }, + json() { + return consumeBody(this, parseJSONFromBytes, instance); + }, + formData() { + return consumeBody(this, (value) => { + const mimeType = bodyMimeType(this); + if (mimeType !== null) { + switch (mimeType.essence) { + case "multipart/form-data": { + const parsed = multipartFormDataParser(value, mimeType); + if (parsed === "failure") { + throw new TypeError("Failed to parse body as FormData."); + } + const fd = new FormData(); + fd[kState] = parsed; + return fd; + } + case "application/x-www-form-urlencoded": { + const entries = new URLSearchParams(value.toString()); + const fd = new FormData(); + for (const [name, value2] of entries) { + fd.append(name, value2); + } + return fd; + } + } + } + throw new TypeError( + 'Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".' + ); + }, instance); + }, + bytes() { + return consumeBody(this, (bytes) => { + return new Uint8Array(bytes); + }, instance); + } + }; + return methods; + } + function mixinBody(prototype) { + Object.assign(prototype.prototype, bodyMixinMethods(prototype)); + } + async function consumeBody(object, convertBytesToJSValue, instance) { + webidl.brandCheck(object, instance); + if (bodyUnusable(object)) { + throw new TypeError("Body is unusable: Body has already been read"); + } + throwIfAborted(object[kState]); + const promise = createDeferredPromise(); + const errorSteps = (error2) => promise.reject(error2); + const successSteps = (data) => { + try { + promise.resolve(convertBytesToJSValue(data)); + } catch (e) { + errorSteps(e); + } + }; + if (object[kState].body == null) { + successSteps(Buffer.allocUnsafe(0)); + return promise.promise; + } + await fullyReadBody(object[kState].body, successSteps, errorSteps); + return promise.promise; + } + function bodyUnusable(object) { + const body = object[kState].body; + return body != null && (body.stream.locked || util.isDisturbed(body.stream)); + } + function parseJSONFromBytes(bytes) { + return JSON.parse(utf8DecodeBytes(bytes)); + } + function bodyMimeType(requestOrResponse) { + const headers = requestOrResponse[kState].headersList; + const mimeType = extractMimeType(headers); + if (mimeType === "failure") { + return null; + } + return mimeType; + } + module.exports = { + extractBody, + safelyExtractBody, + cloneBody, + mixinBody, + streamRegistry, + hasFinalizationRegistry, + bodyUnusable + }; + } +}); + +// +var require_client_h1 = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var util = require_util(); + var { channels } = require_diagnostics(); + var timers = require_timers(); + var { + RequestContentLengthMismatchError, + ResponseContentLengthMismatchError, + RequestAbortedError, + HeadersTimeoutError, + HeadersOverflowError, + SocketError, + InformationalError, + BodyTimeoutError, + HTTPParserError, + ResponseExceededMaxSizeError + } = require_errors(); + var { + kUrl, + kReset: kReset2, + kClient, + kParser, + kBlocking, + kRunning, + kPending, + kSize, + kWriting, + kQueue, + kNoRef, + kKeepAliveDefaultTimeout, + kHostHeader, + kPendingIdx, + kRunningIdx, + kError, + kPipelining, + kSocket, + kKeepAliveTimeoutValue, + kMaxHeadersSize, + kKeepAliveMaxTimeout, + kKeepAliveTimeoutThreshold, + kHeadersTimeout, + kBodyTimeout, + kStrictContentLength, + kMaxRequests, + kCounter, + kMaxResponseSize, + kOnError, + kResume, + kHTTPContext + } = require_symbols(); + var constants3 = require_constants2(); + var EMPTY_BUF = Buffer.alloc(0); + var FastBuffer = Buffer[Symbol.species]; + var addListener = util.addListener; + var removeAllListeners = util.removeAllListeners; + var extractBody; + async function lazyllhttp() { + const llhttpWasmData = process.env.JEST_WORKER_ID ? require_llhttp_wasm() : void 0; + let mod; + try { + mod = await WebAssembly.compile(require_llhttp_simd_wasm()); + } catch (e) { + mod = await WebAssembly.compile(llhttpWasmData || require_llhttp_wasm()); + } + return await WebAssembly.instantiate(mod, { + env: { + /* eslint-disable camelcase */ + wasm_on_url: (p, at, len) => { + return 0; + }, + wasm_on_status: (p, at, len) => { + assert2(currentParser.ptr === p); + const start = at - currentBufferPtr + currentBufferRef.byteOffset; + return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start, len)) || 0; + }, + wasm_on_message_begin: (p) => { + assert2(currentParser.ptr === p); + return currentParser.onMessageBegin() || 0; + }, + wasm_on_header_field: (p, at, len) => { + assert2(currentParser.ptr === p); + const start = at - currentBufferPtr + currentBufferRef.byteOffset; + return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start, len)) || 0; + }, + wasm_on_header_value: (p, at, len) => { + assert2(currentParser.ptr === p); + const start = at - currentBufferPtr + currentBufferRef.byteOffset; + return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start, len)) || 0; + }, + wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => { + assert2(currentParser.ptr === p); + return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0; + }, + wasm_on_body: (p, at, len) => { + assert2(currentParser.ptr === p); + const start = at - currentBufferPtr + currentBufferRef.byteOffset; + return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start, len)) || 0; + }, + wasm_on_message_complete: (p) => { + assert2(currentParser.ptr === p); + return currentParser.onMessageComplete() || 0; + } + /* eslint-enable camelcase */ + } + }); + } + var llhttpInstance = null; + var llhttpPromise = lazyllhttp(); + llhttpPromise.catch(); + var currentParser = null; + var currentBufferRef = null; + var currentBufferSize = 0; + var currentBufferPtr = null; + var USE_NATIVE_TIMER = 0; + var USE_FAST_TIMER = 1; + var TIMEOUT_HEADERS = 2 | USE_FAST_TIMER; + var TIMEOUT_BODY = 4 | USE_FAST_TIMER; + var TIMEOUT_KEEP_ALIVE = 8 | USE_NATIVE_TIMER; + var Parser2 = class { + constructor(client, socket, { exports: exports2 }) { + assert2(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0); + this.llhttp = exports2; + this.ptr = this.llhttp.llhttp_alloc(constants3.TYPE.RESPONSE); + this.client = client; + this.socket = socket; + this.timeout = null; + this.timeoutValue = null; + this.timeoutType = null; + this.statusCode = null; + this.statusText = ""; + this.upgrade = false; + this.headers = []; + this.headersSize = 0; + this.headersMaxSize = client[kMaxHeadersSize]; + this.shouldKeepAlive = false; + this.paused = false; + this.resume = this.resume.bind(this); + this.bytesRead = 0; + this.keepAlive = ""; + this.contentLength = ""; + this.connection = ""; + this.maxResponseSize = client[kMaxResponseSize]; + } + setTimeout(delay, type) { + if (delay !== this.timeoutValue || type & USE_FAST_TIMER ^ this.timeoutType & USE_FAST_TIMER) { + if (this.timeout) { + timers.clearTimeout(this.timeout); + this.timeout = null; + } + if (delay) { + if (type & USE_FAST_TIMER) { + this.timeout = timers.setFastTimeout(onParserTimeout, delay, new WeakRef(this)); + } else { + this.timeout = setTimeout(onParserTimeout, delay, new WeakRef(this)); + this.timeout.unref(); + } + } + this.timeoutValue = delay; + } else if (this.timeout) { + if (this.timeout.refresh) { + this.timeout.refresh(); + } + } + this.timeoutType = type; + } + resume() { + if (this.socket.destroyed || !this.paused) { + return; + } + assert2(this.ptr != null); + assert2(currentParser == null); + this.llhttp.llhttp_resume(this.ptr); + assert2(this.timeoutType === TIMEOUT_BODY); + if (this.timeout) { + if (this.timeout.refresh) { + this.timeout.refresh(); + } + } + this.paused = false; + this.execute(this.socket.read() || EMPTY_BUF); + this.readMore(); + } + readMore() { + while (!this.paused && this.ptr) { + const chunk = this.socket.read(); + if (chunk === null) { + break; + } + this.execute(chunk); + } + } + execute(data) { + assert2(this.ptr != null); + assert2(currentParser == null); + assert2(!this.paused); + const { socket, llhttp } = this; + if (data.length > currentBufferSize) { + if (currentBufferPtr) { + llhttp.free(currentBufferPtr); + } + currentBufferSize = Math.ceil(data.length / 4096) * 4096; + currentBufferPtr = llhttp.malloc(currentBufferSize); + } + new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(data); + try { + let ret; + try { + currentBufferRef = data; + currentParser = this; + ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, data.length); + } catch (err) { + throw err; + } finally { + currentParser = null; + currentBufferRef = null; + } + const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr; + if (ret === constants3.ERROR.PAUSED_UPGRADE) { + this.onUpgrade(data.slice(offset)); + } else if (ret === constants3.ERROR.PAUSED) { + this.paused = true; + socket.unshift(data.slice(offset)); + } else if (ret !== constants3.ERROR.OK) { + const ptr = llhttp.llhttp_get_error_reason(this.ptr); + let message = ""; + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0); + message = "Response does not match the HTTP/1.1 protocol (" + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + ")"; + } + throw new HTTPParserError(message, constants3.ERROR[ret], data.slice(offset)); + } + } catch (err) { + util.destroy(socket, err); + } + } + destroy() { + assert2(this.ptr != null); + assert2(currentParser == null); + this.llhttp.llhttp_free(this.ptr); + this.ptr = null; + this.timeout && timers.clearTimeout(this.timeout); + this.timeout = null; + this.timeoutValue = null; + this.timeoutType = null; + this.paused = false; + } + onStatus(buf) { + this.statusText = buf.toString(); + } + onMessageBegin() { + const { socket, client } = this; + if (socket.destroyed) { + return -1; + } + const request3 = client[kQueue][client[kRunningIdx]]; + if (!request3) { + return -1; + } + request3.onResponseStarted(); + } + onHeaderField(buf) { + const len = this.headers.length; + if ((len & 1) === 0) { + this.headers.push(buf); + } else { + this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]); + } + this.trackHeader(buf.length); + } + onHeaderValue(buf) { + let len = this.headers.length; + if ((len & 1) === 1) { + this.headers.push(buf); + len += 1; + } else { + this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]); + } + const key = this.headers[len - 2]; + if (key.length === 10) { + const headerName = util.bufferToLowerCasedHeaderName(key); + if (headerName === "keep-alive") { + this.keepAlive += buf.toString(); + } else if (headerName === "connection") { + this.connection += buf.toString(); + } + } else if (key.length === 14 && util.bufferToLowerCasedHeaderName(key) === "content-length") { + this.contentLength += buf.toString(); + } + this.trackHeader(buf.length); + } + trackHeader(len) { + this.headersSize += len; + if (this.headersSize >= this.headersMaxSize) { + util.destroy(this.socket, new HeadersOverflowError()); + } + } + onUpgrade(head) { + const { upgrade, client, socket, headers, statusCode } = this; + assert2(upgrade); + assert2(client[kSocket] === socket); + assert2(!socket.destroyed); + assert2(!this.paused); + assert2((headers.length & 1) === 0); + const request3 = client[kQueue][client[kRunningIdx]]; + assert2(request3); + assert2(request3.upgrade || request3.method === "CONNECT"); + this.statusCode = null; + this.statusText = ""; + this.shouldKeepAlive = null; + this.headers = []; + this.headersSize = 0; + socket.unshift(head); + socket[kParser].destroy(); + socket[kParser] = null; + socket[kClient] = null; + socket[kError] = null; + removeAllListeners(socket); + client[kSocket] = null; + client[kHTTPContext] = null; + client[kQueue][client[kRunningIdx]++] = null; + client.emit("disconnect", client[kUrl], [client], new InformationalError("upgrade")); + try { + request3.onUpgrade(statusCode, headers, socket); + } catch (err) { + util.destroy(socket, err); + } + client[kResume](); + } + onHeadersComplete(statusCode, upgrade, shouldKeepAlive) { + const { client, socket, headers, statusText } = this; + if (socket.destroyed) { + return -1; + } + const request3 = client[kQueue][client[kRunningIdx]]; + if (!request3) { + return -1; + } + assert2(!this.upgrade); + assert2(this.statusCode < 200); + if (statusCode === 100) { + util.destroy(socket, new SocketError("bad response", util.getSocketInfo(socket))); + return -1; + } + if (upgrade && !request3.upgrade) { + util.destroy(socket, new SocketError("bad upgrade", util.getSocketInfo(socket))); + return -1; + } + assert2(this.timeoutType === TIMEOUT_HEADERS); + this.statusCode = statusCode; + this.shouldKeepAlive = shouldKeepAlive || // Override llhttp value which does not allow keepAlive for HEAD. + request3.method === "HEAD" && !socket[kReset2] && this.connection.toLowerCase() === "keep-alive"; + if (this.statusCode >= 200) { + const bodyTimeout = request3.bodyTimeout != null ? request3.bodyTimeout : client[kBodyTimeout]; + this.setTimeout(bodyTimeout, TIMEOUT_BODY); + } else if (this.timeout) { + if (this.timeout.refresh) { + this.timeout.refresh(); + } + } + if (request3.method === "CONNECT") { + assert2(client[kRunning] === 1); + this.upgrade = true; + return 2; + } + if (upgrade) { + assert2(client[kRunning] === 1); + this.upgrade = true; + return 2; + } + assert2((this.headers.length & 1) === 0); + this.headers = []; + this.headersSize = 0; + if (this.shouldKeepAlive && client[kPipelining]) { + const keepAliveTimeout = this.keepAlive ? util.parseKeepAliveTimeout(this.keepAlive) : null; + if (keepAliveTimeout != null) { + const timeout = Math.min( + keepAliveTimeout - client[kKeepAliveTimeoutThreshold], + client[kKeepAliveMaxTimeout] + ); + if (timeout <= 0) { + socket[kReset2] = true; + } else { + client[kKeepAliveTimeoutValue] = timeout; + } + } else { + client[kKeepAliveTimeoutValue] = client[kKeepAliveDefaultTimeout]; + } + } else { + socket[kReset2] = true; + } + const pause = request3.onHeaders(statusCode, headers, this.resume, statusText) === false; + if (request3.aborted) { + return -1; + } + if (request3.method === "HEAD") { + return 1; + } + if (statusCode < 200) { + return 1; + } + if (socket[kBlocking]) { + socket[kBlocking] = false; + client[kResume](); + } + return pause ? constants3.ERROR.PAUSED : 0; + } + onBody(buf) { + const { client, socket, statusCode, maxResponseSize } = this; + if (socket.destroyed) { + return -1; + } + const request3 = client[kQueue][client[kRunningIdx]]; + assert2(request3); + assert2(this.timeoutType === TIMEOUT_BODY); + if (this.timeout) { + if (this.timeout.refresh) { + this.timeout.refresh(); + } + } + assert2(statusCode >= 200); + if (maxResponseSize > -1 && this.bytesRead + buf.length > maxResponseSize) { + util.destroy(socket, new ResponseExceededMaxSizeError()); + return -1; + } + this.bytesRead += buf.length; + if (request3.onData(buf) === false) { + return constants3.ERROR.PAUSED; + } + } + onMessageComplete() { + const { client, socket, statusCode, upgrade, headers, contentLength, bytesRead, shouldKeepAlive } = this; + if (socket.destroyed && (!statusCode || shouldKeepAlive)) { + return -1; + } + if (upgrade) { + return; + } + assert2(statusCode >= 100); + assert2((this.headers.length & 1) === 0); + const request3 = client[kQueue][client[kRunningIdx]]; + assert2(request3); + this.statusCode = null; + this.statusText = ""; + this.bytesRead = 0; + this.contentLength = ""; + this.keepAlive = ""; + this.connection = ""; + this.headers = []; + this.headersSize = 0; + if (statusCode < 200) { + return; + } + if (request3.method !== "HEAD" && contentLength && bytesRead !== parseInt(contentLength, 10)) { + util.destroy(socket, new ResponseContentLengthMismatchError()); + return -1; + } + request3.onComplete(headers); + client[kQueue][client[kRunningIdx]++] = null; + if (socket[kWriting]) { + assert2(client[kRunning] === 0); + util.destroy(socket, new InformationalError("reset")); + return constants3.ERROR.PAUSED; + } else if (!shouldKeepAlive) { + util.destroy(socket, new InformationalError("reset")); + return constants3.ERROR.PAUSED; + } else if (socket[kReset2] && client[kRunning] === 0) { + util.destroy(socket, new InformationalError("reset")); + return constants3.ERROR.PAUSED; + } else if (client[kPipelining] == null || client[kPipelining] === 1) { + setImmediate(() => client[kResume]()); + } else { + client[kResume](); + } + } + }; + function onParserTimeout(parser2) { + const { socket, timeoutType, client, paused } = parser2.deref(); + if (timeoutType === TIMEOUT_HEADERS) { + if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) { + assert2(!paused, "cannot be paused while waiting for headers"); + util.destroy(socket, new HeadersTimeoutError()); + } + } else if (timeoutType === TIMEOUT_BODY) { + if (!paused) { + util.destroy(socket, new BodyTimeoutError()); + } + } else if (timeoutType === TIMEOUT_KEEP_ALIVE) { + assert2(client[kRunning] === 0 && client[kKeepAliveTimeoutValue]); + util.destroy(socket, new InformationalError("socket idle timeout")); + } + } + async function connectH1(client, socket) { + client[kSocket] = socket; + if (!llhttpInstance) { + llhttpInstance = await llhttpPromise; + llhttpPromise = null; + } + socket[kNoRef] = false; + socket[kWriting] = false; + socket[kReset2] = false; + socket[kBlocking] = false; + socket[kParser] = new Parser2(client, socket, llhttpInstance); + addListener(socket, "error", function(err) { + assert2(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID"); + const parser2 = this[kParser]; + if (err.code === "ECONNRESET" && parser2.statusCode && !parser2.shouldKeepAlive) { + parser2.onMessageComplete(); + return; + } + this[kError] = err; + this[kClient][kOnError](err); + }); + addListener(socket, "readable", function() { + const parser2 = this[kParser]; + if (parser2) { + parser2.readMore(); + } + }); + addListener(socket, "end", function() { + const parser2 = this[kParser]; + if (parser2.statusCode && !parser2.shouldKeepAlive) { + parser2.onMessageComplete(); + return; + } + util.destroy(this, new SocketError("other side closed", util.getSocketInfo(this))); + }); + addListener(socket, "close", function() { + const client2 = this[kClient]; + const parser2 = this[kParser]; + if (parser2) { + if (!this[kError] && parser2.statusCode && !parser2.shouldKeepAlive) { + parser2.onMessageComplete(); + } + this[kParser].destroy(); + this[kParser] = null; + } + const err = this[kError] || new SocketError("closed", util.getSocketInfo(this)); + client2[kSocket] = null; + client2[kHTTPContext] = null; + if (client2.destroyed) { + assert2(client2[kPending] === 0); + const requests = client2[kQueue].splice(client2[kRunningIdx]); + for (let i = 0; i < requests.length; i++) { + const request3 = requests[i]; + util.errorRequest(client2, request3, err); + } + } else if (client2[kRunning] > 0 && err.code !== "UND_ERR_INFO") { + const request3 = client2[kQueue][client2[kRunningIdx]]; + client2[kQueue][client2[kRunningIdx]++] = null; + util.errorRequest(client2, request3, err); + } + client2[kPendingIdx] = client2[kRunningIdx]; + assert2(client2[kRunning] === 0); + client2.emit("disconnect", client2[kUrl], [client2], err); + client2[kResume](); + }); + let closed = false; + socket.on("close", () => { + closed = true; + }); + return { + version: "h1", + defaultPipelining: 1, + write(...args) { + return writeH1(client, ...args); + }, + resume() { + resumeH1(client); + }, + destroy(err, callback) { + if (closed) { + queueMicrotask(callback); + } else { + socket.destroy(err).on("close", callback); + } + }, + get destroyed() { + return socket.destroyed; + }, + busy(request3) { + if (socket[kWriting] || socket[kReset2] || socket[kBlocking]) { + return true; + } + if (request3) { + if (client[kRunning] > 0 && !request3.idempotent) { + return true; + } + if (client[kRunning] > 0 && (request3.upgrade || request3.method === "CONNECT")) { + return true; + } + if (client[kRunning] > 0 && util.bodyLength(request3.body) !== 0 && (util.isStream(request3.body) || util.isAsyncIterable(request3.body) || util.isFormDataLike(request3.body))) { + return true; + } + } + return false; + } + }; + } + function resumeH1(client) { + const socket = client[kSocket]; + if (socket && !socket.destroyed) { + if (client[kSize] === 0) { + if (!socket[kNoRef] && socket.unref) { + socket.unref(); + socket[kNoRef] = true; + } + } else if (socket[kNoRef] && socket.ref) { + socket.ref(); + socket[kNoRef] = false; + } + if (client[kSize] === 0) { + if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) { + socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE); + } + } else if (client[kRunning] > 0 && socket[kParser].statusCode < 200) { + if (socket[kParser].timeoutType !== TIMEOUT_HEADERS) { + const request3 = client[kQueue][client[kRunningIdx]]; + const headersTimeout = request3.headersTimeout != null ? request3.headersTimeout : client[kHeadersTimeout]; + socket[kParser].setTimeout(headersTimeout, TIMEOUT_HEADERS); + } + } + } + } + function shouldSendContentLength(method) { + return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT"; + } + function writeH1(client, request3) { + const { method, path, host, upgrade, blocking, reset } = request3; + let { body, headers, contentLength } = request3; + const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH"; + if (util.isFormDataLike(body)) { + if (!extractBody) { + extractBody = require_body().extractBody; + } + const [bodyStream, contentType] = extractBody(body); + if (request3.contentType == null) { + headers.push("content-type", contentType); + } + body = bodyStream.stream; + contentLength = bodyStream.length; + } else if (util.isBlobLike(body) && request3.contentType == null && body.type) { + headers.push("content-type", body.type); + } + if (body && typeof body.read === "function") { + body.read(0); + } + const bodyLength = util.bodyLength(body); + contentLength = bodyLength ?? contentLength; + if (contentLength === null) { + contentLength = request3.contentLength; + } + if (contentLength === 0 && !expectsPayload) { + contentLength = null; + } + if (shouldSendContentLength(method) && contentLength > 0 && request3.contentLength !== null && request3.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + util.errorRequest(client, request3, new RequestContentLengthMismatchError()); + return false; + } + process.emitWarning(new RequestContentLengthMismatchError()); + } + const socket = client[kSocket]; + const abort = (err) => { + if (request3.aborted || request3.completed) { + return; + } + util.errorRequest(client, request3, err || new RequestAbortedError()); + util.destroy(body); + util.destroy(socket, new InformationalError("aborted")); + }; + try { + request3.onConnect(abort); + } catch (err) { + util.errorRequest(client, request3, err); + } + if (request3.aborted) { + return false; + } + if (method === "HEAD") { + socket[kReset2] = true; + } + if (upgrade || method === "CONNECT") { + socket[kReset2] = true; + } + if (reset != null) { + socket[kReset2] = reset; + } + if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) { + socket[kReset2] = true; + } + if (blocking) { + socket[kBlocking] = true; + } + let header = `${method} ${path} HTTP/1.1\r +`; + if (typeof host === "string") { + header += `host: ${host}\r +`; + } else { + header += client[kHostHeader]; + } + if (upgrade) { + header += `connection: upgrade\r +upgrade: ${upgrade}\r +`; + } else if (client[kPipelining] && !socket[kReset2]) { + header += "connection: keep-alive\r\n"; + } else { + header += "connection: close\r\n"; + } + if (Array.isArray(headers)) { + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n + 0]; + const val = headers[n + 1]; + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + header += `${key}: ${val[i]}\r +`; + } + } else { + header += `${key}: ${val}\r +`; + } + } + } + if (channels.sendHeaders.hasSubscribers) { + channels.sendHeaders.publish({ request: request3, headers: header, socket }); + } + if (!body || bodyLength === 0) { + writeBuffer(abort, null, client, request3, socket, contentLength, header, expectsPayload); + } else if (util.isBuffer(body)) { + writeBuffer(abort, body, client, request3, socket, contentLength, header, expectsPayload); + } else if (util.isBlobLike(body)) { + if (typeof body.stream === "function") { + writeIterable(abort, body.stream(), client, request3, socket, contentLength, header, expectsPayload); + } else { + writeBlob(abort, body, client, request3, socket, contentLength, header, expectsPayload); + } + } else if (util.isStream(body)) { + writeStream(abort, body, client, request3, socket, contentLength, header, expectsPayload); + } else if (util.isIterable(body)) { + writeIterable(abort, body, client, request3, socket, contentLength, header, expectsPayload); + } else { + assert2(false); + } + return true; + } + function writeStream(abort, body, client, request3, socket, contentLength, header, expectsPayload) { + assert2(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); + let finished = false; + const writer = new AsyncWriter({ abort, socket, request: request3, contentLength, client, expectsPayload, header }); + const onData = function(chunk) { + if (finished) { + return; + } + try { + if (!writer.write(chunk) && this.pause) { + this.pause(); + } + } catch (err) { + util.destroy(this, err); + } + }; + const onDrain = function() { + if (finished) { + return; + } + if (body.resume) { + body.resume(); + } + }; + const onClose = function() { + queueMicrotask(() => { + body.removeListener("error", onFinished); + }); + if (!finished) { + const err = new RequestAbortedError(); + queueMicrotask(() => onFinished(err)); + } + }; + const onFinished = function(err) { + if (finished) { + return; + } + finished = true; + assert2(socket.destroyed || socket[kWriting] && client[kRunning] <= 1); + socket.off("drain", onDrain).off("error", onFinished); + body.removeListener("data", onData).removeListener("end", onFinished).removeListener("close", onClose); + if (!err) { + try { + writer.end(); + } catch (er) { + err = er; + } + } + writer.destroy(err); + if (err && (err.code !== "UND_ERR_INFO" || err.message !== "reset")) { + util.destroy(body, err); + } else { + util.destroy(body); + } + }; + body.on("data", onData).on("end", onFinished).on("error", onFinished).on("close", onClose); + if (body.resume) { + body.resume(); + } + socket.on("drain", onDrain).on("error", onFinished); + if (body.errorEmitted ?? body.errored) { + setImmediate(() => onFinished(body.errored)); + } else if (body.endEmitted ?? body.readableEnded) { + setImmediate(() => onFinished(null)); + } + if (body.closeEmitted ?? body.closed) { + setImmediate(onClose); + } + } + function writeBuffer(abort, body, client, request3, socket, contentLength, header, expectsPayload) { + try { + if (!body) { + if (contentLength === 0) { + socket.write(`${header}content-length: 0\r +\r +`, "latin1"); + } else { + assert2(contentLength === null, "no body must not have content length"); + socket.write(`${header}\r +`, "latin1"); + } + } else if (util.isBuffer(body)) { + assert2(contentLength === body.byteLength, "buffer body must have content length"); + socket.cork(); + socket.write(`${header}content-length: ${contentLength}\r +\r +`, "latin1"); + socket.write(body); + socket.uncork(); + request3.onBodySent(body); + if (!expectsPayload && request3.reset !== false) { + socket[kReset2] = true; + } + } + request3.onRequestSent(); + client[kResume](); + } catch (err) { + abort(err); + } + } + async function writeBlob(abort, body, client, request3, socket, contentLength, header, expectsPayload) { + assert2(contentLength === body.size, "blob body must have content length"); + try { + if (contentLength != null && contentLength !== body.size) { + throw new RequestContentLengthMismatchError(); + } + const buffer = Buffer.from(await body.arrayBuffer()); + socket.cork(); + socket.write(`${header}content-length: ${contentLength}\r +\r +`, "latin1"); + socket.write(buffer); + socket.uncork(); + request3.onBodySent(buffer); + request3.onRequestSent(); + if (!expectsPayload && request3.reset !== false) { + socket[kReset2] = true; + } + client[kResume](); + } catch (err) { + abort(err); + } + } + async function writeIterable(abort, body, client, request3, socket, contentLength, header, expectsPayload) { + assert2(contentLength !== 0 || client[kRunning] === 0, "iterator body cannot be pipelined"); + let callback = null; + function onDrain() { + if (callback) { + const cb = callback; + callback = null; + cb(); + } + } + const waitForDrain = () => new Promise((resolve5, reject) => { + assert2(callback === null); + if (socket[kError]) { + reject(socket[kError]); + } else { + callback = resolve5; + } + }); + socket.on("close", onDrain).on("drain", onDrain); + const writer = new AsyncWriter({ abort, socket, request: request3, contentLength, client, expectsPayload, header }); + try { + for await (const chunk of body) { + if (socket[kError]) { + throw socket[kError]; + } + if (!writer.write(chunk)) { + await waitForDrain(); + } + } + writer.end(); + } catch (err) { + writer.destroy(err); + } finally { + socket.off("close", onDrain).off("drain", onDrain); + } + } + var AsyncWriter = class { + constructor({ abort, socket, request: request3, contentLength, client, expectsPayload, header }) { + this.socket = socket; + this.request = request3; + this.contentLength = contentLength; + this.client = client; + this.bytesWritten = 0; + this.expectsPayload = expectsPayload; + this.header = header; + this.abort = abort; + socket[kWriting] = true; + } + write(chunk) { + const { socket, request: request3, contentLength, client, bytesWritten, expectsPayload, header } = this; + if (socket[kError]) { + throw socket[kError]; + } + if (socket.destroyed) { + return false; + } + const len = Buffer.byteLength(chunk); + if (!len) { + return true; + } + if (contentLength !== null && bytesWritten + len > contentLength) { + if (client[kStrictContentLength]) { + throw new RequestContentLengthMismatchError(); + } + process.emitWarning(new RequestContentLengthMismatchError()); + } + socket.cork(); + if (bytesWritten === 0) { + if (!expectsPayload && request3.reset !== false) { + socket[kReset2] = true; + } + if (contentLength === null) { + socket.write(`${header}transfer-encoding: chunked\r +`, "latin1"); + } else { + socket.write(`${header}content-length: ${contentLength}\r +\r +`, "latin1"); + } + } + if (contentLength === null) { + socket.write(`\r +${len.toString(16)}\r +`, "latin1"); + } + this.bytesWritten += len; + const ret = socket.write(chunk); + socket.uncork(); + request3.onBodySent(chunk); + if (!ret) { + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + if (socket[kParser].timeout.refresh) { + socket[kParser].timeout.refresh(); + } + } + } + return ret; + } + end() { + const { socket, contentLength, client, bytesWritten, expectsPayload, header, request: request3 } = this; + request3.onRequestSent(); + socket[kWriting] = false; + if (socket[kError]) { + throw socket[kError]; + } + if (socket.destroyed) { + return; + } + if (bytesWritten === 0) { + if (expectsPayload) { + socket.write(`${header}content-length: 0\r +\r +`, "latin1"); + } else { + socket.write(`${header}\r +`, "latin1"); + } + } else if (contentLength === null) { + socket.write("\r\n0\r\n\r\n", "latin1"); + } + if (contentLength !== null && bytesWritten !== contentLength) { + if (client[kStrictContentLength]) { + throw new RequestContentLengthMismatchError(); + } else { + process.emitWarning(new RequestContentLengthMismatchError()); + } + } + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + if (socket[kParser].timeout.refresh) { + socket[kParser].timeout.refresh(); + } + } + client[kResume](); + } + destroy(err) { + const { socket, client, abort } = this; + socket[kWriting] = false; + if (err) { + assert2(client[kRunning] <= 1, "pipeline should only contain this request"); + abort(err); + } + } + }; + module.exports = connectH1; + } +}); + +// +var require_client_h2 = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var { pipeline } = __require("node:stream"); + var util = require_util(); + var { + RequestContentLengthMismatchError, + RequestAbortedError, + SocketError, + InformationalError + } = require_errors(); + var { + kUrl, + kReset: kReset2, + kClient, + kRunning, + kPending, + kQueue, + kPendingIdx, + kRunningIdx, + kError, + kSocket, + kStrictContentLength, + kOnError, + kMaxConcurrentStreams, + kHTTP2Session, + kResume, + kSize, + kHTTPContext + } = require_symbols(); + var kOpenStreams = Symbol("open streams"); + var extractBody; + var h2ExperimentalWarned = false; + var http2; + try { + http2 = __require("node:http2"); + } catch { + http2 = { constants: {} }; + } + var { + constants: { + HTTP2_HEADER_AUTHORITY, + HTTP2_HEADER_METHOD, + HTTP2_HEADER_PATH, + HTTP2_HEADER_SCHEME, + HTTP2_HEADER_CONTENT_LENGTH, + HTTP2_HEADER_EXPECT, + HTTP2_HEADER_STATUS + } + } = http2; + function parseH2Headers(headers) { + const result = []; + for (const [name, value] of Object.entries(headers)) { + if (Array.isArray(value)) { + for (const subvalue of value) { + result.push(Buffer.from(name), Buffer.from(subvalue)); + } + } else { + result.push(Buffer.from(name), Buffer.from(value)); + } + } + return result; + } + async function connectH2(client, socket) { + client[kSocket] = socket; + if (!h2ExperimentalWarned) { + h2ExperimentalWarned = true; + process.emitWarning("H2 support is experimental, expect them to change at any time.", { + code: "UNDICI-H2" + }); + } + const session = http2.connect(client[kUrl], { + createConnection: () => socket, + peerMaxConcurrentStreams: client[kMaxConcurrentStreams] + }); + session[kOpenStreams] = 0; + session[kClient] = client; + session[kSocket] = socket; + util.addListener(session, "error", onHttp2SessionError); + util.addListener(session, "frameError", onHttp2FrameError); + util.addListener(session, "end", onHttp2SessionEnd); + util.addListener(session, "goaway", onHTTP2GoAway); + util.addListener(session, "close", function() { + const { [kClient]: client2 } = this; + const { [kSocket]: socket2 } = client2; + const err = this[kSocket][kError] || this[kError] || new SocketError("closed", util.getSocketInfo(socket2)); + client2[kHTTP2Session] = null; + if (client2.destroyed) { + assert2(client2[kPending] === 0); + const requests = client2[kQueue].splice(client2[kRunningIdx]); + for (let i = 0; i < requests.length; i++) { + const request3 = requests[i]; + util.errorRequest(client2, request3, err); + } + } + }); + session.unref(); + client[kHTTP2Session] = session; + socket[kHTTP2Session] = session; + util.addListener(socket, "error", function(err) { + assert2(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID"); + this[kError] = err; + this[kClient][kOnError](err); + }); + util.addListener(socket, "end", function() { + util.destroy(this, new SocketError("other side closed", util.getSocketInfo(this))); + }); + util.addListener(socket, "close", function() { + const err = this[kError] || new SocketError("closed", util.getSocketInfo(this)); + client[kSocket] = null; + if (this[kHTTP2Session] != null) { + this[kHTTP2Session].destroy(err); + } + client[kPendingIdx] = client[kRunningIdx]; + assert2(client[kRunning] === 0); + client.emit("disconnect", client[kUrl], [client], err); + client[kResume](); + }); + let closed = false; + socket.on("close", () => { + closed = true; + }); + return { + version: "h2", + defaultPipelining: Infinity, + write(...args) { + return writeH2(client, ...args); + }, + resume() { + resumeH2(client); + }, + destroy(err, callback) { + if (closed) { + queueMicrotask(callback); + } else { + socket.destroy(err).on("close", callback); + } + }, + get destroyed() { + return socket.destroyed; + }, + busy() { + return false; + } + }; + } + function resumeH2(client) { + const socket = client[kSocket]; + if (socket?.destroyed === false) { + if (client[kSize] === 0 && client[kMaxConcurrentStreams] === 0) { + socket.unref(); + client[kHTTP2Session].unref(); + } else { + socket.ref(); + client[kHTTP2Session].ref(); + } + } + } + function onHttp2SessionError(err) { + assert2(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID"); + this[kSocket][kError] = err; + this[kClient][kOnError](err); + } + function onHttp2FrameError(type, code, id) { + if (id === 0) { + const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`); + this[kSocket][kError] = err; + this[kClient][kOnError](err); + } + } + function onHttp2SessionEnd() { + const err = new SocketError("other side closed", util.getSocketInfo(this[kSocket])); + this.destroy(err); + util.destroy(this[kSocket], err); + } + function onHTTP2GoAway(code) { + const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${code}`, util.getSocketInfo(this)); + const client = this[kClient]; + client[kSocket] = null; + client[kHTTPContext] = null; + if (this[kHTTP2Session] != null) { + this[kHTTP2Session].destroy(err); + this[kHTTP2Session] = null; + } + util.destroy(this[kSocket], err); + if (client[kRunningIdx] < client[kQueue].length) { + const request3 = client[kQueue][client[kRunningIdx]]; + client[kQueue][client[kRunningIdx]++] = null; + util.errorRequest(client, request3, err); + client[kPendingIdx] = client[kRunningIdx]; + } + assert2(client[kRunning] === 0); + client.emit("disconnect", client[kUrl], [client], err); + client[kResume](); + } + function shouldSendContentLength(method) { + return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT"; + } + function writeH2(client, request3) { + const session = client[kHTTP2Session]; + const { method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request3; + let { body } = request3; + if (upgrade) { + util.errorRequest(client, request3, new Error("Upgrade not supported for H2")); + return false; + } + const headers = {}; + for (let n = 0; n < reqHeaders.length; n += 2) { + const key = reqHeaders[n + 0]; + const val = reqHeaders[n + 1]; + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + if (headers[key]) { + headers[key] += `,${val[i]}`; + } else { + headers[key] = val[i]; + } + } + } else { + headers[key] = val; + } + } + let stream; + const { hostname, port } = client[kUrl]; + headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ""}`; + headers[HTTP2_HEADER_METHOD] = method; + const abort = (err) => { + if (request3.aborted || request3.completed) { + return; + } + err = err || new RequestAbortedError(); + util.errorRequest(client, request3, err); + if (stream != null) { + util.destroy(stream, err); + } + util.destroy(body, err); + client[kQueue][client[kRunningIdx]++] = null; + client[kResume](); + }; + try { + request3.onConnect(abort); + } catch (err) { + util.errorRequest(client, request3, err); + } + if (request3.aborted) { + return false; + } + if (method === "CONNECT") { + session.ref(); + stream = session.request(headers, { endStream: false, signal }); + if (stream.id && !stream.pending) { + request3.onUpgrade(null, null, stream); + ++session[kOpenStreams]; + client[kQueue][client[kRunningIdx]++] = null; + } else { + stream.once("ready", () => { + request3.onUpgrade(null, null, stream); + ++session[kOpenStreams]; + client[kQueue][client[kRunningIdx]++] = null; + }); + } + stream.once("close", () => { + session[kOpenStreams] -= 1; + if (session[kOpenStreams] === 0) + session.unref(); + }); + return true; + } + headers[HTTP2_HEADER_PATH] = path; + headers[HTTP2_HEADER_SCHEME] = "https"; + const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH"; + if (body && typeof body.read === "function") { + body.read(0); + } + let contentLength = util.bodyLength(body); + if (util.isFormDataLike(body)) { + extractBody ??= require_body().extractBody; + const [bodyStream, contentType] = extractBody(body); + headers["content-type"] = contentType; + body = bodyStream.stream; + contentLength = bodyStream.length; + } + if (contentLength == null) { + contentLength = request3.contentLength; + } + if (contentLength === 0 || !expectsPayload) { + contentLength = null; + } + if (shouldSendContentLength(method) && contentLength > 0 && request3.contentLength != null && request3.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + util.errorRequest(client, request3, new RequestContentLengthMismatchError()); + return false; + } + process.emitWarning(new RequestContentLengthMismatchError()); + } + if (contentLength != null) { + assert2(body, "no body must not have content length"); + headers[HTTP2_HEADER_CONTENT_LENGTH] = `${contentLength}`; + } + session.ref(); + const shouldEndStream = method === "GET" || method === "HEAD" || body === null; + if (expectContinue) { + headers[HTTP2_HEADER_EXPECT] = "100-continue"; + stream = session.request(headers, { endStream: shouldEndStream, signal }); + stream.once("continue", writeBodyH2); + } else { + stream = session.request(headers, { + endStream: shouldEndStream, + signal + }); + writeBodyH2(); + } + ++session[kOpenStreams]; + stream.once("response", (headers2) => { + const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers2; + request3.onResponseStarted(); + if (request3.aborted) { + const err = new RequestAbortedError(); + util.errorRequest(client, request3, err); + util.destroy(stream, err); + return; + } + if (request3.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), "") === false) { + stream.pause(); + } + stream.on("data", (chunk) => { + if (request3.onData(chunk) === false) { + stream.pause(); + } + }); + }); + stream.once("end", () => { + if (stream.state?.state == null || stream.state.state < 6) { + request3.onComplete([]); + } + if (session[kOpenStreams] === 0) { + session.unref(); + } + abort(new InformationalError("HTTP/2: stream half-closed (remote)")); + client[kQueue][client[kRunningIdx]++] = null; + client[kPendingIdx] = client[kRunningIdx]; + client[kResume](); + }); + stream.once("close", () => { + session[kOpenStreams] -= 1; + if (session[kOpenStreams] === 0) { + session.unref(); + } + }); + stream.once("error", function(err) { + abort(err); + }); + stream.once("frameError", (type, code) => { + abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)); + }); + return true; + function writeBodyH2() { + if (!body || contentLength === 0) { + writeBuffer( + abort, + stream, + null, + client, + request3, + client[kSocket], + contentLength, + expectsPayload + ); + } else if (util.isBuffer(body)) { + writeBuffer( + abort, + stream, + body, + client, + request3, + client[kSocket], + contentLength, + expectsPayload + ); + } else if (util.isBlobLike(body)) { + if (typeof body.stream === "function") { + writeIterable( + abort, + stream, + body.stream(), + client, + request3, + client[kSocket], + contentLength, + expectsPayload + ); + } else { + writeBlob( + abort, + stream, + body, + client, + request3, + client[kSocket], + contentLength, + expectsPayload + ); + } + } else if (util.isStream(body)) { + writeStream( + abort, + client[kSocket], + expectsPayload, + stream, + body, + client, + request3, + contentLength + ); + } else if (util.isIterable(body)) { + writeIterable( + abort, + stream, + body, + client, + request3, + client[kSocket], + contentLength, + expectsPayload + ); + } else { + assert2(false); + } + } + } + function writeBuffer(abort, h2stream, body, client, request3, socket, contentLength, expectsPayload) { + try { + if (body != null && util.isBuffer(body)) { + assert2(contentLength === body.byteLength, "buffer body must have content length"); + h2stream.cork(); + h2stream.write(body); + h2stream.uncork(); + h2stream.end(); + request3.onBodySent(body); + } + if (!expectsPayload) { + socket[kReset2] = true; + } + request3.onRequestSent(); + client[kResume](); + } catch (error2) { + abort(error2); + } + } + function writeStream(abort, socket, expectsPayload, h2stream, body, client, request3, contentLength) { + assert2(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined"); + const pipe = pipeline( + body, + h2stream, + (err) => { + if (err) { + util.destroy(pipe, err); + abort(err); + } else { + util.removeAllListeners(pipe); + request3.onRequestSent(); + if (!expectsPayload) { + socket[kReset2] = true; + } + client[kResume](); + } + } + ); + util.addListener(pipe, "data", onPipeData); + function onPipeData(chunk) { + request3.onBodySent(chunk); + } + } + async function writeBlob(abort, h2stream, body, client, request3, socket, contentLength, expectsPayload) { + assert2(contentLength === body.size, "blob body must have content length"); + try { + if (contentLength != null && contentLength !== body.size) { + throw new RequestContentLengthMismatchError(); + } + const buffer = Buffer.from(await body.arrayBuffer()); + h2stream.cork(); + h2stream.write(buffer); + h2stream.uncork(); + h2stream.end(); + request3.onBodySent(buffer); + request3.onRequestSent(); + if (!expectsPayload) { + socket[kReset2] = true; + } + client[kResume](); + } catch (err) { + abort(err); + } + } + async function writeIterable(abort, h2stream, body, client, request3, socket, contentLength, expectsPayload) { + assert2(contentLength !== 0 || client[kRunning] === 0, "iterator body cannot be pipelined"); + let callback = null; + function onDrain() { + if (callback) { + const cb = callback; + callback = null; + cb(); + } + } + const waitForDrain = () => new Promise((resolve5, reject) => { + assert2(callback === null); + if (socket[kError]) { + reject(socket[kError]); + } else { + callback = resolve5; + } + }); + h2stream.on("close", onDrain).on("drain", onDrain); + try { + for await (const chunk of body) { + if (socket[kError]) { + throw socket[kError]; + } + const res = h2stream.write(chunk); + request3.onBodySent(chunk); + if (!res) { + await waitForDrain(); + } + } + h2stream.end(); + request3.onRequestSent(); + if (!expectsPayload) { + socket[kReset2] = true; + } + client[kResume](); + } catch (err) { + abort(err); + } finally { + h2stream.off("close", onDrain).off("drain", onDrain); + } + } + module.exports = connectH2; + } +}); + +// +var require_redirect_handler = __commonJS({ + ""(exports, module) { + "use strict"; + var util = require_util(); + var { kBodyUsed } = require_symbols(); + var assert2 = __require("node:assert"); + var { InvalidArgumentError } = require_errors(); + var EE = __require("node:events"); + var redirectableStatusCodes = [300, 301, 302, 303, 307, 308]; + var kBody = Symbol("body"); + var BodyAsyncIterable = class { + constructor(body) { + this[kBody] = body; + this[kBodyUsed] = false; + } + async *[Symbol.asyncIterator]() { + assert2(!this[kBodyUsed], "disturbed"); + this[kBodyUsed] = true; + yield* this[kBody]; + } + }; + var RedirectHandler = class { + constructor(dispatch, maxRedirections, opts, handler3) { + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { + throw new InvalidArgumentError("maxRedirections must be a positive number"); + } + util.validateHandler(handler3, opts.method, opts.upgrade); + this.dispatch = dispatch; + this.location = null; + this.abort = null; + this.opts = { ...opts, maxRedirections: 0 }; + this.maxRedirections = maxRedirections; + this.handler = handler3; + this.history = []; + this.redirectionLimitReached = false; + if (util.isStream(this.opts.body)) { + if (util.bodyLength(this.opts.body) === 0) { + this.opts.body.on("data", function() { + assert2(false); + }); + } + if (typeof this.opts.body.readableDidRead !== "boolean") { + this.opts.body[kBodyUsed] = false; + EE.prototype.on.call(this.opts.body, "data", function() { + this[kBodyUsed] = true; + }); + } + } else if (this.opts.body && typeof this.opts.body.pipeTo === "function") { + this.opts.body = new BodyAsyncIterable(this.opts.body); + } else if (this.opts.body && typeof this.opts.body !== "string" && !ArrayBuffer.isView(this.opts.body) && util.isIterable(this.opts.body)) { + this.opts.body = new BodyAsyncIterable(this.opts.body); + } + } + onConnect(abort) { + this.abort = abort; + this.handler.onConnect(abort, { history: this.history }); + } + onUpgrade(statusCode, headers, socket) { + this.handler.onUpgrade(statusCode, headers, socket); + } + onError(error2) { + this.handler.onError(error2); + } + onHeaders(statusCode, headers, resume, statusText) { + this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body) ? null : parseLocation(statusCode, headers); + if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) { + if (this.request) { + this.request.abort(new Error("max redirects")); + } + this.redirectionLimitReached = true; + this.abort(new Error("max redirects")); + return; + } + if (this.opts.origin) { + this.history.push(new URL(this.opts.path, this.opts.origin)); + } + if (!this.location) { + return this.handler.onHeaders(statusCode, headers, resume, statusText); + } + const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin))); + const path = search ? `${pathname}${search}` : pathname; + this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin); + this.opts.path = path; + this.opts.origin = origin; + this.opts.maxRedirections = 0; + this.opts.query = null; + if (statusCode === 303 && this.opts.method !== "HEAD") { + this.opts.method = "GET"; + this.opts.body = null; + } + } + onData(chunk) { + if (this.location) { + } else { + return this.handler.onData(chunk); + } + } + onComplete(trailers) { + if (this.location) { + this.location = null; + this.abort = null; + this.dispatch(this.opts, this); + } else { + this.handler.onComplete(trailers); + } + } + onBodySent(chunk) { + if (this.handler.onBodySent) { + this.handler.onBodySent(chunk); + } + } + }; + function parseLocation(statusCode, headers) { + if (redirectableStatusCodes.indexOf(statusCode) === -1) { + return null; + } + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].length === 8 && util.headerNameToString(headers[i]) === "location") { + return headers[i + 1]; + } + } + } + function shouldRemoveHeader(header, removeContent, unknownOrigin) { + if (header.length === 4) { + return util.headerNameToString(header) === "host"; + } + if (removeContent && util.headerNameToString(header).startsWith("content-")) { + return true; + } + if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { + const name = util.headerNameToString(header); + return name === "authorization" || name === "cookie" || name === "proxy-authorization"; + } + return false; + } + function cleanRequestHeaders(headers, removeContent, unknownOrigin) { + const ret = []; + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) { + if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) { + ret.push(headers[i], headers[i + 1]); + } + } + } else if (headers && typeof headers === "object") { + for (const key of Object.keys(headers)) { + if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) { + ret.push(key, headers[key]); + } + } + } else { + assert2(headers == null, "headers must be an object or an array"); + } + return ret; + } + module.exports = RedirectHandler; + } +}); + +// +var require_redirect_interceptor = __commonJS({ + ""(exports, module) { + "use strict"; + var RedirectHandler = require_redirect_handler(); + function createRedirectInterceptor({ maxRedirections: defaultMaxRedirections }) { + return (dispatch) => { + return function Intercept(opts, handler3) { + const { maxRedirections = defaultMaxRedirections } = opts; + if (!maxRedirections) { + return dispatch(opts, handler3); + } + const redirectHandler = new RedirectHandler(dispatch, maxRedirections, opts, handler3); + opts = { ...opts, maxRedirections: 0 }; + return dispatch(opts, redirectHandler); + }; + }; + } + module.exports = createRedirectInterceptor; + } +}); + +// +var require_client = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var net = __require("node:net"); + var http = __require("node:http"); + var util = require_util(); + var { channels } = require_diagnostics(); + var Request = require_request(); + var DispatcherBase = require_dispatcher_base(); + var { + InvalidArgumentError, + InformationalError, + ClientDestroyedError + } = require_errors(); + var buildConnector = require_connect(); + var { + kUrl, + kServerName, + kClient, + kBusy, + kConnect, + kResuming, + kRunning, + kPending, + kSize, + kQueue, + kConnected, + kConnecting, + kNeedDrain, + kKeepAliveDefaultTimeout, + kHostHeader, + kPendingIdx, + kRunningIdx, + kError, + kPipelining, + kKeepAliveTimeoutValue, + kMaxHeadersSize, + kKeepAliveMaxTimeout, + kKeepAliveTimeoutThreshold, + kHeadersTimeout, + kBodyTimeout, + kStrictContentLength, + kConnector, + kMaxRedirections, + kMaxRequests, + kCounter, + kClose, + kDestroy, + kDispatch, + kInterceptors, + kLocalAddress, + kMaxResponseSize, + kOnError, + kHTTPContext, + kMaxConcurrentStreams, + kResume + } = require_symbols(); + var connectH1 = require_client_h1(); + var connectH2 = require_client_h2(); + var deprecatedInterceptorWarned = false; + var kClosedResolve = Symbol("kClosedResolve"); + var noop4 = () => { + }; + function getPipelining(client) { + return client[kPipelining] ?? client[kHTTPContext]?.defaultPipelining ?? 1; + } + var Client = class extends DispatcherBase { + /** + * + * @param {string|URL} url + * @param {import('../../types/client.js').Client.Options} options + */ + constructor(url, { + interceptors, + maxHeaderSize, + headersTimeout, + socketTimeout, + requestTimeout, + connectTimeout, + bodyTimeout, + idleTimeout, + keepAlive, + keepAliveTimeout, + maxKeepAliveTimeout, + keepAliveMaxTimeout, + keepAliveTimeoutThreshold, + socketPath, + pipelining, + tls, + strictContentLength, + maxCachedSessions, + maxRedirections, + connect: connect2, + maxRequestsPerClient, + localAddress, + maxResponseSize, + autoSelectFamily, + autoSelectFamilyAttemptTimeout, + // h2 + maxConcurrentStreams, + allowH2, + webSocket + } = {}) { + super({ webSocket }); + if (keepAlive !== void 0) { + throw new InvalidArgumentError("unsupported keepAlive, use pipelining=0 instead"); + } + if (socketTimeout !== void 0) { + throw new InvalidArgumentError("unsupported socketTimeout, use headersTimeout & bodyTimeout instead"); + } + if (requestTimeout !== void 0) { + throw new InvalidArgumentError("unsupported requestTimeout, use headersTimeout & bodyTimeout instead"); + } + if (idleTimeout !== void 0) { + throw new InvalidArgumentError("unsupported idleTimeout, use keepAliveTimeout instead"); + } + if (maxKeepAliveTimeout !== void 0) { + throw new InvalidArgumentError("unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead"); + } + if (maxHeaderSize != null && !Number.isFinite(maxHeaderSize)) { + throw new InvalidArgumentError("invalid maxHeaderSize"); + } + if (socketPath != null && typeof socketPath !== "string") { + throw new InvalidArgumentError("invalid socketPath"); + } + if (connectTimeout != null && (!Number.isFinite(connectTimeout) || connectTimeout < 0)) { + throw new InvalidArgumentError("invalid connectTimeout"); + } + if (keepAliveTimeout != null && (!Number.isFinite(keepAliveTimeout) || keepAliveTimeout <= 0)) { + throw new InvalidArgumentError("invalid keepAliveTimeout"); + } + if (keepAliveMaxTimeout != null && (!Number.isFinite(keepAliveMaxTimeout) || keepAliveMaxTimeout <= 0)) { + throw new InvalidArgumentError("invalid keepAliveMaxTimeout"); + } + if (keepAliveTimeoutThreshold != null && !Number.isFinite(keepAliveTimeoutThreshold)) { + throw new InvalidArgumentError("invalid keepAliveTimeoutThreshold"); + } + if (headersTimeout != null && (!Number.isInteger(headersTimeout) || headersTimeout < 0)) { + throw new InvalidArgumentError("headersTimeout must be a positive integer or zero"); + } + if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) { + throw new InvalidArgumentError("bodyTimeout must be a positive integer or zero"); + } + if (connect2 != null && typeof connect2 !== "function" && typeof connect2 !== "object") { + throw new InvalidArgumentError("connect must be a function or an object"); + } + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { + throw new InvalidArgumentError("maxRedirections must be a positive number"); + } + if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) { + throw new InvalidArgumentError("maxRequestsPerClient must be a positive number"); + } + if (localAddress != null && (typeof localAddress !== "string" || net.isIP(localAddress) === 0)) { + throw new InvalidArgumentError("localAddress must be valid string IP address"); + } + if (maxResponseSize != null && (!Number.isInteger(maxResponseSize) || maxResponseSize < -1)) { + throw new InvalidArgumentError("maxResponseSize must be a positive number"); + } + if (autoSelectFamilyAttemptTimeout != null && (!Number.isInteger(autoSelectFamilyAttemptTimeout) || autoSelectFamilyAttemptTimeout < -1)) { + throw new InvalidArgumentError("autoSelectFamilyAttemptTimeout must be a positive number"); + } + if (allowH2 != null && typeof allowH2 !== "boolean") { + throw new InvalidArgumentError("allowH2 must be a valid boolean value"); + } + if (maxConcurrentStreams != null && (typeof maxConcurrentStreams !== "number" || maxConcurrentStreams < 1)) { + throw new InvalidArgumentError("maxConcurrentStreams must be a positive integer, greater than 0"); + } + if (typeof connect2 !== "function") { + connect2 = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0, + ...connect2 + }); + } + if (interceptors?.Client && Array.isArray(interceptors.Client)) { + this[kInterceptors] = interceptors.Client; + if (!deprecatedInterceptorWarned) { + deprecatedInterceptorWarned = true; + process.emitWarning("Client.Options#interceptor is deprecated. Use Dispatcher#compose instead.", { + code: "UNDICI-CLIENT-INTERCEPTOR-DEPRECATED" + }); + } + } else { + this[kInterceptors] = [createRedirectInterceptor({ maxRedirections })]; + } + this[kUrl] = util.parseOrigin(url); + this[kConnector] = connect2; + this[kPipelining] = pipelining != null ? pipelining : 1; + this[kMaxHeadersSize] = maxHeaderSize || http.maxHeaderSize; + this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout; + this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 6e5 : keepAliveMaxTimeout; + this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 2e3 : keepAliveTimeoutThreshold; + this[kKeepAliveTimeoutValue] = this[kKeepAliveDefaultTimeout]; + this[kServerName] = null; + this[kLocalAddress] = localAddress != null ? localAddress : null; + this[kResuming] = 0; + this[kNeedDrain] = 0; + this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ""}\r +`; + this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 3e5; + this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 3e5; + this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength; + this[kMaxRedirections] = maxRedirections; + this[kMaxRequests] = maxRequestsPerClient; + this[kClosedResolve] = null; + this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1; + this[kMaxConcurrentStreams] = maxConcurrentStreams != null ? maxConcurrentStreams : 100; + this[kHTTPContext] = null; + this[kQueue] = []; + this[kRunningIdx] = 0; + this[kPendingIdx] = 0; + this[kResume] = (sync) => resume(this, sync); + this[kOnError] = (err) => onError(this, err); + } + get pipelining() { + return this[kPipelining]; + } + set pipelining(value) { + this[kPipelining] = value; + this[kResume](true); + } + get [kPending]() { + return this[kQueue].length - this[kPendingIdx]; + } + get [kRunning]() { + return this[kPendingIdx] - this[kRunningIdx]; + } + get [kSize]() { + return this[kQueue].length - this[kRunningIdx]; + } + get [kConnected]() { + return !!this[kHTTPContext] && !this[kConnecting] && !this[kHTTPContext].destroyed; + } + get [kBusy]() { + return Boolean( + this[kHTTPContext]?.busy(null) || this[kSize] >= (getPipelining(this) || 1) || this[kPending] > 0 + ); + } + /* istanbul ignore: only used for test */ + [kConnect](cb) { + connect(this); + this.once("connect", cb); + } + [kDispatch](opts, handler3) { + const origin = opts.origin || this[kUrl].origin; + const request3 = new Request(origin, opts, handler3); + this[kQueue].push(request3); + if (this[kResuming]) { + } else if (util.bodyLength(request3.body) == null && util.isIterable(request3.body)) { + this[kResuming] = 1; + queueMicrotask(() => resume(this)); + } else { + this[kResume](true); + } + if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) { + this[kNeedDrain] = 2; + } + return this[kNeedDrain] < 2; + } + async [kClose]() { + return new Promise((resolve5) => { + if (this[kSize]) { + this[kClosedResolve] = resolve5; + } else { + resolve5(null); + } + }); + } + async [kDestroy](err) { + return new Promise((resolve5) => { + const requests = this[kQueue].splice(this[kPendingIdx]); + for (let i = 0; i < requests.length; i++) { + const request3 = requests[i]; + util.errorRequest(this, request3, err); + } + const callback = () => { + if (this[kClosedResolve]) { + this[kClosedResolve](); + this[kClosedResolve] = null; + } + resolve5(null); + }; + if (this[kHTTPContext]) { + this[kHTTPContext].destroy(err, callback); + this[kHTTPContext] = null; + } else { + queueMicrotask(callback); + } + this[kResume](); + }); + } + }; + var createRedirectInterceptor = require_redirect_interceptor(); + function onError(client, err) { + if (client[kRunning] === 0 && err.code !== "UND_ERR_INFO" && err.code !== "UND_ERR_SOCKET") { + assert2(client[kPendingIdx] === client[kRunningIdx]); + const requests = client[kQueue].splice(client[kRunningIdx]); + for (let i = 0; i < requests.length; i++) { + const request3 = requests[i]; + util.errorRequest(client, request3, err); + } + assert2(client[kSize] === 0); + } + } + async function connect(client) { + assert2(!client[kConnecting]); + assert2(!client[kHTTPContext]); + let { host, hostname, protocol, port } = client[kUrl]; + if (hostname[0] === "[") { + const idx = hostname.indexOf("]"); + assert2(idx !== -1); + const ip = hostname.substring(1, idx); + assert2(net.isIP(ip)); + hostname = ip; + } + client[kConnecting] = true; + if (channels.beforeConnect.hasSubscribers) { + channels.beforeConnect.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector] + }); + } + try { + const socket = await new Promise((resolve5, reject) => { + client[kConnector]({ + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, (err, socket2) => { + if (err) { + reject(err); + } else { + resolve5(socket2); + } + }); + }); + if (client.destroyed) { + util.destroy(socket.on("error", noop4), new ClientDestroyedError()); + return; + } + assert2(socket); + try { + client[kHTTPContext] = socket.alpnProtocol === "h2" ? await connectH2(client, socket) : await connectH1(client, socket); + } catch (err) { + socket.destroy().on("error", noop4); + throw err; + } + client[kConnecting] = false; + socket[kCounter] = 0; + socket[kMaxRequests] = client[kMaxRequests]; + socket[kClient] = client; + socket[kError] = null; + if (channels.connected.hasSubscribers) { + channels.connected.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + socket + }); + } + client.emit("connect", client[kUrl], [client]); + } catch (err) { + if (client.destroyed) { + return; + } + client[kConnecting] = false; + if (channels.connectError.hasSubscribers) { + channels.connectError.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + error: err + }); + } + if (err.code === "ERR_TLS_CERT_ALTNAME_INVALID") { + assert2(client[kRunning] === 0); + while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) { + const request3 = client[kQueue][client[kPendingIdx]++]; + util.errorRequest(client, request3, err); + } + } else { + onError(client, err); + } + client.emit("connectionError", client[kUrl], [client], err); + } + client[kResume](); + } + function emitDrain(client) { + client[kNeedDrain] = 0; + client.emit("drain", client[kUrl], [client]); + } + function resume(client, sync) { + if (client[kResuming] === 2) { + return; + } + client[kResuming] = 2; + _resume(client, sync); + client[kResuming] = 0; + if (client[kRunningIdx] > 256) { + client[kQueue].splice(0, client[kRunningIdx]); + client[kPendingIdx] -= client[kRunningIdx]; + client[kRunningIdx] = 0; + } + } + function _resume(client, sync) { + while (true) { + if (client.destroyed) { + assert2(client[kPending] === 0); + return; + } + if (client[kClosedResolve] && !client[kSize]) { + client[kClosedResolve](); + client[kClosedResolve] = null; + return; + } + if (client[kHTTPContext]) { + client[kHTTPContext].resume(); + } + if (client[kBusy]) { + client[kNeedDrain] = 2; + } else if (client[kNeedDrain] === 2) { + if (sync) { + client[kNeedDrain] = 1; + queueMicrotask(() => emitDrain(client)); + } else { + emitDrain(client); + } + continue; + } + if (client[kPending] === 0) { + return; + } + if (client[kRunning] >= (getPipelining(client) || 1)) { + return; + } + const request3 = client[kQueue][client[kPendingIdx]]; + if (client[kUrl].protocol === "https:" && client[kServerName] !== request3.servername) { + if (client[kRunning] > 0) { + return; + } + client[kServerName] = request3.servername; + client[kHTTPContext]?.destroy(new InformationalError("servername changed"), () => { + client[kHTTPContext] = null; + resume(client); + }); + } + if (client[kConnecting]) { + return; + } + if (!client[kHTTPContext]) { + connect(client); + return; + } + if (client[kHTTPContext].destroyed) { + return; + } + if (client[kHTTPContext].busy(request3)) { + return; + } + if (!request3.aborted && client[kHTTPContext].write(request3)) { + client[kPendingIdx]++; + } else { + client[kQueue].splice(client[kPendingIdx], 1); + } + } + } + module.exports = Client; + } +}); + +// +var require_fixed_queue = __commonJS({ + ""(exports, module) { + "use strict"; + var kSize = 2048; + var kMask = kSize - 1; + var FixedCircularBuffer = class { + constructor() { + this.bottom = 0; + this.top = 0; + this.list = new Array(kSize); + this.next = null; + } + isEmpty() { + return this.top === this.bottom; + } + isFull() { + return (this.top + 1 & kMask) === this.bottom; + } + push(data) { + this.list[this.top] = data; + this.top = this.top + 1 & kMask; + } + shift() { + const nextItem = this.list[this.bottom]; + if (nextItem === void 0) + return null; + this.list[this.bottom] = void 0; + this.bottom = this.bottom + 1 & kMask; + return nextItem; + } + }; + module.exports = class FixedQueue { + constructor() { + this.head = this.tail = new FixedCircularBuffer(); + } + isEmpty() { + return this.head.isEmpty(); + } + push(data) { + if (this.head.isFull()) { + this.head = this.head.next = new FixedCircularBuffer(); + } + this.head.push(data); + } + shift() { + const tail = this.tail; + const next = tail.shift(); + if (tail.isEmpty() && tail.next !== null) { + this.tail = tail.next; + } + return next; + } + }; + } +}); + +// +var require_pool_stats = __commonJS({ + ""(exports, module) { + var { kFree, kConnected, kPending, kQueued, kRunning, kSize } = require_symbols(); + var kPool = Symbol("pool"); + var PoolStats = class { + constructor(pool) { + this[kPool] = pool; + } + get connected() { + return this[kPool][kConnected]; + } + get free() { + return this[kPool][kFree]; + } + get pending() { + return this[kPool][kPending]; + } + get queued() { + return this[kPool][kQueued]; + } + get running() { + return this[kPool][kRunning]; + } + get size() { + return this[kPool][kSize]; + } + }; + module.exports = PoolStats; + } +}); + +// +var require_pool_base = __commonJS({ + ""(exports, module) { + "use strict"; + var DispatcherBase = require_dispatcher_base(); + var FixedQueue = require_fixed_queue(); + var { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = require_symbols(); + var PoolStats = require_pool_stats(); + var kClients = Symbol("clients"); + var kNeedDrain = Symbol("needDrain"); + var kQueue = Symbol("queue"); + var kClosedResolve = Symbol("closed resolve"); + var kOnDrain = Symbol("onDrain"); + var kOnConnect = Symbol("onConnect"); + var kOnDisconnect = Symbol("onDisconnect"); + var kOnConnectionError = Symbol("onConnectionError"); + var kGetDispatcher = Symbol("get dispatcher"); + var kAddClient = Symbol("add client"); + var kRemoveClient = Symbol("remove client"); + var kStats = Symbol("stats"); + var PoolBase = class extends DispatcherBase { + constructor(opts) { + super(opts); + this[kQueue] = new FixedQueue(); + this[kClients] = []; + this[kQueued] = 0; + const pool = this; + this[kOnDrain] = function onDrain(origin, targets) { + const queue = pool[kQueue]; + let needDrain = false; + while (!needDrain) { + const item = queue.shift(); + if (!item) { + break; + } + pool[kQueued]--; + needDrain = !this.dispatch(item.opts, item.handler); + } + this[kNeedDrain] = needDrain; + if (!this[kNeedDrain] && pool[kNeedDrain]) { + pool[kNeedDrain] = false; + pool.emit("drain", origin, [pool, ...targets]); + } + if (pool[kClosedResolve] && queue.isEmpty()) { + Promise.all(pool[kClients].map((c) => c.close())).then(pool[kClosedResolve]); + } + }; + this[kOnConnect] = (origin, targets) => { + pool.emit("connect", origin, [pool, ...targets]); + }; + this[kOnDisconnect] = (origin, targets, err) => { + pool.emit("disconnect", origin, [pool, ...targets], err); + }; + this[kOnConnectionError] = (origin, targets, err) => { + pool.emit("connectionError", origin, [pool, ...targets], err); + }; + this[kStats] = new PoolStats(this); + } + get [kBusy]() { + return this[kNeedDrain]; + } + get [kConnected]() { + return this[kClients].filter((client) => client[kConnected]).length; + } + get [kFree]() { + return this[kClients].filter((client) => client[kConnected] && !client[kNeedDrain]).length; + } + get [kPending]() { + let ret = this[kQueued]; + for (const { [kPending]: pending } of this[kClients]) { + ret += pending; + } + return ret; + } + get [kRunning]() { + let ret = 0; + for (const { [kRunning]: running } of this[kClients]) { + ret += running; + } + return ret; + } + get [kSize]() { + let ret = this[kQueued]; + for (const { [kSize]: size } of this[kClients]) { + ret += size; + } + return ret; + } + get stats() { + return this[kStats]; + } + async [kClose]() { + if (this[kQueue].isEmpty()) { + await Promise.all(this[kClients].map((c) => c.close())); + } else { + await new Promise((resolve5) => { + this[kClosedResolve] = resolve5; + }); + } + } + async [kDestroy](err) { + while (true) { + const item = this[kQueue].shift(); + if (!item) { + break; + } + item.handler.onError(err); + } + await Promise.all(this[kClients].map((c) => c.destroy(err))); + } + [kDispatch](opts, handler3) { + const dispatcher = this[kGetDispatcher](); + if (!dispatcher) { + this[kNeedDrain] = true; + this[kQueue].push({ opts, handler: handler3 }); + this[kQueued]++; + } else if (!dispatcher.dispatch(opts, handler3)) { + dispatcher[kNeedDrain] = true; + this[kNeedDrain] = !this[kGetDispatcher](); + } + return !this[kNeedDrain]; + } + [kAddClient](client) { + client.on("drain", this[kOnDrain]).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]); + this[kClients].push(client); + if (this[kNeedDrain]) { + queueMicrotask(() => { + if (this[kNeedDrain]) { + this[kOnDrain](client[kUrl], [this, client]); + } + }); + } + return this; + } + [kRemoveClient](client) { + client.close(() => { + const idx = this[kClients].indexOf(client); + if (idx !== -1) { + this[kClients].splice(idx, 1); + } + }); + this[kNeedDrain] = this[kClients].some((dispatcher) => !dispatcher[kNeedDrain] && dispatcher.closed !== true && dispatcher.destroyed !== true); + } + }; + module.exports = { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher + }; + } +}); + +// +var require_pool = __commonJS({ + ""(exports, module) { + "use strict"; + var { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kGetDispatcher + } = require_pool_base(); + var Client = require_client(); + var { + InvalidArgumentError + } = require_errors(); + var util = require_util(); + var { kUrl, kInterceptors } = require_symbols(); + var buildConnector = require_connect(); + var kOptions = Symbol("options"); + var kConnections = Symbol("connections"); + var kFactory = Symbol("factory"); + function defaultFactory(origin, opts) { + return new Client(origin, opts); + } + var Pool = class extends PoolBase { + constructor(origin, { + connections, + factory = defaultFactory, + connect, + connectTimeout, + tls, + maxCachedSessions, + socketPath, + autoSelectFamily, + autoSelectFamilyAttemptTimeout, + allowH2, + ...options + } = {}) { + if (connections != null && (!Number.isFinite(connections) || connections < 0)) { + throw new InvalidArgumentError("invalid connections"); + } + if (typeof factory !== "function") { + throw new InvalidArgumentError("factory must be a function."); + } + if (connect != null && typeof connect !== "function" && typeof connect !== "object") { + throw new InvalidArgumentError("connect must be a function or an object"); + } + if (typeof connect !== "function") { + connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : void 0, + ...connect + }); + } + super(options); + this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool) ? options.interceptors.Pool : []; + this[kConnections] = connections || null; + this[kUrl] = util.parseOrigin(origin); + this[kOptions] = { ...util.deepClone(options), connect, allowH2 }; + this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0; + this[kFactory] = factory; + this.on("connectionError", (origin2, targets, error2) => { + for (const target of targets) { + const idx = this[kClients].indexOf(target); + if (idx !== -1) { + this[kClients].splice(idx, 1); + } + } + }); + } + [kGetDispatcher]() { + for (const client of this[kClients]) { + if (!client[kNeedDrain]) { + return client; + } + } + if (!this[kConnections] || this[kClients].length < this[kConnections]) { + const dispatcher = this[kFactory](this[kUrl], this[kOptions]); + this[kAddClient](dispatcher); + return dispatcher; + } + } + }; + module.exports = Pool; + } +}); + +// +var require_balanced_pool = __commonJS({ + ""(exports, module) { + "use strict"; + var { + BalancedPoolMissingUpstreamError, + InvalidArgumentError + } = require_errors(); + var { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher + } = require_pool_base(); + var Pool = require_pool(); + var { kUrl, kInterceptors } = require_symbols(); + var { parseOrigin } = require_util(); + var kFactory = Symbol("factory"); + var kOptions = Symbol("options"); + var kGreatestCommonDivisor = Symbol("kGreatestCommonDivisor"); + var kCurrentWeight = Symbol("kCurrentWeight"); + var kIndex = Symbol("kIndex"); + var kWeight = Symbol("kWeight"); + var kMaxWeightPerServer = Symbol("kMaxWeightPerServer"); + var kErrorPenalty = Symbol("kErrorPenalty"); + function getGreatestCommonDivisor(a, b) { + if (a === 0) + return b; + while (b !== 0) { + const t = b; + b = a % b; + a = t; + } + return a; + } + function defaultFactory(origin, opts) { + return new Pool(origin, opts); + } + var BalancedPool = class extends PoolBase { + constructor(upstreams = [], { factory = defaultFactory, ...opts } = {}) { + super(); + this[kOptions] = opts; + this[kIndex] = -1; + this[kCurrentWeight] = 0; + this[kMaxWeightPerServer] = this[kOptions].maxWeightPerServer || 100; + this[kErrorPenalty] = this[kOptions].errorPenalty || 15; + if (!Array.isArray(upstreams)) { + upstreams = [upstreams]; + } + if (typeof factory !== "function") { + throw new InvalidArgumentError("factory must be a function."); + } + this[kInterceptors] = opts.interceptors?.BalancedPool && Array.isArray(opts.interceptors.BalancedPool) ? opts.interceptors.BalancedPool : []; + this[kFactory] = factory; + for (const upstream of upstreams) { + this.addUpstream(upstream); + } + this._updateBalancedPoolStats(); + } + addUpstream(upstream) { + const upstreamOrigin = parseOrigin(upstream).origin; + if (this[kClients].find((pool2) => pool2[kUrl].origin === upstreamOrigin && pool2.closed !== true && pool2.destroyed !== true)) { + return this; + } + const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])); + this[kAddClient](pool); + pool.on("connect", () => { + pool[kWeight] = Math.min(this[kMaxWeightPerServer], pool[kWeight] + this[kErrorPenalty]); + }); + pool.on("connectionError", () => { + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]); + this._updateBalancedPoolStats(); + }); + pool.on("disconnect", (...args) => { + const err = args[2]; + if (err && err.code === "UND_ERR_SOCKET") { + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]); + this._updateBalancedPoolStats(); + } + }); + for (const client of this[kClients]) { + client[kWeight] = this[kMaxWeightPerServer]; + } + this._updateBalancedPoolStats(); + return this; + } + _updateBalancedPoolStats() { + let result = 0; + for (let i = 0; i < this[kClients].length; i++) { + result = getGreatestCommonDivisor(this[kClients][i][kWeight], result); + } + this[kGreatestCommonDivisor] = result; + } + removeUpstream(upstream) { + const upstreamOrigin = parseOrigin(upstream).origin; + const pool = this[kClients].find((pool2) => pool2[kUrl].origin === upstreamOrigin && pool2.closed !== true && pool2.destroyed !== true); + if (pool) { + this[kRemoveClient](pool); + } + return this; + } + get upstreams() { + return this[kClients].filter((dispatcher) => dispatcher.closed !== true && dispatcher.destroyed !== true).map((p) => p[kUrl].origin); + } + [kGetDispatcher]() { + if (this[kClients].length === 0) { + throw new BalancedPoolMissingUpstreamError(); + } + const dispatcher = this[kClients].find((dispatcher2) => !dispatcher2[kNeedDrain] && dispatcher2.closed !== true && dispatcher2.destroyed !== true); + if (!dispatcher) { + return; + } + const allClientsBusy = this[kClients].map((pool) => pool[kNeedDrain]).reduce((a, b) => a && b, true); + if (allClientsBusy) { + return; + } + let counter = 0; + let maxWeightIndex = this[kClients].findIndex((pool) => !pool[kNeedDrain]); + while (counter++ < this[kClients].length) { + this[kIndex] = (this[kIndex] + 1) % this[kClients].length; + const pool = this[kClients][this[kIndex]]; + if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) { + maxWeightIndex = this[kIndex]; + } + if (this[kIndex] === 0) { + this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor]; + if (this[kCurrentWeight] <= 0) { + this[kCurrentWeight] = this[kMaxWeightPerServer]; + } + } + if (pool[kWeight] >= this[kCurrentWeight] && !pool[kNeedDrain]) { + return pool; + } + } + this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight]; + this[kIndex] = maxWeightIndex; + return this[kClients][maxWeightIndex]; + } + }; + module.exports = BalancedPool; + } +}); + +// +var require_agent = __commonJS({ + ""(exports, module) { + "use strict"; + var { InvalidArgumentError } = require_errors(); + var { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = require_symbols(); + var DispatcherBase = require_dispatcher_base(); + var Pool = require_pool(); + var Client = require_client(); + var util = require_util(); + var createRedirectInterceptor = require_redirect_interceptor(); + var kOnConnect = Symbol("onConnect"); + var kOnDisconnect = Symbol("onDisconnect"); + var kOnConnectionError = Symbol("onConnectionError"); + var kMaxRedirections = Symbol("maxRedirections"); + var kOnDrain = Symbol("onDrain"); + var kFactory = Symbol("factory"); + var kOptions = Symbol("options"); + function defaultFactory(origin, opts) { + return opts && opts.connections === 1 ? new Client(origin, opts) : new Pool(origin, opts); + } + var Agent = class extends DispatcherBase { + constructor({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { + if (typeof factory !== "function") { + throw new InvalidArgumentError("factory must be a function."); + } + if (connect != null && typeof connect !== "function" && typeof connect !== "object") { + throw new InvalidArgumentError("connect must be a function or an object"); + } + if (!Number.isInteger(maxRedirections) || maxRedirections < 0) { + throw new InvalidArgumentError("maxRedirections must be a positive number"); + } + super(options); + if (connect && typeof connect !== "function") { + connect = { ...connect }; + } + this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent) ? options.interceptors.Agent : [createRedirectInterceptor({ maxRedirections })]; + this[kOptions] = { ...util.deepClone(options), connect }; + this[kOptions].interceptors = options.interceptors ? { ...options.interceptors } : void 0; + this[kMaxRedirections] = maxRedirections; + this[kFactory] = factory; + this[kClients] = /* @__PURE__ */ new Map(); + this[kOnDrain] = (origin, targets) => { + this.emit("drain", origin, [this, ...targets]); + }; + this[kOnConnect] = (origin, targets) => { + this.emit("connect", origin, [this, ...targets]); + }; + this[kOnDisconnect] = (origin, targets, err) => { + this.emit("disconnect", origin, [this, ...targets], err); + }; + this[kOnConnectionError] = (origin, targets, err) => { + this.emit("connectionError", origin, [this, ...targets], err); + }; + } + get [kRunning]() { + let ret = 0; + for (const client of this[kClients].values()) { + ret += client[kRunning]; + } + return ret; + } + [kDispatch](opts, handler3) { + let key; + if (opts.origin && (typeof opts.origin === "string" || opts.origin instanceof URL)) { + key = String(opts.origin); + } else { + throw new InvalidArgumentError("opts.origin must be a non-empty string or URL."); + } + let dispatcher = this[kClients].get(key); + if (!dispatcher) { + dispatcher = this[kFactory](opts.origin, this[kOptions]).on("drain", this[kOnDrain]).on("connect", this[kOnConnect]).on("disconnect", this[kOnDisconnect]).on("connectionError", this[kOnConnectionError]); + this[kClients].set(key, dispatcher); + } + return dispatcher.dispatch(opts, handler3); + } + async [kClose]() { + const closePromises = []; + for (const client of this[kClients].values()) { + closePromises.push(client.close()); + } + this[kClients].clear(); + await Promise.all(closePromises); + } + async [kDestroy](err) { + const destroyPromises = []; + for (const client of this[kClients].values()) { + destroyPromises.push(client.destroy(err)); + } + this[kClients].clear(); + await Promise.all(destroyPromises); + } + }; + module.exports = Agent; + } +}); + +// +var require_proxy_agent = __commonJS({ + ""(exports, module) { + "use strict"; + var { kProxy, kClose, kDestroy, kDispatch, kInterceptors } = require_symbols(); + var { URL: URL3 } = __require("node:url"); + var Agent = require_agent(); + var Pool = require_pool(); + var DispatcherBase = require_dispatcher_base(); + var { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require_errors(); + var buildConnector = require_connect(); + var Client = require_client(); + var kAgent = Symbol("proxy agent"); + var kClient = Symbol("proxy client"); + var kProxyHeaders = Symbol("proxy headers"); + var kRequestTls = Symbol("request tls settings"); + var kProxyTls = Symbol("proxy tls settings"); + var kConnectEndpoint = Symbol("connect endpoint function"); + var kTunnelProxy = Symbol("tunnel proxy"); + function defaultProtocolPort(protocol) { + return protocol === "https:" ? 443 : 80; + } + function defaultFactory(origin, opts) { + return new Pool(origin, opts); + } + var noop4 = () => { + }; + function defaultAgentFactory(origin, opts) { + if (opts.connections === 1) { + return new Client(origin, opts); + } + return new Pool(origin, opts); + } + var Http1ProxyWrapper = class extends DispatcherBase { + #client; + constructor(proxyUrl, { headers = {}, connect, factory }) { + super(); + if (!proxyUrl) { + throw new InvalidArgumentError("Proxy URL is mandatory"); + } + this[kProxyHeaders] = headers; + if (factory) { + this.#client = factory(proxyUrl, { connect }); + } else { + this.#client = new Client(proxyUrl, { connect }); + } + } + [kDispatch](opts, handler3) { + const onHeaders = handler3.onHeaders; + handler3.onHeaders = function(statusCode, data, resume) { + if (statusCode === 407) { + if (typeof handler3.onError === "function") { + handler3.onError(new InvalidArgumentError("Proxy Authentication Required (407)")); + } + return; + } + if (onHeaders) + onHeaders.call(this, statusCode, data, resume); + }; + const { + origin, + path = "/", + headers = {} + } = opts; + opts.path = origin + path; + if (!("host" in headers) && !("Host" in headers)) { + const { host } = new URL3(origin); + headers.host = host; + } + opts.headers = { ...this[kProxyHeaders], ...headers }; + return this.#client[kDispatch](opts, handler3); + } + async [kClose]() { + return this.#client.close(); + } + async [kDestroy](err) { + return this.#client.destroy(err); + } + }; + var ProxyAgent2 = class extends DispatcherBase { + constructor(opts) { + super(); + if (!opts || typeof opts === "object" && !(opts instanceof URL3) && !opts.uri) { + throw new InvalidArgumentError("Proxy uri is mandatory"); + } + const { clientFactory = defaultFactory } = opts; + if (typeof clientFactory !== "function") { + throw new InvalidArgumentError("Proxy opts.clientFactory must be a function."); + } + const { proxyTunnel = true } = opts; + const url = this.#getUrl(opts); + const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url; + this[kProxy] = { uri: href, protocol }; + this[kInterceptors] = opts.interceptors?.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent) ? opts.interceptors.ProxyAgent : []; + this[kRequestTls] = opts.requestTls; + this[kProxyTls] = opts.proxyTls; + this[kProxyHeaders] = opts.headers || {}; + this[kTunnelProxy] = proxyTunnel; + if (opts.auth && opts.token) { + throw new InvalidArgumentError("opts.auth cannot be used in combination with opts.token"); + } else if (opts.auth) { + this[kProxyHeaders]["proxy-authorization"] = `Basic ${opts.auth}`; + } else if (opts.token) { + this[kProxyHeaders]["proxy-authorization"] = opts.token; + } else if (username && password) { + this[kProxyHeaders]["proxy-authorization"] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString("base64")}`; + } + const connect = buildConnector({ ...opts.proxyTls }); + this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }); + const agentFactory = opts.factory || defaultAgentFactory; + const factory = (origin2, options) => { + const { protocol: protocol2 } = new URL3(origin2); + if (!this[kTunnelProxy] && protocol2 === "http:" && this[kProxy].protocol === "http:") { + return new Http1ProxyWrapper(this[kProxy].uri, { + headers: this[kProxyHeaders], + connect, + factory: agentFactory + }); + } + return agentFactory(origin2, options); + }; + this[kClient] = clientFactory(url, { connect }); + this[kAgent] = new Agent({ + ...opts, + factory, + connect: async (opts2, callback) => { + let requestedPath = opts2.host; + if (!opts2.port) { + requestedPath += `:${defaultProtocolPort(opts2.protocol)}`; + } + try { + const { socket, statusCode } = await this[kClient].connect({ + origin, + port, + path: requestedPath, + signal: opts2.signal, + headers: { + ...this[kProxyHeaders], + host: opts2.host + }, + servername: this[kProxyTls]?.servername || proxyHostname + }); + if (statusCode !== 200) { + socket.on("error", noop4).destroy(); + callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`)); + } + if (opts2.protocol !== "https:") { + callback(null, socket); + return; + } + let servername; + if (this[kRequestTls]) { + servername = this[kRequestTls].servername; + } else { + servername = opts2.servername; + } + this[kConnectEndpoint]({ ...opts2, servername, httpSocket: socket }, callback); + } catch (err) { + if (err.code === "ERR_TLS_CERT_ALTNAME_INVALID") { + callback(new SecureProxyConnectionError(err)); + } else { + callback(err); + } + } + } + }); + } + dispatch(opts, handler3) { + const headers = buildHeaders(opts.headers); + throwIfProxyAuthIsSent(headers); + if (headers && !("host" in headers) && !("Host" in headers)) { + const { host } = new URL3(opts.origin); + headers.host = host; + } + return this[kAgent].dispatch( + { + ...opts, + headers + }, + handler3 + ); + } + /** + * @param {import('../types/proxy-agent').ProxyAgent.Options | string | URL} opts + * @returns {URL} + */ + #getUrl(opts) { + if (typeof opts === "string") { + return new URL3(opts); + } else if (opts instanceof URL3) { + return opts; + } else { + return new URL3(opts.uri); + } + } + async [kClose]() { + await this[kAgent].close(); + await this[kClient].close(); + } + async [kDestroy]() { + await this[kAgent].destroy(); + await this[kClient].destroy(); + } + }; + function buildHeaders(headers) { + if (Array.isArray(headers)) { + const headersPair = {}; + for (let i = 0; i < headers.length; i += 2) { + headersPair[headers[i]] = headers[i + 1]; + } + return headersPair; + } + return headers; + } + function throwIfProxyAuthIsSent(headers) { + const existProxyAuth = headers && Object.keys(headers).find((key) => key.toLowerCase() === "proxy-authorization"); + if (existProxyAuth) { + throw new InvalidArgumentError("Proxy-Authorization should be sent in ProxyAgent constructor"); + } + } + module.exports = ProxyAgent2; + } +}); + +// +var require_env_http_proxy_agent = __commonJS({ + ""(exports, module) { + "use strict"; + var DispatcherBase = require_dispatcher_base(); + var { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require_symbols(); + var ProxyAgent2 = require_proxy_agent(); + var Agent = require_agent(); + var DEFAULT_PORTS = { + "http:": 80, + "https:": 443 + }; + var experimentalWarned = false; + var EnvHttpProxyAgent = class extends DispatcherBase { + #noProxyValue = null; + #noProxyEntries = null; + #opts = null; + constructor(opts = {}) { + super(); + this.#opts = opts; + if (!experimentalWarned) { + experimentalWarned = true; + process.emitWarning("EnvHttpProxyAgent is experimental, expect them to change at any time.", { + code: "UNDICI-EHPA" + }); + } + const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts; + this[kNoProxyAgent] = new Agent(agentOpts); + const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY; + if (HTTP_PROXY) { + this[kHttpProxyAgent] = new ProxyAgent2({ ...agentOpts, uri: HTTP_PROXY }); + } else { + this[kHttpProxyAgent] = this[kNoProxyAgent]; + } + const HTTPS_PROXY = httpsProxy ?? process.env.https_proxy ?? process.env.HTTPS_PROXY; + if (HTTPS_PROXY) { + this[kHttpsProxyAgent] = new ProxyAgent2({ ...agentOpts, uri: HTTPS_PROXY }); + } else { + this[kHttpsProxyAgent] = this[kHttpProxyAgent]; + } + this.#parseNoProxy(); + } + [kDispatch](opts, handler3) { + const url = new URL(opts.origin); + const agent = this.#getProxyAgentForUrl(url); + return agent.dispatch(opts, handler3); + } + async [kClose]() { + await this[kNoProxyAgent].close(); + if (!this[kHttpProxyAgent][kClosed]) { + await this[kHttpProxyAgent].close(); + } + if (!this[kHttpsProxyAgent][kClosed]) { + await this[kHttpsProxyAgent].close(); + } + } + async [kDestroy](err) { + await this[kNoProxyAgent].destroy(err); + if (!this[kHttpProxyAgent][kDestroyed]) { + await this[kHttpProxyAgent].destroy(err); + } + if (!this[kHttpsProxyAgent][kDestroyed]) { + await this[kHttpsProxyAgent].destroy(err); + } + } + #getProxyAgentForUrl(url) { + let { protocol, host: hostname, port } = url; + hostname = hostname.replace(/:\d*$/, "").toLowerCase(); + port = Number.parseInt(port, 10) || DEFAULT_PORTS[protocol] || 0; + if (!this.#shouldProxy(hostname, port)) { + return this[kNoProxyAgent]; + } + if (protocol === "https:") { + return this[kHttpsProxyAgent]; + } + return this[kHttpProxyAgent]; + } + #shouldProxy(hostname, port) { + if (this.#noProxyChanged) { + this.#parseNoProxy(); + } + if (this.#noProxyEntries.length === 0) { + return true; + } + if (this.#noProxyValue === "*") { + return false; + } + for (let i = 0; i < this.#noProxyEntries.length; i++) { + const entry = this.#noProxyEntries[i]; + if (entry.port && entry.port !== port) { + continue; + } + if (!/^[.*]/.test(entry.hostname)) { + if (hostname === entry.hostname) { + return false; + } + } else { + if (hostname.endsWith(entry.hostname.replace(/^\*/, ""))) { + return false; + } + } + } + return true; + } + #parseNoProxy() { + const noProxyValue = this.#opts.noProxy ?? this.#noProxyEnv; + const noProxySplit = noProxyValue.split(/[,\s]/); + const noProxyEntries = []; + for (let i = 0; i < noProxySplit.length; i++) { + const entry = noProxySplit[i]; + if (!entry) { + continue; + } + const parsed = entry.match(/^(.+):(\d+)$/); + noProxyEntries.push({ + hostname: (parsed ? parsed[1] : entry).toLowerCase(), + port: parsed ? Number.parseInt(parsed[2], 10) : 0 + }); + } + this.#noProxyValue = noProxyValue; + this.#noProxyEntries = noProxyEntries; + } + get #noProxyChanged() { + if (this.#opts.noProxy !== void 0) { + return false; + } + return this.#noProxyValue !== this.#noProxyEnv; + } + get #noProxyEnv() { + return process.env.no_proxy ?? process.env.NO_PROXY ?? ""; + } + }; + module.exports = EnvHttpProxyAgent; + } +}); + +// +var require_retry_handler = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var { kRetryHandlerDefaultRetry } = require_symbols(); + var { RequestRetryError } = require_errors(); + var { + isDisturbed, + parseHeaders, + parseRangeHeader, + wrapRequestBody + } = require_util(); + function calculateRetryAfterHeader(retryAfter) { + const current = Date.now(); + return new Date(retryAfter).getTime() - current; + } + var RetryHandler = class _RetryHandler { + constructor(opts, handlers) { + const { retryOptions, ...dispatchOpts } = opts; + const { + // Retry scoped + retry: retryFn, + maxRetries, + maxTimeout, + minTimeout, + timeoutFactor, + // Response scoped + methods, + errorCodes, + retryAfter, + statusCodes + } = retryOptions ?? {}; + this.dispatch = handlers.dispatch; + this.handler = handlers.handler; + this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }; + this.abort = null; + this.aborted = false; + this.retryOpts = { + retry: retryFn ?? _RetryHandler[kRetryHandlerDefaultRetry], + retryAfter: retryAfter ?? true, + maxTimeout: maxTimeout ?? 30 * 1e3, + // 30s, + minTimeout: minTimeout ?? 500, + // .5s + timeoutFactor: timeoutFactor ?? 2, + maxRetries: maxRetries ?? 5, + // What errors we should retry + methods: methods ?? ["GET", "HEAD", "OPTIONS", "PUT", "DELETE", "TRACE"], + // Indicates which errors to retry + statusCodes: statusCodes ?? [500, 502, 503, 504, 429], + // List of errors to retry + errorCodes: errorCodes ?? [ + "ECONNRESET", + "ECONNREFUSED", + "ENOTFOUND", + "ENETDOWN", + "ENETUNREACH", + "EHOSTDOWN", + "EHOSTUNREACH", + "EPIPE", + "UND_ERR_SOCKET" + ] + }; + this.retryCount = 0; + this.retryCountCheckpoint = 0; + this.start = 0; + this.end = null; + this.etag = null; + this.resume = null; + this.handler.onConnect((reason) => { + this.aborted = true; + if (this.abort) { + this.abort(reason); + } else { + this.reason = reason; + } + }); + } + onRequestSent() { + if (this.handler.onRequestSent) { + this.handler.onRequestSent(); + } + } + onUpgrade(statusCode, headers, socket) { + if (this.handler.onUpgrade) { + this.handler.onUpgrade(statusCode, headers, socket); + } + } + onConnect(abort) { + if (this.aborted) { + abort(this.reason); + } else { + this.abort = abort; + } + } + onBodySent(chunk) { + if (this.handler.onBodySent) + return this.handler.onBodySent(chunk); + } + static [kRetryHandlerDefaultRetry](err, { state, opts }, cb) { + const { statusCode, code, headers } = err; + const { method, retryOptions } = opts; + const { + maxRetries, + minTimeout, + maxTimeout, + timeoutFactor, + statusCodes, + errorCodes, + methods + } = retryOptions; + const { counter } = state; + if (code && code !== "UND_ERR_REQ_RETRY" && !errorCodes.includes(code)) { + cb(err); + return; + } + if (Array.isArray(methods) && !methods.includes(method)) { + cb(err); + return; + } + if (statusCode != null && Array.isArray(statusCodes) && !statusCodes.includes(statusCode)) { + cb(err); + return; + } + if (counter > maxRetries) { + cb(err); + return; + } + let retryAfterHeader = headers?.["retry-after"]; + if (retryAfterHeader) { + retryAfterHeader = Number(retryAfterHeader); + retryAfterHeader = Number.isNaN(retryAfterHeader) ? calculateRetryAfterHeader(retryAfterHeader) : retryAfterHeader * 1e3; + } + const retryTimeout = retryAfterHeader > 0 ? Math.min(retryAfterHeader, maxTimeout) : Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout); + setTimeout(() => cb(null), retryTimeout); + } + onHeaders(statusCode, rawHeaders, resume, statusMessage) { + const headers = parseHeaders(rawHeaders); + this.retryCount += 1; + if (statusCode >= 300) { + if (this.retryOpts.statusCodes.includes(statusCode) === false) { + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ); + } else { + this.abort( + new RequestRetryError("Request failed", statusCode, { + headers, + data: { + count: this.retryCount + } + }) + ); + return false; + } + } + if (this.resume != null) { + this.resume = null; + if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) { + this.abort( + new RequestRetryError("server does not support the range header and the payload was partially consumed", statusCode, { + headers, + data: { count: this.retryCount } + }) + ); + return false; + } + const contentRange = parseRangeHeader(headers["content-range"]); + if (!contentRange) { + this.abort( + new RequestRetryError("Content-Range mismatch", statusCode, { + headers, + data: { count: this.retryCount } + }) + ); + return false; + } + if (this.etag != null && this.etag !== headers.etag) { + this.abort( + new RequestRetryError("ETag mismatch", statusCode, { + headers, + data: { count: this.retryCount } + }) + ); + return false; + } + const { start, size, end = size - 1 } = contentRange; + assert2(this.start === start, "content-range mismatch"); + assert2(this.end == null || this.end === end, "content-range mismatch"); + this.resume = resume; + return true; + } + if (this.end == null) { + if (statusCode === 206) { + const range = parseRangeHeader(headers["content-range"]); + if (range == null) { + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ); + } + const { start, size, end = size - 1 } = range; + assert2( + start != null && Number.isFinite(start), + "content-range mismatch" + ); + assert2(end != null && Number.isFinite(end), "invalid content-length"); + this.start = start; + this.end = end; + } + if (this.end == null) { + const contentLength = headers["content-length"]; + this.end = contentLength != null ? Number(contentLength) - 1 : null; + } + assert2(Number.isFinite(this.start)); + assert2( + this.end == null || Number.isFinite(this.end), + "invalid content-length" + ); + this.resume = resume; + this.etag = headers.etag != null ? headers.etag : null; + if (this.etag != null && this.etag.startsWith("W/")) { + this.etag = null; + } + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ); + } + const err = new RequestRetryError("Request failed", statusCode, { + headers, + data: { count: this.retryCount } + }); + this.abort(err); + return false; + } + onData(chunk) { + this.start += chunk.length; + return this.handler.onData(chunk); + } + onComplete(rawTrailers) { + this.retryCount = 0; + return this.handler.onComplete(rawTrailers); + } + onError(err) { + if (this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err); + } + if (this.retryCount - this.retryCountCheckpoint > 0) { + this.retryCount = this.retryCountCheckpoint + (this.retryCount - this.retryCountCheckpoint); + } else { + this.retryCount += 1; + } + this.retryOpts.retry( + err, + { + state: { counter: this.retryCount }, + opts: { retryOptions: this.retryOpts, ...this.opts } + }, + onRetry.bind(this) + ); + function onRetry(err2) { + if (err2 != null || this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err2); + } + if (this.start !== 0) { + const headers = { range: `bytes=${this.start}-${this.end ?? ""}` }; + if (this.etag != null) { + headers["if-match"] = this.etag; + } + this.opts = { + ...this.opts, + headers: { + ...this.opts.headers, + ...headers + } + }; + } + try { + this.retryCountCheckpoint = this.retryCount; + this.dispatch(this.opts, this); + } catch (err3) { + this.handler.onError(err3); + } + } + } + }; + module.exports = RetryHandler; + } +}); + +// +var require_retry_agent = __commonJS({ + ""(exports, module) { + "use strict"; + var Dispatcher = require_dispatcher(); + var RetryHandler = require_retry_handler(); + var RetryAgent = class extends Dispatcher { + #agent = null; + #options = null; + constructor(agent, options = {}) { + super(options); + this.#agent = agent; + this.#options = options; + } + dispatch(opts, handler3) { + const retry = new RetryHandler({ + ...opts, + retryOptions: this.#options + }, { + dispatch: this.#agent.dispatch.bind(this.#agent), + handler: handler3 + }); + return this.#agent.dispatch(opts, retry); + } + close() { + return this.#agent.close(); + } + destroy() { + return this.#agent.destroy(); + } + }; + module.exports = RetryAgent; + } +}); + +// +var require_readable = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var { Readable } = __require("node:stream"); + var { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require_errors(); + var util = require_util(); + var { ReadableStreamFrom } = require_util(); + var kConsume = Symbol("kConsume"); + var kReading = Symbol("kReading"); + var kBody = Symbol("kBody"); + var kAbort = Symbol("kAbort"); + var kContentType = Symbol("kContentType"); + var kContentLength = Symbol("kContentLength"); + var noop4 = () => { + }; + var BodyReadable = class extends Readable { + constructor({ + resume, + abort, + contentType = "", + contentLength, + highWaterMark = 64 * 1024 + // Same as nodejs fs streams. + }) { + super({ + autoDestroy: true, + read: resume, + highWaterMark + }); + this._readableState.dataEmitted = false; + this[kAbort] = abort; + this[kConsume] = null; + this[kBody] = null; + this[kContentType] = contentType; + this[kContentLength] = contentLength; + this[kReading] = false; + } + destroy(err) { + if (!err && !this._readableState.endEmitted) { + err = new RequestAbortedError(); + } + if (err) { + this[kAbort](); + } + return super.destroy(err); + } + _destroy(err, callback) { + if (!this[kReading]) { + setImmediate(() => { + callback(err); + }); + } else { + callback(err); + } + } + on(ev, ...args) { + if (ev === "data" || ev === "readable") { + this[kReading] = true; + } + return super.on(ev, ...args); + } + addListener(ev, ...args) { + return this.on(ev, ...args); + } + off(ev, ...args) { + const ret = super.off(ev, ...args); + if (ev === "data" || ev === "readable") { + this[kReading] = this.listenerCount("data") > 0 || this.listenerCount("readable") > 0; + } + return ret; + } + removeListener(ev, ...args) { + return this.off(ev, ...args); + } + push(chunk) { + if (this[kConsume] && chunk !== null) { + consumePush(this[kConsume], chunk); + return this[kReading] ? super.push(chunk) : true; + } + return super.push(chunk); + } + // https://fetch.spec.whatwg.org/#dom-body-text + async text() { + return consume(this, "text"); + } + // https://fetch.spec.whatwg.org/#dom-body-json + async json() { + return consume(this, "json"); + } + // https://fetch.spec.whatwg.org/#dom-body-blob + async blob() { + return consume(this, "blob"); + } + // https://fetch.spec.whatwg.org/#dom-body-bytes + async bytes() { + return consume(this, "bytes"); + } + // https://fetch.spec.whatwg.org/#dom-body-arraybuffer + async arrayBuffer() { + return consume(this, "arrayBuffer"); + } + // https://fetch.spec.whatwg.org/#dom-body-formdata + async formData() { + throw new NotSupportedError(); + } + // https://fetch.spec.whatwg.org/#dom-body-bodyused + get bodyUsed() { + return util.isDisturbed(this); + } + // https://fetch.spec.whatwg.org/#dom-body-body + get body() { + if (!this[kBody]) { + this[kBody] = ReadableStreamFrom(this); + if (this[kConsume]) { + this[kBody].getReader(); + assert2(this[kBody].locked); + } + } + return this[kBody]; + } + async dump(opts) { + let limit = Number.isFinite(opts?.limit) ? opts.limit : 128 * 1024; + const signal = opts?.signal; + if (signal != null && (typeof signal !== "object" || !("aborted" in signal))) { + throw new InvalidArgumentError("signal must be an AbortSignal"); + } + signal?.throwIfAborted(); + if (this._readableState.closeEmitted) { + return null; + } + return await new Promise((resolve5, reject) => { + if (this[kContentLength] > limit) { + this.destroy(new AbortError()); + } + const onAbort = () => { + this.destroy(signal.reason ?? new AbortError()); + }; + signal?.addEventListener("abort", onAbort); + this.on("close", function() { + signal?.removeEventListener("abort", onAbort); + if (signal?.aborted) { + reject(signal.reason ?? new AbortError()); + } else { + resolve5(null); + } + }).on("error", noop4).on("data", function(chunk) { + limit -= chunk.length; + if (limit <= 0) { + this.destroy(); + } + }).resume(); + }); + } + }; + function isLocked(self2) { + return self2[kBody] && self2[kBody].locked === true || self2[kConsume]; + } + function isUnusable(self2) { + return util.isDisturbed(self2) || isLocked(self2); + } + async function consume(stream, type) { + assert2(!stream[kConsume]); + return new Promise((resolve5, reject) => { + if (isUnusable(stream)) { + const rState = stream._readableState; + if (rState.destroyed && rState.closeEmitted === false) { + stream.on("error", (err) => { + reject(err); + }).on("close", () => { + reject(new TypeError("unusable")); + }); + } else { + reject(rState.errored ?? new TypeError("unusable")); + } + } else { + queueMicrotask(() => { + stream[kConsume] = { + type, + stream, + resolve: resolve5, + reject, + length: 0, + body: [] + }; + stream.on("error", function(err) { + consumeFinish(this[kConsume], err); + }).on("close", function() { + if (this[kConsume].body !== null) { + consumeFinish(this[kConsume], new RequestAbortedError()); + } + }); + consumeStart(stream[kConsume]); + }); + } + }); + } + function consumeStart(consume2) { + if (consume2.body === null) { + return; + } + const { _readableState: state } = consume2.stream; + if (state.bufferIndex) { + const start = state.bufferIndex; + const end = state.buffer.length; + for (let n = start; n < end; n++) { + consumePush(consume2, state.buffer[n]); + } + } else { + for (const chunk of state.buffer) { + consumePush(consume2, chunk); + } + } + if (state.endEmitted) { + consumeEnd(this[kConsume]); + } else { + consume2.stream.on("end", function() { + consumeEnd(this[kConsume]); + }); + } + consume2.stream.resume(); + while (consume2.stream.read() != null) { + } + } + function chunksDecode(chunks, length) { + if (chunks.length === 0 || length === 0) { + return ""; + } + const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length); + const bufferLength = buffer.length; + const start = bufferLength > 2 && buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191 ? 3 : 0; + return buffer.utf8Slice(start, bufferLength); + } + function chunksConcat(chunks, length) { + if (chunks.length === 0 || length === 0) { + return new Uint8Array(0); + } + if (chunks.length === 1) { + return new Uint8Array(chunks[0]); + } + const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer); + let offset = 0; + for (let i = 0; i < chunks.length; ++i) { + const chunk = chunks[i]; + buffer.set(chunk, offset); + offset += chunk.length; + } + return buffer; + } + function consumeEnd(consume2) { + const { type, body, resolve: resolve5, stream, length } = consume2; + try { + if (type === "text") { + resolve5(chunksDecode(body, length)); + } else if (type === "json") { + resolve5(JSON.parse(chunksDecode(body, length))); + } else if (type === "arrayBuffer") { + resolve5(chunksConcat(body, length).buffer); + } else if (type === "blob") { + resolve5(new Blob(body, { type: stream[kContentType] })); + } else if (type === "bytes") { + resolve5(chunksConcat(body, length)); + } + consumeFinish(consume2); + } catch (err) { + stream.destroy(err); + } + } + function consumePush(consume2, chunk) { + consume2.length += chunk.length; + consume2.body.push(chunk); + } + function consumeFinish(consume2, err) { + if (consume2.body === null) { + return; + } + if (err) { + consume2.reject(err); + } else { + consume2.resolve(); + } + consume2.type = null; + consume2.stream = null; + consume2.resolve = null; + consume2.reject = null; + consume2.length = 0; + consume2.body = null; + } + module.exports = { Readable: BodyReadable, chunksDecode }; + } +}); + +// +var require_util3 = __commonJS({ + ""(exports, module) { + var assert2 = __require("node:assert"); + var { + ResponseStatusCodeError + } = require_errors(); + var { chunksDecode } = require_readable(); + var CHUNK_LIMIT = 128 * 1024; + async function getResolveErrorBodyCallback({ callback, body, contentType, statusCode, statusMessage, headers }) { + assert2(body); + let chunks = []; + let length = 0; + try { + for await (const chunk of body) { + chunks.push(chunk); + length += chunk.length; + if (length > CHUNK_LIMIT) { + chunks = []; + length = 0; + break; + } + } + } catch { + chunks = []; + length = 0; + } + const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ""}`; + if (statusCode === 204 || !contentType || !length) { + queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers))); + return; + } + const stackTraceLimit = Error.stackTraceLimit; + Error.stackTraceLimit = 0; + let payload; + try { + if (isContentTypeApplicationJson(contentType)) { + payload = JSON.parse(chunksDecode(chunks, length)); + } else if (isContentTypeText(contentType)) { + payload = chunksDecode(chunks, length); + } + } catch { + } finally { + Error.stackTraceLimit = stackTraceLimit; + } + queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers, payload))); + } + var isContentTypeApplicationJson = (contentType) => { + return contentType.length > 15 && contentType[11] === "/" && contentType[0] === "a" && contentType[1] === "p" && contentType[2] === "p" && contentType[3] === "l" && contentType[4] === "i" && contentType[5] === "c" && contentType[6] === "a" && contentType[7] === "t" && contentType[8] === "i" && contentType[9] === "o" && contentType[10] === "n" && contentType[12] === "j" && contentType[13] === "s" && contentType[14] === "o" && contentType[15] === "n"; + }; + var isContentTypeText = (contentType) => { + return contentType.length > 4 && contentType[4] === "/" && contentType[0] === "t" && contentType[1] === "e" && contentType[2] === "x" && contentType[3] === "t"; + }; + module.exports = { + getResolveErrorBodyCallback, + isContentTypeApplicationJson, + isContentTypeText + }; + } +}); + +// +var require_api_request = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var { Readable } = require_readable(); + var { InvalidArgumentError, RequestAbortedError } = require_errors(); + var util = require_util(); + var { getResolveErrorBodyCallback } = require_util3(); + var { AsyncResource } = __require("node:async_hooks"); + var RequestHandler = class extends AsyncResource { + constructor(opts, callback) { + if (!opts || typeof opts !== "object") { + throw new InvalidArgumentError("invalid opts"); + } + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError, highWaterMark } = opts; + try { + if (typeof callback !== "function") { + throw new InvalidArgumentError("invalid callback"); + } + if (highWaterMark && (typeof highWaterMark !== "number" || highWaterMark < 0)) { + throw new InvalidArgumentError("invalid highWaterMark"); + } + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") { + throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + } + if (method === "CONNECT") { + throw new InvalidArgumentError("invalid method"); + } + if (onInfo && typeof onInfo !== "function") { + throw new InvalidArgumentError("invalid onInfo callback"); + } + super("UNDICI_REQUEST"); + } catch (err) { + if (util.isStream(body)) { + util.destroy(body.on("error", util.nop), err); + } + throw err; + } + this.method = method; + this.responseHeaders = responseHeaders || null; + this.opaque = opaque || null; + this.callback = callback; + this.res = null; + this.abort = null; + this.body = body; + this.trailers = {}; + this.context = null; + this.onInfo = onInfo || null; + this.throwOnError = throwOnError; + this.highWaterMark = highWaterMark; + this.signal = signal; + this.reason = null; + this.removeAbortListener = null; + if (util.isStream(body)) { + body.on("error", (err) => { + this.onError(err); + }); + } + if (this.signal) { + if (this.signal.aborted) { + this.reason = this.signal.reason ?? new RequestAbortedError(); + } else { + this.removeAbortListener = util.addAbortListener(this.signal, () => { + this.reason = this.signal.reason ?? new RequestAbortedError(); + if (this.res) { + util.destroy(this.res.on("error", util.nop), this.reason); + } else if (this.abort) { + this.abort(this.reason); + } + if (this.removeAbortListener) { + this.res?.off("close", this.removeAbortListener); + this.removeAbortListener(); + this.removeAbortListener = null; + } + }); + } + } + } + onConnect(abort, context3) { + if (this.reason) { + abort(this.reason); + return; + } + assert2(this.callback); + this.abort = abort; + this.context = context3; + } + onHeaders(statusCode, rawHeaders, resume, statusMessage) { + const { callback, opaque, abort, context: context3, responseHeaders, highWaterMark } = this; + const headers = responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + if (statusCode < 200) { + if (this.onInfo) { + this.onInfo({ statusCode, headers }); + } + return; + } + const parsedHeaders = responseHeaders === "raw" ? util.parseHeaders(rawHeaders) : headers; + const contentType = parsedHeaders["content-type"]; + const contentLength = parsedHeaders["content-length"]; + const res = new Readable({ + resume, + abort, + contentType, + contentLength: this.method !== "HEAD" && contentLength ? Number(contentLength) : null, + highWaterMark + }); + if (this.removeAbortListener) { + res.on("close", this.removeAbortListener); + } + this.callback = null; + this.res = res; + if (callback !== null) { + if (this.throwOnError && statusCode >= 400) { + this.runInAsyncScope( + getResolveErrorBodyCallback, + null, + { callback, body: res, contentType, statusCode, statusMessage, headers } + ); + } else { + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + trailers: this.trailers, + opaque, + body: res, + context: context3 + }); + } + } + } + onData(chunk) { + return this.res.push(chunk); + } + onComplete(trailers) { + util.parseHeaders(trailers, this.trailers); + this.res.push(null); + } + onError(err) { + const { res, callback, body, opaque } = this; + if (callback) { + this.callback = null; + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }); + }); + } + if (res) { + this.res = null; + queueMicrotask(() => { + util.destroy(res, err); + }); + } + if (body) { + this.body = null; + util.destroy(body, err); + } + if (this.removeAbortListener) { + res?.off("close", this.removeAbortListener); + this.removeAbortListener(); + this.removeAbortListener = null; + } + } + }; + function request3(opts, callback) { + if (callback === void 0) { + return new Promise((resolve5, reject) => { + request3.call(this, opts, (err, data) => { + return err ? reject(err) : resolve5(data); + }); + }); + } + try { + this.dispatch(opts, new RequestHandler(opts, callback)); + } catch (err) { + if (typeof callback !== "function") { + throw err; + } + const opaque = opts?.opaque; + queueMicrotask(() => callback(err, { opaque })); + } + } + module.exports = request3; + module.exports.RequestHandler = RequestHandler; + } +}); + +// +var require_abort_signal = __commonJS({ + ""(exports, module) { + var { addAbortListener } = require_util(); + var { RequestAbortedError } = require_errors(); + var kListener = Symbol("kListener"); + var kSignal = Symbol("kSignal"); + function abort(self2) { + if (self2.abort) { + self2.abort(self2[kSignal]?.reason); + } else { + self2.reason = self2[kSignal]?.reason ?? new RequestAbortedError(); + } + removeSignal(self2); + } + function addSignal(self2, signal) { + self2.reason = null; + self2[kSignal] = null; + self2[kListener] = null; + if (!signal) { + return; + } + if (signal.aborted) { + abort(self2); + return; + } + self2[kSignal] = signal; + self2[kListener] = () => { + abort(self2); + }; + addAbortListener(self2[kSignal], self2[kListener]); + } + function removeSignal(self2) { + if (!self2[kSignal]) { + return; + } + if ("removeEventListener" in self2[kSignal]) { + self2[kSignal].removeEventListener("abort", self2[kListener]); + } else { + self2[kSignal].removeListener("abort", self2[kListener]); + } + self2[kSignal] = null; + self2[kListener] = null; + } + module.exports = { + addSignal, + removeSignal + }; + } +}); + +// +var require_api_stream = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var { finished, PassThrough } = __require("node:stream"); + var { InvalidArgumentError, InvalidReturnValueError } = require_errors(); + var util = require_util(); + var { getResolveErrorBodyCallback } = require_util3(); + var { AsyncResource } = __require("node:async_hooks"); + var { addSignal, removeSignal } = require_abort_signal(); + var StreamHandler = class extends AsyncResource { + constructor(opts, factory, callback) { + if (!opts || typeof opts !== "object") { + throw new InvalidArgumentError("invalid opts"); + } + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts; + try { + if (typeof callback !== "function") { + throw new InvalidArgumentError("invalid callback"); + } + if (typeof factory !== "function") { + throw new InvalidArgumentError("invalid factory"); + } + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") { + throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + } + if (method === "CONNECT") { + throw new InvalidArgumentError("invalid method"); + } + if (onInfo && typeof onInfo !== "function") { + throw new InvalidArgumentError("invalid onInfo callback"); + } + super("UNDICI_STREAM"); + } catch (err) { + if (util.isStream(body)) { + util.destroy(body.on("error", util.nop), err); + } + throw err; + } + this.responseHeaders = responseHeaders || null; + this.opaque = opaque || null; + this.factory = factory; + this.callback = callback; + this.res = null; + this.abort = null; + this.context = null; + this.trailers = null; + this.body = body; + this.onInfo = onInfo || null; + this.throwOnError = throwOnError || false; + if (util.isStream(body)) { + body.on("error", (err) => { + this.onError(err); + }); + } + addSignal(this, signal); + } + onConnect(abort, context3) { + if (this.reason) { + abort(this.reason); + return; + } + assert2(this.callback); + this.abort = abort; + this.context = context3; + } + onHeaders(statusCode, rawHeaders, resume, statusMessage) { + const { factory, opaque, context: context3, callback, responseHeaders } = this; + const headers = responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + if (statusCode < 200) { + if (this.onInfo) { + this.onInfo({ statusCode, headers }); + } + return; + } + this.factory = null; + let res; + if (this.throwOnError && statusCode >= 400) { + const parsedHeaders = responseHeaders === "raw" ? util.parseHeaders(rawHeaders) : headers; + const contentType = parsedHeaders["content-type"]; + res = new PassThrough(); + this.callback = null; + this.runInAsyncScope( + getResolveErrorBodyCallback, + null, + { callback, body: res, contentType, statusCode, statusMessage, headers } + ); + } else { + if (factory === null) { + return; + } + res = this.runInAsyncScope(factory, null, { + statusCode, + headers, + opaque, + context: context3 + }); + if (!res || typeof res.write !== "function" || typeof res.end !== "function" || typeof res.on !== "function") { + throw new InvalidReturnValueError("expected Writable"); + } + finished(res, { readable: false }, (err) => { + const { callback: callback2, res: res2, opaque: opaque2, trailers, abort } = this; + this.res = null; + if (err || !res2.readable) { + util.destroy(res2, err); + } + this.callback = null; + this.runInAsyncScope(callback2, null, err || null, { opaque: opaque2, trailers }); + if (err) { + abort(); + } + }); + } + res.on("drain", resume); + this.res = res; + const needDrain = res.writableNeedDrain !== void 0 ? res.writableNeedDrain : res._writableState?.needDrain; + return needDrain !== true; + } + onData(chunk) { + const { res } = this; + return res ? res.write(chunk) : true; + } + onComplete(trailers) { + const { res } = this; + removeSignal(this); + if (!res) { + return; + } + this.trailers = util.parseHeaders(trailers); + res.end(); + } + onError(err) { + const { res, callback, opaque, body } = this; + removeSignal(this); + this.factory = null; + if (res) { + this.res = null; + util.destroy(res, err); + } else if (callback) { + this.callback = null; + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }); + }); + } + if (body) { + this.body = null; + util.destroy(body, err); + } + } + }; + function stream(opts, factory, callback) { + if (callback === void 0) { + return new Promise((resolve5, reject) => { + stream.call(this, opts, factory, (err, data) => { + return err ? reject(err) : resolve5(data); + }); + }); + } + try { + this.dispatch(opts, new StreamHandler(opts, factory, callback)); + } catch (err) { + if (typeof callback !== "function") { + throw err; + } + const opaque = opts?.opaque; + queueMicrotask(() => callback(err, { opaque })); + } + } + module.exports = stream; + } +}); + +// +var require_api_pipeline = __commonJS({ + ""(exports, module) { + "use strict"; + var { + Readable, + Duplex, + PassThrough + } = __require("node:stream"); + var { + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError + } = require_errors(); + var util = require_util(); + var { AsyncResource } = __require("node:async_hooks"); + var { addSignal, removeSignal } = require_abort_signal(); + var assert2 = __require("node:assert"); + var kResume = Symbol("resume"); + var PipelineRequest = class extends Readable { + constructor() { + super({ autoDestroy: true }); + this[kResume] = null; + } + _read() { + const { [kResume]: resume } = this; + if (resume) { + this[kResume] = null; + resume(); + } + } + _destroy(err, callback) { + this._read(); + callback(err); + } + }; + var PipelineResponse = class extends Readable { + constructor(resume) { + super({ autoDestroy: true }); + this[kResume] = resume; + } + _read() { + this[kResume](); + } + _destroy(err, callback) { + if (!err && !this._readableState.endEmitted) { + err = new RequestAbortedError(); + } + callback(err); + } + }; + var PipelineHandler = class extends AsyncResource { + constructor(opts, handler3) { + if (!opts || typeof opts !== "object") { + throw new InvalidArgumentError("invalid opts"); + } + if (typeof handler3 !== "function") { + throw new InvalidArgumentError("invalid handler"); + } + const { signal, method, opaque, onInfo, responseHeaders } = opts; + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") { + throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + } + if (method === "CONNECT") { + throw new InvalidArgumentError("invalid method"); + } + if (onInfo && typeof onInfo !== "function") { + throw new InvalidArgumentError("invalid onInfo callback"); + } + super("UNDICI_PIPELINE"); + this.opaque = opaque || null; + this.responseHeaders = responseHeaders || null; + this.handler = handler3; + this.abort = null; + this.context = null; + this.onInfo = onInfo || null; + this.req = new PipelineRequest().on("error", util.nop); + this.ret = new Duplex({ + readableObjectMode: opts.objectMode, + autoDestroy: true, + read: () => { + const { body } = this; + if (body?.resume) { + body.resume(); + } + }, + write: (chunk, encoding, callback) => { + const { req } = this; + if (req.push(chunk, encoding) || req._readableState.destroyed) { + callback(); + } else { + req[kResume] = callback; + } + }, + destroy: (err, callback) => { + const { body, req, res, ret, abort } = this; + if (!err && !ret._readableState.endEmitted) { + err = new RequestAbortedError(); + } + if (abort && err) { + abort(); + } + util.destroy(body, err); + util.destroy(req, err); + util.destroy(res, err); + removeSignal(this); + callback(err); + } + }).on("prefinish", () => { + const { req } = this; + req.push(null); + }); + this.res = null; + addSignal(this, signal); + } + onConnect(abort, context3) { + const { ret, res } = this; + if (this.reason) { + abort(this.reason); + return; + } + assert2(!res, "pipeline cannot be retried"); + assert2(!ret.destroyed); + this.abort = abort; + this.context = context3; + } + onHeaders(statusCode, rawHeaders, resume) { + const { opaque, handler: handler3, context: context3 } = this; + if (statusCode < 200) { + if (this.onInfo) { + const headers = this.responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + this.onInfo({ statusCode, headers }); + } + return; + } + this.res = new PipelineResponse(resume); + let body; + try { + this.handler = null; + const headers = this.responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + body = this.runInAsyncScope(handler3, null, { + statusCode, + headers, + opaque, + body: this.res, + context: context3 + }); + } catch (err) { + this.res.on("error", util.nop); + throw err; + } + if (!body || typeof body.on !== "function") { + throw new InvalidReturnValueError("expected Readable"); + } + body.on("data", (chunk) => { + const { ret, body: body2 } = this; + if (!ret.push(chunk) && body2.pause) { + body2.pause(); + } + }).on("error", (err) => { + const { ret } = this; + util.destroy(ret, err); + }).on("end", () => { + const { ret } = this; + ret.push(null); + }).on("close", () => { + const { ret } = this; + if (!ret._readableState.ended) { + util.destroy(ret, new RequestAbortedError()); + } + }); + this.body = body; + } + onData(chunk) { + const { res } = this; + return res.push(chunk); + } + onComplete(trailers) { + const { res } = this; + res.push(null); + } + onError(err) { + const { ret } = this; + this.handler = null; + util.destroy(ret, err); + } + }; + function pipeline(opts, handler3) { + try { + const pipelineHandler = new PipelineHandler(opts, handler3); + this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler); + return pipelineHandler.ret; + } catch (err) { + return new PassThrough().destroy(err); + } + } + module.exports = pipeline; + } +}); + +// +var require_api_upgrade = __commonJS({ + ""(exports, module) { + "use strict"; + var { InvalidArgumentError, SocketError } = require_errors(); + var { AsyncResource } = __require("node:async_hooks"); + var util = require_util(); + var { addSignal, removeSignal } = require_abort_signal(); + var assert2 = __require("node:assert"); + var UpgradeHandler = class extends AsyncResource { + constructor(opts, callback) { + if (!opts || typeof opts !== "object") { + throw new InvalidArgumentError("invalid opts"); + } + if (typeof callback !== "function") { + throw new InvalidArgumentError("invalid callback"); + } + const { signal, opaque, responseHeaders } = opts; + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") { + throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + } + super("UNDICI_UPGRADE"); + this.responseHeaders = responseHeaders || null; + this.opaque = opaque || null; + this.callback = callback; + this.abort = null; + this.context = null; + addSignal(this, signal); + } + onConnect(abort, context3) { + if (this.reason) { + abort(this.reason); + return; + } + assert2(this.callback); + this.abort = abort; + this.context = null; + } + onHeaders() { + throw new SocketError("bad upgrade", null); + } + onUpgrade(statusCode, rawHeaders, socket) { + assert2(statusCode === 101); + const { callback, opaque, context: context3 } = this; + removeSignal(this); + this.callback = null; + const headers = this.responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + this.runInAsyncScope(callback, null, null, { + headers, + socket, + opaque, + context: context3 + }); + } + onError(err) { + const { callback, opaque } = this; + removeSignal(this); + if (callback) { + this.callback = null; + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }); + }); + } + } + }; + function upgrade(opts, callback) { + if (callback === void 0) { + return new Promise((resolve5, reject) => { + upgrade.call(this, opts, (err, data) => { + return err ? reject(err) : resolve5(data); + }); + }); + } + try { + const upgradeHandler = new UpgradeHandler(opts, callback); + this.dispatch({ + ...opts, + method: opts.method || "GET", + upgrade: opts.protocol || "Websocket" + }, upgradeHandler); + } catch (err) { + if (typeof callback !== "function") { + throw err; + } + const opaque = opts?.opaque; + queueMicrotask(() => callback(err, { opaque })); + } + } + module.exports = upgrade; + } +}); + +// +var require_api_connect = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var { AsyncResource } = __require("node:async_hooks"); + var { InvalidArgumentError, SocketError } = require_errors(); + var util = require_util(); + var { addSignal, removeSignal } = require_abort_signal(); + var ConnectHandler = class extends AsyncResource { + constructor(opts, callback) { + if (!opts || typeof opts !== "object") { + throw new InvalidArgumentError("invalid opts"); + } + if (typeof callback !== "function") { + throw new InvalidArgumentError("invalid callback"); + } + const { signal, opaque, responseHeaders } = opts; + if (signal && typeof signal.on !== "function" && typeof signal.addEventListener !== "function") { + throw new InvalidArgumentError("signal must be an EventEmitter or EventTarget"); + } + super("UNDICI_CONNECT"); + this.opaque = opaque || null; + this.responseHeaders = responseHeaders || null; + this.callback = callback; + this.abort = null; + addSignal(this, signal); + } + onConnect(abort, context3) { + if (this.reason) { + abort(this.reason); + return; + } + assert2(this.callback); + this.abort = abort; + this.context = context3; + } + onHeaders() { + throw new SocketError("bad connect", null); + } + onUpgrade(statusCode, rawHeaders, socket) { + const { callback, opaque, context: context3 } = this; + removeSignal(this); + this.callback = null; + let headers = rawHeaders; + if (headers != null) { + headers = this.responseHeaders === "raw" ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders); + } + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + socket, + opaque, + context: context3 + }); + } + onError(err) { + const { callback, opaque } = this; + removeSignal(this); + if (callback) { + this.callback = null; + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }); + }); + } + } + }; + function connect(opts, callback) { + if (callback === void 0) { + return new Promise((resolve5, reject) => { + connect.call(this, opts, (err, data) => { + return err ? reject(err) : resolve5(data); + }); + }); + } + try { + const connectHandler = new ConnectHandler(opts, callback); + this.dispatch({ ...opts, method: "CONNECT" }, connectHandler); + } catch (err) { + if (typeof callback !== "function") { + throw err; + } + const opaque = opts?.opaque; + queueMicrotask(() => callback(err, { opaque })); + } + } + module.exports = connect; + } +}); + +// +var require_api = __commonJS({ + ""(exports, module) { + "use strict"; + module.exports.request = require_api_request(); + module.exports.stream = require_api_stream(); + module.exports.pipeline = require_api_pipeline(); + module.exports.upgrade = require_api_upgrade(); + module.exports.connect = require_api_connect(); + } +}); + +// +var require_mock_errors = __commonJS({ + ""(exports, module) { + "use strict"; + var { UndiciError } = require_errors(); + var kMockNotMatchedError = Symbol.for("undici.error.UND_MOCK_ERR_MOCK_NOT_MATCHED"); + var MockNotMatchedError = class _MockNotMatchedError extends UndiciError { + constructor(message) { + super(message); + Error.captureStackTrace(this, _MockNotMatchedError); + this.name = "MockNotMatchedError"; + this.message = message || "The request does not match any registered mock dispatches"; + this.code = "UND_MOCK_ERR_MOCK_NOT_MATCHED"; + } + static [Symbol.hasInstance](instance) { + return instance && instance[kMockNotMatchedError] === true; + } + [kMockNotMatchedError] = true; + }; + module.exports = { + MockNotMatchedError + }; + } +}); + +// +var require_mock_symbols = __commonJS({ + ""(exports, module) { + "use strict"; + module.exports = { + kAgent: Symbol("agent"), + kOptions: Symbol("options"), + kFactory: Symbol("factory"), + kDispatches: Symbol("dispatches"), + kDispatchKey: Symbol("dispatch key"), + kDefaultHeaders: Symbol("default headers"), + kDefaultTrailers: Symbol("default trailers"), + kContentLength: Symbol("content length"), + kMockAgent: Symbol("mock agent"), + kMockAgentSet: Symbol("mock agent set"), + kMockAgentGet: Symbol("mock agent get"), + kMockDispatch: Symbol("mock dispatch"), + kClose: Symbol("close"), + kOriginalClose: Symbol("original agent close"), + kOrigin: Symbol("origin"), + kIsMockActive: Symbol("is mock active"), + kNetConnect: Symbol("net connect"), + kGetNetConnect: Symbol("get net connect"), + kConnected: Symbol("connected") + }; + } +}); + +// +var require_mock_utils = __commonJS({ + ""(exports, module) { + "use strict"; + var { MockNotMatchedError } = require_mock_errors(); + var { + kDispatches, + kMockAgent, + kOriginalDispatch, + kOrigin, + kGetNetConnect + } = require_mock_symbols(); + var { buildURL } = require_util(); + var { STATUS_CODES } = __require("node:http"); + var { + types: { + isPromise: isPromise2 + } + } = __require("node:util"); + function matchValue(match, value) { + if (typeof match === "string") { + return match === value; + } + if (match instanceof RegExp) { + return match.test(value); + } + if (typeof match === "function") { + return match(value) === true; + } + return false; + } + function lowerCaseEntries(headers) { + return Object.fromEntries( + Object.entries(headers).map(([headerName, headerValue]) => { + return [headerName.toLocaleLowerCase(), headerValue]; + }) + ); + } + function getHeaderByName(headers, key) { + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].toLocaleLowerCase() === key.toLocaleLowerCase()) { + return headers[i + 1]; + } + } + return void 0; + } else if (typeof headers.get === "function") { + return headers.get(key); + } else { + return lowerCaseEntries(headers)[key.toLocaleLowerCase()]; + } + } + function buildHeadersFromArray(headers) { + const clone = headers.slice(); + const entries = []; + for (let index = 0; index < clone.length; index += 2) { + entries.push([clone[index], clone[index + 1]]); + } + return Object.fromEntries(entries); + } + function matchHeaders(mockDispatch2, headers) { + if (typeof mockDispatch2.headers === "function") { + if (Array.isArray(headers)) { + headers = buildHeadersFromArray(headers); + } + return mockDispatch2.headers(headers ? lowerCaseEntries(headers) : {}); + } + if (typeof mockDispatch2.headers === "undefined") { + return true; + } + if (typeof headers !== "object" || typeof mockDispatch2.headers !== "object") { + return false; + } + for (const [matchHeaderName, matchHeaderValue] of Object.entries(mockDispatch2.headers)) { + const headerValue = getHeaderByName(headers, matchHeaderName); + if (!matchValue(matchHeaderValue, headerValue)) { + return false; + } + } + return true; + } + function safeUrl(path) { + if (typeof path !== "string") { + return path; + } + const pathSegments = path.split("?"); + if (pathSegments.length !== 2) { + return path; + } + const qp = new URLSearchParams(pathSegments.pop()); + qp.sort(); + return [...pathSegments, qp.toString()].join("?"); + } + function matchKey(mockDispatch2, { path, method, body, headers }) { + const pathMatch = matchValue(mockDispatch2.path, path); + const methodMatch = matchValue(mockDispatch2.method, method); + const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true; + const headersMatch = matchHeaders(mockDispatch2, headers); + return pathMatch && methodMatch && bodyMatch && headersMatch; + } + function getResponseData3(data) { + if (Buffer.isBuffer(data)) { + return data; + } else if (data instanceof Uint8Array) { + return data; + } else if (data instanceof ArrayBuffer) { + return data; + } else if (typeof data === "object") { + return JSON.stringify(data); + } else { + return data.toString(); + } + } + function getMockDispatch(mockDispatches, key) { + const basePath = key.query ? buildURL(key.path, key.query) : key.path; + const resolvedPath = typeof basePath === "string" ? safeUrl(basePath) : basePath; + let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(safeUrl(path), resolvedPath)); + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`); + } + matchedMockDispatches = matchedMockDispatches.filter(({ method }) => matchValue(method, key.method)); + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for method '${key.method}' on path '${resolvedPath}'`); + } + matchedMockDispatches = matchedMockDispatches.filter(({ body }) => typeof body !== "undefined" ? matchValue(body, key.body) : true); + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for body '${key.body}' on path '${resolvedPath}'`); + } + matchedMockDispatches = matchedMockDispatches.filter((mockDispatch2) => matchHeaders(mockDispatch2, key.headers)); + if (matchedMockDispatches.length === 0) { + const headers = typeof key.headers === "object" ? JSON.stringify(key.headers) : key.headers; + throw new MockNotMatchedError(`Mock dispatch not matched for headers '${headers}' on path '${resolvedPath}'`); + } + return matchedMockDispatches[0]; + } + function addMockDispatch(mockDispatches, key, data) { + const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false }; + const replyData = typeof data === "function" ? { callback: data } : { ...data }; + const newMockDispatch = { ...baseData, ...key, pending: true, data: { error: null, ...replyData } }; + mockDispatches.push(newMockDispatch); + return newMockDispatch; + } + function deleteMockDispatch(mockDispatches, key) { + const index = mockDispatches.findIndex((dispatch) => { + if (!dispatch.consumed) { + return false; + } + return matchKey(dispatch, key); + }); + if (index !== -1) { + mockDispatches.splice(index, 1); + } + } + function buildKey(opts) { + const { path, method, body, headers, query: query2 } = opts; + return { + path, + method, + body, + headers, + query: query2 + }; + } + function generateKeyValues(data) { + const keys = Object.keys(data); + const result = []; + for (let i = 0; i < keys.length; ++i) { + const key = keys[i]; + const value = data[key]; + const name = Buffer.from(`${key}`); + if (Array.isArray(value)) { + for (let j = 0; j < value.length; ++j) { + result.push(name, Buffer.from(`${value[j]}`)); + } + } else { + result.push(name, Buffer.from(`${value}`)); + } + } + return result; + } + function getStatusText(statusCode) { + return STATUS_CODES[statusCode] || "unknown"; + } + async function getResponse(body) { + const buffers = []; + for await (const data of body) { + buffers.push(data); + } + return Buffer.concat(buffers).toString("utf8"); + } + function mockDispatch(opts, handler3) { + const key = buildKey(opts); + const mockDispatch2 = getMockDispatch(this[kDispatches], key); + mockDispatch2.timesInvoked++; + if (mockDispatch2.data.callback) { + mockDispatch2.data = { ...mockDispatch2.data, ...mockDispatch2.data.callback(opts) }; + } + const { data: { statusCode, data, headers, trailers, error: error2 }, delay, persist } = mockDispatch2; + const { timesInvoked, times } = mockDispatch2; + mockDispatch2.consumed = !persist && timesInvoked >= times; + mockDispatch2.pending = timesInvoked < times; + if (error2 !== null) { + deleteMockDispatch(this[kDispatches], key); + handler3.onError(error2); + return true; + } + if (typeof delay === "number" && delay > 0) { + setTimeout(() => { + handleReply(this[kDispatches]); + }, delay); + } else { + handleReply(this[kDispatches]); + } + function handleReply(mockDispatches, _data = data) { + const optsHeaders = Array.isArray(opts.headers) ? buildHeadersFromArray(opts.headers) : opts.headers; + const body = typeof _data === "function" ? _data({ ...opts, headers: optsHeaders }) : _data; + if (isPromise2(body)) { + body.then((newData) => handleReply(mockDispatches, newData)); + return; + } + const responseData = getResponseData3(body); + const responseHeaders = generateKeyValues(headers); + const responseTrailers = generateKeyValues(trailers); + handler3.onConnect?.((err) => handler3.onError(err), null); + handler3.onHeaders?.(statusCode, responseHeaders, resume, getStatusText(statusCode)); + handler3.onData?.(Buffer.from(responseData)); + handler3.onComplete?.(responseTrailers); + deleteMockDispatch(mockDispatches, key); + } + function resume() { + } + return true; + } + function buildMockDispatch() { + const agent = this[kMockAgent]; + const origin = this[kOrigin]; + const originalDispatch = this[kOriginalDispatch]; + return function dispatch(opts, handler3) { + if (agent.isMockActive) { + try { + mockDispatch.call(this, opts, handler3); + } catch (error2) { + if (error2 instanceof MockNotMatchedError) { + const netConnect = agent[kGetNetConnect](); + if (netConnect === false) { + throw new MockNotMatchedError(`${error2.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`); + } + if (checkNetConnect(netConnect, origin)) { + originalDispatch.call(this, opts, handler3); + } else { + throw new MockNotMatchedError(`${error2.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)`); + } + } else { + throw error2; + } + } + } else { + originalDispatch.call(this, opts, handler3); + } + }; + } + function checkNetConnect(netConnect, origin) { + const url = new URL(origin); + if (netConnect === true) { + return true; + } else if (Array.isArray(netConnect) && netConnect.some((matcher) => matchValue(matcher, url.host))) { + return true; + } + return false; + } + function buildMockOptions(opts) { + if (opts) { + const { agent, ...mockOptions } = opts; + return mockOptions; + } + } + module.exports = { + getResponseData: getResponseData3, + getMockDispatch, + addMockDispatch, + deleteMockDispatch, + buildKey, + generateKeyValues, + matchValue, + getResponse, + getStatusText, + mockDispatch, + buildMockDispatch, + checkNetConnect, + buildMockOptions, + getHeaderByName, + buildHeadersFromArray + }; + } +}); + +// +var require_mock_interceptor = __commonJS({ + ""(exports, module) { + "use strict"; + var { getResponseData: getResponseData3, buildKey, addMockDispatch } = require_mock_utils(); + var { + kDispatches, + kDispatchKey, + kDefaultHeaders, + kDefaultTrailers, + kContentLength, + kMockDispatch + } = require_mock_symbols(); + var { InvalidArgumentError } = require_errors(); + var { buildURL } = require_util(); + var MockScope = class { + constructor(mockDispatch) { + this[kMockDispatch] = mockDispatch; + } + /** + * Delay a reply by a set amount in ms. + */ + delay(waitInMs) { + if (typeof waitInMs !== "number" || !Number.isInteger(waitInMs) || waitInMs <= 0) { + throw new InvalidArgumentError("waitInMs must be a valid integer > 0"); + } + this[kMockDispatch].delay = waitInMs; + return this; + } + /** + * For a defined reply, never mark as consumed. + */ + persist() { + this[kMockDispatch].persist = true; + return this; + } + /** + * Allow one to define a reply for a set amount of matching requests. + */ + times(repeatTimes) { + if (typeof repeatTimes !== "number" || !Number.isInteger(repeatTimes) || repeatTimes <= 0) { + throw new InvalidArgumentError("repeatTimes must be a valid integer > 0"); + } + this[kMockDispatch].times = repeatTimes; + return this; + } + }; + var MockInterceptor = class { + constructor(opts, mockDispatches) { + if (typeof opts !== "object") { + throw new InvalidArgumentError("opts must be an object"); + } + if (typeof opts.path === "undefined") { + throw new InvalidArgumentError("opts.path must be defined"); + } + if (typeof opts.method === "undefined") { + opts.method = "GET"; + } + if (typeof opts.path === "string") { + if (opts.query) { + opts.path = buildURL(opts.path, opts.query); + } else { + const parsedURL = new URL(opts.path, "data://"); + opts.path = parsedURL.pathname + parsedURL.search; + } + } + if (typeof opts.method === "string") { + opts.method = opts.method.toUpperCase(); + } + this[kDispatchKey] = buildKey(opts); + this[kDispatches] = mockDispatches; + this[kDefaultHeaders] = {}; + this[kDefaultTrailers] = {}; + this[kContentLength] = false; + } + createMockScopeDispatchData({ statusCode, data, responseOptions }) { + const responseData = getResponseData3(data); + const contentLength = this[kContentLength] ? { "content-length": responseData.length } : {}; + const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers }; + const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers }; + return { statusCode, data, headers, trailers }; + } + validateReplyParameters(replyParameters) { + if (typeof replyParameters.statusCode === "undefined") { + throw new InvalidArgumentError("statusCode must be defined"); + } + if (typeof replyParameters.responseOptions !== "object" || replyParameters.responseOptions === null) { + throw new InvalidArgumentError("responseOptions must be an object"); + } + } + /** + * Mock an undici request with a defined reply. + */ + reply(replyOptionsCallbackOrStatusCode) { + if (typeof replyOptionsCallbackOrStatusCode === "function") { + const wrappedDefaultsCallback = (opts) => { + const resolvedData = replyOptionsCallbackOrStatusCode(opts); + if (typeof resolvedData !== "object" || resolvedData === null) { + throw new InvalidArgumentError("reply options callback must return an object"); + } + const replyParameters2 = { data: "", responseOptions: {}, ...resolvedData }; + this.validateReplyParameters(replyParameters2); + return { + ...this.createMockScopeDispatchData(replyParameters2) + }; + }; + const newMockDispatch2 = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback); + return new MockScope(newMockDispatch2); + } + const replyParameters = { + statusCode: replyOptionsCallbackOrStatusCode, + data: arguments[1] === void 0 ? "" : arguments[1], + responseOptions: arguments[2] === void 0 ? {} : arguments[2] + }; + this.validateReplyParameters(replyParameters); + const dispatchData = this.createMockScopeDispatchData(replyParameters); + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData); + return new MockScope(newMockDispatch); + } + /** + * Mock an undici request with a defined error. + */ + replyWithError(error2) { + if (typeof error2 === "undefined") { + throw new InvalidArgumentError("error must be defined"); + } + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error: error2 }); + return new MockScope(newMockDispatch); + } + /** + * Set default reply headers on the interceptor for subsequent replies + */ + defaultReplyHeaders(headers) { + if (typeof headers === "undefined") { + throw new InvalidArgumentError("headers must be defined"); + } + this[kDefaultHeaders] = headers; + return this; + } + /** + * Set default reply trailers on the interceptor for subsequent replies + */ + defaultReplyTrailers(trailers) { + if (typeof trailers === "undefined") { + throw new InvalidArgumentError("trailers must be defined"); + } + this[kDefaultTrailers] = trailers; + return this; + } + /** + * Set reply content length header for replies on the interceptor + */ + replyContentLength() { + this[kContentLength] = true; + return this; + } + }; + module.exports.MockInterceptor = MockInterceptor; + module.exports.MockScope = MockScope; + } +}); + +// +var require_mock_client = __commonJS({ + ""(exports, module) { + "use strict"; + var { promisify } = __require("node:util"); + var Client = require_client(); + var { buildMockDispatch } = require_mock_utils(); + var { + kDispatches, + kMockAgent, + kClose, + kOriginalClose, + kOrigin, + kOriginalDispatch, + kConnected + } = require_mock_symbols(); + var { MockInterceptor } = require_mock_interceptor(); + var Symbols = require_symbols(); + var { InvalidArgumentError } = require_errors(); + var MockClient = class extends Client { + constructor(origin, opts) { + super(origin, opts); + if (!opts || !opts.agent || typeof opts.agent.dispatch !== "function") { + throw new InvalidArgumentError("Argument opts.agent must implement Agent"); + } + this[kMockAgent] = opts.agent; + this[kOrigin] = origin; + this[kDispatches] = []; + this[kConnected] = 1; + this[kOriginalDispatch] = this.dispatch; + this[kOriginalClose] = this.close.bind(this); + this.dispatch = buildMockDispatch.call(this); + this.close = this[kClose]; + } + get [Symbols.kConnected]() { + return this[kConnected]; + } + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept(opts) { + return new MockInterceptor(opts, this[kDispatches]); + } + async [kClose]() { + await promisify(this[kOriginalClose])(); + this[kConnected] = 0; + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]); + } + }; + module.exports = MockClient; + } +}); + +// +var require_mock_pool = __commonJS({ + ""(exports, module) { + "use strict"; + var { promisify } = __require("node:util"); + var Pool = require_pool(); + var { buildMockDispatch } = require_mock_utils(); + var { + kDispatches, + kMockAgent, + kClose, + kOriginalClose, + kOrigin, + kOriginalDispatch, + kConnected + } = require_mock_symbols(); + var { MockInterceptor } = require_mock_interceptor(); + var Symbols = require_symbols(); + var { InvalidArgumentError } = require_errors(); + var MockPool = class extends Pool { + constructor(origin, opts) { + super(origin, opts); + if (!opts || !opts.agent || typeof opts.agent.dispatch !== "function") { + throw new InvalidArgumentError("Argument opts.agent must implement Agent"); + } + this[kMockAgent] = opts.agent; + this[kOrigin] = origin; + this[kDispatches] = []; + this[kConnected] = 1; + this[kOriginalDispatch] = this.dispatch; + this[kOriginalClose] = this.close.bind(this); + this.dispatch = buildMockDispatch.call(this); + this.close = this[kClose]; + } + get [Symbols.kConnected]() { + return this[kConnected]; + } + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept(opts) { + return new MockInterceptor(opts, this[kDispatches]); + } + async [kClose]() { + await promisify(this[kOriginalClose])(); + this[kConnected] = 0; + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]); + } + }; + module.exports = MockPool; + } +}); + +// +var require_pluralizer = __commonJS({ + ""(exports, module) { + "use strict"; + var singulars = { + pronoun: "it", + is: "is", + was: "was", + this: "this" + }; + var plurals = { + pronoun: "they", + is: "are", + was: "were", + this: "these" + }; + module.exports = class Pluralizer { + constructor(singular, plural) { + this.singular = singular; + this.plural = plural; + } + pluralize(count) { + const one = count === 1; + const keys = one ? singulars : plurals; + const noun = one ? this.singular : this.plural; + return { ...keys, count, noun }; + } + }; + } +}); + +// +var require_pending_interceptors_formatter = __commonJS({ + ""(exports, module) { + "use strict"; + var { Transform } = __require("node:stream"); + var { Console } = __require("node:console"); + var PERSISTENT = process.versions.icu ? "\u2705" : "Y "; + var NOT_PERSISTENT = process.versions.icu ? "\u274C" : "N "; + module.exports = class PendingInterceptorsFormatter { + constructor({ disableColors } = {}) { + this.transform = new Transform({ + transform(chunk, _enc, cb) { + cb(null, chunk); + } + }); + this.logger = new Console({ + stdout: this.transform, + inspectOptions: { + colors: !disableColors && !process.env.CI + } + }); + } + format(pendingInterceptors) { + const withPrettyHeaders = pendingInterceptors.map( + ({ method, path, data: { statusCode }, persist, times, timesInvoked, origin }) => ({ + Method: method, + Origin: origin, + Path: path, + "Status code": statusCode, + Persistent: persist ? PERSISTENT : NOT_PERSISTENT, + Invocations: timesInvoked, + Remaining: persist ? Infinity : times - timesInvoked + }) + ); + this.logger.table(withPrettyHeaders); + return this.transform.read().toString(); + } + }; + } +}); + +// +var require_mock_agent = __commonJS({ + ""(exports, module) { + "use strict"; + var { kClients } = require_symbols(); + var Agent = require_agent(); + var { + kAgent, + kMockAgentSet, + kMockAgentGet, + kDispatches, + kIsMockActive, + kNetConnect, + kGetNetConnect, + kOptions, + kFactory + } = require_mock_symbols(); + var MockClient = require_mock_client(); + var MockPool = require_mock_pool(); + var { matchValue, buildMockOptions } = require_mock_utils(); + var { InvalidArgumentError, UndiciError } = require_errors(); + var Dispatcher = require_dispatcher(); + var Pluralizer = require_pluralizer(); + var PendingInterceptorsFormatter = require_pending_interceptors_formatter(); + var MockAgent = class extends Dispatcher { + constructor(opts) { + super(opts); + this[kNetConnect] = true; + this[kIsMockActive] = true; + if (opts?.agent && typeof opts.agent.dispatch !== "function") { + throw new InvalidArgumentError("Argument opts.agent must implement Agent"); + } + const agent = opts?.agent ? opts.agent : new Agent(opts); + this[kAgent] = agent; + this[kClients] = agent[kClients]; + this[kOptions] = buildMockOptions(opts); + } + get(origin) { + let dispatcher = this[kMockAgentGet](origin); + if (!dispatcher) { + dispatcher = this[kFactory](origin); + this[kMockAgentSet](origin, dispatcher); + } + return dispatcher; + } + dispatch(opts, handler3) { + this.get(opts.origin); + return this[kAgent].dispatch(opts, handler3); + } + async close() { + await this[kAgent].close(); + this[kClients].clear(); + } + deactivate() { + this[kIsMockActive] = false; + } + activate() { + this[kIsMockActive] = true; + } + enableNetConnect(matcher) { + if (typeof matcher === "string" || typeof matcher === "function" || matcher instanceof RegExp) { + if (Array.isArray(this[kNetConnect])) { + this[kNetConnect].push(matcher); + } else { + this[kNetConnect] = [matcher]; + } + } else if (typeof matcher === "undefined") { + this[kNetConnect] = true; + } else { + throw new InvalidArgumentError("Unsupported matcher. Must be one of String|Function|RegExp."); + } + } + disableNetConnect() { + this[kNetConnect] = false; + } + // This is required to bypass issues caused by using global symbols - see: + // https://github.com/nodejs/undici/issues/1447 + get isMockActive() { + return this[kIsMockActive]; + } + [kMockAgentSet](origin, dispatcher) { + this[kClients].set(origin, dispatcher); + } + [kFactory](origin) { + const mockOptions = Object.assign({ agent: this }, this[kOptions]); + return this[kOptions] && this[kOptions].connections === 1 ? new MockClient(origin, mockOptions) : new MockPool(origin, mockOptions); + } + [kMockAgentGet](origin) { + const client = this[kClients].get(origin); + if (client) { + return client; + } + if (typeof origin !== "string") { + const dispatcher = this[kFactory]("http://localhost:9999"); + this[kMockAgentSet](origin, dispatcher); + return dispatcher; + } + for (const [keyMatcher, nonExplicitDispatcher] of Array.from(this[kClients])) { + if (nonExplicitDispatcher && typeof keyMatcher !== "string" && matchValue(keyMatcher, origin)) { + const dispatcher = this[kFactory](origin); + this[kMockAgentSet](origin, dispatcher); + dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches]; + return dispatcher; + } + } + } + [kGetNetConnect]() { + return this[kNetConnect]; + } + pendingInterceptors() { + const mockAgentClients = this[kClients]; + return Array.from(mockAgentClients.entries()).flatMap(([origin, scope]) => scope[kDispatches].map((dispatch) => ({ ...dispatch, origin }))).filter(({ pending }) => pending); + } + assertNoPendingInterceptors({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) { + const pending = this.pendingInterceptors(); + if (pending.length === 0) { + return; + } + const pluralizer = new Pluralizer("interceptor", "interceptors").pluralize(pending.length); + throw new UndiciError(` +${pluralizer.count} ${pluralizer.noun} ${pluralizer.is} pending: + +${pendingInterceptorsFormatter.format(pending)} +`.trim()); + } + }; + module.exports = MockAgent; + } +}); + +// +var require_global2 = __commonJS({ + ""(exports, module) { + "use strict"; + var globalDispatcher = Symbol.for("undici.globalDispatcher.1"); + var { InvalidArgumentError } = require_errors(); + var Agent = require_agent(); + if (getGlobalDispatcher() === void 0) { + setGlobalDispatcher(new Agent()); + } + function setGlobalDispatcher(agent) { + if (!agent || typeof agent.dispatch !== "function") { + throw new InvalidArgumentError("Argument agent must implement Agent"); + } + Object.defineProperty(globalThis, globalDispatcher, { + value: agent, + writable: true, + enumerable: false, + configurable: false + }); + } + function getGlobalDispatcher() { + return globalThis[globalDispatcher]; + } + module.exports = { + setGlobalDispatcher, + getGlobalDispatcher + }; + } +}); + +// +var require_decorator_handler = __commonJS({ + ""(exports, module) { + "use strict"; + module.exports = class DecoratorHandler { + #handler; + constructor(handler3) { + if (typeof handler3 !== "object" || handler3 === null) { + throw new TypeError("handler must be an object"); + } + this.#handler = handler3; + } + onConnect(...args) { + return this.#handler.onConnect?.(...args); + } + onError(...args) { + return this.#handler.onError?.(...args); + } + onUpgrade(...args) { + return this.#handler.onUpgrade?.(...args); + } + onResponseStarted(...args) { + return this.#handler.onResponseStarted?.(...args); + } + onHeaders(...args) { + return this.#handler.onHeaders?.(...args); + } + onData(...args) { + return this.#handler.onData?.(...args); + } + onComplete(...args) { + return this.#handler.onComplete?.(...args); + } + onBodySent(...args) { + return this.#handler.onBodySent?.(...args); + } + }; + } +}); + +// +var require_redirect = __commonJS({ + ""(exports, module) { + "use strict"; + var RedirectHandler = require_redirect_handler(); + module.exports = (opts) => { + const globalMaxRedirections = opts?.maxRedirections; + return (dispatch) => { + return function redirectInterceptor(opts2, handler3) { + const { maxRedirections = globalMaxRedirections, ...baseOpts } = opts2; + if (!maxRedirections) { + return dispatch(opts2, handler3); + } + const redirectHandler = new RedirectHandler( + dispatch, + maxRedirections, + opts2, + handler3 + ); + return dispatch(baseOpts, redirectHandler); + }; + }; + }; + } +}); + +// +var require_retry = __commonJS({ + ""(exports, module) { + "use strict"; + var RetryHandler = require_retry_handler(); + module.exports = (globalOpts) => { + return (dispatch) => { + return function retryInterceptor(opts, handler3) { + return dispatch( + opts, + new RetryHandler( + { ...opts, retryOptions: { ...globalOpts, ...opts.retryOptions } }, + { + handler: handler3, + dispatch + } + ) + ); + }; + }; + }; + } +}); + +// +var require_dump = __commonJS({ + ""(exports, module) { + "use strict"; + var util = require_util(); + var { InvalidArgumentError, RequestAbortedError } = require_errors(); + var DecoratorHandler = require_decorator_handler(); + var DumpHandler = class extends DecoratorHandler { + #maxSize = 1024 * 1024; + #abort = null; + #dumped = false; + #aborted = false; + #size = 0; + #reason = null; + #handler = null; + constructor({ maxSize }, handler3) { + super(handler3); + if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) { + throw new InvalidArgumentError("maxSize must be a number greater than 0"); + } + this.#maxSize = maxSize ?? this.#maxSize; + this.#handler = handler3; + } + onConnect(abort) { + this.#abort = abort; + this.#handler.onConnect(this.#customAbort.bind(this)); + } + #customAbort(reason) { + this.#aborted = true; + this.#reason = reason; + } + // TODO: will require adjustment after new hooks are out + onHeaders(statusCode, rawHeaders, resume, statusMessage) { + const headers = util.parseHeaders(rawHeaders); + const contentLength = headers["content-length"]; + if (contentLength != null && contentLength > this.#maxSize) { + throw new RequestAbortedError( + `Response size (${contentLength}) larger than maxSize (${this.#maxSize})` + ); + } + if (this.#aborted) { + return true; + } + return this.#handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ); + } + onError(err) { + if (this.#dumped) { + return; + } + err = this.#reason ?? err; + this.#handler.onError(err); + } + onData(chunk) { + this.#size = this.#size + chunk.length; + if (this.#size >= this.#maxSize) { + this.#dumped = true; + if (this.#aborted) { + this.#handler.onError(this.#reason); + } else { + this.#handler.onComplete([]); + } + } + return true; + } + onComplete(trailers) { + if (this.#dumped) { + return; + } + if (this.#aborted) { + this.#handler.onError(this.reason); + return; + } + this.#handler.onComplete(trailers); + } + }; + function createDumpInterceptor({ maxSize: defaultMaxSize } = { + maxSize: 1024 * 1024 + }) { + return (dispatch) => { + return function Intercept(opts, handler3) { + const { dumpMaxSize = defaultMaxSize } = opts; + const dumpHandler = new DumpHandler( + { maxSize: dumpMaxSize }, + handler3 + ); + return dispatch(opts, dumpHandler); + }; + }; + } + module.exports = createDumpInterceptor; + } +}); + +// +var require_dns = __commonJS({ + ""(exports, module) { + "use strict"; + var { isIP } = __require("node:net"); + var { lookup } = __require("node:dns"); + var DecoratorHandler = require_decorator_handler(); + var { InvalidArgumentError, InformationalError } = require_errors(); + var maxInt = Math.pow(2, 31) - 1; + var DNSInstance = class { + #maxTTL = 0; + #maxItems = 0; + #records = /* @__PURE__ */ new Map(); + dualStack = true; + affinity = null; + lookup = null; + pick = null; + constructor(opts) { + this.#maxTTL = opts.maxTTL; + this.#maxItems = opts.maxItems; + this.dualStack = opts.dualStack; + this.affinity = opts.affinity; + this.lookup = opts.lookup ?? this.#defaultLookup; + this.pick = opts.pick ?? this.#defaultPick; + } + get full() { + return this.#records.size === this.#maxItems; + } + runLookup(origin, opts, cb) { + const ips = this.#records.get(origin.hostname); + if (ips == null && this.full) { + cb(null, origin.origin); + return; + } + const newOpts = { + affinity: this.affinity, + dualStack: this.dualStack, + lookup: this.lookup, + pick: this.pick, + ...opts.dns, + maxTTL: this.#maxTTL, + maxItems: this.#maxItems + }; + if (ips == null) { + this.lookup(origin, newOpts, (err, addresses) => { + if (err || addresses == null || addresses.length === 0) { + cb(err ?? new InformationalError("No DNS entries found")); + return; + } + this.setRecords(origin, addresses); + const records = this.#records.get(origin.hostname); + const ip = this.pick( + origin, + records, + newOpts.affinity + ); + let port; + if (typeof ip.port === "number") { + port = `:${ip.port}`; + } else if (origin.port !== "") { + port = `:${origin.port}`; + } else { + port = ""; + } + cb( + null, + `${origin.protocol}//${ip.family === 6 ? `[${ip.address}]` : ip.address}${port}` + ); + }); + } else { + const ip = this.pick( + origin, + ips, + newOpts.affinity + ); + if (ip == null) { + this.#records.delete(origin.hostname); + this.runLookup(origin, opts, cb); + return; + } + let port; + if (typeof ip.port === "number") { + port = `:${ip.port}`; + } else if (origin.port !== "") { + port = `:${origin.port}`; + } else { + port = ""; + } + cb( + null, + `${origin.protocol}//${ip.family === 6 ? `[${ip.address}]` : ip.address}${port}` + ); + } + } + #defaultLookup(origin, opts, cb) { + lookup( + origin.hostname, + { + all: true, + family: this.dualStack === false ? this.affinity : 0, + order: "ipv4first" + }, + (err, addresses) => { + if (err) { + return cb(err); + } + const results = /* @__PURE__ */ new Map(); + for (const addr of addresses) { + results.set(`${addr.address}:${addr.family}`, addr); + } + cb(null, results.values()); + } + ); + } + #defaultPick(origin, hostnameRecords, affinity) { + let ip = null; + const { records, offset } = hostnameRecords; + let family; + if (this.dualStack) { + if (affinity == null) { + if (offset == null || offset === maxInt) { + hostnameRecords.offset = 0; + affinity = 4; + } else { + hostnameRecords.offset++; + affinity = (hostnameRecords.offset & 1) === 1 ? 6 : 4; + } + } + if (records[affinity] != null && records[affinity].ips.length > 0) { + family = records[affinity]; + } else { + family = records[affinity === 4 ? 6 : 4]; + } + } else { + family = records[affinity]; + } + if (family == null || family.ips.length === 0) { + return ip; + } + if (family.offset == null || family.offset === maxInt) { + family.offset = 0; + } else { + family.offset++; + } + const position = family.offset % family.ips.length; + ip = family.ips[position] ?? null; + if (ip == null) { + return ip; + } + if (Date.now() - ip.timestamp > ip.ttl) { + family.ips.splice(position, 1); + return this.pick(origin, hostnameRecords, affinity); + } + return ip; + } + setRecords(origin, addresses) { + const timestamp = Date.now(); + const records = { records: { 4: null, 6: null } }; + for (const record of addresses) { + record.timestamp = timestamp; + if (typeof record.ttl === "number") { + record.ttl = Math.min(record.ttl, this.#maxTTL); + } else { + record.ttl = this.#maxTTL; + } + const familyRecords = records.records[record.family] ?? { ips: [] }; + familyRecords.ips.push(record); + records.records[record.family] = familyRecords; + } + this.#records.set(origin.hostname, records); + } + getHandler(meta, opts) { + return new DNSDispatchHandler(this, meta, opts); + } + }; + var DNSDispatchHandler = class extends DecoratorHandler { + #state = null; + #opts = null; + #dispatch = null; + #handler = null; + #origin = null; + constructor(state, { origin, handler: handler3, dispatch }, opts) { + super(handler3); + this.#origin = origin; + this.#handler = handler3; + this.#opts = { ...opts }; + this.#state = state; + this.#dispatch = dispatch; + } + onError(err) { + switch (err.code) { + case "ETIMEDOUT": + case "ECONNREFUSED": { + if (this.#state.dualStack) { + this.#state.runLookup(this.#origin, this.#opts, (err2, newOrigin) => { + if (err2) { + return this.#handler.onError(err2); + } + const dispatchOpts = { + ...this.#opts, + origin: newOrigin + }; + this.#dispatch(dispatchOpts, this); + }); + return; + } + this.#handler.onError(err); + return; + } + case "ENOTFOUND": + this.#state.deleteRecord(this.#origin); + default: + this.#handler.onError(err); + break; + } + } + }; + module.exports = (interceptorOpts) => { + if (interceptorOpts?.maxTTL != null && (typeof interceptorOpts?.maxTTL !== "number" || interceptorOpts?.maxTTL < 0)) { + throw new InvalidArgumentError("Invalid maxTTL. Must be a positive number"); + } + if (interceptorOpts?.maxItems != null && (typeof interceptorOpts?.maxItems !== "number" || interceptorOpts?.maxItems < 1)) { + throw new InvalidArgumentError( + "Invalid maxItems. Must be a positive number and greater than zero" + ); + } + if (interceptorOpts?.affinity != null && interceptorOpts?.affinity !== 4 && interceptorOpts?.affinity !== 6) { + throw new InvalidArgumentError("Invalid affinity. Must be either 4 or 6"); + } + if (interceptorOpts?.dualStack != null && typeof interceptorOpts?.dualStack !== "boolean") { + throw new InvalidArgumentError("Invalid dualStack. Must be a boolean"); + } + if (interceptorOpts?.lookup != null && typeof interceptorOpts?.lookup !== "function") { + throw new InvalidArgumentError("Invalid lookup. Must be a function"); + } + if (interceptorOpts?.pick != null && typeof interceptorOpts?.pick !== "function") { + throw new InvalidArgumentError("Invalid pick. Must be a function"); + } + const dualStack = interceptorOpts?.dualStack ?? true; + let affinity; + if (dualStack) { + affinity = interceptorOpts?.affinity ?? null; + } else { + affinity = interceptorOpts?.affinity ?? 4; + } + const opts = { + maxTTL: interceptorOpts?.maxTTL ?? 1e4, + // Expressed in ms + lookup: interceptorOpts?.lookup ?? null, + pick: interceptorOpts?.pick ?? null, + dualStack, + affinity, + maxItems: interceptorOpts?.maxItems ?? Infinity + }; + const instance = new DNSInstance(opts); + return (dispatch) => { + return function dnsInterceptor(origDispatchOpts, handler3) { + const origin = origDispatchOpts.origin.constructor === URL ? origDispatchOpts.origin : new URL(origDispatchOpts.origin); + if (isIP(origin.hostname) !== 0) { + return dispatch(origDispatchOpts, handler3); + } + instance.runLookup(origin, origDispatchOpts, (err, newOrigin) => { + if (err) { + return handler3.onError(err); + } + let dispatchOpts = null; + dispatchOpts = { + ...origDispatchOpts, + servername: origin.hostname, + // For SNI on TLS + origin: newOrigin, + headers: { + host: origin.hostname, + ...origDispatchOpts.headers + } + }; + dispatch( + dispatchOpts, + instance.getHandler({ origin, dispatch, handler: handler3 }, origDispatchOpts) + ); + }); + return true; + }; + }; + }; + } +}); + +// +var require_headers = __commonJS({ + ""(exports, module) { + "use strict"; + var { kConstruct } = require_symbols(); + var { kEnumerableProperty } = require_util(); + var { + iteratorMixin, + isValidHeaderName, + isValidHeaderValue + } = require_util2(); + var { webidl } = require_webidl(); + var assert2 = __require("node:assert"); + var util = __require("node:util"); + var kHeadersMap = Symbol("headers map"); + var kHeadersSortedMap = Symbol("headers map sorted"); + function isHTTPWhiteSpaceCharCode(code) { + return code === 10 || code === 13 || code === 9 || code === 32; + } + function headerValueNormalize(potentialValue) { + let i = 0; + let j = potentialValue.length; + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) + --j; + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) + ++i; + return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j); + } + function fill(headers, object) { + if (Array.isArray(object)) { + for (let i = 0; i < object.length; ++i) { + const header = object[i]; + if (header.length !== 2) { + throw webidl.errors.exception({ + header: "Headers constructor", + message: `expected name/value pair to be length 2, found ${header.length}.` + }); + } + appendHeader(headers, header[0], header[1]); + } + } else if (typeof object === "object" && object !== null) { + const keys = Object.keys(object); + for (let i = 0; i < keys.length; ++i) { + appendHeader(headers, keys[i], object[keys[i]]); + } + } else { + throw webidl.errors.conversionFailed({ + prefix: "Headers constructor", + argument: "Argument 1", + types: ["sequence>", "record"] + }); + } + } + function appendHeader(headers, name, value) { + value = headerValueNormalize(value); + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: "Headers.append", + value: name, + type: "header name" + }); + } else if (!isValidHeaderValue(value)) { + throw webidl.errors.invalidArgument({ + prefix: "Headers.append", + value, + type: "header value" + }); + } + if (getHeadersGuard(headers) === "immutable") { + throw new TypeError("immutable"); + } + return getHeadersList(headers).append(name, value, false); + } + function compareHeaderName(a, b) { + return a[0] < b[0] ? -1 : 1; + } + var HeadersList = class _HeadersList { + /** @type {[string, string][]|null} */ + cookies = null; + constructor(init) { + if (init instanceof _HeadersList) { + this[kHeadersMap] = new Map(init[kHeadersMap]); + this[kHeadersSortedMap] = init[kHeadersSortedMap]; + this.cookies = init.cookies === null ? null : [...init.cookies]; + } else { + this[kHeadersMap] = new Map(init); + this[kHeadersSortedMap] = null; + } + } + /** + * @see https://fetch.spec.whatwg.org/#header-list-contains + * @param {string} name + * @param {boolean} isLowerCase + */ + contains(name, isLowerCase) { + return this[kHeadersMap].has(isLowerCase ? name : name.toLowerCase()); + } + clear() { + this[kHeadersMap].clear(); + this[kHeadersSortedMap] = null; + this.cookies = null; + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-append + * @param {string} name + * @param {string} value + * @param {boolean} isLowerCase + */ + append(name, value, isLowerCase) { + this[kHeadersSortedMap] = null; + const lowercaseName = isLowerCase ? name : name.toLowerCase(); + const exists2 = this[kHeadersMap].get(lowercaseName); + if (exists2) { + const delimiter = lowercaseName === "cookie" ? "; " : ", "; + this[kHeadersMap].set(lowercaseName, { + name: exists2.name, + value: `${exists2.value}${delimiter}${value}` + }); + } else { + this[kHeadersMap].set(lowercaseName, { name, value }); + } + if (lowercaseName === "set-cookie") { + (this.cookies ??= []).push(value); + } + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-set + * @param {string} name + * @param {string} value + * @param {boolean} isLowerCase + */ + set(name, value, isLowerCase) { + this[kHeadersSortedMap] = null; + const lowercaseName = isLowerCase ? name : name.toLowerCase(); + if (lowercaseName === "set-cookie") { + this.cookies = [value]; + } + this[kHeadersMap].set(lowercaseName, { name, value }); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-delete + * @param {string} name + * @param {boolean} isLowerCase + */ + delete(name, isLowerCase) { + this[kHeadersSortedMap] = null; + if (!isLowerCase) + name = name.toLowerCase(); + if (name === "set-cookie") { + this.cookies = null; + } + this[kHeadersMap].delete(name); + } + /** + * @see https://fetch.spec.whatwg.org/#concept-header-list-get + * @param {string} name + * @param {boolean} isLowerCase + * @returns {string | null} + */ + get(name, isLowerCase) { + return this[kHeadersMap].get(isLowerCase ? name : name.toLowerCase())?.value ?? null; + } + *[Symbol.iterator]() { + for (const { 0: name, 1: { value } } of this[kHeadersMap]) { + yield [name, value]; + } + } + get entries() { + const headers = {}; + if (this[kHeadersMap].size !== 0) { + for (const { name, value } of this[kHeadersMap].values()) { + headers[name] = value; + } + } + return headers; + } + rawValues() { + return this[kHeadersMap].values(); + } + get entriesList() { + const headers = []; + if (this[kHeadersMap].size !== 0) { + for (const { 0: lowerName, 1: { name, value } } of this[kHeadersMap]) { + if (lowerName === "set-cookie") { + for (const cookie of this.cookies) { + headers.push([name, cookie]); + } + } else { + headers.push([name, value]); + } + } + } + return headers; + } + // https://fetch.spec.whatwg.org/#convert-header-names-to-a-sorted-lowercase-set + toSortedArray() { + const size = this[kHeadersMap].size; + const array = new Array(size); + if (size <= 32) { + if (size === 0) { + return array; + } + const iterator3 = this[kHeadersMap][Symbol.iterator](); + const firstValue = iterator3.next().value; + array[0] = [firstValue[0], firstValue[1].value]; + assert2(firstValue[1].value !== null); + for (let i = 1, j = 0, right2 = 0, left2 = 0, pivot = 0, x, value; i < size; ++i) { + value = iterator3.next().value; + x = array[i] = [value[0], value[1].value]; + assert2(x[1] !== null); + left2 = 0; + right2 = i; + while (left2 < right2) { + pivot = left2 + (right2 - left2 >> 1); + if (array[pivot][0] <= x[0]) { + left2 = pivot + 1; + } else { + right2 = pivot; + } + } + if (i !== pivot) { + j = i; + while (j > left2) { + array[j] = array[--j]; + } + array[left2] = x; + } + } + if (!iterator3.next().done) { + throw new TypeError("Unreachable"); + } + return array; + } else { + let i = 0; + for (const { 0: name, 1: { value } } of this[kHeadersMap]) { + array[i++] = [name, value]; + assert2(value !== null); + } + return array.sort(compareHeaderName); + } + } + }; + var Headers2 = class _Headers { + #guard; + #headersList; + constructor(init = void 0) { + webidl.util.markAsUncloneable(this); + if (init === kConstruct) { + return; + } + this.#headersList = new HeadersList(); + this.#guard = "none"; + if (init !== void 0) { + init = webidl.converters.HeadersInit(init, "Headers contructor", "init"); + fill(this, init); + } + } + // https://fetch.spec.whatwg.org/#dom-headers-append + append(name, value) { + webidl.brandCheck(this, _Headers); + webidl.argumentLengthCheck(arguments, 2, "Headers.append"); + const prefix = "Headers.append"; + name = webidl.converters.ByteString(name, prefix, "name"); + value = webidl.converters.ByteString(value, prefix, "value"); + return appendHeader(this, name, value); + } + // https://fetch.spec.whatwg.org/#dom-headers-delete + delete(name) { + webidl.brandCheck(this, _Headers); + webidl.argumentLengthCheck(arguments, 1, "Headers.delete"); + const prefix = "Headers.delete"; + name = webidl.converters.ByteString(name, prefix, "name"); + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: "Headers.delete", + value: name, + type: "header name" + }); + } + if (this.#guard === "immutable") { + throw new TypeError("immutable"); + } + if (!this.#headersList.contains(name, false)) { + return; + } + this.#headersList.delete(name, false); + } + // https://fetch.spec.whatwg.org/#dom-headers-get + get(name) { + webidl.brandCheck(this, _Headers); + webidl.argumentLengthCheck(arguments, 1, "Headers.get"); + const prefix = "Headers.get"; + name = webidl.converters.ByteString(name, prefix, "name"); + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix, + value: name, + type: "header name" + }); + } + return this.#headersList.get(name, false); + } + // https://fetch.spec.whatwg.org/#dom-headers-has + has(name) { + webidl.brandCheck(this, _Headers); + webidl.argumentLengthCheck(arguments, 1, "Headers.has"); + const prefix = "Headers.has"; + name = webidl.converters.ByteString(name, prefix, "name"); + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix, + value: name, + type: "header name" + }); + } + return this.#headersList.contains(name, false); + } + // https://fetch.spec.whatwg.org/#dom-headers-set + set(name, value) { + webidl.brandCheck(this, _Headers); + webidl.argumentLengthCheck(arguments, 2, "Headers.set"); + const prefix = "Headers.set"; + name = webidl.converters.ByteString(name, prefix, "name"); + value = webidl.converters.ByteString(value, prefix, "value"); + value = headerValueNormalize(value); + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix, + value: name, + type: "header name" + }); + } else if (!isValidHeaderValue(value)) { + throw webidl.errors.invalidArgument({ + prefix, + value, + type: "header value" + }); + } + if (this.#guard === "immutable") { + throw new TypeError("immutable"); + } + this.#headersList.set(name, value, false); + } + // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie + getSetCookie() { + webidl.brandCheck(this, _Headers); + const list = this.#headersList.cookies; + if (list) { + return [...list]; + } + return []; + } + // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine + get [kHeadersSortedMap]() { + if (this.#headersList[kHeadersSortedMap]) { + return this.#headersList[kHeadersSortedMap]; + } + const headers = []; + const names = this.#headersList.toSortedArray(); + const cookies = this.#headersList.cookies; + if (cookies === null || cookies.length === 1) { + return this.#headersList[kHeadersSortedMap] = names; + } + for (let i = 0; i < names.length; ++i) { + const { 0: name, 1: value } = names[i]; + if (name === "set-cookie") { + for (let j = 0; j < cookies.length; ++j) { + headers.push([name, cookies[j]]); + } + } else { + headers.push([name, value]); + } + } + return this.#headersList[kHeadersSortedMap] = headers; + } + [util.inspect.custom](depth, options) { + options.depth ??= depth; + return `Headers ${util.formatWithOptions(options, this.#headersList.entries)}`; + } + static getHeadersGuard(o) { + return o.#guard; + } + static setHeadersGuard(o, guard) { + o.#guard = guard; + } + static getHeadersList(o) { + return o.#headersList; + } + static setHeadersList(o, list) { + o.#headersList = list; + } + }; + var { getHeadersGuard, setHeadersGuard, getHeadersList, setHeadersList } = Headers2; + Reflect.deleteProperty(Headers2, "getHeadersGuard"); + Reflect.deleteProperty(Headers2, "setHeadersGuard"); + Reflect.deleteProperty(Headers2, "getHeadersList"); + Reflect.deleteProperty(Headers2, "setHeadersList"); + iteratorMixin("Headers", Headers2, kHeadersSortedMap, 0, 1); + Object.defineProperties(Headers2.prototype, { + append: kEnumerableProperty, + delete: kEnumerableProperty, + get: kEnumerableProperty, + has: kEnumerableProperty, + set: kEnumerableProperty, + getSetCookie: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "Headers", + configurable: true + }, + [util.inspect.custom]: { + enumerable: false + } + }); + webidl.converters.HeadersInit = function(V, prefix, argument) { + if (webidl.util.Type(V) === "Object") { + const iterator3 = Reflect.get(V, Symbol.iterator); + if (!util.types.isProxy(V) && iterator3 === Headers2.prototype.entries) { + try { + return getHeadersList(V).entriesList; + } catch { + } + } + if (typeof iterator3 === "function") { + return webidl.converters["sequence>"](V, prefix, argument, iterator3.bind(V)); + } + return webidl.converters["record"](V, prefix, argument); + } + throw webidl.errors.conversionFailed({ + prefix: "Headers constructor", + argument: "Argument 1", + types: ["sequence>", "record"] + }); + }; + module.exports = { + fill, + // for test. + compareHeaderName, + Headers: Headers2, + HeadersList, + getHeadersGuard, + setHeadersGuard, + setHeadersList, + getHeadersList + }; + } +}); + +// +var require_response = __commonJS({ + ""(exports, module) { + "use strict"; + var { Headers: Headers2, HeadersList, fill, getHeadersGuard, setHeadersGuard, setHeadersList } = require_headers(); + var { extractBody, cloneBody, mixinBody, hasFinalizationRegistry, streamRegistry, bodyUnusable } = require_body(); + var util = require_util(); + var nodeUtil = __require("node:util"); + var { kEnumerableProperty } = util; + var { + isValidReasonPhrase, + isCancelled, + isAborted, + isBlobLike, + serializeJavascriptValueToJSONString, + isErrorLike, + isomorphicEncode, + environmentSettingsObject: relevantRealm + } = require_util2(); + var { + redirectStatusSet, + nullBodyStatus + } = require_constants3(); + var { kState, kHeaders } = require_symbols2(); + var { webidl } = require_webidl(); + var { FormData } = require_formdata(); + var { URLSerializer } = require_data_url(); + var { kConstruct } = require_symbols(); + var assert2 = __require("node:assert"); + var { types: types2 } = __require("node:util"); + var textEncoder = new TextEncoder("utf-8"); + var Response = class _Response { + // Creates network error Response. + static error() { + const responseObject = fromInnerResponse(makeNetworkError(), "immutable"); + return responseObject; + } + // https://fetch.spec.whatwg.org/#dom-response-json + static json(data, init = {}) { + webidl.argumentLengthCheck(arguments, 1, "Response.json"); + if (init !== null) { + init = webidl.converters.ResponseInit(init); + } + const bytes = textEncoder.encode( + serializeJavascriptValueToJSONString(data) + ); + const body = extractBody(bytes); + const responseObject = fromInnerResponse(makeResponse({}), "response"); + initializeResponse(responseObject, init, { body: body[0], type: "application/json" }); + return responseObject; + } + // Creates a redirect Response that redirects to url with status status. + static redirect(url, status = 302) { + webidl.argumentLengthCheck(arguments, 1, "Response.redirect"); + url = webidl.converters.USVString(url); + status = webidl.converters["unsigned short"](status); + let parsedURL; + try { + parsedURL = new URL(url, relevantRealm.settingsObject.baseUrl); + } catch (err) { + throw new TypeError(`Failed to parse URL from ${url}`, { cause: err }); + } + if (!redirectStatusSet.has(status)) { + throw new RangeError(`Invalid status code ${status}`); + } + const responseObject = fromInnerResponse(makeResponse({}), "immutable"); + responseObject[kState].status = status; + const value = isomorphicEncode(URLSerializer(parsedURL)); + responseObject[kState].headersList.append("location", value, true); + return responseObject; + } + // https://fetch.spec.whatwg.org/#dom-response + constructor(body = null, init = {}) { + webidl.util.markAsUncloneable(this); + if (body === kConstruct) { + return; + } + if (body !== null) { + body = webidl.converters.BodyInit(body); + } + init = webidl.converters.ResponseInit(init); + this[kState] = makeResponse({}); + this[kHeaders] = new Headers2(kConstruct); + setHeadersGuard(this[kHeaders], "response"); + setHeadersList(this[kHeaders], this[kState].headersList); + let bodyWithType = null; + if (body != null) { + const [extractedBody, type] = extractBody(body); + bodyWithType = { body: extractedBody, type }; + } + initializeResponse(this, init, bodyWithType); + } + // Returns response’s type, e.g., "cors". + get type() { + webidl.brandCheck(this, _Response); + return this[kState].type; + } + // Returns response’s URL, if it has one; otherwise the empty string. + get url() { + webidl.brandCheck(this, _Response); + const urlList = this[kState].urlList; + const url = urlList[urlList.length - 1] ?? null; + if (url === null) { + return ""; + } + return URLSerializer(url, true); + } + // Returns whether response was obtained through a redirect. + get redirected() { + webidl.brandCheck(this, _Response); + return this[kState].urlList.length > 1; + } + // Returns response’s status. + get status() { + webidl.brandCheck(this, _Response); + return this[kState].status; + } + // Returns whether response’s status is an ok status. + get ok() { + webidl.brandCheck(this, _Response); + return this[kState].status >= 200 && this[kState].status <= 299; + } + // Returns response’s status message. + get statusText() { + webidl.brandCheck(this, _Response); + return this[kState].statusText; + } + // Returns response’s headers as Headers. + get headers() { + webidl.brandCheck(this, _Response); + return this[kHeaders]; + } + get body() { + webidl.brandCheck(this, _Response); + return this[kState].body ? this[kState].body.stream : null; + } + get bodyUsed() { + webidl.brandCheck(this, _Response); + return !!this[kState].body && util.isDisturbed(this[kState].body.stream); + } + // Returns a clone of response. + clone() { + webidl.brandCheck(this, _Response); + if (bodyUnusable(this)) { + throw webidl.errors.exception({ + header: "Response.clone", + message: "Body has already been consumed." + }); + } + const clonedResponse = cloneResponse(this[kState]); + if (hasFinalizationRegistry && this[kState].body?.stream) { + streamRegistry.register(this, new WeakRef(this[kState].body.stream)); + } + return fromInnerResponse(clonedResponse, getHeadersGuard(this[kHeaders])); + } + [nodeUtil.inspect.custom](depth, options) { + if (options.depth === null) { + options.depth = 2; + } + options.colors ??= true; + const properties = { + status: this.status, + statusText: this.statusText, + headers: this.headers, + body: this.body, + bodyUsed: this.bodyUsed, + ok: this.ok, + redirected: this.redirected, + type: this.type, + url: this.url + }; + return `Response ${nodeUtil.formatWithOptions(options, properties)}`; + } + }; + mixinBody(Response); + Object.defineProperties(Response.prototype, { + type: kEnumerableProperty, + url: kEnumerableProperty, + status: kEnumerableProperty, + ok: kEnumerableProperty, + redirected: kEnumerableProperty, + statusText: kEnumerableProperty, + headers: kEnumerableProperty, + clone: kEnumerableProperty, + body: kEnumerableProperty, + bodyUsed: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "Response", + configurable: true + } + }); + Object.defineProperties(Response, { + json: kEnumerableProperty, + redirect: kEnumerableProperty, + error: kEnumerableProperty + }); + function cloneResponse(response) { + if (response.internalResponse) { + return filterResponse( + cloneResponse(response.internalResponse), + response.type + ); + } + const newResponse = makeResponse({ ...response, body: null }); + if (response.body != null) { + newResponse.body = cloneBody(newResponse, response.body); + } + return newResponse; + } + function makeResponse(init) { + return { + aborted: false, + rangeRequested: false, + timingAllowPassed: false, + requestIncludesCredentials: false, + type: "default", + status: 200, + timingInfo: null, + cacheState: "", + statusText: "", + ...init, + headersList: init?.headersList ? new HeadersList(init?.headersList) : new HeadersList(), + urlList: init?.urlList ? [...init.urlList] : [] + }; + } + function makeNetworkError(reason) { + const isError = isErrorLike(reason); + return makeResponse({ + type: "error", + status: 0, + error: isError ? reason : new Error(reason ? String(reason) : reason), + aborted: reason && reason.name === "AbortError" + }); + } + function isNetworkError(response) { + return ( + // A network error is a response whose type is "error", + response.type === "error" && // status is 0 + response.status === 0 + ); + } + function makeFilteredResponse(response, state) { + state = { + internalResponse: response, + ...state + }; + return new Proxy(response, { + get(target, p) { + return p in state ? state[p] : target[p]; + }, + set(target, p, value) { + assert2(!(p in state)); + target[p] = value; + return true; + } + }); + } + function filterResponse(response, type) { + if (type === "basic") { + return makeFilteredResponse(response, { + type: "basic", + headersList: response.headersList + }); + } else if (type === "cors") { + return makeFilteredResponse(response, { + type: "cors", + headersList: response.headersList + }); + } else if (type === "opaque") { + return makeFilteredResponse(response, { + type: "opaque", + urlList: Object.freeze([]), + status: 0, + statusText: "", + body: null + }); + } else if (type === "opaqueredirect") { + return makeFilteredResponse(response, { + type: "opaqueredirect", + status: 0, + statusText: "", + headersList: [], + body: null + }); + } else { + assert2(false); + } + } + function makeAppropriateNetworkError(fetchParams, err = null) { + assert2(isCancelled(fetchParams)); + return isAborted(fetchParams) ? makeNetworkError(Object.assign(new DOMException("The operation was aborted.", "AbortError"), { cause: err })) : makeNetworkError(Object.assign(new DOMException("Request was cancelled."), { cause: err })); + } + function initializeResponse(response, init, body) { + if (init.status !== null && (init.status < 200 || init.status > 599)) { + throw new RangeError('init["status"] must be in the range of 200 to 599, inclusive.'); + } + if ("statusText" in init && init.statusText != null) { + if (!isValidReasonPhrase(String(init.statusText))) { + throw new TypeError("Invalid statusText"); + } + } + if ("status" in init && init.status != null) { + response[kState].status = init.status; + } + if ("statusText" in init && init.statusText != null) { + response[kState].statusText = init.statusText; + } + if ("headers" in init && init.headers != null) { + fill(response[kHeaders], init.headers); + } + if (body) { + if (nullBodyStatus.includes(response.status)) { + throw webidl.errors.exception({ + header: "Response constructor", + message: `Invalid response status code ${response.status}` + }); + } + response[kState].body = body.body; + if (body.type != null && !response[kState].headersList.contains("content-type", true)) { + response[kState].headersList.append("content-type", body.type, true); + } + } + } + function fromInnerResponse(innerResponse, guard) { + const response = new Response(kConstruct); + response[kState] = innerResponse; + response[kHeaders] = new Headers2(kConstruct); + setHeadersList(response[kHeaders], innerResponse.headersList); + setHeadersGuard(response[kHeaders], guard); + if (hasFinalizationRegistry && innerResponse.body?.stream) { + streamRegistry.register(response, new WeakRef(innerResponse.body.stream)); + } + return response; + } + webidl.converters.ReadableStream = webidl.interfaceConverter( + ReadableStream + ); + webidl.converters.FormData = webidl.interfaceConverter( + FormData + ); + webidl.converters.URLSearchParams = webidl.interfaceConverter( + URLSearchParams + ); + webidl.converters.XMLHttpRequestBodyInit = function(V, prefix, name) { + if (typeof V === "string") { + return webidl.converters.USVString(V, prefix, name); + } + if (isBlobLike(V)) { + return webidl.converters.Blob(V, prefix, name, { strict: false }); + } + if (ArrayBuffer.isView(V) || types2.isArrayBuffer(V)) { + return webidl.converters.BufferSource(V, prefix, name); + } + if (util.isFormDataLike(V)) { + return webidl.converters.FormData(V, prefix, name, { strict: false }); + } + if (V instanceof URLSearchParams) { + return webidl.converters.URLSearchParams(V, prefix, name); + } + return webidl.converters.DOMString(V, prefix, name); + }; + webidl.converters.BodyInit = function(V, prefix, argument) { + if (V instanceof ReadableStream) { + return webidl.converters.ReadableStream(V, prefix, argument); + } + if (V?.[Symbol.asyncIterator]) { + return V; + } + return webidl.converters.XMLHttpRequestBodyInit(V, prefix, argument); + }; + webidl.converters.ResponseInit = webidl.dictionaryConverter([ + { + key: "status", + converter: webidl.converters["unsigned short"], + defaultValue: () => 200 + }, + { + key: "statusText", + converter: webidl.converters.ByteString, + defaultValue: () => "" + }, + { + key: "headers", + converter: webidl.converters.HeadersInit + } + ]); + module.exports = { + isNetworkError, + makeNetworkError, + makeResponse, + makeAppropriateNetworkError, + filterResponse, + Response, + cloneResponse, + fromInnerResponse + }; + } +}); + +// +var require_dispatcher_weakref = __commonJS({ + ""(exports, module) { + "use strict"; + var { kConnected, kSize } = require_symbols(); + var CompatWeakRef = class { + constructor(value) { + this.value = value; + } + deref() { + return this.value[kConnected] === 0 && this.value[kSize] === 0 ? void 0 : this.value; + } + }; + var CompatFinalizer = class { + constructor(finalizer) { + this.finalizer = finalizer; + } + register(dispatcher, key) { + if (dispatcher.on) { + dispatcher.on("disconnect", () => { + if (dispatcher[kConnected] === 0 && dispatcher[kSize] === 0) { + this.finalizer(key); + } + }); + } + } + unregister(key) { + } + }; + module.exports = function() { + if (process.env.NODE_V8_COVERAGE && process.version.startsWith("v18")) { + process._rawDebug("Using compatibility WeakRef and FinalizationRegistry"); + return { + WeakRef: CompatWeakRef, + FinalizationRegistry: CompatFinalizer + }; + } + return { WeakRef, FinalizationRegistry }; + }; + } +}); + +// +var require_request2 = __commonJS({ + ""(exports, module) { + "use strict"; + var { extractBody, mixinBody, cloneBody, bodyUnusable } = require_body(); + var { Headers: Headers2, fill: fillHeaders, HeadersList, setHeadersGuard, getHeadersGuard, setHeadersList, getHeadersList } = require_headers(); + var { FinalizationRegistry: FinalizationRegistry2 } = require_dispatcher_weakref()(); + var util = require_util(); + var nodeUtil = __require("node:util"); + var { + isValidHTTPToken, + sameOrigin, + environmentSettingsObject + } = require_util2(); + var { + forbiddenMethodsSet, + corsSafeListedMethodsSet, + referrerPolicy, + requestRedirect, + requestMode, + requestCredentials, + requestCache, + requestDuplex + } = require_constants3(); + var { kEnumerableProperty, normalizedMethodRecordsBase, normalizedMethodRecords } = util; + var { kHeaders, kSignal, kState, kDispatcher } = require_symbols2(); + var { webidl } = require_webidl(); + var { URLSerializer } = require_data_url(); + var { kConstruct } = require_symbols(); + var assert2 = __require("node:assert"); + var { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = __require("node:events"); + var kAbortController = Symbol("abortController"); + var requestFinalizer = new FinalizationRegistry2(({ signal, abort }) => { + signal.removeEventListener("abort", abort); + }); + var dependentControllerMap = /* @__PURE__ */ new WeakMap(); + function buildAbort(acRef) { + return abort; + function abort() { + const ac = acRef.deref(); + if (ac !== void 0) { + requestFinalizer.unregister(abort); + this.removeEventListener("abort", abort); + ac.abort(this.reason); + const controllerList = dependentControllerMap.get(ac.signal); + if (controllerList !== void 0) { + if (controllerList.size !== 0) { + for (const ref of controllerList) { + const ctrl = ref.deref(); + if (ctrl !== void 0) { + ctrl.abort(this.reason); + } + } + controllerList.clear(); + } + dependentControllerMap.delete(ac.signal); + } + } + } + } + var patchMethodWarning = false; + var Request = class _Request { + // https://fetch.spec.whatwg.org/#dom-request + constructor(input, init = {}) { + webidl.util.markAsUncloneable(this); + if (input === kConstruct) { + return; + } + const prefix = "Request constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + input = webidl.converters.RequestInfo(input, prefix, "input"); + init = webidl.converters.RequestInit(init, prefix, "init"); + let request3 = null; + let fallbackMode = null; + const baseUrl2 = environmentSettingsObject.settingsObject.baseUrl; + let signal = null; + if (typeof input === "string") { + this[kDispatcher] = init.dispatcher; + let parsedURL; + try { + parsedURL = new URL(input, baseUrl2); + } catch (err) { + throw new TypeError("Failed to parse URL from " + input, { cause: err }); + } + if (parsedURL.username || parsedURL.password) { + throw new TypeError( + "Request cannot be constructed from a URL that includes credentials: " + input + ); + } + request3 = makeRequest({ urlList: [parsedURL] }); + fallbackMode = "cors"; + } else { + this[kDispatcher] = init.dispatcher || input[kDispatcher]; + assert2(input instanceof _Request); + request3 = input[kState]; + signal = input[kSignal]; + } + const origin = environmentSettingsObject.settingsObject.origin; + let window2 = "client"; + if (request3.window?.constructor?.name === "EnvironmentSettingsObject" && sameOrigin(request3.window, origin)) { + window2 = request3.window; + } + if (init.window != null) { + throw new TypeError(`'window' option '${window2}' must be null`); + } + if ("window" in init) { + window2 = "no-window"; + } + request3 = makeRequest({ + // URL request’s URL. + // undici implementation note: this is set as the first item in request's urlList in makeRequest + // method request’s method. + method: request3.method, + // header list A copy of request’s header list. + // undici implementation note: headersList is cloned in makeRequest + headersList: request3.headersList, + // unsafe-request flag Set. + unsafeRequest: request3.unsafeRequest, + // client This’s relevant settings object. + client: environmentSettingsObject.settingsObject, + // window window. + window: window2, + // priority request’s priority. + priority: request3.priority, + // origin request’s origin. The propagation of the origin is only significant for navigation requests + // being handled by a service worker. In this scenario a request can have an origin that is different + // from the current client. + origin: request3.origin, + // referrer request’s referrer. + referrer: request3.referrer, + // referrer policy request’s referrer policy. + referrerPolicy: request3.referrerPolicy, + // mode request’s mode. + mode: request3.mode, + // credentials mode request’s credentials mode. + credentials: request3.credentials, + // cache mode request’s cache mode. + cache: request3.cache, + // redirect mode request’s redirect mode. + redirect: request3.redirect, + // integrity metadata request’s integrity metadata. + integrity: request3.integrity, + // keepalive request’s keepalive. + keepalive: request3.keepalive, + // reload-navigation flag request’s reload-navigation flag. + reloadNavigation: request3.reloadNavigation, + // history-navigation flag request’s history-navigation flag. + historyNavigation: request3.historyNavigation, + // URL list A clone of request’s URL list. + urlList: [...request3.urlList] + }); + const initHasKey = Object.keys(init).length !== 0; + if (initHasKey) { + if (request3.mode === "navigate") { + request3.mode = "same-origin"; + } + request3.reloadNavigation = false; + request3.historyNavigation = false; + request3.origin = "client"; + request3.referrer = "client"; + request3.referrerPolicy = ""; + request3.url = request3.urlList[request3.urlList.length - 1]; + request3.urlList = [request3.url]; + } + if (init.referrer !== void 0) { + const referrer = init.referrer; + if (referrer === "") { + request3.referrer = "no-referrer"; + } else { + let parsedReferrer; + try { + parsedReferrer = new URL(referrer, baseUrl2); + } catch (err) { + throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err }); + } + if (parsedReferrer.protocol === "about:" && parsedReferrer.hostname === "client" || origin && !sameOrigin(parsedReferrer, environmentSettingsObject.settingsObject.baseUrl)) { + request3.referrer = "client"; + } else { + request3.referrer = parsedReferrer; + } + } + } + if (init.referrerPolicy !== void 0) { + request3.referrerPolicy = init.referrerPolicy; + } + let mode; + if (init.mode !== void 0) { + mode = init.mode; + } else { + mode = fallbackMode; + } + if (mode === "navigate") { + throw webidl.errors.exception({ + header: "Request constructor", + message: "invalid request mode navigate." + }); + } + if (mode != null) { + request3.mode = mode; + } + if (init.credentials !== void 0) { + request3.credentials = init.credentials; + } + if (init.cache !== void 0) { + request3.cache = init.cache; + } + if (request3.cache === "only-if-cached" && request3.mode !== "same-origin") { + throw new TypeError( + "'only-if-cached' can be set only with 'same-origin' mode" + ); + } + if (init.redirect !== void 0) { + request3.redirect = init.redirect; + } + if (init.integrity != null) { + request3.integrity = String(init.integrity); + } + if (init.keepalive !== void 0) { + request3.keepalive = Boolean(init.keepalive); + } + if (init.method !== void 0) { + let method = init.method; + const mayBeNormalized = normalizedMethodRecords[method]; + if (mayBeNormalized !== void 0) { + request3.method = mayBeNormalized; + } else { + if (!isValidHTTPToken(method)) { + throw new TypeError(`'${method}' is not a valid HTTP method.`); + } + const upperCase = method.toUpperCase(); + if (forbiddenMethodsSet.has(upperCase)) { + throw new TypeError(`'${method}' HTTP method is unsupported.`); + } + method = normalizedMethodRecordsBase[upperCase] ?? method; + request3.method = method; + } + if (!patchMethodWarning && request3.method === "patch") { + process.emitWarning("Using `patch` is highly likely to result in a `405 Method Not Allowed`. `PATCH` is much more likely to succeed.", { + code: "UNDICI-FETCH-patch" + }); + patchMethodWarning = true; + } + } + if (init.signal !== void 0) { + signal = init.signal; + } + this[kState] = request3; + const ac = new AbortController(); + this[kSignal] = ac.signal; + if (signal != null) { + if (!signal || typeof signal.aborted !== "boolean" || typeof signal.addEventListener !== "function") { + throw new TypeError( + "Failed to construct 'Request': member signal is not of type AbortSignal." + ); + } + if (signal.aborted) { + ac.abort(signal.reason); + } else { + this[kAbortController] = ac; + const acRef = new WeakRef(ac); + const abort = buildAbort(acRef); + try { + if (typeof getMaxListeners === "function" && getMaxListeners(signal) === defaultMaxListeners) { + setMaxListeners(1500, signal); + } else if (getEventListeners(signal, "abort").length >= defaultMaxListeners) { + setMaxListeners(1500, signal); + } + } catch { + } + util.addAbortListener(signal, abort); + requestFinalizer.register(ac, { signal, abort }, abort); + } + } + this[kHeaders] = new Headers2(kConstruct); + setHeadersList(this[kHeaders], request3.headersList); + setHeadersGuard(this[kHeaders], "request"); + if (mode === "no-cors") { + if (!corsSafeListedMethodsSet.has(request3.method)) { + throw new TypeError( + `'${request3.method} is unsupported in no-cors mode.` + ); + } + setHeadersGuard(this[kHeaders], "request-no-cors"); + } + if (initHasKey) { + const headersList = getHeadersList(this[kHeaders]); + const headers = init.headers !== void 0 ? init.headers : new HeadersList(headersList); + headersList.clear(); + if (headers instanceof HeadersList) { + for (const { name, value } of headers.rawValues()) { + headersList.append(name, value, false); + } + headersList.cookies = headers.cookies; + } else { + fillHeaders(this[kHeaders], headers); + } + } + const inputBody = input instanceof _Request ? input[kState].body : null; + if ((init.body != null || inputBody != null) && (request3.method === "GET" || request3.method === "HEAD")) { + throw new TypeError("Request with GET/HEAD method cannot have body."); + } + let initBody = null; + if (init.body != null) { + const [extractedBody, contentType] = extractBody( + init.body, + request3.keepalive + ); + initBody = extractedBody; + if (contentType && !getHeadersList(this[kHeaders]).contains("content-type", true)) { + this[kHeaders].append("content-type", contentType); + } + } + const inputOrInitBody = initBody ?? inputBody; + if (inputOrInitBody != null && inputOrInitBody.source == null) { + if (initBody != null && init.duplex == null) { + throw new TypeError("RequestInit: duplex option is required when sending a body."); + } + if (request3.mode !== "same-origin" && request3.mode !== "cors") { + throw new TypeError( + 'If request is made from ReadableStream, mode should be "same-origin" or "cors"' + ); + } + request3.useCORSPreflightFlag = true; + } + let finalBody = inputOrInitBody; + if (initBody == null && inputBody != null) { + if (bodyUnusable(input)) { + throw new TypeError( + "Cannot construct a Request with a Request object that has already been used." + ); + } + const identityTransform = new TransformStream(); + inputBody.stream.pipeThrough(identityTransform); + finalBody = { + source: inputBody.source, + length: inputBody.length, + stream: identityTransform.readable + }; + } + this[kState].body = finalBody; + } + // Returns request’s HTTP method, which is "GET" by default. + get method() { + webidl.brandCheck(this, _Request); + return this[kState].method; + } + // Returns the URL of request as a string. + get url() { + webidl.brandCheck(this, _Request); + return URLSerializer(this[kState].url); + } + // Returns a Headers object consisting of the headers associated with request. + // Note that headers added in the network layer by the user agent will not + // be accounted for in this object, e.g., the "Host" header. + get headers() { + webidl.brandCheck(this, _Request); + return this[kHeaders]; + } + // Returns the kind of resource requested by request, e.g., "document" + // or "script". + get destination() { + webidl.brandCheck(this, _Request); + return this[kState].destination; + } + // Returns the referrer of request. Its value can be a same-origin URL if + // explicitly set in init, the empty string to indicate no referrer, and + // "about:client" when defaulting to the global’s default. This is used + // during fetching to determine the value of the `Referer` header of the + // request being made. + get referrer() { + webidl.brandCheck(this, _Request); + if (this[kState].referrer === "no-referrer") { + return ""; + } + if (this[kState].referrer === "client") { + return "about:client"; + } + return this[kState].referrer.toString(); + } + // Returns the referrer policy associated with request. + // This is used during fetching to compute the value of the request’s + // referrer. + get referrerPolicy() { + webidl.brandCheck(this, _Request); + return this[kState].referrerPolicy; + } + // Returns the mode associated with request, which is a string indicating + // whether the request will use CORS, or will be restricted to same-origin + // URLs. + get mode() { + webidl.brandCheck(this, _Request); + return this[kState].mode; + } + // Returns the credentials mode associated with request, + // which is a string indicating whether credentials will be sent with the + // request always, never, or only when sent to a same-origin URL. + get credentials() { + return this[kState].credentials; + } + // Returns the cache mode associated with request, + // which is a string indicating how the request will + // interact with the browser’s cache when fetching. + get cache() { + webidl.brandCheck(this, _Request); + return this[kState].cache; + } + // Returns the redirect mode associated with request, + // which is a string indicating how redirects for the + // request will be handled during fetching. A request + // will follow redirects by default. + get redirect() { + webidl.brandCheck(this, _Request); + return this[kState].redirect; + } + // Returns request’s subresource integrity metadata, which is a + // cryptographic hash of the resource being fetched. Its value + // consists of multiple hashes separated by whitespace. [SRI] + get integrity() { + webidl.brandCheck(this, _Request); + return this[kState].integrity; + } + // Returns a boolean indicating whether or not request can outlive the + // global in which it was created. + get keepalive() { + webidl.brandCheck(this, _Request); + return this[kState].keepalive; + } + // Returns a boolean indicating whether or not request is for a reload + // navigation. + get isReloadNavigation() { + webidl.brandCheck(this, _Request); + return this[kState].reloadNavigation; + } + // Returns a boolean indicating whether or not request is for a history + // navigation (a.k.a. back-forward navigation). + get isHistoryNavigation() { + webidl.brandCheck(this, _Request); + return this[kState].historyNavigation; + } + // Returns the signal associated with request, which is an AbortSignal + // object indicating whether or not request has been aborted, and its + // abort event handler. + get signal() { + webidl.brandCheck(this, _Request); + return this[kSignal]; + } + get body() { + webidl.brandCheck(this, _Request); + return this[kState].body ? this[kState].body.stream : null; + } + get bodyUsed() { + webidl.brandCheck(this, _Request); + return !!this[kState].body && util.isDisturbed(this[kState].body.stream); + } + get duplex() { + webidl.brandCheck(this, _Request); + return "half"; + } + // Returns a clone of request. + clone() { + webidl.brandCheck(this, _Request); + if (bodyUnusable(this)) { + throw new TypeError("unusable"); + } + const clonedRequest = cloneRequest(this[kState]); + const ac = new AbortController(); + if (this.signal.aborted) { + ac.abort(this.signal.reason); + } else { + let list = dependentControllerMap.get(this.signal); + if (list === void 0) { + list = /* @__PURE__ */ new Set(); + dependentControllerMap.set(this.signal, list); + } + const acRef = new WeakRef(ac); + list.add(acRef); + util.addAbortListener( + ac.signal, + buildAbort(acRef) + ); + } + return fromInnerRequest(clonedRequest, ac.signal, getHeadersGuard(this[kHeaders])); + } + [nodeUtil.inspect.custom](depth, options) { + if (options.depth === null) { + options.depth = 2; + } + options.colors ??= true; + const properties = { + method: this.method, + url: this.url, + headers: this.headers, + destination: this.destination, + referrer: this.referrer, + referrerPolicy: this.referrerPolicy, + mode: this.mode, + credentials: this.credentials, + cache: this.cache, + redirect: this.redirect, + integrity: this.integrity, + keepalive: this.keepalive, + isReloadNavigation: this.isReloadNavigation, + isHistoryNavigation: this.isHistoryNavigation, + signal: this.signal + }; + return `Request ${nodeUtil.formatWithOptions(options, properties)}`; + } + }; + mixinBody(Request); + function makeRequest(init) { + return { + method: init.method ?? "GET", + localURLsOnly: init.localURLsOnly ?? false, + unsafeRequest: init.unsafeRequest ?? false, + body: init.body ?? null, + client: init.client ?? null, + reservedClient: init.reservedClient ?? null, + replacesClientId: init.replacesClientId ?? "", + window: init.window ?? "client", + keepalive: init.keepalive ?? false, + serviceWorkers: init.serviceWorkers ?? "all", + initiator: init.initiator ?? "", + destination: init.destination ?? "", + priority: init.priority ?? null, + origin: init.origin ?? "client", + policyContainer: init.policyContainer ?? "client", + referrer: init.referrer ?? "client", + referrerPolicy: init.referrerPolicy ?? "", + mode: init.mode ?? "no-cors", + useCORSPreflightFlag: init.useCORSPreflightFlag ?? false, + credentials: init.credentials ?? "same-origin", + useCredentials: init.useCredentials ?? false, + cache: init.cache ?? "default", + redirect: init.redirect ?? "follow", + integrity: init.integrity ?? "", + cryptoGraphicsNonceMetadata: init.cryptoGraphicsNonceMetadata ?? "", + parserMetadata: init.parserMetadata ?? "", + reloadNavigation: init.reloadNavigation ?? false, + historyNavigation: init.historyNavigation ?? false, + userActivation: init.userActivation ?? false, + taintedOrigin: init.taintedOrigin ?? false, + redirectCount: init.redirectCount ?? 0, + responseTainting: init.responseTainting ?? "basic", + preventNoCacheCacheControlHeaderModification: init.preventNoCacheCacheControlHeaderModification ?? false, + done: init.done ?? false, + timingAllowFailed: init.timingAllowFailed ?? false, + urlList: init.urlList, + url: init.urlList[0], + headersList: init.headersList ? new HeadersList(init.headersList) : new HeadersList() + }; + } + function cloneRequest(request3) { + const newRequest = makeRequest({ ...request3, body: null }); + if (request3.body != null) { + newRequest.body = cloneBody(newRequest, request3.body); + } + return newRequest; + } + function fromInnerRequest(innerRequest, signal, guard) { + const request3 = new Request(kConstruct); + request3[kState] = innerRequest; + request3[kSignal] = signal; + request3[kHeaders] = new Headers2(kConstruct); + setHeadersList(request3[kHeaders], innerRequest.headersList); + setHeadersGuard(request3[kHeaders], guard); + return request3; + } + Object.defineProperties(Request.prototype, { + method: kEnumerableProperty, + url: kEnumerableProperty, + headers: kEnumerableProperty, + redirect: kEnumerableProperty, + clone: kEnumerableProperty, + signal: kEnumerableProperty, + duplex: kEnumerableProperty, + destination: kEnumerableProperty, + body: kEnumerableProperty, + bodyUsed: kEnumerableProperty, + isHistoryNavigation: kEnumerableProperty, + isReloadNavigation: kEnumerableProperty, + keepalive: kEnumerableProperty, + integrity: kEnumerableProperty, + cache: kEnumerableProperty, + credentials: kEnumerableProperty, + attribute: kEnumerableProperty, + referrerPolicy: kEnumerableProperty, + referrer: kEnumerableProperty, + mode: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "Request", + configurable: true + } + }); + webidl.converters.Request = webidl.interfaceConverter( + Request + ); + webidl.converters.RequestInfo = function(V, prefix, argument) { + if (typeof V === "string") { + return webidl.converters.USVString(V, prefix, argument); + } + if (V instanceof Request) { + return webidl.converters.Request(V, prefix, argument); + } + return webidl.converters.USVString(V, prefix, argument); + }; + webidl.converters.AbortSignal = webidl.interfaceConverter( + AbortSignal + ); + webidl.converters.RequestInit = webidl.dictionaryConverter([ + { + key: "method", + converter: webidl.converters.ByteString + }, + { + key: "headers", + converter: webidl.converters.HeadersInit + }, + { + key: "body", + converter: webidl.nullableConverter( + webidl.converters.BodyInit + ) + }, + { + key: "referrer", + converter: webidl.converters.USVString + }, + { + key: "referrerPolicy", + converter: webidl.converters.DOMString, + // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy + allowedValues: referrerPolicy + }, + { + key: "mode", + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#concept-request-mode + allowedValues: requestMode + }, + { + key: "credentials", + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestcredentials + allowedValues: requestCredentials + }, + { + key: "cache", + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestcache + allowedValues: requestCache + }, + { + key: "redirect", + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestredirect + allowedValues: requestRedirect + }, + { + key: "integrity", + converter: webidl.converters.DOMString + }, + { + key: "keepalive", + converter: webidl.converters.boolean + }, + { + key: "signal", + converter: webidl.nullableConverter( + (signal) => webidl.converters.AbortSignal( + signal, + "RequestInit", + "signal", + { strict: false } + ) + ) + }, + { + key: "window", + converter: webidl.converters.any + }, + { + key: "duplex", + converter: webidl.converters.DOMString, + allowedValues: requestDuplex + }, + { + key: "dispatcher", + // undici specific option + converter: webidl.converters.any + } + ]); + module.exports = { Request, makeRequest, fromInnerRequest, cloneRequest }; + } +}); + +// +var require_fetch = __commonJS({ + ""(exports, module) { + "use strict"; + var { + makeNetworkError, + makeAppropriateNetworkError, + filterResponse, + makeResponse, + fromInnerResponse + } = require_response(); + var { HeadersList } = require_headers(); + var { Request, cloneRequest } = require_request2(); + var zlib = __require("node:zlib"); + var { + bytesMatch, + makePolicyContainer, + clonePolicyContainer, + requestBadPort, + TAOCheck, + appendRequestOriginHeader, + responseLocationURL, + requestCurrentURL, + setRequestReferrerPolicyOnRedirect, + tryUpgradeRequestToAPotentiallyTrustworthyURL, + createOpaqueTimingInfo, + appendFetchMetadata, + corsCheck, + crossOriginResourcePolicyCheck, + determineRequestsReferrer, + coarsenedSharedCurrentTime, + createDeferredPromise, + isBlobLike, + sameOrigin, + isCancelled, + isAborted, + isErrorLike, + fullyReadBody, + readableStreamClose, + isomorphicEncode, + urlIsLocal, + urlIsHttpHttpsScheme, + urlHasHttpsScheme, + clampAndCoarsenConnectionTimingInfo, + simpleRangeHeaderValue, + buildContentRange, + createInflate, + extractMimeType + } = require_util2(); + var { kState, kDispatcher } = require_symbols2(); + var assert2 = __require("node:assert"); + var { safelyExtractBody, extractBody } = require_body(); + var { + redirectStatusSet, + nullBodyStatus, + safeMethodsSet, + requestBodyHeader, + subresourceSet + } = require_constants3(); + var EE = __require("node:events"); + var { Readable, pipeline, finished } = __require("node:stream"); + var { addAbortListener, isErrored, isReadable, bufferToLowerCasedHeaderName } = require_util(); + var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url(); + var { getGlobalDispatcher } = require_global2(); + var { webidl } = require_webidl(); + var { STATUS_CODES } = __require("node:http"); + var GET_OR_HEAD = ["GET", "HEAD"]; + var defaultUserAgent = typeof __UNDICI_IS_NODE__ !== "undefined" || typeof esbuildDetection !== "undefined" ? "node" : "undici"; + var resolveObjectURL; + var Fetch = class extends EE { + constructor(dispatcher) { + super(); + this.dispatcher = dispatcher; + this.connection = null; + this.dump = false; + this.state = "ongoing"; + } + terminate(reason) { + if (this.state !== "ongoing") { + return; + } + this.state = "terminated"; + this.connection?.destroy(reason); + this.emit("terminated", reason); + } + // https://fetch.spec.whatwg.org/#fetch-controller-abort + abort(error2) { + if (this.state !== "ongoing") { + return; + } + this.state = "aborted"; + if (!error2) { + error2 = new DOMException("The operation was aborted.", "AbortError"); + } + this.serializedAbortReason = error2; + this.connection?.destroy(error2); + this.emit("terminated", error2); + } + }; + function handleFetchDone(response) { + finalizeAndReportTiming(response, "fetch"); + } + function fetch3(input, init = void 0) { + webidl.argumentLengthCheck(arguments, 1, "globalThis.fetch"); + let p = createDeferredPromise(); + let requestObject; + try { + requestObject = new Request(input, init); + } catch (e) { + p.reject(e); + return p.promise; + } + const request3 = requestObject[kState]; + if (requestObject.signal.aborted) { + abortFetch(p, request3, null, requestObject.signal.reason); + return p.promise; + } + const globalObject = request3.client.globalObject; + if (globalObject?.constructor?.name === "ServiceWorkerGlobalScope") { + request3.serviceWorkers = "none"; + } + let responseObject = null; + let locallyAborted = false; + let controller = null; + addAbortListener( + requestObject.signal, + () => { + locallyAborted = true; + assert2(controller != null); + controller.abort(requestObject.signal.reason); + const realResponse = responseObject?.deref(); + abortFetch(p, request3, realResponse, requestObject.signal.reason); + } + ); + const processResponse = (response) => { + if (locallyAborted) { + return; + } + if (response.aborted) { + abortFetch(p, request3, responseObject, controller.serializedAbortReason); + return; + } + if (response.type === "error") { + p.reject(new TypeError("fetch failed", { cause: response.error })); + return; + } + responseObject = new WeakRef(fromInnerResponse(response, "immutable")); + p.resolve(responseObject.deref()); + p = null; + }; + controller = fetching({ + request: request3, + processResponseEndOfBody: handleFetchDone, + processResponse, + dispatcher: requestObject[kDispatcher] + // undici + }); + return p.promise; + } + function finalizeAndReportTiming(response, initiatorType = "other") { + if (response.type === "error" && response.aborted) { + return; + } + if (!response.urlList?.length) { + return; + } + const originalURL = response.urlList[0]; + let timingInfo = response.timingInfo; + let cacheState = response.cacheState; + if (!urlIsHttpHttpsScheme(originalURL)) { + return; + } + if (timingInfo === null) { + return; + } + if (!response.timingAllowPassed) { + timingInfo = createOpaqueTimingInfo({ + startTime: timingInfo.startTime + }); + cacheState = ""; + } + timingInfo.endTime = coarsenedSharedCurrentTime(); + response.timingInfo = timingInfo; + markResourceTiming( + timingInfo, + originalURL.href, + initiatorType, + globalThis, + cacheState + ); + } + var markResourceTiming = performance.markResourceTiming; + function abortFetch(p, request3, responseObject, error2) { + if (p) { + p.reject(error2); + } + if (request3.body != null && isReadable(request3.body?.stream)) { + request3.body.stream.cancel(error2).catch((err) => { + if (err.code === "ERR_INVALID_STATE") { + return; + } + throw err; + }); + } + if (responseObject == null) { + return; + } + const response = responseObject[kState]; + if (response.body != null && isReadable(response.body?.stream)) { + response.body.stream.cancel(error2).catch((err) => { + if (err.code === "ERR_INVALID_STATE") { + return; + } + throw err; + }); + } + } + function fetching({ + request: request3, + processRequestBodyChunkLength, + processRequestEndOfBody, + processResponse, + processResponseEndOfBody, + processResponseConsumeBody, + useParallelQueue = false, + dispatcher = getGlobalDispatcher() + // undici + }) { + assert2(dispatcher); + let taskDestination = null; + let crossOriginIsolatedCapability = false; + if (request3.client != null) { + taskDestination = request3.client.globalObject; + crossOriginIsolatedCapability = request3.client.crossOriginIsolatedCapability; + } + const currentTime = coarsenedSharedCurrentTime(crossOriginIsolatedCapability); + const timingInfo = createOpaqueTimingInfo({ + startTime: currentTime + }); + const fetchParams = { + controller: new Fetch(dispatcher), + request: request3, + timingInfo, + processRequestBodyChunkLength, + processRequestEndOfBody, + processResponse, + processResponseConsumeBody, + processResponseEndOfBody, + taskDestination, + crossOriginIsolatedCapability + }; + assert2(!request3.body || request3.body.stream); + if (request3.window === "client") { + request3.window = request3.client?.globalObject?.constructor?.name === "Window" ? request3.client : "no-window"; + } + if (request3.origin === "client") { + request3.origin = request3.client.origin; + } + if (request3.policyContainer === "client") { + if (request3.client != null) { + request3.policyContainer = clonePolicyContainer( + request3.client.policyContainer + ); + } else { + request3.policyContainer = makePolicyContainer(); + } + } + if (!request3.headersList.contains("accept", true)) { + const value = "*/*"; + request3.headersList.append("accept", value, true); + } + if (!request3.headersList.contains("accept-language", true)) { + request3.headersList.append("accept-language", "*", true); + } + if (request3.priority === null) { + } + if (subresourceSet.has(request3.destination)) { + } + mainFetch(fetchParams).catch((err) => { + fetchParams.controller.terminate(err); + }); + return fetchParams.controller; + } + async function mainFetch(fetchParams, recursive = false) { + const request3 = fetchParams.request; + let response = null; + if (request3.localURLsOnly && !urlIsLocal(requestCurrentURL(request3))) { + response = makeNetworkError("local URLs only"); + } + tryUpgradeRequestToAPotentiallyTrustworthyURL(request3); + if (requestBadPort(request3) === "blocked") { + response = makeNetworkError("bad port"); + } + if (request3.referrerPolicy === "") { + request3.referrerPolicy = request3.policyContainer.referrerPolicy; + } + if (request3.referrer !== "no-referrer") { + request3.referrer = determineRequestsReferrer(request3); + } + if (response === null) { + response = await (async () => { + const currentURL = requestCurrentURL(request3); + if ( + // - request’s current URL’s origin is same origin with request’s origin, + // and request’s response tainting is "basic" + sameOrigin(currentURL, request3.url) && request3.responseTainting === "basic" || // request’s current URL’s scheme is "data" + currentURL.protocol === "data:" || // - request’s mode is "navigate" or "websocket" + (request3.mode === "navigate" || request3.mode === "websocket") + ) { + request3.responseTainting = "basic"; + return await schemeFetch(fetchParams); + } + if (request3.mode === "same-origin") { + return makeNetworkError('request mode cannot be "same-origin"'); + } + if (request3.mode === "no-cors") { + if (request3.redirect !== "follow") { + return makeNetworkError( + 'redirect mode cannot be "follow" for "no-cors" request' + ); + } + request3.responseTainting = "opaque"; + return await schemeFetch(fetchParams); + } + if (!urlIsHttpHttpsScheme(requestCurrentURL(request3))) { + return makeNetworkError("URL scheme must be a HTTP(S) scheme"); + } + request3.responseTainting = "cors"; + return await httpFetch(fetchParams); + })(); + } + if (recursive) { + return response; + } + if (response.status !== 0 && !response.internalResponse) { + if (request3.responseTainting === "cors") { + } + if (request3.responseTainting === "basic") { + response = filterResponse(response, "basic"); + } else if (request3.responseTainting === "cors") { + response = filterResponse(response, "cors"); + } else if (request3.responseTainting === "opaque") { + response = filterResponse(response, "opaque"); + } else { + assert2(false); + } + } + let internalResponse = response.status === 0 ? response : response.internalResponse; + if (internalResponse.urlList.length === 0) { + internalResponse.urlList.push(...request3.urlList); + } + if (!request3.timingAllowFailed) { + response.timingAllowPassed = true; + } + if (response.type === "opaque" && internalResponse.status === 206 && internalResponse.rangeRequested && !request3.headers.contains("range", true)) { + response = internalResponse = makeNetworkError(); + } + if (response.status !== 0 && (request3.method === "HEAD" || request3.method === "CONNECT" || nullBodyStatus.includes(internalResponse.status))) { + internalResponse.body = null; + fetchParams.controller.dump = true; + } + if (request3.integrity) { + const processBodyError = (reason) => fetchFinale(fetchParams, makeNetworkError(reason)); + if (request3.responseTainting === "opaque" || response.body == null) { + processBodyError(response.error); + return; + } + const processBody = (bytes) => { + if (!bytesMatch(bytes, request3.integrity)) { + processBodyError("integrity mismatch"); + return; + } + response.body = safelyExtractBody(bytes)[0]; + fetchFinale(fetchParams, response); + }; + await fullyReadBody(response.body, processBody, processBodyError); + } else { + fetchFinale(fetchParams, response); + } + } + function schemeFetch(fetchParams) { + if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) { + return Promise.resolve(makeAppropriateNetworkError(fetchParams)); + } + const { request: request3 } = fetchParams; + const { protocol: scheme } = requestCurrentURL(request3); + switch (scheme) { + case "about:": { + return Promise.resolve(makeNetworkError("about scheme is not supported")); + } + case "blob:": { + if (!resolveObjectURL) { + resolveObjectURL = __require("node:buffer").resolveObjectURL; + } + const blobURLEntry = requestCurrentURL(request3); + if (blobURLEntry.search.length !== 0) { + return Promise.resolve(makeNetworkError("NetworkError when attempting to fetch resource.")); + } + const blob = resolveObjectURL(blobURLEntry.toString()); + if (request3.method !== "GET" || !isBlobLike(blob)) { + return Promise.resolve(makeNetworkError("invalid method")); + } + const response = makeResponse(); + const fullLength = blob.size; + const serializedFullLength = isomorphicEncode(`${fullLength}`); + const type = blob.type; + if (!request3.headersList.contains("range", true)) { + const bodyWithType = extractBody(blob); + response.statusText = "OK"; + response.body = bodyWithType[0]; + response.headersList.set("content-length", serializedFullLength, true); + response.headersList.set("content-type", type, true); + } else { + response.rangeRequested = true; + const rangeHeader = request3.headersList.get("range", true); + const rangeValue = simpleRangeHeaderValue(rangeHeader, true); + if (rangeValue === "failure") { + return Promise.resolve(makeNetworkError("failed to fetch the data URL")); + } + let { rangeStartValue: rangeStart, rangeEndValue: rangeEnd } = rangeValue; + if (rangeStart === null) { + rangeStart = fullLength - rangeEnd; + rangeEnd = rangeStart + rangeEnd - 1; + } else { + if (rangeStart >= fullLength) { + return Promise.resolve(makeNetworkError("Range start is greater than the blob's size.")); + } + if (rangeEnd === null || rangeEnd >= fullLength) { + rangeEnd = fullLength - 1; + } + } + const slicedBlob = blob.slice(rangeStart, rangeEnd, type); + const slicedBodyWithType = extractBody(slicedBlob); + response.body = slicedBodyWithType[0]; + const serializedSlicedLength = isomorphicEncode(`${slicedBlob.size}`); + const contentRange = buildContentRange(rangeStart, rangeEnd, fullLength); + response.status = 206; + response.statusText = "Partial Content"; + response.headersList.set("content-length", serializedSlicedLength, true); + response.headersList.set("content-type", type, true); + response.headersList.set("content-range", contentRange, true); + } + return Promise.resolve(response); + } + case "data:": { + const currentURL = requestCurrentURL(request3); + const dataURLStruct = dataURLProcessor(currentURL); + if (dataURLStruct === "failure") { + return Promise.resolve(makeNetworkError("failed to fetch the data URL")); + } + const mimeType = serializeAMimeType(dataURLStruct.mimeType); + return Promise.resolve(makeResponse({ + statusText: "OK", + headersList: [ + ["content-type", { name: "Content-Type", value: mimeType }] + ], + body: safelyExtractBody(dataURLStruct.body)[0] + })); + } + case "file:": { + return Promise.resolve(makeNetworkError("not implemented... yet...")); + } + case "http:": + case "https:": { + return httpFetch(fetchParams).catch((err) => makeNetworkError(err)); + } + default: { + return Promise.resolve(makeNetworkError("unknown scheme")); + } + } + } + function finalizeResponse(fetchParams, response) { + fetchParams.request.done = true; + if (fetchParams.processResponseDone != null) { + queueMicrotask(() => fetchParams.processResponseDone(response)); + } + } + function fetchFinale(fetchParams, response) { + let timingInfo = fetchParams.timingInfo; + const processResponseEndOfBody = () => { + const unsafeEndTime = Date.now(); + if (fetchParams.request.destination === "document") { + fetchParams.controller.fullTimingInfo = timingInfo; + } + fetchParams.controller.reportTimingSteps = () => { + if (fetchParams.request.url.protocol !== "https:") { + return; + } + timingInfo.endTime = unsafeEndTime; + let cacheState = response.cacheState; + const bodyInfo = response.bodyInfo; + if (!response.timingAllowPassed) { + timingInfo = createOpaqueTimingInfo(timingInfo); + cacheState = ""; + } + let responseStatus = 0; + if (fetchParams.request.mode !== "navigator" || !response.hasCrossOriginRedirects) { + responseStatus = response.status; + const mimeType = extractMimeType(response.headersList); + if (mimeType !== "failure") { + bodyInfo.contentType = minimizeSupportedMimeType(mimeType); + } + } + if (fetchParams.request.initiatorType != null) { + markResourceTiming(timingInfo, fetchParams.request.url.href, fetchParams.request.initiatorType, globalThis, cacheState, bodyInfo, responseStatus); + } + }; + const processResponseEndOfBodyTask = () => { + fetchParams.request.done = true; + if (fetchParams.processResponseEndOfBody != null) { + queueMicrotask(() => fetchParams.processResponseEndOfBody(response)); + } + if (fetchParams.request.initiatorType != null) { + fetchParams.controller.reportTimingSteps(); + } + }; + queueMicrotask(() => processResponseEndOfBodyTask()); + }; + if (fetchParams.processResponse != null) { + queueMicrotask(() => { + fetchParams.processResponse(response); + fetchParams.processResponse = null; + }); + } + const internalResponse = response.type === "error" ? response : response.internalResponse ?? response; + if (internalResponse.body == null) { + processResponseEndOfBody(); + } else { + finished(internalResponse.body.stream, () => { + processResponseEndOfBody(); + }); + } + } + async function httpFetch(fetchParams) { + const request3 = fetchParams.request; + let response = null; + let actualResponse = null; + const timingInfo = fetchParams.timingInfo; + if (request3.serviceWorkers === "all") { + } + if (response === null) { + if (request3.redirect === "follow") { + request3.serviceWorkers = "none"; + } + actualResponse = response = await httpNetworkOrCacheFetch(fetchParams); + if (request3.responseTainting === "cors" && corsCheck(request3, response) === "failure") { + return makeNetworkError("cors failure"); + } + if (TAOCheck(request3, response) === "failure") { + request3.timingAllowFailed = true; + } + } + if ((request3.responseTainting === "opaque" || response.type === "opaque") && crossOriginResourcePolicyCheck( + request3.origin, + request3.client, + request3.destination, + actualResponse + ) === "blocked") { + return makeNetworkError("blocked"); + } + if (redirectStatusSet.has(actualResponse.status)) { + if (request3.redirect !== "manual") { + fetchParams.controller.connection.destroy(void 0, false); + } + if (request3.redirect === "error") { + response = makeNetworkError("unexpected redirect"); + } else if (request3.redirect === "manual") { + response = actualResponse; + } else if (request3.redirect === "follow") { + response = await httpRedirectFetch(fetchParams, response); + } else { + assert2(false); + } + } + response.timingInfo = timingInfo; + return response; + } + function httpRedirectFetch(fetchParams, response) { + const request3 = fetchParams.request; + const actualResponse = response.internalResponse ? response.internalResponse : response; + let locationURL; + try { + locationURL = responseLocationURL( + actualResponse, + requestCurrentURL(request3).hash + ); + if (locationURL == null) { + return response; + } + } catch (err) { + return Promise.resolve(makeNetworkError(err)); + } + if (!urlIsHttpHttpsScheme(locationURL)) { + return Promise.resolve(makeNetworkError("URL scheme must be a HTTP(S) scheme")); + } + if (request3.redirectCount === 20) { + return Promise.resolve(makeNetworkError("redirect count exceeded")); + } + request3.redirectCount += 1; + if (request3.mode === "cors" && (locationURL.username || locationURL.password) && !sameOrigin(request3, locationURL)) { + return Promise.resolve(makeNetworkError('cross origin not allowed for request mode "cors"')); + } + if (request3.responseTainting === "cors" && (locationURL.username || locationURL.password)) { + return Promise.resolve(makeNetworkError( + 'URL cannot contain credentials for request mode "cors"' + )); + } + if (actualResponse.status !== 303 && request3.body != null && request3.body.source == null) { + return Promise.resolve(makeNetworkError()); + } + if ([301, 302].includes(actualResponse.status) && request3.method === "POST" || actualResponse.status === 303 && !GET_OR_HEAD.includes(request3.method)) { + request3.method = "GET"; + request3.body = null; + for (const headerName of requestBodyHeader) { + request3.headersList.delete(headerName); + } + } + if (!sameOrigin(requestCurrentURL(request3), locationURL)) { + request3.headersList.delete("authorization", true); + request3.headersList.delete("proxy-authorization", true); + request3.headersList.delete("cookie", true); + request3.headersList.delete("host", true); + } + if (request3.body != null) { + assert2(request3.body.source != null); + request3.body = safelyExtractBody(request3.body.source)[0]; + } + const timingInfo = fetchParams.timingInfo; + timingInfo.redirectEndTime = timingInfo.postRedirectStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability); + if (timingInfo.redirectStartTime === 0) { + timingInfo.redirectStartTime = timingInfo.startTime; + } + request3.urlList.push(locationURL); + setRequestReferrerPolicyOnRedirect(request3, actualResponse); + return mainFetch(fetchParams, true); + } + async function httpNetworkOrCacheFetch(fetchParams, isAuthenticationFetch = false, isNewConnectionFetch = false) { + const request3 = fetchParams.request; + let httpFetchParams = null; + let httpRequest = null; + let response = null; + const httpCache = null; + const revalidatingFlag = false; + if (request3.window === "no-window" && request3.redirect === "error") { + httpFetchParams = fetchParams; + httpRequest = request3; + } else { + httpRequest = cloneRequest(request3); + httpFetchParams = { ...fetchParams }; + httpFetchParams.request = httpRequest; + } + const includeCredentials = request3.credentials === "include" || request3.credentials === "same-origin" && request3.responseTainting === "basic"; + const contentLength = httpRequest.body ? httpRequest.body.length : null; + let contentLengthHeaderValue = null; + if (httpRequest.body == null && ["POST", "PUT"].includes(httpRequest.method)) { + contentLengthHeaderValue = "0"; + } + if (contentLength != null) { + contentLengthHeaderValue = isomorphicEncode(`${contentLength}`); + } + if (contentLengthHeaderValue != null) { + httpRequest.headersList.append("content-length", contentLengthHeaderValue, true); + } + if (contentLength != null && httpRequest.keepalive) { + } + if (httpRequest.referrer instanceof URL) { + httpRequest.headersList.append("referer", isomorphicEncode(httpRequest.referrer.href), true); + } + appendRequestOriginHeader(httpRequest); + appendFetchMetadata(httpRequest); + if (!httpRequest.headersList.contains("user-agent", true)) { + httpRequest.headersList.append("user-agent", defaultUserAgent); + } + if (httpRequest.cache === "default" && (httpRequest.headersList.contains("if-modified-since", true) || httpRequest.headersList.contains("if-none-match", true) || httpRequest.headersList.contains("if-unmodified-since", true) || httpRequest.headersList.contains("if-match", true) || httpRequest.headersList.contains("if-range", true))) { + httpRequest.cache = "no-store"; + } + if (httpRequest.cache === "no-cache" && !httpRequest.preventNoCacheCacheControlHeaderModification && !httpRequest.headersList.contains("cache-control", true)) { + httpRequest.headersList.append("cache-control", "max-age=0", true); + } + if (httpRequest.cache === "no-store" || httpRequest.cache === "reload") { + if (!httpRequest.headersList.contains("pragma", true)) { + httpRequest.headersList.append("pragma", "no-cache", true); + } + if (!httpRequest.headersList.contains("cache-control", true)) { + httpRequest.headersList.append("cache-control", "no-cache", true); + } + } + if (httpRequest.headersList.contains("range", true)) { + httpRequest.headersList.append("accept-encoding", "identity", true); + } + if (!httpRequest.headersList.contains("accept-encoding", true)) { + if (urlHasHttpsScheme(requestCurrentURL(httpRequest))) { + httpRequest.headersList.append("accept-encoding", "br, gzip, deflate", true); + } else { + httpRequest.headersList.append("accept-encoding", "gzip, deflate", true); + } + } + httpRequest.headersList.delete("host", true); + if (includeCredentials) { + } + if (httpCache == null) { + httpRequest.cache = "no-store"; + } + if (httpRequest.cache !== "no-store" && httpRequest.cache !== "reload") { + } + if (response == null) { + if (httpRequest.cache === "only-if-cached") { + return makeNetworkError("only if cached"); + } + const forwardResponse = await httpNetworkFetch( + httpFetchParams, + includeCredentials, + isNewConnectionFetch + ); + if (!safeMethodsSet.has(httpRequest.method) && forwardResponse.status >= 200 && forwardResponse.status <= 399) { + } + if (revalidatingFlag && forwardResponse.status === 304) { + } + if (response == null) { + response = forwardResponse; + } + } + response.urlList = [...httpRequest.urlList]; + if (httpRequest.headersList.contains("range", true)) { + response.rangeRequested = true; + } + response.requestIncludesCredentials = includeCredentials; + if (response.status === 407) { + if (request3.window === "no-window") { + return makeNetworkError(); + } + if (isCancelled(fetchParams)) { + return makeAppropriateNetworkError(fetchParams); + } + return makeNetworkError("proxy authentication required"); + } + if ( + // response’s status is 421 + response.status === 421 && // isNewConnectionFetch is false + !isNewConnectionFetch && // request’s body is null, or request’s body is non-null and request’s body’s source is non-null + (request3.body == null || request3.body.source != null) + ) { + if (isCancelled(fetchParams)) { + return makeAppropriateNetworkError(fetchParams); + } + fetchParams.controller.connection.destroy(); + response = await httpNetworkOrCacheFetch( + fetchParams, + isAuthenticationFetch, + true + ); + } + if (isAuthenticationFetch) { + } + return response; + } + async function httpNetworkFetch(fetchParams, includeCredentials = false, forceNewConnection = false) { + assert2(!fetchParams.controller.connection || fetchParams.controller.connection.destroyed); + fetchParams.controller.connection = { + abort: null, + destroyed: false, + destroy(err, abort = true) { + if (!this.destroyed) { + this.destroyed = true; + if (abort) { + this.abort?.(err ?? new DOMException("The operation was aborted.", "AbortError")); + } + } + } + }; + const request3 = fetchParams.request; + let response = null; + const timingInfo = fetchParams.timingInfo; + const httpCache = null; + if (httpCache == null) { + request3.cache = "no-store"; + } + const newConnection = forceNewConnection ? "yes" : "no"; + if (request3.mode === "websocket") { + } else { + } + let requestBody = null; + if (request3.body == null && fetchParams.processRequestEndOfBody) { + queueMicrotask(() => fetchParams.processRequestEndOfBody()); + } else if (request3.body != null) { + const processBodyChunk = async function* (bytes) { + if (isCancelled(fetchParams)) { + return; + } + yield bytes; + fetchParams.processRequestBodyChunkLength?.(bytes.byteLength); + }; + const processEndOfBody = () => { + if (isCancelled(fetchParams)) { + return; + } + if (fetchParams.processRequestEndOfBody) { + fetchParams.processRequestEndOfBody(); + } + }; + const processBodyError = (e) => { + if (isCancelled(fetchParams)) { + return; + } + if (e.name === "AbortError") { + fetchParams.controller.abort(); + } else { + fetchParams.controller.terminate(e); + } + }; + requestBody = async function* () { + try { + for await (const bytes of request3.body.stream) { + yield* processBodyChunk(bytes); + } + processEndOfBody(); + } catch (err) { + processBodyError(err); + } + }(); + } + try { + const { body, status, statusText, headersList, socket } = await dispatch({ body: requestBody }); + if (socket) { + response = makeResponse({ status, statusText, headersList, socket }); + } else { + const iterator3 = body[Symbol.asyncIterator](); + fetchParams.controller.next = () => iterator3.next(); + response = makeResponse({ status, statusText, headersList }); + } + } catch (err) { + if (err.name === "AbortError") { + fetchParams.controller.connection.destroy(); + return makeAppropriateNetworkError(fetchParams, err); + } + return makeNetworkError(err); + } + const pullAlgorithm = async () => { + await fetchParams.controller.resume(); + }; + const cancelAlgorithm = (reason) => { + if (!isCancelled(fetchParams)) { + fetchParams.controller.abort(reason); + } + }; + const stream = new ReadableStream( + { + async start(controller) { + fetchParams.controller.controller = controller; + }, + async pull(controller) { + await pullAlgorithm(controller); + }, + async cancel(reason) { + await cancelAlgorithm(reason); + }, + type: "bytes" + } + ); + response.body = { stream, source: null, length: null }; + fetchParams.controller.onAborted = onAborted; + fetchParams.controller.on("terminated", onAborted); + fetchParams.controller.resume = async () => { + while (true) { + let bytes; + let isFailure; + try { + const { done, value } = await fetchParams.controller.next(); + if (isAborted(fetchParams)) { + break; + } + bytes = done ? void 0 : value; + } catch (err) { + if (fetchParams.controller.ended && !timingInfo.encodedBodySize) { + bytes = void 0; + } else { + bytes = err; + isFailure = true; + } + } + if (bytes === void 0) { + readableStreamClose(fetchParams.controller.controller); + finalizeResponse(fetchParams, response); + return; + } + timingInfo.decodedBodySize += bytes?.byteLength ?? 0; + if (isFailure) { + fetchParams.controller.terminate(bytes); + return; + } + const buffer = new Uint8Array(bytes); + if (buffer.byteLength) { + fetchParams.controller.controller.enqueue(buffer); + } + if (isErrored(stream)) { + fetchParams.controller.terminate(); + return; + } + if (fetchParams.controller.controller.desiredSize <= 0) { + return; + } + } + }; + function onAborted(reason) { + if (isAborted(fetchParams)) { + response.aborted = true; + if (isReadable(stream)) { + fetchParams.controller.controller.error( + fetchParams.controller.serializedAbortReason + ); + } + } else { + if (isReadable(stream)) { + fetchParams.controller.controller.error(new TypeError("terminated", { + cause: isErrorLike(reason) ? reason : void 0 + })); + } + } + fetchParams.controller.connection.destroy(); + } + return response; + function dispatch({ body }) { + const url = requestCurrentURL(request3); + const agent = fetchParams.controller.dispatcher; + return new Promise((resolve5, reject) => agent.dispatch( + { + path: url.pathname + url.search, + origin: url.origin, + method: request3.method, + body: agent.isMockActive ? request3.body && (request3.body.source || request3.body.stream) : body, + headers: request3.headersList.entries, + maxRedirections: 0, + upgrade: request3.mode === "websocket" ? "websocket" : void 0 + }, + { + body: null, + abort: null, + onConnect(abort) { + const { connection } = fetchParams.controller; + timingInfo.finalConnectionTimingInfo = clampAndCoarsenConnectionTimingInfo(void 0, timingInfo.postRedirectStartTime, fetchParams.crossOriginIsolatedCapability); + if (connection.destroyed) { + abort(new DOMException("The operation was aborted.", "AbortError")); + } else { + fetchParams.controller.on("terminated", abort); + this.abort = connection.abort = abort; + } + timingInfo.finalNetworkRequestStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability); + }, + onResponseStarted() { + timingInfo.finalNetworkResponseStartTime = coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability); + }, + onHeaders(status, rawHeaders, resume, statusText) { + if (status < 200) { + return; + } + let location = ""; + const headersList = new HeadersList(); + for (let i = 0; i < rawHeaders.length; i += 2) { + headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString("latin1"), true); + } + location = headersList.get("location", true); + this.body = new Readable({ read: resume }); + const decoders = []; + const willFollow = location && request3.redirect === "follow" && redirectStatusSet.has(status); + if (request3.method !== "HEAD" && request3.method !== "CONNECT" && !nullBodyStatus.includes(status) && !willFollow) { + const contentEncoding = headersList.get("content-encoding", true); + const codings = contentEncoding ? contentEncoding.toLowerCase().split(",") : []; + const maxContentEncodings = 5; + if (codings.length > maxContentEncodings) { + reject(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`)); + return true; + } + for (let i = codings.length - 1; i >= 0; --i) { + const coding = codings[i].trim(); + if (coding === "x-gzip" || coding === "gzip") { + decoders.push(zlib.createGunzip({ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + flush: zlib.constants.Z_SYNC_FLUSH, + finishFlush: zlib.constants.Z_SYNC_FLUSH + })); + } else if (coding === "deflate") { + decoders.push(createInflate({ + flush: zlib.constants.Z_SYNC_FLUSH, + finishFlush: zlib.constants.Z_SYNC_FLUSH + })); + } else if (coding === "br") { + decoders.push(zlib.createBrotliDecompress({ + flush: zlib.constants.BROTLI_OPERATION_FLUSH, + finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH + })); + } else { + decoders.length = 0; + break; + } + } + } + const onError = this.onError.bind(this); + resolve5({ + status, + statusText, + headersList, + body: decoders.length ? pipeline(this.body, ...decoders, (err) => { + if (err) { + this.onError(err); + } + }).on("error", onError) : this.body.on("error", onError) + }); + return true; + }, + onData(chunk) { + if (fetchParams.controller.dump) { + return; + } + const bytes = chunk; + timingInfo.encodedBodySize += bytes.byteLength; + return this.body.push(bytes); + }, + onComplete() { + if (this.abort) { + fetchParams.controller.off("terminated", this.abort); + } + if (fetchParams.controller.onAborted) { + fetchParams.controller.off("terminated", fetchParams.controller.onAborted); + } + fetchParams.controller.ended = true; + this.body.push(null); + }, + onError(error2) { + if (this.abort) { + fetchParams.controller.off("terminated", this.abort); + } + this.body?.destroy(error2); + fetchParams.controller.terminate(error2); + reject(error2); + }, + onUpgrade(status, rawHeaders, socket) { + if (status !== 101) { + return; + } + const headersList = new HeadersList(); + for (let i = 0; i < rawHeaders.length; i += 2) { + headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString("latin1"), true); + } + resolve5({ + status, + statusText: STATUS_CODES[status], + headersList, + socket + }); + return true; + } + } + )); + } + } + module.exports = { + fetch: fetch3, + Fetch, + fetching, + finalizeAndReportTiming + }; + } +}); + +// +var require_symbols3 = __commonJS({ + ""(exports, module) { + "use strict"; + module.exports = { + kState: Symbol("FileReader state"), + kResult: Symbol("FileReader result"), + kError: Symbol("FileReader error"), + kLastProgressEventFired: Symbol("FileReader last progress event fired timestamp"), + kEvents: Symbol("FileReader events"), + kAborted: Symbol("FileReader aborted") + }; + } +}); + +// +var require_progressevent = __commonJS({ + ""(exports, module) { + "use strict"; + var { webidl } = require_webidl(); + var kState = Symbol("ProgressEvent state"); + var ProgressEvent = class _ProgressEvent extends Event { + constructor(type, eventInitDict = {}) { + type = webidl.converters.DOMString(type, "ProgressEvent constructor", "type"); + eventInitDict = webidl.converters.ProgressEventInit(eventInitDict ?? {}); + super(type, eventInitDict); + this[kState] = { + lengthComputable: eventInitDict.lengthComputable, + loaded: eventInitDict.loaded, + total: eventInitDict.total + }; + } + get lengthComputable() { + webidl.brandCheck(this, _ProgressEvent); + return this[kState].lengthComputable; + } + get loaded() { + webidl.brandCheck(this, _ProgressEvent); + return this[kState].loaded; + } + get total() { + webidl.brandCheck(this, _ProgressEvent); + return this[kState].total; + } + }; + webidl.converters.ProgressEventInit = webidl.dictionaryConverter([ + { + key: "lengthComputable", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "loaded", + converter: webidl.converters["unsigned long long"], + defaultValue: () => 0 + }, + { + key: "total", + converter: webidl.converters["unsigned long long"], + defaultValue: () => 0 + }, + { + key: "bubbles", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "cancelable", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "composed", + converter: webidl.converters.boolean, + defaultValue: () => false + } + ]); + module.exports = { + ProgressEvent + }; + } +}); + +// +var require_encoding = __commonJS({ + ""(exports, module) { + "use strict"; + function getEncoding(label) { + if (!label) { + return "failure"; + } + switch (label.trim().toLowerCase()) { + case "unicode-1-1-utf-8": + case "unicode11utf8": + case "unicode20utf8": + case "utf-8": + case "utf8": + case "x-unicode20utf8": + return "UTF-8"; + case "866": + case "cp866": + case "csibm866": + case "ibm866": + return "IBM866"; + case "csisolatin2": + case "iso-8859-2": + case "iso-ir-101": + case "iso8859-2": + case "iso88592": + case "iso_8859-2": + case "iso_8859-2:1987": + case "l2": + case "latin2": + return "ISO-8859-2"; + case "csisolatin3": + case "iso-8859-3": + case "iso-ir-109": + case "iso8859-3": + case "iso88593": + case "iso_8859-3": + case "iso_8859-3:1988": + case "l3": + case "latin3": + return "ISO-8859-3"; + case "csisolatin4": + case "iso-8859-4": + case "iso-ir-110": + case "iso8859-4": + case "iso88594": + case "iso_8859-4": + case "iso_8859-4:1988": + case "l4": + case "latin4": + return "ISO-8859-4"; + case "csisolatincyrillic": + case "cyrillic": + case "iso-8859-5": + case "iso-ir-144": + case "iso8859-5": + case "iso88595": + case "iso_8859-5": + case "iso_8859-5:1988": + return "ISO-8859-5"; + case "arabic": + case "asmo-708": + case "csiso88596e": + case "csiso88596i": + case "csisolatinarabic": + case "ecma-114": + case "iso-8859-6": + case "iso-8859-6-e": + case "iso-8859-6-i": + case "iso-ir-127": + case "iso8859-6": + case "iso88596": + case "iso_8859-6": + case "iso_8859-6:1987": + return "ISO-8859-6"; + case "csisolatingreek": + case "ecma-118": + case "elot_928": + case "greek": + case "greek8": + case "iso-8859-7": + case "iso-ir-126": + case "iso8859-7": + case "iso88597": + case "iso_8859-7": + case "iso_8859-7:1987": + case "sun_eu_greek": + return "ISO-8859-7"; + case "csiso88598e": + case "csisolatinhebrew": + case "hebrew": + case "iso-8859-8": + case "iso-8859-8-e": + case "iso-ir-138": + case "iso8859-8": + case "iso88598": + case "iso_8859-8": + case "iso_8859-8:1988": + case "visual": + return "ISO-8859-8"; + case "csiso88598i": + case "iso-8859-8-i": + case "logical": + return "ISO-8859-8-I"; + case "csisolatin6": + case "iso-8859-10": + case "iso-ir-157": + case "iso8859-10": + case "iso885910": + case "l6": + case "latin6": + return "ISO-8859-10"; + case "iso-8859-13": + case "iso8859-13": + case "iso885913": + return "ISO-8859-13"; + case "iso-8859-14": + case "iso8859-14": + case "iso885914": + return "ISO-8859-14"; + case "csisolatin9": + case "iso-8859-15": + case "iso8859-15": + case "iso885915": + case "iso_8859-15": + case "l9": + return "ISO-8859-15"; + case "iso-8859-16": + return "ISO-8859-16"; + case "cskoi8r": + case "koi": + case "koi8": + case "koi8-r": + case "koi8_r": + return "KOI8-R"; + case "koi8-ru": + case "koi8-u": + return "KOI8-U"; + case "csmacintosh": + case "mac": + case "macintosh": + case "x-mac-roman": + return "macintosh"; + case "iso-8859-11": + case "iso8859-11": + case "iso885911": + case "tis-620": + case "windows-874": + return "windows-874"; + case "cp1250": + case "windows-1250": + case "x-cp1250": + return "windows-1250"; + case "cp1251": + case "windows-1251": + case "x-cp1251": + return "windows-1251"; + case "ansi_x3.4-1968": + case "ascii": + case "cp1252": + case "cp819": + case "csisolatin1": + case "ibm819": + case "iso-8859-1": + case "iso-ir-100": + case "iso8859-1": + case "iso88591": + case "iso_8859-1": + case "iso_8859-1:1987": + case "l1": + case "latin1": + case "us-ascii": + case "windows-1252": + case "x-cp1252": + return "windows-1252"; + case "cp1253": + case "windows-1253": + case "x-cp1253": + return "windows-1253"; + case "cp1254": + case "csisolatin5": + case "iso-8859-9": + case "iso-ir-148": + case "iso8859-9": + case "iso88599": + case "iso_8859-9": + case "iso_8859-9:1989": + case "l5": + case "latin5": + case "windows-1254": + case "x-cp1254": + return "windows-1254"; + case "cp1255": + case "windows-1255": + case "x-cp1255": + return "windows-1255"; + case "cp1256": + case "windows-1256": + case "x-cp1256": + return "windows-1256"; + case "cp1257": + case "windows-1257": + case "x-cp1257": + return "windows-1257"; + case "cp1258": + case "windows-1258": + case "x-cp1258": + return "windows-1258"; + case "x-mac-cyrillic": + case "x-mac-ukrainian": + return "x-mac-cyrillic"; + case "chinese": + case "csgb2312": + case "csiso58gb231280": + case "gb2312": + case "gb_2312": + case "gb_2312-80": + case "gbk": + case "iso-ir-58": + case "x-gbk": + return "GBK"; + case "gb18030": + return "gb18030"; + case "big5": + case "big5-hkscs": + case "cn-big5": + case "csbig5": + case "x-x-big5": + return "Big5"; + case "cseucpkdfmtjapanese": + case "euc-jp": + case "x-euc-jp": + return "EUC-JP"; + case "csiso2022jp": + case "iso-2022-jp": + return "ISO-2022-JP"; + case "csshiftjis": + case "ms932": + case "ms_kanji": + case "shift-jis": + case "shift_jis": + case "sjis": + case "windows-31j": + case "x-sjis": + return "Shift_JIS"; + case "cseuckr": + case "csksc56011987": + case "euc-kr": + case "iso-ir-149": + case "korean": + case "ks_c_5601-1987": + case "ks_c_5601-1989": + case "ksc5601": + case "ksc_5601": + case "windows-949": + return "EUC-KR"; + case "csiso2022kr": + case "hz-gb-2312": + case "iso-2022-cn": + case "iso-2022-cn-ext": + case "iso-2022-kr": + case "replacement": + return "replacement"; + case "unicodefffe": + case "utf-16be": + return "UTF-16BE"; + case "csunicode": + case "iso-10646-ucs-2": + case "ucs-2": + case "unicode": + case "unicodefeff": + case "utf-16": + case "utf-16le": + return "UTF-16LE"; + case "x-user-defined": + return "x-user-defined"; + default: + return "failure"; + } + } + module.exports = { + getEncoding + }; + } +}); + +// +var require_util4 = __commonJS({ + ""(exports, module) { + "use strict"; + var { + kState, + kError, + kResult, + kAborted, + kLastProgressEventFired + } = require_symbols3(); + var { ProgressEvent } = require_progressevent(); + var { getEncoding } = require_encoding(); + var { serializeAMimeType, parseMIMEType } = require_data_url(); + var { types: types2 } = __require("node:util"); + var { StringDecoder } = __require("string_decoder"); + var { btoa: btoa2 } = __require("node:buffer"); + var staticPropertyDescriptors = { + enumerable: true, + writable: false, + configurable: false + }; + function readOperation(fr, blob, type, encodingName) { + if (fr[kState] === "loading") { + throw new DOMException("Invalid state", "InvalidStateError"); + } + fr[kState] = "loading"; + fr[kResult] = null; + fr[kError] = null; + const stream = blob.stream(); + const reader = stream.getReader(); + const bytes = []; + let chunkPromise = reader.read(); + let isFirstChunk = true; + (async () => { + while (!fr[kAborted]) { + try { + const { done, value } = await chunkPromise; + if (isFirstChunk && !fr[kAborted]) { + queueMicrotask(() => { + fireAProgressEvent("loadstart", fr); + }); + } + isFirstChunk = false; + if (!done && types2.isUint8Array(value)) { + bytes.push(value); + if ((fr[kLastProgressEventFired] === void 0 || Date.now() - fr[kLastProgressEventFired] >= 50) && !fr[kAborted]) { + fr[kLastProgressEventFired] = Date.now(); + queueMicrotask(() => { + fireAProgressEvent("progress", fr); + }); + } + chunkPromise = reader.read(); + } else if (done) { + queueMicrotask(() => { + fr[kState] = "done"; + try { + const result = packageData(bytes, type, blob.type, encodingName); + if (fr[kAborted]) { + return; + } + fr[kResult] = result; + fireAProgressEvent("load", fr); + } catch (error2) { + fr[kError] = error2; + fireAProgressEvent("error", fr); + } + if (fr[kState] !== "loading") { + fireAProgressEvent("loadend", fr); + } + }); + break; + } + } catch (error2) { + if (fr[kAborted]) { + return; + } + queueMicrotask(() => { + fr[kState] = "done"; + fr[kError] = error2; + fireAProgressEvent("error", fr); + if (fr[kState] !== "loading") { + fireAProgressEvent("loadend", fr); + } + }); + break; + } + } + })(); + } + function fireAProgressEvent(e, reader) { + const event = new ProgressEvent(e, { + bubbles: false, + cancelable: false + }); + reader.dispatchEvent(event); + } + function packageData(bytes, type, mimeType, encodingName) { + switch (type) { + case "DataURL": { + let dataURL = "data:"; + const parsed = parseMIMEType(mimeType || "application/octet-stream"); + if (parsed !== "failure") { + dataURL += serializeAMimeType(parsed); + } + dataURL += ";base64,"; + const decoder = new StringDecoder("latin1"); + for (const chunk of bytes) { + dataURL += btoa2(decoder.write(chunk)); + } + dataURL += btoa2(decoder.end()); + return dataURL; + } + case "Text": { + let encoding = "failure"; + if (encodingName) { + encoding = getEncoding(encodingName); + } + if (encoding === "failure" && mimeType) { + const type2 = parseMIMEType(mimeType); + if (type2 !== "failure") { + encoding = getEncoding(type2.parameters.get("charset")); + } + } + if (encoding === "failure") { + encoding = "UTF-8"; + } + return decode(bytes, encoding); + } + case "ArrayBuffer": { + const sequence = combineByteSequences(bytes); + return sequence.buffer; + } + case "BinaryString": { + let binaryString = ""; + const decoder = new StringDecoder("latin1"); + for (const chunk of bytes) { + binaryString += decoder.write(chunk); + } + binaryString += decoder.end(); + return binaryString; + } + } + } + function decode(ioQueue, encoding) { + const bytes = combineByteSequences(ioQueue); + const BOMEncoding = BOMSniffing(bytes); + let slice = 0; + if (BOMEncoding !== null) { + encoding = BOMEncoding; + slice = BOMEncoding === "UTF-8" ? 3 : 2; + } + const sliced = bytes.slice(slice); + return new TextDecoder(encoding).decode(sliced); + } + function BOMSniffing(ioQueue) { + const [a, b, c] = ioQueue; + if (a === 239 && b === 187 && c === 191) { + return "UTF-8"; + } else if (a === 254 && b === 255) { + return "UTF-16BE"; + } else if (a === 255 && b === 254) { + return "UTF-16LE"; + } + return null; + } + function combineByteSequences(sequences) { + const size = sequences.reduce((a, b) => { + return a + b.byteLength; + }, 0); + let offset = 0; + return sequences.reduce((a, b) => { + a.set(b, offset); + offset += b.byteLength; + return a; + }, new Uint8Array(size)); + } + module.exports = { + staticPropertyDescriptors, + readOperation, + fireAProgressEvent + }; + } +}); + +// +var require_filereader = __commonJS({ + ""(exports, module) { + "use strict"; + var { + staticPropertyDescriptors, + readOperation, + fireAProgressEvent + } = require_util4(); + var { + kState, + kError, + kResult, + kEvents, + kAborted + } = require_symbols3(); + var { webidl } = require_webidl(); + var { kEnumerableProperty } = require_util(); + var FileReader = class _FileReader extends EventTarget { + constructor() { + super(); + this[kState] = "empty"; + this[kResult] = null; + this[kError] = null; + this[kEvents] = { + loadend: null, + error: null, + abort: null, + load: null, + progress: null, + loadstart: null + }; + } + /** + * @see https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer + * @param {import('buffer').Blob} blob + */ + readAsArrayBuffer(blob) { + webidl.brandCheck(this, _FileReader); + webidl.argumentLengthCheck(arguments, 1, "FileReader.readAsArrayBuffer"); + blob = webidl.converters.Blob(blob, { strict: false }); + readOperation(this, blob, "ArrayBuffer"); + } + /** + * @see https://w3c.github.io/FileAPI/#readAsBinaryString + * @param {import('buffer').Blob} blob + */ + readAsBinaryString(blob) { + webidl.brandCheck(this, _FileReader); + webidl.argumentLengthCheck(arguments, 1, "FileReader.readAsBinaryString"); + blob = webidl.converters.Blob(blob, { strict: false }); + readOperation(this, blob, "BinaryString"); + } + /** + * @see https://w3c.github.io/FileAPI/#readAsDataText + * @param {import('buffer').Blob} blob + * @param {string?} encoding + */ + readAsText(blob, encoding = void 0) { + webidl.brandCheck(this, _FileReader); + webidl.argumentLengthCheck(arguments, 1, "FileReader.readAsText"); + blob = webidl.converters.Blob(blob, { strict: false }); + if (encoding !== void 0) { + encoding = webidl.converters.DOMString(encoding, "FileReader.readAsText", "encoding"); + } + readOperation(this, blob, "Text", encoding); + } + /** + * @see https://w3c.github.io/FileAPI/#dfn-readAsDataURL + * @param {import('buffer').Blob} blob + */ + readAsDataURL(blob) { + webidl.brandCheck(this, _FileReader); + webidl.argumentLengthCheck(arguments, 1, "FileReader.readAsDataURL"); + blob = webidl.converters.Blob(blob, { strict: false }); + readOperation(this, blob, "DataURL"); + } + /** + * @see https://w3c.github.io/FileAPI/#dfn-abort + */ + abort() { + if (this[kState] === "empty" || this[kState] === "done") { + this[kResult] = null; + return; + } + if (this[kState] === "loading") { + this[kState] = "done"; + this[kResult] = null; + } + this[kAborted] = true; + fireAProgressEvent("abort", this); + if (this[kState] !== "loading") { + fireAProgressEvent("loadend", this); + } + } + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-readystate + */ + get readyState() { + webidl.brandCheck(this, _FileReader); + switch (this[kState]) { + case "empty": + return this.EMPTY; + case "loading": + return this.LOADING; + case "done": + return this.DONE; + } + } + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-result + */ + get result() { + webidl.brandCheck(this, _FileReader); + return this[kResult]; + } + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-error + */ + get error() { + webidl.brandCheck(this, _FileReader); + return this[kError]; + } + get onloadend() { + webidl.brandCheck(this, _FileReader); + return this[kEvents].loadend; + } + set onloadend(fn) { + webidl.brandCheck(this, _FileReader); + if (this[kEvents].loadend) { + this.removeEventListener("loadend", this[kEvents].loadend); + } + if (typeof fn === "function") { + this[kEvents].loadend = fn; + this.addEventListener("loadend", fn); + } else { + this[kEvents].loadend = null; + } + } + get onerror() { + webidl.brandCheck(this, _FileReader); + return this[kEvents].error; + } + set onerror(fn) { + webidl.brandCheck(this, _FileReader); + if (this[kEvents].error) { + this.removeEventListener("error", this[kEvents].error); + } + if (typeof fn === "function") { + this[kEvents].error = fn; + this.addEventListener("error", fn); + } else { + this[kEvents].error = null; + } + } + get onloadstart() { + webidl.brandCheck(this, _FileReader); + return this[kEvents].loadstart; + } + set onloadstart(fn) { + webidl.brandCheck(this, _FileReader); + if (this[kEvents].loadstart) { + this.removeEventListener("loadstart", this[kEvents].loadstart); + } + if (typeof fn === "function") { + this[kEvents].loadstart = fn; + this.addEventListener("loadstart", fn); + } else { + this[kEvents].loadstart = null; + } + } + get onprogress() { + webidl.brandCheck(this, _FileReader); + return this[kEvents].progress; + } + set onprogress(fn) { + webidl.brandCheck(this, _FileReader); + if (this[kEvents].progress) { + this.removeEventListener("progress", this[kEvents].progress); + } + if (typeof fn === "function") { + this[kEvents].progress = fn; + this.addEventListener("progress", fn); + } else { + this[kEvents].progress = null; + } + } + get onload() { + webidl.brandCheck(this, _FileReader); + return this[kEvents].load; + } + set onload(fn) { + webidl.brandCheck(this, _FileReader); + if (this[kEvents].load) { + this.removeEventListener("load", this[kEvents].load); + } + if (typeof fn === "function") { + this[kEvents].load = fn; + this.addEventListener("load", fn); + } else { + this[kEvents].load = null; + } + } + get onabort() { + webidl.brandCheck(this, _FileReader); + return this[kEvents].abort; + } + set onabort(fn) { + webidl.brandCheck(this, _FileReader); + if (this[kEvents].abort) { + this.removeEventListener("abort", this[kEvents].abort); + } + if (typeof fn === "function") { + this[kEvents].abort = fn; + this.addEventListener("abort", fn); + } else { + this[kEvents].abort = null; + } + } + }; + FileReader.EMPTY = FileReader.prototype.EMPTY = 0; + FileReader.LOADING = FileReader.prototype.LOADING = 1; + FileReader.DONE = FileReader.prototype.DONE = 2; + Object.defineProperties(FileReader.prototype, { + EMPTY: staticPropertyDescriptors, + LOADING: staticPropertyDescriptors, + DONE: staticPropertyDescriptors, + readAsArrayBuffer: kEnumerableProperty, + readAsBinaryString: kEnumerableProperty, + readAsText: kEnumerableProperty, + readAsDataURL: kEnumerableProperty, + abort: kEnumerableProperty, + readyState: kEnumerableProperty, + result: kEnumerableProperty, + error: kEnumerableProperty, + onloadstart: kEnumerableProperty, + onprogress: kEnumerableProperty, + onload: kEnumerableProperty, + onabort: kEnumerableProperty, + onerror: kEnumerableProperty, + onloadend: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "FileReader", + writable: false, + enumerable: false, + configurable: true + } + }); + Object.defineProperties(FileReader, { + EMPTY: staticPropertyDescriptors, + LOADING: staticPropertyDescriptors, + DONE: staticPropertyDescriptors + }); + module.exports = { + FileReader + }; + } +}); + +// +var require_symbols4 = __commonJS({ + ""(exports, module) { + "use strict"; + module.exports = { + kConstruct: require_symbols().kConstruct + }; + } +}); + +// +var require_util5 = __commonJS({ + ""(exports, module) { + "use strict"; + var assert2 = __require("node:assert"); + var { URLSerializer } = require_data_url(); + var { isValidHeaderName } = require_util2(); + function urlEquals(A, B, excludeFragment = false) { + const serializedA = URLSerializer(A, excludeFragment); + const serializedB = URLSerializer(B, excludeFragment); + return serializedA === serializedB; + } + function getFieldValues(header) { + assert2(header !== null); + const values = []; + for (let value of header.split(",")) { + value = value.trim(); + if (isValidHeaderName(value)) { + values.push(value); + } + } + return values; + } + module.exports = { + urlEquals, + getFieldValues + }; + } +}); + +// +var require_cache = __commonJS({ + ""(exports, module) { + "use strict"; + var { kConstruct } = require_symbols4(); + var { urlEquals, getFieldValues } = require_util5(); + var { kEnumerableProperty, isDisturbed } = require_util(); + var { webidl } = require_webidl(); + var { Response, cloneResponse, fromInnerResponse } = require_response(); + var { Request, fromInnerRequest } = require_request2(); + var { kState } = require_symbols2(); + var { fetching } = require_fetch(); + var { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = require_util2(); + var assert2 = __require("node:assert"); + var Cache = class _Cache { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-request-response-list + * @type {requestResponseList} + */ + #relevantRequestResponseList; + constructor() { + if (arguments[0] !== kConstruct) { + webidl.illegalConstructor(); + } + webidl.util.markAsUncloneable(this); + this.#relevantRequestResponseList = arguments[1]; + } + async match(request3, options = {}) { + webidl.brandCheck(this, _Cache); + const prefix = "Cache.match"; + webidl.argumentLengthCheck(arguments, 1, prefix); + request3 = webidl.converters.RequestInfo(request3, prefix, "request"); + options = webidl.converters.CacheQueryOptions(options, prefix, "options"); + const p = this.#internalMatchAll(request3, options, 1); + if (p.length === 0) { + return; + } + return p[0]; + } + async matchAll(request3 = void 0, options = {}) { + webidl.brandCheck(this, _Cache); + const prefix = "Cache.matchAll"; + if (request3 !== void 0) + request3 = webidl.converters.RequestInfo(request3, prefix, "request"); + options = webidl.converters.CacheQueryOptions(options, prefix, "options"); + return this.#internalMatchAll(request3, options); + } + async add(request3) { + webidl.brandCheck(this, _Cache); + const prefix = "Cache.add"; + webidl.argumentLengthCheck(arguments, 1, prefix); + request3 = webidl.converters.RequestInfo(request3, prefix, "request"); + const requests = [request3]; + const responseArrayPromise = this.addAll(requests); + return await responseArrayPromise; + } + async addAll(requests) { + webidl.brandCheck(this, _Cache); + const prefix = "Cache.addAll"; + webidl.argumentLengthCheck(arguments, 1, prefix); + const responsePromises = []; + const requestList = []; + for (let request3 of requests) { + if (request3 === void 0) { + throw webidl.errors.conversionFailed({ + prefix, + argument: "Argument 1", + types: ["undefined is not allowed"] + }); + } + request3 = webidl.converters.RequestInfo(request3); + if (typeof request3 === "string") { + continue; + } + const r = request3[kState]; + if (!urlIsHttpHttpsScheme(r.url) || r.method !== "GET") { + throw webidl.errors.exception({ + header: prefix, + message: "Expected http/s scheme when method is not GET." + }); + } + } + const fetchControllers = []; + for (const request3 of requests) { + const r = new Request(request3)[kState]; + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: prefix, + message: "Expected http/s scheme." + }); + } + r.initiator = "fetch"; + r.destination = "subresource"; + requestList.push(r); + const responsePromise = createDeferredPromise(); + fetchControllers.push(fetching({ + request: r, + processResponse(response) { + if (response.type === "error" || response.status === 206 || response.status < 200 || response.status > 299) { + responsePromise.reject(webidl.errors.exception({ + header: "Cache.addAll", + message: "Received an invalid status code or the request failed." + })); + } else if (response.headersList.contains("vary")) { + const fieldValues = getFieldValues(response.headersList.get("vary")); + for (const fieldValue of fieldValues) { + if (fieldValue === "*") { + responsePromise.reject(webidl.errors.exception({ + header: "Cache.addAll", + message: "invalid vary field value" + })); + for (const controller of fetchControllers) { + controller.abort(); + } + return; + } + } + } + }, + processResponseEndOfBody(response) { + if (response.aborted) { + responsePromise.reject(new DOMException("aborted", "AbortError")); + return; + } + responsePromise.resolve(response); + } + })); + responsePromises.push(responsePromise.promise); + } + const p = Promise.all(responsePromises); + const responses = await p; + const operations = []; + let index = 0; + for (const response of responses) { + const operation = { + type: "put", + // 7.3.2 + request: requestList[index], + // 7.3.3 + response + // 7.3.4 + }; + operations.push(operation); + index++; + } + const cacheJobPromise = createDeferredPromise(); + let errorData = null; + try { + this.#batchCacheOperations(operations); + } catch (e) { + errorData = e; + } + queueMicrotask(() => { + if (errorData === null) { + cacheJobPromise.resolve(void 0); + } else { + cacheJobPromise.reject(errorData); + } + }); + return cacheJobPromise.promise; + } + async put(request3, response) { + webidl.brandCheck(this, _Cache); + const prefix = "Cache.put"; + webidl.argumentLengthCheck(arguments, 2, prefix); + request3 = webidl.converters.RequestInfo(request3, prefix, "request"); + response = webidl.converters.Response(response, prefix, "response"); + let innerRequest = null; + if (request3 instanceof Request) { + innerRequest = request3[kState]; + } else { + innerRequest = new Request(request3)[kState]; + } + if (!urlIsHttpHttpsScheme(innerRequest.url) || innerRequest.method !== "GET") { + throw webidl.errors.exception({ + header: prefix, + message: "Expected an http/s scheme when method is not GET" + }); + } + const innerResponse = response[kState]; + if (innerResponse.status === 206) { + throw webidl.errors.exception({ + header: prefix, + message: "Got 206 status" + }); + } + if (innerResponse.headersList.contains("vary")) { + const fieldValues = getFieldValues(innerResponse.headersList.get("vary")); + for (const fieldValue of fieldValues) { + if (fieldValue === "*") { + throw webidl.errors.exception({ + header: prefix, + message: "Got * vary field value" + }); + } + } + } + if (innerResponse.body && (isDisturbed(innerResponse.body.stream) || innerResponse.body.stream.locked)) { + throw webidl.errors.exception({ + header: prefix, + message: "Response body is locked or disturbed" + }); + } + const clonedResponse = cloneResponse(innerResponse); + const bodyReadPromise = createDeferredPromise(); + if (innerResponse.body != null) { + const stream = innerResponse.body.stream; + const reader = stream.getReader(); + readAllBytes(reader).then(bodyReadPromise.resolve, bodyReadPromise.reject); + } else { + bodyReadPromise.resolve(void 0); + } + const operations = []; + const operation = { + type: "put", + // 14. + request: innerRequest, + // 15. + response: clonedResponse + // 16. + }; + operations.push(operation); + const bytes = await bodyReadPromise.promise; + if (clonedResponse.body != null) { + clonedResponse.body.source = bytes; + } + const cacheJobPromise = createDeferredPromise(); + let errorData = null; + try { + this.#batchCacheOperations(operations); + } catch (e) { + errorData = e; + } + queueMicrotask(() => { + if (errorData === null) { + cacheJobPromise.resolve(); + } else { + cacheJobPromise.reject(errorData); + } + }); + return cacheJobPromise.promise; + } + async delete(request3, options = {}) { + webidl.brandCheck(this, _Cache); + const prefix = "Cache.delete"; + webidl.argumentLengthCheck(arguments, 1, prefix); + request3 = webidl.converters.RequestInfo(request3, prefix, "request"); + options = webidl.converters.CacheQueryOptions(options, prefix, "options"); + let r = null; + if (request3 instanceof Request) { + r = request3[kState]; + if (r.method !== "GET" && !options.ignoreMethod) { + return false; + } + } else { + assert2(typeof request3 === "string"); + r = new Request(request3)[kState]; + } + const operations = []; + const operation = { + type: "delete", + request: r, + options + }; + operations.push(operation); + const cacheJobPromise = createDeferredPromise(); + let errorData = null; + let requestResponses; + try { + requestResponses = this.#batchCacheOperations(operations); + } catch (e) { + errorData = e; + } + queueMicrotask(() => { + if (errorData === null) { + cacheJobPromise.resolve(!!requestResponses?.length); + } else { + cacheJobPromise.reject(errorData); + } + }); + return cacheJobPromise.promise; + } + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys + * @param {any} request + * @param {import('../../types/cache').CacheQueryOptions} options + * @returns {Promise} + */ + async keys(request3 = void 0, options = {}) { + webidl.brandCheck(this, _Cache); + const prefix = "Cache.keys"; + if (request3 !== void 0) + request3 = webidl.converters.RequestInfo(request3, prefix, "request"); + options = webidl.converters.CacheQueryOptions(options, prefix, "options"); + let r = null; + if (request3 !== void 0) { + if (request3 instanceof Request) { + r = request3[kState]; + if (r.method !== "GET" && !options.ignoreMethod) { + return []; + } + } else if (typeof request3 === "string") { + r = new Request(request3)[kState]; + } + } + const promise = createDeferredPromise(); + const requests = []; + if (request3 === void 0) { + for (const requestResponse of this.#relevantRequestResponseList) { + requests.push(requestResponse[0]); + } + } else { + const requestResponses = this.#queryCache(r, options); + for (const requestResponse of requestResponses) { + requests.push(requestResponse[0]); + } + } + queueMicrotask(() => { + const requestList = []; + for (const request4 of requests) { + const requestObject = fromInnerRequest( + request4, + new AbortController().signal, + "immutable" + ); + requestList.push(requestObject); + } + promise.resolve(Object.freeze(requestList)); + }); + return promise.promise; + } + /** + * @see https://w3c.github.io/ServiceWorker/#batch-cache-operations-algorithm + * @param {CacheBatchOperation[]} operations + * @returns {requestResponseList} + */ + #batchCacheOperations(operations) { + const cache = this.#relevantRequestResponseList; + const backupCache = [...cache]; + const addedItems = []; + const resultList = []; + try { + for (const operation of operations) { + if (operation.type !== "delete" && operation.type !== "put") { + throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: 'operation type does not match "delete" or "put"' + }); + } + if (operation.type === "delete" && operation.response != null) { + throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "delete operation should not have an associated response" + }); + } + if (this.#queryCache(operation.request, operation.options, addedItems).length) { + throw new DOMException("???", "InvalidStateError"); + } + let requestResponses; + if (operation.type === "delete") { + requestResponses = this.#queryCache(operation.request, operation.options); + if (requestResponses.length === 0) { + return []; + } + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse); + assert2(idx !== -1); + cache.splice(idx, 1); + } + } else if (operation.type === "put") { + if (operation.response == null) { + throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "put operation should have an associated response" + }); + } + const r = operation.request; + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "expected http or https scheme" + }); + } + if (r.method !== "GET") { + throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "not get method" + }); + } + if (operation.options != null) { + throw webidl.errors.exception({ + header: "Cache.#batchCacheOperations", + message: "options must not be defined" + }); + } + requestResponses = this.#queryCache(operation.request); + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse); + assert2(idx !== -1); + cache.splice(idx, 1); + } + cache.push([operation.request, operation.response]); + addedItems.push([operation.request, operation.response]); + } + resultList.push([operation.request, operation.response]); + } + return resultList; + } catch (e) { + this.#relevantRequestResponseList.length = 0; + this.#relevantRequestResponseList = backupCache; + throw e; + } + } + /** + * @see https://w3c.github.io/ServiceWorker/#query-cache + * @param {any} requestQuery + * @param {import('../../types/cache').CacheQueryOptions} options + * @param {requestResponseList} targetStorage + * @returns {requestResponseList} + */ + #queryCache(requestQuery, options, targetStorage) { + const resultList = []; + const storage = targetStorage ?? this.#relevantRequestResponseList; + for (const requestResponse of storage) { + const [cachedRequest, cachedResponse] = requestResponse; + if (this.#requestMatchesCachedItem(requestQuery, cachedRequest, cachedResponse, options)) { + resultList.push(requestResponse); + } + } + return resultList; + } + /** + * @see https://w3c.github.io/ServiceWorker/#request-matches-cached-item-algorithm + * @param {any} requestQuery + * @param {any} request + * @param {any | null} response + * @param {import('../../types/cache').CacheQueryOptions | undefined} options + * @returns {boolean} + */ + #requestMatchesCachedItem(requestQuery, request3, response = null, options) { + const queryURL = new URL(requestQuery.url); + const cachedURL = new URL(request3.url); + if (options?.ignoreSearch) { + cachedURL.search = ""; + queryURL.search = ""; + } + if (!urlEquals(queryURL, cachedURL, true)) { + return false; + } + if (response == null || options?.ignoreVary || !response.headersList.contains("vary")) { + return true; + } + const fieldValues = getFieldValues(response.headersList.get("vary")); + for (const fieldValue of fieldValues) { + if (fieldValue === "*") { + return false; + } + const requestValue = request3.headersList.get(fieldValue); + const queryValue = requestQuery.headersList.get(fieldValue); + if (requestValue !== queryValue) { + return false; + } + } + return true; + } + #internalMatchAll(request3, options, maxResponses = Infinity) { + let r = null; + if (request3 !== void 0) { + if (request3 instanceof Request) { + r = request3[kState]; + if (r.method !== "GET" && !options.ignoreMethod) { + return []; + } + } else if (typeof request3 === "string") { + r = new Request(request3)[kState]; + } + } + const responses = []; + if (request3 === void 0) { + for (const requestResponse of this.#relevantRequestResponseList) { + responses.push(requestResponse[1]); + } + } else { + const requestResponses = this.#queryCache(r, options); + for (const requestResponse of requestResponses) { + responses.push(requestResponse[1]); + } + } + const responseList = []; + for (const response of responses) { + const responseObject = fromInnerResponse(response, "immutable"); + responseList.push(responseObject.clone()); + if (responseList.length >= maxResponses) { + break; + } + } + return Object.freeze(responseList); + } + }; + Object.defineProperties(Cache.prototype, { + [Symbol.toStringTag]: { + value: "Cache", + configurable: true + }, + match: kEnumerableProperty, + matchAll: kEnumerableProperty, + add: kEnumerableProperty, + addAll: kEnumerableProperty, + put: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty + }); + var cacheQueryOptionConverters = [ + { + key: "ignoreSearch", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "ignoreMethod", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "ignoreVary", + converter: webidl.converters.boolean, + defaultValue: () => false + } + ]; + webidl.converters.CacheQueryOptions = webidl.dictionaryConverter(cacheQueryOptionConverters); + webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([ + ...cacheQueryOptionConverters, + { + key: "cacheName", + converter: webidl.converters.DOMString + } + ]); + webidl.converters.Response = webidl.interfaceConverter(Response); + webidl.converters["sequence"] = webidl.sequenceConverter( + webidl.converters.RequestInfo + ); + module.exports = { + Cache + }; + } +}); + +// +var require_cachestorage = __commonJS({ + ""(exports, module) { + "use strict"; + var { kConstruct } = require_symbols4(); + var { Cache } = require_cache(); + var { webidl } = require_webidl(); + var { kEnumerableProperty } = require_util(); + var CacheStorage = class _CacheStorage { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-name-to-cache-map + * @type {Map} + */ + async has(cacheName) { + webidl.brandCheck(this, _CacheStorage); + const prefix = "CacheStorage.has"; + webidl.argumentLengthCheck(arguments, 1, prefix); + cacheName = webidl.converters.DOMString(cacheName, prefix, "cacheName"); + return this.#caches.has(cacheName); + } + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cachestorage-open + * @param {string} cacheName + * @returns {Promise} + */ + async open(cacheName) { + webidl.brandCheck(this, _CacheStorage); + const prefix = "CacheStorage.open"; + webidl.argumentLengthCheck(arguments, 1, prefix); + cacheName = webidl.converters.DOMString(cacheName, prefix, "cacheName"); + if (this.#caches.has(cacheName)) { + const cache2 = this.#caches.get(cacheName); + return new Cache(kConstruct, cache2); + } + const cache = []; + this.#caches.set(cacheName, cache); + return new Cache(kConstruct, cache); + } + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete + * @param {string} cacheName + * @returns {Promise} + */ + async delete(cacheName) { + webidl.brandCheck(this, _CacheStorage); + const prefix = "CacheStorage.delete"; + webidl.argumentLengthCheck(arguments, 1, prefix); + cacheName = webidl.converters.DOMString(cacheName, prefix, "cacheName"); + return this.#caches.delete(cacheName); + } + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-keys + * @returns {Promise} + */ + async keys() { + webidl.brandCheck(this, _CacheStorage); + const keys = this.#caches.keys(); + return [...keys]; + } + }; + Object.defineProperties(CacheStorage.prototype, { + [Symbol.toStringTag]: { + value: "CacheStorage", + configurable: true + }, + match: kEnumerableProperty, + has: kEnumerableProperty, + open: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty + }); + module.exports = { + CacheStorage + }; + } +}); + +// +var require_constants4 = __commonJS({ + ""(exports, module) { + "use strict"; + var maxAttributeValueSize = 1024; + var maxNameValuePairSize = 4096; + module.exports = { + maxAttributeValueSize, + maxNameValuePairSize + }; + } +}); + +// +var require_util6 = __commonJS({ + ""(exports, module) { + "use strict"; + function isCTLExcludingHtab(value) { + for (let i = 0; i < value.length; ++i) { + const code = value.charCodeAt(i); + if (code >= 0 && code <= 8 || code >= 10 && code <= 31 || code === 127) { + return true; + } + } + return false; + } + function validateCookieName(name) { + for (let i = 0; i < name.length; ++i) { + const code = name.charCodeAt(i); + if (code < 33 || // exclude CTLs (0-31), SP and HT + code > 126 || // exclude non-ascii and DEL + code === 34 || // " + code === 40 || // ( + code === 41 || // ) + code === 60 || // < + code === 62 || // > + code === 64 || // @ + code === 44 || // , + code === 59 || // ; + code === 58 || // : + code === 92 || // \ + code === 47 || // / + code === 91 || // [ + code === 93 || // ] + code === 63 || // ? + code === 61 || // = + code === 123 || // { + code === 125) { + throw new Error("Invalid cookie name"); + } + } + } + function validateCookieValue(value) { + let len = value.length; + let i = 0; + if (value[0] === '"') { + if (len === 1 || value[len - 1] !== '"') { + throw new Error("Invalid cookie value"); + } + --len; + ++i; + } + while (i < len) { + const code = value.charCodeAt(i++); + if (code < 33 || // exclude CTLs (0-31) + code > 126 || // non-ascii and DEL (127) + code === 34 || // " + code === 44 || // , + code === 59 || // ; + code === 92) { + throw new Error("Invalid cookie value"); + } + } + } + function validateCookiePath(path) { + for (let i = 0; i < path.length; ++i) { + const code = path.charCodeAt(i); + if (code < 32 || // exclude CTLs (0-31) + code === 127 || // DEL + code === 59) { + throw new Error("Invalid cookie path"); + } + } + } + function validateCookieDomain(domain) { + if (domain.startsWith("-") || domain.endsWith(".") || domain.endsWith("-")) { + throw new Error("Invalid cookie domain"); + } + } + var IMFDays = [ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat" + ]; + var IMFMonths = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" + ]; + var IMFPaddedNumbers = Array(61).fill(0).map((_, i) => i.toString().padStart(2, "0")); + function toIMFDate(date) { + if (typeof date === "number") { + date = new Date(date); + } + return `${IMFDays[date.getUTCDay()]}, ${IMFPaddedNumbers[date.getUTCDate()]} ${IMFMonths[date.getUTCMonth()]} ${date.getUTCFullYear()} ${IMFPaddedNumbers[date.getUTCHours()]}:${IMFPaddedNumbers[date.getUTCMinutes()]}:${IMFPaddedNumbers[date.getUTCSeconds()]} GMT`; + } + function validateCookieMaxAge(maxAge) { + if (maxAge < 0) { + throw new Error("Invalid cookie max-age"); + } + } + function stringify(cookie) { + if (cookie.name.length === 0) { + return null; + } + validateCookieName(cookie.name); + validateCookieValue(cookie.value); + const out = [`${cookie.name}=${cookie.value}`]; + if (cookie.name.startsWith("__Secure-")) { + cookie.secure = true; + } + if (cookie.name.startsWith("__Host-")) { + cookie.secure = true; + cookie.domain = null; + cookie.path = "/"; + } + if (cookie.secure) { + out.push("Secure"); + } + if (cookie.httpOnly) { + out.push("HttpOnly"); + } + if (typeof cookie.maxAge === "number") { + validateCookieMaxAge(cookie.maxAge); + out.push(`Max-Age=${cookie.maxAge}`); + } + if (cookie.domain) { + validateCookieDomain(cookie.domain); + out.push(`Domain=${cookie.domain}`); + } + if (cookie.path) { + validateCookiePath(cookie.path); + out.push(`Path=${cookie.path}`); + } + if (cookie.expires && cookie.expires.toString() !== "Invalid Date") { + out.push(`Expires=${toIMFDate(cookie.expires)}`); + } + if (cookie.sameSite) { + out.push(`SameSite=${cookie.sameSite}`); + } + for (const part of cookie.unparsed) { + if (!part.includes("=")) { + throw new Error("Invalid unparsed"); + } + const [key, ...value] = part.split("="); + out.push(`${key.trim()}=${value.join("=")}`); + } + return out.join("; "); + } + module.exports = { + isCTLExcludingHtab, + validateCookieName, + validateCookiePath, + validateCookieValue, + toIMFDate, + stringify + }; + } +}); + +// +var require_parse = __commonJS({ + ""(exports, module) { + "use strict"; + var { maxNameValuePairSize, maxAttributeValueSize } = require_constants4(); + var { isCTLExcludingHtab } = require_util6(); + var { collectASequenceOfCodePointsFast } = require_data_url(); + var assert2 = __require("node:assert"); + function parseSetCookie(header) { + if (isCTLExcludingHtab(header)) { + return null; + } + let nameValuePair = ""; + let unparsedAttributes = ""; + let name = ""; + let value = ""; + if (header.includes(";")) { + const position = { position: 0 }; + nameValuePair = collectASequenceOfCodePointsFast(";", header, position); + unparsedAttributes = header.slice(position.position); + } else { + nameValuePair = header; + } + if (!nameValuePair.includes("=")) { + value = nameValuePair; + } else { + const position = { position: 0 }; + name = collectASequenceOfCodePointsFast( + "=", + nameValuePair, + position + ); + value = nameValuePair.slice(position.position + 1); + } + name = name.trim(); + value = value.trim(); + if (name.length + value.length > maxNameValuePairSize) { + return null; + } + return { + name, + value, + ...parseUnparsedAttributes(unparsedAttributes) + }; + } + function parseUnparsedAttributes(unparsedAttributes, cookieAttributeList = {}) { + if (unparsedAttributes.length === 0) { + return cookieAttributeList; + } + assert2(unparsedAttributes[0] === ";"); + unparsedAttributes = unparsedAttributes.slice(1); + let cookieAv = ""; + if (unparsedAttributes.includes(";")) { + cookieAv = collectASequenceOfCodePointsFast( + ";", + unparsedAttributes, + { position: 0 } + ); + unparsedAttributes = unparsedAttributes.slice(cookieAv.length); + } else { + cookieAv = unparsedAttributes; + unparsedAttributes = ""; + } + let attributeName = ""; + let attributeValue = ""; + if (cookieAv.includes("=")) { + const position = { position: 0 }; + attributeName = collectASequenceOfCodePointsFast( + "=", + cookieAv, + position + ); + attributeValue = cookieAv.slice(position.position + 1); + } else { + attributeName = cookieAv; + } + attributeName = attributeName.trim(); + attributeValue = attributeValue.trim(); + if (attributeValue.length > maxAttributeValueSize) { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList); + } + const attributeNameLowercase = attributeName.toLowerCase(); + if (attributeNameLowercase === "expires") { + const expiryTime = new Date(attributeValue); + cookieAttributeList.expires = expiryTime; + } else if (attributeNameLowercase === "max-age") { + const charCode = attributeValue.charCodeAt(0); + if ((charCode < 48 || charCode > 57) && attributeValue[0] !== "-") { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList); + } + if (!/^\d+$/.test(attributeValue)) { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList); + } + const deltaSeconds = Number(attributeValue); + cookieAttributeList.maxAge = deltaSeconds; + } else if (attributeNameLowercase === "domain") { + let cookieDomain = attributeValue; + if (cookieDomain[0] === ".") { + cookieDomain = cookieDomain.slice(1); + } + cookieDomain = cookieDomain.toLowerCase(); + cookieAttributeList.domain = cookieDomain; + } else if (attributeNameLowercase === "path") { + let cookiePath = ""; + if (attributeValue.length === 0 || attributeValue[0] !== "/") { + cookiePath = "/"; + } else { + cookiePath = attributeValue; + } + cookieAttributeList.path = cookiePath; + } else if (attributeNameLowercase === "secure") { + cookieAttributeList.secure = true; + } else if (attributeNameLowercase === "httponly") { + cookieAttributeList.httpOnly = true; + } else if (attributeNameLowercase === "samesite") { + let enforcement = "Default"; + const attributeValueLowercase = attributeValue.toLowerCase(); + if (attributeValueLowercase.includes("none")) { + enforcement = "None"; + } + if (attributeValueLowercase.includes("strict")) { + enforcement = "Strict"; + } + if (attributeValueLowercase.includes("lax")) { + enforcement = "Lax"; + } + cookieAttributeList.sameSite = enforcement; + } else { + cookieAttributeList.unparsed ??= []; + cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`); + } + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList); + } + module.exports = { + parseSetCookie, + parseUnparsedAttributes + }; + } +}); + +// +var require_cookies = __commonJS({ + ""(exports, module) { + "use strict"; + var { parseSetCookie } = require_parse(); + var { stringify } = require_util6(); + var { webidl } = require_webidl(); + var { Headers: Headers2 } = require_headers(); + function getCookies(headers) { + webidl.argumentLengthCheck(arguments, 1, "getCookies"); + webidl.brandCheck(headers, Headers2, { strict: false }); + const cookie = headers.get("cookie"); + const out = {}; + if (!cookie) { + return out; + } + for (const piece of cookie.split(";")) { + const [name, ...value] = piece.split("="); + out[name.trim()] = value.join("="); + } + return out; + } + function deleteCookie(headers, name, attributes) { + webidl.brandCheck(headers, Headers2, { strict: false }); + const prefix = "deleteCookie"; + webidl.argumentLengthCheck(arguments, 2, prefix); + name = webidl.converters.DOMString(name, prefix, "name"); + attributes = webidl.converters.DeleteCookieAttributes(attributes); + setCookie(headers, { + name, + value: "", + expires: /* @__PURE__ */ new Date(0), + ...attributes + }); + } + function getSetCookies(headers) { + webidl.argumentLengthCheck(arguments, 1, "getSetCookies"); + webidl.brandCheck(headers, Headers2, { strict: false }); + const cookies = headers.getSetCookie(); + if (!cookies) { + return []; + } + return cookies.map((pair) => parseSetCookie(pair)); + } + function setCookie(headers, cookie) { + webidl.argumentLengthCheck(arguments, 2, "setCookie"); + webidl.brandCheck(headers, Headers2, { strict: false }); + cookie = webidl.converters.Cookie(cookie); + const str = stringify(cookie); + if (str) { + headers.append("Set-Cookie", str); + } + } + webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([ + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: "path", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: "domain", + defaultValue: () => null + } + ]); + webidl.converters.Cookie = webidl.dictionaryConverter([ + { + converter: webidl.converters.DOMString, + key: "name" + }, + { + converter: webidl.converters.DOMString, + key: "value" + }, + { + converter: webidl.nullableConverter((value) => { + if (typeof value === "number") { + return webidl.converters["unsigned long long"](value); + } + return new Date(value); + }), + key: "expires", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters["long long"]), + key: "maxAge", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: "domain", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: "path", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: "secure", + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: "httpOnly", + defaultValue: () => null + }, + { + converter: webidl.converters.USVString, + key: "sameSite", + allowedValues: ["Strict", "Lax", "None"] + }, + { + converter: webidl.sequenceConverter(webidl.converters.DOMString), + key: "unparsed", + defaultValue: () => new Array(0) + } + ]); + module.exports = { + getCookies, + deleteCookie, + getSetCookies, + setCookie + }; + } +}); + +// +var require_events = __commonJS({ + ""(exports, module) { + "use strict"; + var { webidl } = require_webidl(); + var { kEnumerableProperty } = require_util(); + var { kConstruct } = require_symbols(); + var { MessagePort } = __require("node:worker_threads"); + var MessageEvent = class _MessageEvent extends Event { + #eventInit; + constructor(type, eventInitDict = {}) { + if (type === kConstruct) { + super(arguments[1], arguments[2]); + webidl.util.markAsUncloneable(this); + return; + } + const prefix = "MessageEvent constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + type = webidl.converters.DOMString(type, prefix, "type"); + eventInitDict = webidl.converters.MessageEventInit(eventInitDict, prefix, "eventInitDict"); + super(type, eventInitDict); + this.#eventInit = eventInitDict; + webidl.util.markAsUncloneable(this); + } + get data() { + webidl.brandCheck(this, _MessageEvent); + return this.#eventInit.data; + } + get origin() { + webidl.brandCheck(this, _MessageEvent); + return this.#eventInit.origin; + } + get lastEventId() { + webidl.brandCheck(this, _MessageEvent); + return this.#eventInit.lastEventId; + } + get source() { + webidl.brandCheck(this, _MessageEvent); + return this.#eventInit.source; + } + get ports() { + webidl.brandCheck(this, _MessageEvent); + if (!Object.isFrozen(this.#eventInit.ports)) { + Object.freeze(this.#eventInit.ports); + } + return this.#eventInit.ports; + } + initMessageEvent(type, bubbles = false, cancelable = false, data = null, origin = "", lastEventId = "", source = null, ports = []) { + webidl.brandCheck(this, _MessageEvent); + webidl.argumentLengthCheck(arguments, 1, "MessageEvent.initMessageEvent"); + return new _MessageEvent(type, { + bubbles, + cancelable, + data, + origin, + lastEventId, + source, + ports + }); + } + static createFastMessageEvent(type, init) { + const messageEvent = new _MessageEvent(kConstruct, type, init); + messageEvent.#eventInit = init; + messageEvent.#eventInit.data ??= null; + messageEvent.#eventInit.origin ??= ""; + messageEvent.#eventInit.lastEventId ??= ""; + messageEvent.#eventInit.source ??= null; + messageEvent.#eventInit.ports ??= []; + return messageEvent; + } + }; + var { createFastMessageEvent } = MessageEvent; + delete MessageEvent.createFastMessageEvent; + var CloseEvent = class _CloseEvent extends Event { + #eventInit; + constructor(type, eventInitDict = {}) { + const prefix = "CloseEvent constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + type = webidl.converters.DOMString(type, prefix, "type"); + eventInitDict = webidl.converters.CloseEventInit(eventInitDict); + super(type, eventInitDict); + this.#eventInit = eventInitDict; + webidl.util.markAsUncloneable(this); + } + get wasClean() { + webidl.brandCheck(this, _CloseEvent); + return this.#eventInit.wasClean; + } + get code() { + webidl.brandCheck(this, _CloseEvent); + return this.#eventInit.code; + } + get reason() { + webidl.brandCheck(this, _CloseEvent); + return this.#eventInit.reason; + } + }; + var ErrorEvent = class _ErrorEvent extends Event { + #eventInit; + constructor(type, eventInitDict) { + const prefix = "ErrorEvent constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + super(type, eventInitDict); + webidl.util.markAsUncloneable(this); + type = webidl.converters.DOMString(type, prefix, "type"); + eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {}); + this.#eventInit = eventInitDict; + } + get message() { + webidl.brandCheck(this, _ErrorEvent); + return this.#eventInit.message; + } + get filename() { + webidl.brandCheck(this, _ErrorEvent); + return this.#eventInit.filename; + } + get lineno() { + webidl.brandCheck(this, _ErrorEvent); + return this.#eventInit.lineno; + } + get colno() { + webidl.brandCheck(this, _ErrorEvent); + return this.#eventInit.colno; + } + get error() { + webidl.brandCheck(this, _ErrorEvent); + return this.#eventInit.error; + } + }; + Object.defineProperties(MessageEvent.prototype, { + [Symbol.toStringTag]: { + value: "MessageEvent", + configurable: true + }, + data: kEnumerableProperty, + origin: kEnumerableProperty, + lastEventId: kEnumerableProperty, + source: kEnumerableProperty, + ports: kEnumerableProperty, + initMessageEvent: kEnumerableProperty + }); + Object.defineProperties(CloseEvent.prototype, { + [Symbol.toStringTag]: { + value: "CloseEvent", + configurable: true + }, + reason: kEnumerableProperty, + code: kEnumerableProperty, + wasClean: kEnumerableProperty + }); + Object.defineProperties(ErrorEvent.prototype, { + [Symbol.toStringTag]: { + value: "ErrorEvent", + configurable: true + }, + message: kEnumerableProperty, + filename: kEnumerableProperty, + lineno: kEnumerableProperty, + colno: kEnumerableProperty, + error: kEnumerableProperty + }); + webidl.converters.MessagePort = webidl.interfaceConverter(MessagePort); + webidl.converters["sequence"] = webidl.sequenceConverter( + webidl.converters.MessagePort + ); + var eventInit = [ + { + key: "bubbles", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "cancelable", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "composed", + converter: webidl.converters.boolean, + defaultValue: () => false + } + ]; + webidl.converters.MessageEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: "data", + converter: webidl.converters.any, + defaultValue: () => null + }, + { + key: "origin", + converter: webidl.converters.USVString, + defaultValue: () => "" + }, + { + key: "lastEventId", + converter: webidl.converters.DOMString, + defaultValue: () => "" + }, + { + key: "source", + // Node doesn't implement WindowProxy or ServiceWorker, so the only + // valid value for source is a MessagePort. + converter: webidl.nullableConverter(webidl.converters.MessagePort), + defaultValue: () => null + }, + { + key: "ports", + converter: webidl.converters["sequence"], + defaultValue: () => new Array(0) + } + ]); + webidl.converters.CloseEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: "wasClean", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "code", + converter: webidl.converters["unsigned short"], + defaultValue: () => 0 + }, + { + key: "reason", + converter: webidl.converters.USVString, + defaultValue: () => "" + } + ]); + webidl.converters.ErrorEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: "message", + converter: webidl.converters.DOMString, + defaultValue: () => "" + }, + { + key: "filename", + converter: webidl.converters.USVString, + defaultValue: () => "" + }, + { + key: "lineno", + converter: webidl.converters["unsigned long"], + defaultValue: () => 0 + }, + { + key: "colno", + converter: webidl.converters["unsigned long"], + defaultValue: () => 0 + }, + { + key: "error", + converter: webidl.converters.any + } + ]); + module.exports = { + MessageEvent, + CloseEvent, + ErrorEvent, + createFastMessageEvent + }; + } +}); + +// +var require_constants5 = __commonJS({ + ""(exports, module) { + "use strict"; + var uid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + var staticPropertyDescriptors = { + enumerable: true, + writable: false, + configurable: false + }; + var states = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3 + }; + var sentCloseFrameState = { + NOT_SENT: 0, + PROCESSING: 1, + SENT: 2 + }; + var opcodes = { + CONTINUATION: 0, + TEXT: 1, + BINARY: 2, + CLOSE: 8, + PING: 9, + PONG: 10 + }; + var maxUnsigned16Bit = 2 ** 16 - 1; + var parserStates = { + INFO: 0, + PAYLOADLENGTH_16: 2, + PAYLOADLENGTH_64: 3, + READ_DATA: 4 + }; + var emptyBuffer = Buffer.allocUnsafe(0); + var sendHints = { + string: 1, + typedArray: 2, + arrayBuffer: 3, + blob: 4 + }; + module.exports = { + uid, + sentCloseFrameState, + staticPropertyDescriptors, + states, + opcodes, + maxUnsigned16Bit, + parserStates, + emptyBuffer, + sendHints + }; + } +}); + +// +var require_symbols5 = __commonJS({ + ""(exports, module) { + "use strict"; + module.exports = { + kWebSocketURL: Symbol("url"), + kReadyState: Symbol("ready state"), + kController: Symbol("controller"), + kResponse: Symbol("response"), + kBinaryType: Symbol("binary type"), + kSentClose: Symbol("sent close"), + kReceivedClose: Symbol("received close"), + kByteParser: Symbol("byte parser") + }; + } +}); + +// +var require_util7 = __commonJS({ + ""(exports, module) { + "use strict"; + var { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = require_symbols5(); + var { states, opcodes } = require_constants5(); + var { ErrorEvent, createFastMessageEvent } = require_events(); + var { isUtf8 } = __require("node:buffer"); + var { collectASequenceOfCodePointsFast, removeHTTPWhitespace } = require_data_url(); + function isConnecting(ws) { + return ws[kReadyState] === states.CONNECTING; + } + function isEstablished(ws) { + return ws[kReadyState] === states.OPEN; + } + function isClosing(ws) { + return ws[kReadyState] === states.CLOSING; + } + function isClosed(ws) { + return ws[kReadyState] === states.CLOSED; + } + function fireEvent(e, target, eventFactory = (type, init) => new Event(type, init), eventInitDict = {}) { + const event = eventFactory(e, eventInitDict); + target.dispatchEvent(event); + } + function websocketMessageReceived(ws, type, data) { + if (ws[kReadyState] !== states.OPEN) { + return; + } + let dataForEvent; + if (type === opcodes.TEXT) { + try { + dataForEvent = utf8Decode(data); + } catch { + failWebsocketConnection(ws, "Received invalid UTF-8 in text frame."); + return; + } + } else if (type === opcodes.BINARY) { + if (ws[kBinaryType] === "blob") { + dataForEvent = new Blob([data]); + } else { + dataForEvent = toArrayBuffer(data); + } + } + fireEvent("message", ws, createFastMessageEvent, { + origin: ws[kWebSocketURL].origin, + data: dataForEvent + }); + } + function toArrayBuffer(buffer) { + if (buffer.byteLength === buffer.buffer.byteLength) { + return buffer.buffer; + } + return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); + } + function isValidSubprotocol(protocol) { + if (protocol.length === 0) { + return false; + } + for (let i = 0; i < protocol.length; ++i) { + const code = protocol.charCodeAt(i); + if (code < 33 || // CTL, contains SP (0x20) and HT (0x09) + code > 126 || code === 34 || // " + code === 40 || // ( + code === 41 || // ) + code === 44 || // , + code === 47 || // / + code === 58 || // : + code === 59 || // ; + code === 60 || // < + code === 61 || // = + code === 62 || // > + code === 63 || // ? + code === 64 || // @ + code === 91 || // [ + code === 92 || // \ + code === 93 || // ] + code === 123 || // { + code === 125) { + return false; + } + } + return true; + } + function isValidStatusCode(code) { + if (code >= 1e3 && code < 1015) { + return code !== 1004 && // reserved + code !== 1005 && // "MUST NOT be set as a status code" + code !== 1006; + } + return code >= 3e3 && code <= 4999; + } + function failWebsocketConnection(ws, reason) { + const { [kController]: controller, [kResponse]: response } = ws; + controller.abort(); + if (response?.socket && !response.socket.destroyed) { + response.socket.destroy(); + } + if (reason) { + fireEvent("error", ws, (type, init) => new ErrorEvent(type, init), { + error: new Error(reason), + message: reason + }); + } + } + function isControlFrame(opcode) { + return opcode === opcodes.CLOSE || opcode === opcodes.PING || opcode === opcodes.PONG; + } + function isContinuationFrame(opcode) { + return opcode === opcodes.CONTINUATION; + } + function isTextBinaryFrame(opcode) { + return opcode === opcodes.TEXT || opcode === opcodes.BINARY; + } + function isValidOpcode(opcode) { + return isTextBinaryFrame(opcode) || isContinuationFrame(opcode) || isControlFrame(opcode); + } + function parseExtensions(extensions) { + const position = { position: 0 }; + const extensionList = /* @__PURE__ */ new Map(); + while (position.position < extensions.length) { + const pair = collectASequenceOfCodePointsFast(";", extensions, position); + const [name, value = ""] = pair.split("="); + extensionList.set( + removeHTTPWhitespace(name, true, false), + removeHTTPWhitespace(value, false, true) + ); + position.position++; + } + return extensionList; + } + function isValidClientWindowBits(value) { + if (value.length === 0) { + return false; + } + for (let i = 0; i < value.length; i++) { + const byte = value.charCodeAt(i); + if (byte < 48 || byte > 57) { + return false; + } + } + const num = Number.parseInt(value, 10); + return num >= 8 && num <= 15; + } + var hasIntl = typeof process.versions.icu === "string"; + var fatalDecoder = hasIntl ? new TextDecoder("utf-8", { fatal: true }) : void 0; + var utf8Decode = hasIntl ? fatalDecoder.decode.bind(fatalDecoder) : function(buffer) { + if (isUtf8(buffer)) { + return buffer.toString("utf-8"); + } + throw new TypeError("Invalid utf-8 received."); + }; + module.exports = { + isConnecting, + isEstablished, + isClosing, + isClosed, + fireEvent, + isValidSubprotocol, + isValidStatusCode, + failWebsocketConnection, + websocketMessageReceived, + utf8Decode, + isControlFrame, + isContinuationFrame, + isTextBinaryFrame, + isValidOpcode, + parseExtensions, + isValidClientWindowBits + }; + } +}); + +// +var require_frame = __commonJS({ + ""(exports, module) { + "use strict"; + var { maxUnsigned16Bit } = require_constants5(); + var BUFFER_SIZE = 16386; + var crypto; + var buffer = null; + var bufIdx = BUFFER_SIZE; + try { + crypto = __require("node:crypto"); + } catch { + crypto = { + // not full compatibility, but minimum. + randomFillSync: function randomFillSync(buffer2, _offset, _size) { + for (let i = 0; i < buffer2.length; ++i) { + buffer2[i] = Math.random() * 255 | 0; + } + return buffer2; + } + }; + } + function generateMask() { + if (bufIdx === BUFFER_SIZE) { + bufIdx = 0; + crypto.randomFillSync(buffer ??= Buffer.allocUnsafe(BUFFER_SIZE), 0, BUFFER_SIZE); + } + return [buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++], buffer[bufIdx++]]; + } + var WebsocketFrameSend = class { + /** + * @param {Buffer|undefined} data + */ + constructor(data) { + this.frameData = data; + } + createFrame(opcode) { + const frameData = this.frameData; + const maskKey = generateMask(); + const bodyLength = frameData?.byteLength ?? 0; + let payloadLength = bodyLength; + let offset = 6; + if (bodyLength > maxUnsigned16Bit) { + offset += 8; + payloadLength = 127; + } else if (bodyLength > 125) { + offset += 2; + payloadLength = 126; + } + const buffer2 = Buffer.allocUnsafe(bodyLength + offset); + buffer2[0] = buffer2[1] = 0; + buffer2[0] |= 128; + buffer2[0] = (buffer2[0] & 240) + opcode; + buffer2[offset - 4] = maskKey[0]; + buffer2[offset - 3] = maskKey[1]; + buffer2[offset - 2] = maskKey[2]; + buffer2[offset - 1] = maskKey[3]; + buffer2[1] = payloadLength; + if (payloadLength === 126) { + buffer2.writeUInt16BE(bodyLength, 2); + } else if (payloadLength === 127) { + buffer2[2] = buffer2[3] = 0; + buffer2.writeUIntBE(bodyLength, 4, 6); + } + buffer2[1] |= 128; + for (let i = 0; i < bodyLength; ++i) { + buffer2[offset + i] = frameData[i] ^ maskKey[i & 3]; + } + return buffer2; + } + }; + module.exports = { + WebsocketFrameSend + }; + } +}); + +// +var require_connection = __commonJS({ + ""(exports, module) { + "use strict"; + var { uid, states, sentCloseFrameState, emptyBuffer, opcodes } = require_constants5(); + var { + kReadyState, + kSentClose, + kByteParser, + kReceivedClose, + kResponse + } = require_symbols5(); + var { fireEvent, failWebsocketConnection, isClosing, isClosed, isEstablished, parseExtensions } = require_util7(); + var { channels } = require_diagnostics(); + var { CloseEvent } = require_events(); + var { makeRequest } = require_request2(); + var { fetching } = require_fetch(); + var { Headers: Headers2, getHeadersList } = require_headers(); + var { getDecodeSplit } = require_util2(); + var { WebsocketFrameSend } = require_frame(); + var crypto; + try { + crypto = __require("node:crypto"); + } catch { + } + function establishWebSocketConnection(url, protocols, client, ws, onEstablish, options) { + const requestURL = url; + requestURL.protocol = url.protocol === "ws:" ? "http:" : "https:"; + const request3 = makeRequest({ + urlList: [requestURL], + client, + serviceWorkers: "none", + referrer: "no-referrer", + mode: "websocket", + credentials: "include", + cache: "no-store", + redirect: "error" + }); + if (options.headers) { + const headersList = getHeadersList(new Headers2(options.headers)); + request3.headersList = headersList; + } + const keyValue = crypto.randomBytes(16).toString("base64"); + request3.headersList.append("sec-websocket-key", keyValue); + request3.headersList.append("sec-websocket-version", "13"); + for (const protocol of protocols) { + request3.headersList.append("sec-websocket-protocol", protocol); + } + const permessageDeflate = "permessage-deflate; client_max_window_bits"; + request3.headersList.append("sec-websocket-extensions", permessageDeflate); + const controller = fetching({ + request: request3, + useParallelQueue: true, + dispatcher: options.dispatcher, + processResponse(response) { + if (response.type === "error" || response.status !== 101) { + failWebsocketConnection(ws, "Received network error or non-101 status code."); + return; + } + if (protocols.length !== 0 && !response.headersList.get("Sec-WebSocket-Protocol")) { + failWebsocketConnection(ws, "Server did not respond with sent protocols."); + return; + } + if (response.headersList.get("Upgrade")?.toLowerCase() !== "websocket") { + failWebsocketConnection(ws, 'Server did not set Upgrade header to "websocket".'); + return; + } + if (response.headersList.get("Connection")?.toLowerCase() !== "upgrade") { + failWebsocketConnection(ws, 'Server did not set Connection header to "upgrade".'); + return; + } + const secWSAccept = response.headersList.get("Sec-WebSocket-Accept"); + const digest = crypto.createHash("sha1").update(keyValue + uid).digest("base64"); + if (secWSAccept !== digest) { + failWebsocketConnection(ws, "Incorrect hash received in Sec-WebSocket-Accept header."); + return; + } + const secExtension = response.headersList.get("Sec-WebSocket-Extensions"); + let extensions; + if (secExtension !== null) { + extensions = parseExtensions(secExtension); + if (!extensions.has("permessage-deflate")) { + failWebsocketConnection(ws, "Sec-WebSocket-Extensions header does not match."); + return; + } + } + const secProtocol = response.headersList.get("Sec-WebSocket-Protocol"); + if (secProtocol !== null) { + const requestProtocols = getDecodeSplit("sec-websocket-protocol", request3.headersList); + if (!requestProtocols.includes(secProtocol)) { + failWebsocketConnection(ws, "Protocol was not set in the opening handshake."); + return; + } + } + response.socket.on("data", onSocketData); + response.socket.on("close", onSocketClose); + response.socket.on("error", onSocketError); + if (channels.open.hasSubscribers) { + channels.open.publish({ + address: response.socket.address(), + protocol: secProtocol, + extensions: secExtension + }); + } + onEstablish(response, extensions); + } + }); + return controller; + } + function closeWebSocketConnection(ws, code, reason, reasonByteLength) { + if (isClosing(ws) || isClosed(ws)) { + } else if (!isEstablished(ws)) { + failWebsocketConnection(ws, "Connection was closed before it was established."); + ws[kReadyState] = states.CLOSING; + } else if (ws[kSentClose] === sentCloseFrameState.NOT_SENT) { + ws[kSentClose] = sentCloseFrameState.PROCESSING; + const frame = new WebsocketFrameSend(); + if (code !== void 0 && reason === void 0) { + frame.frameData = Buffer.allocUnsafe(2); + frame.frameData.writeUInt16BE(code, 0); + } else if (code !== void 0 && reason !== void 0) { + frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength); + frame.frameData.writeUInt16BE(code, 0); + frame.frameData.write(reason, 2, "utf-8"); + } else { + frame.frameData = emptyBuffer; + } + const socket = ws[kResponse].socket; + socket.write(frame.createFrame(opcodes.CLOSE)); + ws[kSentClose] = sentCloseFrameState.SENT; + ws[kReadyState] = states.CLOSING; + } else { + ws[kReadyState] = states.CLOSING; + } + } + function onSocketData(chunk) { + if (!this.ws[kByteParser].write(chunk)) { + this.pause(); + } + } + function onSocketClose() { + const { ws } = this; + const { [kResponse]: response } = ws; + response.socket.off("data", onSocketData); + response.socket.off("close", onSocketClose); + response.socket.off("error", onSocketError); + const wasClean = ws[kSentClose] === sentCloseFrameState.SENT && ws[kReceivedClose]; + let code = 1005; + let reason = ""; + const result = ws[kByteParser].closingInfo; + if (result && !result.error) { + code = result.code ?? 1005; + reason = result.reason; + } else if (!ws[kReceivedClose]) { + code = 1006; + } + ws[kReadyState] = states.CLOSED; + fireEvent("close", ws, (type, init) => new CloseEvent(type, init), { + wasClean, + code, + reason + }); + if (channels.close.hasSubscribers) { + channels.close.publish({ + websocket: ws, + code, + reason + }); + } + } + function onSocketError(error2) { + const { ws } = this; + ws[kReadyState] = states.CLOSING; + if (channels.socketError.hasSubscribers) { + channels.socketError.publish(error2); + } + this.destroy(); + } + module.exports = { + establishWebSocketConnection, + closeWebSocketConnection + }; + } +}); + +// +var require_permessage_deflate = __commonJS({ + ""(exports, module) { + "use strict"; + var { createInflateRaw, Z_DEFAULT_WINDOWBITS } = __require("node:zlib"); + var { isValidClientWindowBits } = require_util7(); + var { MessageSizeExceededError } = require_errors(); + var tail = Buffer.from([0, 0, 255, 255]); + var kBuffer = Symbol("kBuffer"); + var kLength = Symbol("kLength"); + var PerMessageDeflate = class { + /** @type {import('node:zlib').InflateRaw} */ + #inflate; + #options = {}; + #maxPayloadSize = 0; + /** + * @param {Map} extensions + */ + constructor(extensions, options) { + this.#options.serverNoContextTakeover = extensions.has("server_no_context_takeover"); + this.#options.serverMaxWindowBits = extensions.get("server_max_window_bits"); + this.#maxPayloadSize = options.maxPayloadSize; + } + /** + * Decompress a compressed payload. + * @param {Buffer} chunk Compressed data + * @param {boolean} fin Final fragment flag + * @param {Function} callback Callback function + */ + decompress(chunk, fin, callback) { + if (!this.#inflate) { + let windowBits = Z_DEFAULT_WINDOWBITS; + if (this.#options.serverMaxWindowBits) { + if (!isValidClientWindowBits(this.#options.serverMaxWindowBits)) { + callback(new Error("Invalid server_max_window_bits")); + return; + } + windowBits = Number.parseInt(this.#options.serverMaxWindowBits); + } + try { + this.#inflate = createInflateRaw({ windowBits }); + } catch (err) { + callback(err); + return; + } + this.#inflate[kBuffer] = []; + this.#inflate[kLength] = 0; + this.#inflate.on("data", (data) => { + this.#inflate[kLength] += data.length; + if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) { + callback(new MessageSizeExceededError()); + this.#inflate.removeAllListeners(); + this.#inflate = null; + return; + } + this.#inflate[kBuffer].push(data); + }); + this.#inflate.on("error", (err) => { + this.#inflate = null; + callback(err); + }); + } + this.#inflate.write(chunk); + if (fin) { + this.#inflate.write(tail); + } + this.#inflate.flush(() => { + if (!this.#inflate) { + return; + } + const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength]); + this.#inflate[kBuffer].length = 0; + this.#inflate[kLength] = 0; + callback(null, full); + }); + } + }; + module.exports = { PerMessageDeflate }; + } +}); + +// +var require_receiver = __commonJS({ + ""(exports, module) { + "use strict"; + var { Writable } = __require("node:stream"); + var assert2 = __require("node:assert"); + var { parserStates, opcodes, states, emptyBuffer, sentCloseFrameState } = require_constants5(); + var { kReadyState, kSentClose, kResponse, kReceivedClose } = require_symbols5(); + var { channels } = require_diagnostics(); + var { + isValidStatusCode, + isValidOpcode, + failWebsocketConnection, + websocketMessageReceived, + utf8Decode, + isControlFrame, + isTextBinaryFrame, + isContinuationFrame + } = require_util7(); + var { WebsocketFrameSend } = require_frame(); + var { closeWebSocketConnection } = require_connection(); + var { PerMessageDeflate } = require_permessage_deflate(); + var { MessageSizeExceededError } = require_errors(); + var ByteParser = class extends Writable { + #buffers = []; + #fragmentsBytes = 0; + #byteOffset = 0; + #loop = false; + #state = parserStates.INFO; + #info = {}; + #fragments = []; + /** @type {Map} */ + #extensions; + /** @type {number} */ + #maxPayloadSize; + /** + * @param {import('./websocket').WebSocket} ws + * @param {Map|null} extensions + * @param {{ maxPayloadSize?: number }} [options] + */ + constructor(ws, extensions, options = {}) { + super(); + this.ws = ws; + this.#extensions = extensions == null ? /* @__PURE__ */ new Map() : extensions; + this.#maxPayloadSize = options.maxPayloadSize ?? 0; + if (this.#extensions.has("permessage-deflate")) { + this.#extensions.set("permessage-deflate", new PerMessageDeflate(extensions, options)); + } + } + /** + * @param {Buffer} chunk + * @param {() => void} callback + */ + _write(chunk, _, callback) { + this.#buffers.push(chunk); + this.#byteOffset += chunk.length; + this.#loop = true; + this.run(callback); + } + #validatePayloadLength() { + if (this.#maxPayloadSize > 0 && !isControlFrame(this.#info.opcode) && this.#info.payloadLength > this.#maxPayloadSize) { + failWebsocketConnection(this.ws, "Payload size exceeds maximum allowed size"); + return false; + } + return true; + } + /** + * Runs whenever a new chunk is received. + * Callback is called whenever there are no more chunks buffering, + * or not enough bytes are buffered to parse. + */ + run(callback) { + while (this.#loop) { + if (this.#state === parserStates.INFO) { + if (this.#byteOffset < 2) { + return callback(); + } + const buffer = this.consume(2); + const fin = (buffer[0] & 128) !== 0; + const opcode = buffer[0] & 15; + const masked = (buffer[1] & 128) === 128; + const fragmented = !fin && opcode !== opcodes.CONTINUATION; + const payloadLength = buffer[1] & 127; + const rsv1 = buffer[0] & 64; + const rsv2 = buffer[0] & 32; + const rsv3 = buffer[0] & 16; + if (!isValidOpcode(opcode)) { + failWebsocketConnection(this.ws, "Invalid opcode received"); + return callback(); + } + if (masked) { + failWebsocketConnection(this.ws, "Frame cannot be masked"); + return callback(); + } + if (rsv1 !== 0 && !this.#extensions.has("permessage-deflate")) { + failWebsocketConnection(this.ws, "Expected RSV1 to be clear."); + return; + } + if (rsv2 !== 0 || rsv3 !== 0) { + failWebsocketConnection(this.ws, "RSV1, RSV2, RSV3 must be clear"); + return; + } + if (fragmented && !isTextBinaryFrame(opcode)) { + failWebsocketConnection(this.ws, "Invalid frame type was fragmented."); + return; + } + if (isTextBinaryFrame(opcode) && this.#fragments.length > 0) { + failWebsocketConnection(this.ws, "Expected continuation frame"); + return; + } + if (this.#info.fragmented && fragmented) { + failWebsocketConnection(this.ws, "Fragmented frame exceeded 125 bytes."); + return; + } + if ((payloadLength > 125 || fragmented) && isControlFrame(opcode)) { + failWebsocketConnection(this.ws, "Control frame either too large or fragmented"); + return; + } + if (isContinuationFrame(opcode) && this.#fragments.length === 0 && !this.#info.compressed) { + failWebsocketConnection(this.ws, "Unexpected continuation frame"); + return; + } + if (payloadLength <= 125) { + this.#info.payloadLength = payloadLength; + this.#state = parserStates.READ_DATA; + if (!this.#validatePayloadLength()) { + return; + } + } else if (payloadLength === 126) { + this.#state = parserStates.PAYLOADLENGTH_16; + } else if (payloadLength === 127) { + this.#state = parserStates.PAYLOADLENGTH_64; + } + if (isTextBinaryFrame(opcode)) { + this.#info.binaryType = opcode; + this.#info.compressed = rsv1 !== 0; + } + this.#info.opcode = opcode; + this.#info.masked = masked; + this.#info.fin = fin; + this.#info.fragmented = fragmented; + } else if (this.#state === parserStates.PAYLOADLENGTH_16) { + if (this.#byteOffset < 2) { + return callback(); + } + const buffer = this.consume(2); + this.#info.payloadLength = buffer.readUInt16BE(0); + this.#state = parserStates.READ_DATA; + if (!this.#validatePayloadLength()) { + return; + } + } else if (this.#state === parserStates.PAYLOADLENGTH_64) { + if (this.#byteOffset < 8) { + return callback(); + } + const buffer = this.consume(8); + const upper = buffer.readUInt32BE(0); + const lower = buffer.readUInt32BE(4); + if (upper !== 0 || lower > 2 ** 31 - 1) { + failWebsocketConnection(this.ws, "Received payload length > 2^31 bytes."); + return; + } + this.#info.payloadLength = lower; + this.#state = parserStates.READ_DATA; + if (!this.#validatePayloadLength()) { + return; + } + } else if (this.#state === parserStates.READ_DATA) { + if (this.#byteOffset < this.#info.payloadLength) { + return callback(); + } + const body = this.consume(this.#info.payloadLength); + if (isControlFrame(this.#info.opcode)) { + this.#loop = this.parseControlFrame(body); + this.#state = parserStates.INFO; + } else { + if (!this.#info.compressed) { + this.writeFragments(body); + if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { + failWebsocketConnection(this.ws, new MessageSizeExceededError().message); + return; + } + if (!this.#info.fragmented && this.#info.fin) { + websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments()); + } + this.#state = parserStates.INFO; + } else { + this.#extensions.get("permessage-deflate").decompress( + body, + this.#info.fin, + (error2, data) => { + if (error2) { + failWebsocketConnection(this.ws, error2.message); + return; + } + this.writeFragments(data); + if (this.#maxPayloadSize > 0 && this.#fragmentsBytes > this.#maxPayloadSize) { + failWebsocketConnection(this.ws, new MessageSizeExceededError().message); + return; + } + if (!this.#info.fin) { + this.#state = parserStates.INFO; + this.#loop = true; + this.run(callback); + return; + } + websocketMessageReceived(this.ws, this.#info.binaryType, this.consumeFragments()); + this.#loop = true; + this.#state = parserStates.INFO; + this.run(callback); + } + ); + this.#loop = false; + break; + } + } + } + } + } + /** + * Take n bytes from the buffered Buffers + * @param {number} n + * @returns {Buffer} + */ + consume(n) { + if (n > this.#byteOffset) { + throw new Error("Called consume() before buffers satiated."); + } else if (n === 0) { + return emptyBuffer; + } + if (this.#buffers[0].length === n) { + this.#byteOffset -= this.#buffers[0].length; + return this.#buffers.shift(); + } + const buffer = Buffer.allocUnsafe(n); + let offset = 0; + while (offset !== n) { + const next = this.#buffers[0]; + const { length } = next; + if (length + offset === n) { + buffer.set(this.#buffers.shift(), offset); + break; + } else if (length + offset > n) { + buffer.set(next.subarray(0, n - offset), offset); + this.#buffers[0] = next.subarray(n - offset); + break; + } else { + buffer.set(this.#buffers.shift(), offset); + offset += next.length; + } + } + this.#byteOffset -= n; + return buffer; + } + writeFragments(fragment) { + this.#fragmentsBytes += fragment.length; + this.#fragments.push(fragment); + } + consumeFragments() { + const fragments = this.#fragments; + if (fragments.length === 1) { + this.#fragmentsBytes = 0; + return fragments.shift(); + } + const output = Buffer.concat(fragments, this.#fragmentsBytes); + this.#fragments = []; + this.#fragmentsBytes = 0; + return output; + } + parseCloseBody(data) { + assert2(data.length !== 1); + let code; + if (data.length >= 2) { + code = data.readUInt16BE(0); + } + if (code !== void 0 && !isValidStatusCode(code)) { + return { code: 1002, reason: "Invalid status code", error: true }; + } + let reason = data.subarray(2); + if (reason[0] === 239 && reason[1] === 187 && reason[2] === 191) { + reason = reason.subarray(3); + } + try { + reason = utf8Decode(reason); + } catch { + return { code: 1007, reason: "Invalid UTF-8", error: true }; + } + return { code, reason, error: false }; + } + /** + * Parses control frames. + * @param {Buffer} body + */ + parseControlFrame(body) { + const { opcode, payloadLength } = this.#info; + if (opcode === opcodes.CLOSE) { + if (payloadLength === 1) { + failWebsocketConnection(this.ws, "Received close frame with a 1-byte body."); + return false; + } + this.#info.closeInfo = this.parseCloseBody(body); + if (this.#info.closeInfo.error) { + const { code, reason } = this.#info.closeInfo; + closeWebSocketConnection(this.ws, code, reason, reason.length); + failWebsocketConnection(this.ws, reason); + return false; + } + if (this.ws[kSentClose] !== sentCloseFrameState.SENT) { + let body2 = emptyBuffer; + if (this.#info.closeInfo.code) { + body2 = Buffer.allocUnsafe(2); + body2.writeUInt16BE(this.#info.closeInfo.code, 0); + } + const closeFrame = new WebsocketFrameSend(body2); + this.ws[kResponse].socket.write( + closeFrame.createFrame(opcodes.CLOSE), + (err) => { + if (!err) { + this.ws[kSentClose] = sentCloseFrameState.SENT; + } + } + ); + } + this.ws[kReadyState] = states.CLOSING; + this.ws[kReceivedClose] = true; + return false; + } else if (opcode === opcodes.PING) { + if (!this.ws[kReceivedClose]) { + const frame = new WebsocketFrameSend(body); + this.ws[kResponse].socket.write(frame.createFrame(opcodes.PONG)); + if (channels.ping.hasSubscribers) { + channels.ping.publish({ + payload: body + }); + } + } + } else if (opcode === opcodes.PONG) { + if (channels.pong.hasSubscribers) { + channels.pong.publish({ + payload: body + }); + } + } + return true; + } + get closingInfo() { + return this.#info.closeInfo; + } + }; + module.exports = { + ByteParser + }; + } +}); + +// +var require_sender = __commonJS({ + ""(exports, module) { + "use strict"; + var { WebsocketFrameSend } = require_frame(); + var { opcodes, sendHints } = require_constants5(); + var FixedQueue = require_fixed_queue(); + var FastBuffer = Buffer[Symbol.species]; + var SendQueue = class { + /** + * @type {FixedQueue} + */ + #queue = new FixedQueue(); + /** + * @type {boolean} + */ + #running = false; + /** @type {import('node:net').Socket} */ + #socket; + constructor(socket) { + this.#socket = socket; + } + add(item, cb, hint) { + if (hint !== sendHints.blob) { + const frame = createFrame(item, hint); + if (!this.#running) { + this.#socket.write(frame, cb); + } else { + const node2 = { + promise: null, + callback: cb, + frame + }; + this.#queue.push(node2); + } + return; + } + const node = { + promise: item.arrayBuffer().then((ab) => { + node.promise = null; + node.frame = createFrame(ab, hint); + }), + callback: cb, + frame: null + }; + this.#queue.push(node); + if (!this.#running) { + this.#run(); + } + } + async #run() { + this.#running = true; + const queue = this.#queue; + while (!queue.isEmpty()) { + const node = queue.shift(); + if (node.promise !== null) { + await node.promise; + } + this.#socket.write(node.frame, node.callback); + node.callback = node.frame = null; + } + this.#running = false; + } + }; + function createFrame(data, hint) { + return new WebsocketFrameSend(toBuffer(data, hint)).createFrame(hint === sendHints.string ? opcodes.TEXT : opcodes.BINARY); + } + function toBuffer(data, hint) { + switch (hint) { + case sendHints.string: + return Buffer.from(data); + case sendHints.arrayBuffer: + case sendHints.blob: + return new FastBuffer(data); + case sendHints.typedArray: + return new FastBuffer(data.buffer, data.byteOffset, data.byteLength); + } + } + module.exports = { SendQueue }; + } +}); + +// +var require_websocket = __commonJS({ + ""(exports, module) { + "use strict"; + var { webidl } = require_webidl(); + var { URLSerializer } = require_data_url(); + var { environmentSettingsObject } = require_util2(); + var { staticPropertyDescriptors, states, sentCloseFrameState, sendHints } = require_constants5(); + var { + kWebSocketURL, + kReadyState, + kController, + kBinaryType, + kResponse, + kSentClose, + kByteParser + } = require_symbols5(); + var { + isConnecting, + isEstablished, + isClosing, + isValidSubprotocol, + fireEvent + } = require_util7(); + var { establishWebSocketConnection, closeWebSocketConnection } = require_connection(); + var { ByteParser } = require_receiver(); + var { kEnumerableProperty, isBlobLike } = require_util(); + var { getGlobalDispatcher } = require_global2(); + var { types: types2 } = __require("node:util"); + var { ErrorEvent, CloseEvent } = require_events(); + var { SendQueue } = require_sender(); + var WebSocket = class _WebSocket extends EventTarget { + #events = { + open: null, + error: null, + close: null, + message: null + }; + #bufferedAmount = 0; + #protocol = ""; + #extensions = ""; + /** @type {SendQueue} */ + #sendQueue; + /** + * @param {string} url + * @param {string|string[]} protocols + */ + constructor(url, protocols = []) { + super(); + webidl.util.markAsUncloneable(this); + const prefix = "WebSocket constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + const options = webidl.converters["DOMString or sequence or WebSocketInit"](protocols, prefix, "options"); + url = webidl.converters.USVString(url, prefix, "url"); + protocols = options.protocols; + const baseURL = environmentSettingsObject.settingsObject.baseUrl; + let urlRecord; + try { + urlRecord = new URL(url, baseURL); + } catch (e) { + throw new DOMException(e, "SyntaxError"); + } + if (urlRecord.protocol === "http:") { + urlRecord.protocol = "ws:"; + } else if (urlRecord.protocol === "https:") { + urlRecord.protocol = "wss:"; + } + if (urlRecord.protocol !== "ws:" && urlRecord.protocol !== "wss:") { + throw new DOMException( + `Expected a ws: or wss: protocol, got ${urlRecord.protocol}`, + "SyntaxError" + ); + } + if (urlRecord.hash || urlRecord.href.endsWith("#")) { + throw new DOMException("Got fragment", "SyntaxError"); + } + if (typeof protocols === "string") { + protocols = [protocols]; + } + if (protocols.length !== new Set(protocols.map((p) => p.toLowerCase())).size) { + throw new DOMException("Invalid Sec-WebSocket-Protocol value", "SyntaxError"); + } + if (protocols.length > 0 && !protocols.every((p) => isValidSubprotocol(p))) { + throw new DOMException("Invalid Sec-WebSocket-Protocol value", "SyntaxError"); + } + this[kWebSocketURL] = new URL(urlRecord.href); + const client = environmentSettingsObject.settingsObject; + this[kController] = establishWebSocketConnection( + urlRecord, + protocols, + client, + this, + (response, extensions) => this.#onConnectionEstablished(response, extensions), + options + ); + this[kReadyState] = _WebSocket.CONNECTING; + this[kSentClose] = sentCloseFrameState.NOT_SENT; + this[kBinaryType] = "blob"; + } + /** + * @see https://websockets.spec.whatwg.org/#dom-websocket-close + * @param {number|undefined} code + * @param {string|undefined} reason + */ + close(code = void 0, reason = void 0) { + webidl.brandCheck(this, _WebSocket); + const prefix = "WebSocket.close"; + if (code !== void 0) { + code = webidl.converters["unsigned short"](code, prefix, "code", { clamp: true }); + } + if (reason !== void 0) { + reason = webidl.converters.USVString(reason, prefix, "reason"); + } + if (code !== void 0) { + if (code !== 1e3 && (code < 3e3 || code > 4999)) { + throw new DOMException("invalid code", "InvalidAccessError"); + } + } + let reasonByteLength = 0; + if (reason !== void 0) { + reasonByteLength = Buffer.byteLength(reason); + if (reasonByteLength > 123) { + throw new DOMException( + `Reason must be less than 123 bytes; received ${reasonByteLength}`, + "SyntaxError" + ); + } + } + closeWebSocketConnection(this, code, reason, reasonByteLength); + } + /** + * @see https://websockets.spec.whatwg.org/#dom-websocket-send + * @param {NodeJS.TypedArray|ArrayBuffer|Blob|string} data + */ + send(data) { + webidl.brandCheck(this, _WebSocket); + const prefix = "WebSocket.send"; + webidl.argumentLengthCheck(arguments, 1, prefix); + data = webidl.converters.WebSocketSendData(data, prefix, "data"); + if (isConnecting(this)) { + throw new DOMException("Sent before connected.", "InvalidStateError"); + } + if (!isEstablished(this) || isClosing(this)) { + return; + } + if (typeof data === "string") { + const length = Buffer.byteLength(data); + this.#bufferedAmount += length; + this.#sendQueue.add(data, () => { + this.#bufferedAmount -= length; + }, sendHints.string); + } else if (types2.isArrayBuffer(data)) { + this.#bufferedAmount += data.byteLength; + this.#sendQueue.add(data, () => { + this.#bufferedAmount -= data.byteLength; + }, sendHints.arrayBuffer); + } else if (ArrayBuffer.isView(data)) { + this.#bufferedAmount += data.byteLength; + this.#sendQueue.add(data, () => { + this.#bufferedAmount -= data.byteLength; + }, sendHints.typedArray); + } else if (isBlobLike(data)) { + this.#bufferedAmount += data.size; + this.#sendQueue.add(data, () => { + this.#bufferedAmount -= data.size; + }, sendHints.blob); + } + } + get readyState() { + webidl.brandCheck(this, _WebSocket); + return this[kReadyState]; + } + get bufferedAmount() { + webidl.brandCheck(this, _WebSocket); + return this.#bufferedAmount; + } + get url() { + webidl.brandCheck(this, _WebSocket); + return URLSerializer(this[kWebSocketURL]); + } + get extensions() { + webidl.brandCheck(this, _WebSocket); + return this.#extensions; + } + get protocol() { + webidl.brandCheck(this, _WebSocket); + return this.#protocol; + } + get onopen() { + webidl.brandCheck(this, _WebSocket); + return this.#events.open; + } + set onopen(fn) { + webidl.brandCheck(this, _WebSocket); + if (this.#events.open) { + this.removeEventListener("open", this.#events.open); + } + if (typeof fn === "function") { + this.#events.open = fn; + this.addEventListener("open", fn); + } else { + this.#events.open = null; + } + } + get onerror() { + webidl.brandCheck(this, _WebSocket); + return this.#events.error; + } + set onerror(fn) { + webidl.brandCheck(this, _WebSocket); + if (this.#events.error) { + this.removeEventListener("error", this.#events.error); + } + if (typeof fn === "function") { + this.#events.error = fn; + this.addEventListener("error", fn); + } else { + this.#events.error = null; + } + } + get onclose() { + webidl.brandCheck(this, _WebSocket); + return this.#events.close; + } + set onclose(fn) { + webidl.brandCheck(this, _WebSocket); + if (this.#events.close) { + this.removeEventListener("close", this.#events.close); + } + if (typeof fn === "function") { + this.#events.close = fn; + this.addEventListener("close", fn); + } else { + this.#events.close = null; + } + } + get onmessage() { + webidl.brandCheck(this, _WebSocket); + return this.#events.message; + } + set onmessage(fn) { + webidl.brandCheck(this, _WebSocket); + if (this.#events.message) { + this.removeEventListener("message", this.#events.message); + } + if (typeof fn === "function") { + this.#events.message = fn; + this.addEventListener("message", fn); + } else { + this.#events.message = null; + } + } + get binaryType() { + webidl.brandCheck(this, _WebSocket); + return this[kBinaryType]; + } + set binaryType(type) { + webidl.brandCheck(this, _WebSocket); + if (type !== "blob" && type !== "arraybuffer") { + this[kBinaryType] = "blob"; + } else { + this[kBinaryType] = type; + } + } + /** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + */ + #onConnectionEstablished(response, parsedExtensions) { + this[kResponse] = response; + const maxPayloadSize = this[kController]?.dispatcher?.webSocketOptions?.maxPayloadSize; + const parser2 = new ByteParser(this, parsedExtensions, { + maxPayloadSize + }); + parser2.on("drain", onParserDrain); + parser2.on("error", onParserError.bind(this)); + response.socket.ws = this; + this[kByteParser] = parser2; + this.#sendQueue = new SendQueue(response.socket); + this[kReadyState] = states.OPEN; + const extensions = response.headersList.get("sec-websocket-extensions"); + if (extensions !== null) { + this.#extensions = extensions; + } + const protocol = response.headersList.get("sec-websocket-protocol"); + if (protocol !== null) { + this.#protocol = protocol; + } + fireEvent("open", this); + } + }; + WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING; + WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN; + WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING; + WebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED; + Object.defineProperties(WebSocket.prototype, { + CONNECTING: staticPropertyDescriptors, + OPEN: staticPropertyDescriptors, + CLOSING: staticPropertyDescriptors, + CLOSED: staticPropertyDescriptors, + url: kEnumerableProperty, + readyState: kEnumerableProperty, + bufferedAmount: kEnumerableProperty, + onopen: kEnumerableProperty, + onerror: kEnumerableProperty, + onclose: kEnumerableProperty, + close: kEnumerableProperty, + onmessage: kEnumerableProperty, + binaryType: kEnumerableProperty, + send: kEnumerableProperty, + extensions: kEnumerableProperty, + protocol: kEnumerableProperty, + [Symbol.toStringTag]: { + value: "WebSocket", + writable: false, + enumerable: false, + configurable: true + } + }); + Object.defineProperties(WebSocket, { + CONNECTING: staticPropertyDescriptors, + OPEN: staticPropertyDescriptors, + CLOSING: staticPropertyDescriptors, + CLOSED: staticPropertyDescriptors + }); + webidl.converters["sequence"] = webidl.sequenceConverter( + webidl.converters.DOMString + ); + webidl.converters["DOMString or sequence"] = function(V, prefix, argument) { + if (webidl.util.Type(V) === "Object" && Symbol.iterator in V) { + return webidl.converters["sequence"](V); + } + return webidl.converters.DOMString(V, prefix, argument); + }; + webidl.converters.WebSocketInit = webidl.dictionaryConverter([ + { + key: "protocols", + converter: webidl.converters["DOMString or sequence"], + defaultValue: () => new Array(0) + }, + { + key: "dispatcher", + converter: webidl.converters.any, + defaultValue: () => getGlobalDispatcher() + }, + { + key: "headers", + converter: webidl.nullableConverter(webidl.converters.HeadersInit) + } + ]); + webidl.converters["DOMString or sequence or WebSocketInit"] = function(V) { + if (webidl.util.Type(V) === "Object" && !(Symbol.iterator in V)) { + return webidl.converters.WebSocketInit(V); + } + return { protocols: webidl.converters["DOMString or sequence"](V) }; + }; + webidl.converters.WebSocketSendData = function(V) { + if (webidl.util.Type(V) === "Object") { + if (isBlobLike(V)) { + return webidl.converters.Blob(V, { strict: false }); + } + if (ArrayBuffer.isView(V) || types2.isArrayBuffer(V)) { + return webidl.converters.BufferSource(V); + } + } + return webidl.converters.USVString(V); + }; + function onParserDrain() { + this.ws[kResponse].socket.resume(); + } + function onParserError(err) { + let message; + let code; + if (err instanceof CloseEvent) { + message = err.reason; + code = err.code; + } else { + message = err.message; + } + fireEvent("error", this, () => new ErrorEvent("error", { error: err, message })); + closeWebSocketConnection(this, code); + } + module.exports = { + WebSocket + }; + } +}); + +// +var require_util8 = __commonJS({ + ""(exports, module) { + "use strict"; + function isValidLastEventId(value) { + return value.indexOf("\0") === -1; + } + function isASCIINumber(value) { + if (value.length === 0) + return false; + for (let i = 0; i < value.length; i++) { + if (value.charCodeAt(i) < 48 || value.charCodeAt(i) > 57) + return false; + } + return true; + } + function delay(ms) { + return new Promise((resolve5) => { + setTimeout(resolve5, ms).unref(); + }); + } + module.exports = { + isValidLastEventId, + isASCIINumber, + delay + }; + } +}); + +// +var require_eventsource_stream = __commonJS({ + ""(exports, module) { + "use strict"; + var { Transform } = __require("node:stream"); + var { isASCIINumber, isValidLastEventId } = require_util8(); + var BOM = [239, 187, 191]; + var LF = 10; + var CR = 13; + var COLON = 58; + var SPACE = 32; + var EventSourceStream = class extends Transform { + /** + * @type {eventSourceSettings} + */ + state = null; + /** + * Leading byte-order-mark check. + * @type {boolean} + */ + checkBOM = true; + /** + * @type {boolean} + */ + crlfCheck = false; + /** + * @type {boolean} + */ + eventEndCheck = false; + /** + * @type {Buffer} + */ + buffer = null; + pos = 0; + event = { + data: void 0, + event: void 0, + id: void 0, + retry: void 0 + }; + /** + * @param {object} options + * @param {eventSourceSettings} options.eventSourceSettings + * @param {Function} [options.push] + */ + constructor(options = {}) { + options.readableObjectMode = true; + super(options); + this.state = options.eventSourceSettings || {}; + if (options.push) { + this.push = options.push; + } + } + /** + * @param {Buffer} chunk + * @param {string} _encoding + * @param {Function} callback + * @returns {void} + */ + _transform(chunk, _encoding, callback) { + if (chunk.length === 0) { + callback(); + return; + } + if (this.buffer) { + this.buffer = Buffer.concat([this.buffer, chunk]); + } else { + this.buffer = chunk; + } + if (this.checkBOM) { + switch (this.buffer.length) { + case 1: + if (this.buffer[0] === BOM[0]) { + callback(); + return; + } + this.checkBOM = false; + callback(); + return; + case 2: + if (this.buffer[0] === BOM[0] && this.buffer[1] === BOM[1]) { + callback(); + return; + } + this.checkBOM = false; + break; + case 3: + if (this.buffer[0] === BOM[0] && this.buffer[1] === BOM[1] && this.buffer[2] === BOM[2]) { + this.buffer = Buffer.alloc(0); + this.checkBOM = false; + callback(); + return; + } + this.checkBOM = false; + break; + default: + if (this.buffer[0] === BOM[0] && this.buffer[1] === BOM[1] && this.buffer[2] === BOM[2]) { + this.buffer = this.buffer.subarray(3); + } + this.checkBOM = false; + break; + } + } + while (this.pos < this.buffer.length) { + if (this.eventEndCheck) { + if (this.crlfCheck) { + if (this.buffer[this.pos] === LF) { + this.buffer = this.buffer.subarray(this.pos + 1); + this.pos = 0; + this.crlfCheck = false; + continue; + } + this.crlfCheck = false; + } + if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (this.buffer[this.pos] === CR) { + this.crlfCheck = true; + } + this.buffer = this.buffer.subarray(this.pos + 1); + this.pos = 0; + if (this.event.data !== void 0 || this.event.event || this.event.id || this.event.retry) { + this.processEvent(this.event); + } + this.clearEvent(); + continue; + } + this.eventEndCheck = false; + continue; + } + if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (this.buffer[this.pos] === CR) { + this.crlfCheck = true; + } + this.parseLine(this.buffer.subarray(0, this.pos), this.event); + this.buffer = this.buffer.subarray(this.pos + 1); + this.pos = 0; + this.eventEndCheck = true; + continue; + } + this.pos++; + } + callback(); + } + /** + * @param {Buffer} line + * @param {EventStreamEvent} event + */ + parseLine(line, event) { + if (line.length === 0) { + return; + } + const colonPosition = line.indexOf(COLON); + if (colonPosition === 0) { + return; + } + let field = ""; + let value = ""; + if (colonPosition !== -1) { + field = line.subarray(0, colonPosition).toString("utf8"); + let valueStart = colonPosition + 1; + if (line[valueStart] === SPACE) { + ++valueStart; + } + value = line.subarray(valueStart).toString("utf8"); + } else { + field = line.toString("utf8"); + value = ""; + } + switch (field) { + case "data": + if (event[field] === void 0) { + event[field] = value; + } else { + event[field] += ` +${value}`; + } + break; + case "retry": + if (isASCIINumber(value)) { + event[field] = value; + } + break; + case "id": + if (isValidLastEventId(value)) { + event[field] = value; + } + break; + case "event": + if (value.length > 0) { + event[field] = value; + } + break; + } + } + /** + * @param {EventSourceStreamEvent} event + */ + processEvent(event) { + if (event.retry && isASCIINumber(event.retry)) { + this.state.reconnectionTime = parseInt(event.retry, 10); + } + if (event.id && isValidLastEventId(event.id)) { + this.state.lastEventId = event.id; + } + if (event.data !== void 0) { + this.push({ + type: event.event || "message", + options: { + data: event.data, + lastEventId: this.state.lastEventId, + origin: this.state.origin + } + }); + } + } + clearEvent() { + this.event = { + data: void 0, + event: void 0, + id: void 0, + retry: void 0 + }; + } + }; + module.exports = { + EventSourceStream + }; + } +}); + +// +var require_eventsource = __commonJS({ + ""(exports, module) { + "use strict"; + var { pipeline } = __require("node:stream"); + var { fetching } = require_fetch(); + var { makeRequest } = require_request2(); + var { webidl } = require_webidl(); + var { EventSourceStream } = require_eventsource_stream(); + var { parseMIMEType } = require_data_url(); + var { createFastMessageEvent } = require_events(); + var { isNetworkError } = require_response(); + var { delay } = require_util8(); + var { kEnumerableProperty } = require_util(); + var { environmentSettingsObject } = require_util2(); + var experimentalWarned = false; + var defaultReconnectionTime = 3e3; + var CONNECTING = 0; + var OPEN = 1; + var CLOSED = 2; + var ANONYMOUS = "anonymous"; + var USE_CREDENTIALS = "use-credentials"; + var EventSource = class _EventSource extends EventTarget { + #events = { + open: null, + error: null, + message: null + }; + #url = null; + #withCredentials = false; + #readyState = CONNECTING; + #request = null; + #controller = null; + #dispatcher; + /** + * @type {import('./eventsource-stream').eventSourceSettings} + */ + #state; + /** + * Creates a new EventSource object. + * @param {string} url + * @param {EventSourceInit} [eventSourceInitDict] + * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface + */ + constructor(url, eventSourceInitDict = {}) { + super(); + webidl.util.markAsUncloneable(this); + const prefix = "EventSource constructor"; + webidl.argumentLengthCheck(arguments, 1, prefix); + if (!experimentalWarned) { + experimentalWarned = true; + process.emitWarning("EventSource is experimental, expect them to change at any time.", { + code: "UNDICI-ES" + }); + } + url = webidl.converters.USVString(url, prefix, "url"); + eventSourceInitDict = webidl.converters.EventSourceInitDict(eventSourceInitDict, prefix, "eventSourceInitDict"); + this.#dispatcher = eventSourceInitDict.dispatcher; + this.#state = { + lastEventId: "", + reconnectionTime: defaultReconnectionTime + }; + const settings = environmentSettingsObject; + let urlRecord; + try { + urlRecord = new URL(url, settings.settingsObject.baseUrl); + this.#state.origin = urlRecord.origin; + } catch (e) { + throw new DOMException(e, "SyntaxError"); + } + this.#url = urlRecord.href; + let corsAttributeState = ANONYMOUS; + if (eventSourceInitDict.withCredentials) { + corsAttributeState = USE_CREDENTIALS; + this.#withCredentials = true; + } + const initRequest = { + redirect: "follow", + keepalive: true, + // @see https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes + mode: "cors", + credentials: corsAttributeState === "anonymous" ? "same-origin" : "omit", + referrer: "no-referrer" + }; + initRequest.client = environmentSettingsObject.settingsObject; + initRequest.headersList = [["accept", { name: "accept", value: "text/event-stream" }]]; + initRequest.cache = "no-store"; + initRequest.initiator = "other"; + initRequest.urlList = [new URL(this.#url)]; + this.#request = makeRequest(initRequest); + this.#connect(); + } + /** + * Returns the state of this EventSource object's connection. It can have the + * values described below. + * @returns {0|1|2} + * @readonly + */ + get readyState() { + return this.#readyState; + } + /** + * Returns the URL providing the event stream. + * @readonly + * @returns {string} + */ + get url() { + return this.#url; + } + /** + * Returns a boolean indicating whether the EventSource object was + * instantiated with CORS credentials set (true), or not (false, the default). + */ + get withCredentials() { + return this.#withCredentials; + } + #connect() { + if (this.#readyState === CLOSED) + return; + this.#readyState = CONNECTING; + const fetchParams = { + request: this.#request, + dispatcher: this.#dispatcher + }; + const processEventSourceEndOfBody = (response) => { + if (isNetworkError(response)) { + this.dispatchEvent(new Event("error")); + this.close(); + } + this.#reconnect(); + }; + fetchParams.processResponseEndOfBody = processEventSourceEndOfBody; + fetchParams.processResponse = (response) => { + if (isNetworkError(response)) { + if (response.aborted) { + this.close(); + this.dispatchEvent(new Event("error")); + return; + } else { + this.#reconnect(); + return; + } + } + const contentType = response.headersList.get("content-type", true); + const mimeType = contentType !== null ? parseMIMEType(contentType) : "failure"; + const contentTypeValid = mimeType !== "failure" && mimeType.essence === "text/event-stream"; + if (response.status !== 200 || contentTypeValid === false) { + this.close(); + this.dispatchEvent(new Event("error")); + return; + } + this.#readyState = OPEN; + this.dispatchEvent(new Event("open")); + this.#state.origin = response.urlList[response.urlList.length - 1].origin; + const eventSourceStream = new EventSourceStream({ + eventSourceSettings: this.#state, + push: (event) => { + this.dispatchEvent(createFastMessageEvent( + event.type, + event.options + )); + } + }); + pipeline( + response.body.stream, + eventSourceStream, + (error2) => { + if (error2?.aborted === false) { + this.close(); + this.dispatchEvent(new Event("error")); + } + } + ); + }; + this.#controller = fetching(fetchParams); + } + /** + * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#sse-processing-model + * @returns {Promise} + */ + async #reconnect() { + if (this.#readyState === CLOSED) + return; + this.#readyState = CONNECTING; + this.dispatchEvent(new Event("error")); + await delay(this.#state.reconnectionTime); + if (this.#readyState !== CONNECTING) + return; + if (this.#state.lastEventId.length) { + this.#request.headersList.set("last-event-id", this.#state.lastEventId, true); + } + this.#connect(); + } + /** + * Closes the connection, if any, and sets the readyState attribute to + * CLOSED. + */ + close() { + webidl.brandCheck(this, _EventSource); + if (this.#readyState === CLOSED) + return; + this.#readyState = CLOSED; + this.#controller.abort(); + this.#request = null; + } + get onopen() { + return this.#events.open; + } + set onopen(fn) { + if (this.#events.open) { + this.removeEventListener("open", this.#events.open); + } + if (typeof fn === "function") { + this.#events.open = fn; + this.addEventListener("open", fn); + } else { + this.#events.open = null; + } + } + get onmessage() { + return this.#events.message; + } + set onmessage(fn) { + if (this.#events.message) { + this.removeEventListener("message", this.#events.message); + } + if (typeof fn === "function") { + this.#events.message = fn; + this.addEventListener("message", fn); + } else { + this.#events.message = null; + } + } + get onerror() { + return this.#events.error; + } + set onerror(fn) { + if (this.#events.error) { + this.removeEventListener("error", this.#events.error); + } + if (typeof fn === "function") { + this.#events.error = fn; + this.addEventListener("error", fn); + } else { + this.#events.error = null; + } + } + }; + var constantsPropertyDescriptors = { + CONNECTING: { + __proto__: null, + configurable: false, + enumerable: true, + value: CONNECTING, + writable: false + }, + OPEN: { + __proto__: null, + configurable: false, + enumerable: true, + value: OPEN, + writable: false + }, + CLOSED: { + __proto__: null, + configurable: false, + enumerable: true, + value: CLOSED, + writable: false + } + }; + Object.defineProperties(EventSource, constantsPropertyDescriptors); + Object.defineProperties(EventSource.prototype, constantsPropertyDescriptors); + Object.defineProperties(EventSource.prototype, { + close: kEnumerableProperty, + onerror: kEnumerableProperty, + onmessage: kEnumerableProperty, + onopen: kEnumerableProperty, + readyState: kEnumerableProperty, + url: kEnumerableProperty, + withCredentials: kEnumerableProperty + }); + webidl.converters.EventSourceInitDict = webidl.dictionaryConverter([ + { + key: "withCredentials", + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: "dispatcher", + // undici only + converter: webidl.converters.any + } + ]); + module.exports = { + EventSource, + defaultReconnectionTime + }; + } +}); + +// +var require_undici = __commonJS({ + ""(exports, module) { + "use strict"; + var Client = require_client(); + var Dispatcher = require_dispatcher(); + var Pool = require_pool(); + var BalancedPool = require_balanced_pool(); + var Agent = require_agent(); + var ProxyAgent2 = require_proxy_agent(); + var EnvHttpProxyAgent = require_env_http_proxy_agent(); + var RetryAgent = require_retry_agent(); + var errors = require_errors(); + var util = require_util(); + var { InvalidArgumentError } = errors; + var api = require_api(); + var buildConnector = require_connect(); + var MockClient = require_mock_client(); + var MockAgent = require_mock_agent(); + var MockPool = require_mock_pool(); + var mockErrors = require_mock_errors(); + var RetryHandler = require_retry_handler(); + var { getGlobalDispatcher, setGlobalDispatcher } = require_global2(); + var DecoratorHandler = require_decorator_handler(); + var RedirectHandler = require_redirect_handler(); + var createRedirectInterceptor = require_redirect_interceptor(); + Object.assign(Dispatcher.prototype, api); + module.exports.Dispatcher = Dispatcher; + module.exports.Client = Client; + module.exports.Pool = Pool; + module.exports.BalancedPool = BalancedPool; + module.exports.Agent = Agent; + module.exports.ProxyAgent = ProxyAgent2; + module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent; + module.exports.RetryAgent = RetryAgent; + module.exports.RetryHandler = RetryHandler; + module.exports.DecoratorHandler = DecoratorHandler; + module.exports.RedirectHandler = RedirectHandler; + module.exports.createRedirectInterceptor = createRedirectInterceptor; + module.exports.interceptors = { + redirect: require_redirect(), + retry: require_retry(), + dump: require_dump(), + dns: require_dns() + }; + module.exports.buildConnector = buildConnector; + module.exports.errors = errors; + module.exports.util = { + parseHeaders: util.parseHeaders, + headerNameToString: util.headerNameToString + }; + function makeDispatcher(fn) { + return (url, opts, handler3) => { + if (typeof opts === "function") { + handler3 = opts; + opts = null; + } + if (!url || typeof url !== "string" && typeof url !== "object" && !(url instanceof URL)) { + throw new InvalidArgumentError("invalid url"); + } + if (opts != null && typeof opts !== "object") { + throw new InvalidArgumentError("invalid opts"); + } + if (opts && opts.path != null) { + if (typeof opts.path !== "string") { + throw new InvalidArgumentError("invalid opts.path"); + } + let path = opts.path; + if (!opts.path.startsWith("/")) { + path = `/${path}`; + } + url = new URL(util.parseOrigin(url).origin + path); + } else { + if (!opts) { + opts = typeof url === "object" ? url : {}; + } + url = util.parseURL(url); + } + const { agent, dispatcher = getGlobalDispatcher() } = opts; + if (agent) { + throw new InvalidArgumentError("unsupported opts.agent. Did you mean opts.client?"); + } + return fn.call(dispatcher, { + ...opts, + origin: url.origin, + path: url.search ? `${url.pathname}${url.search}` : url.pathname, + method: opts.method || (opts.body ? "PUT" : "GET") + }, handler3); + }; + } + module.exports.setGlobalDispatcher = setGlobalDispatcher; + module.exports.getGlobalDispatcher = getGlobalDispatcher; + var fetchImpl = require_fetch().fetch; + module.exports.fetch = async function fetch3(init, options = void 0) { + try { + return await fetchImpl(init, options); + } catch (err) { + if (err && typeof err === "object") { + Error.captureStackTrace(err); + } + throw err; + } + }; + module.exports.Headers = require_headers().Headers; + module.exports.Response = require_response().Response; + module.exports.Request = require_request2().Request; + module.exports.FormData = require_formdata().FormData; + module.exports.File = globalThis.File ?? __require("node:buffer").File; + module.exports.FileReader = require_filereader().FileReader; + var { setGlobalOrigin, getGlobalOrigin } = require_global(); + module.exports.setGlobalOrigin = setGlobalOrigin; + module.exports.getGlobalOrigin = getGlobalOrigin; + var { CacheStorage } = require_cachestorage(); + var { kConstruct } = require_symbols4(); + module.exports.caches = new CacheStorage(kConstruct); + var { deleteCookie, getCookies, getSetCookies, setCookie } = require_cookies(); + module.exports.deleteCookie = deleteCookie; + module.exports.getCookies = getCookies; + module.exports.getSetCookies = getSetCookies; + module.exports.setCookie = setCookie; + var { parseMIMEType, serializeAMimeType } = require_data_url(); + module.exports.parseMIMEType = parseMIMEType; + module.exports.serializeAMimeType = serializeAMimeType; + var { CloseEvent, ErrorEvent, MessageEvent } = require_events(); + module.exports.WebSocket = require_websocket().WebSocket; + module.exports.CloseEvent = CloseEvent; + module.exports.ErrorEvent = ErrorEvent; + module.exports.MessageEvent = MessageEvent; + module.exports.request = makeDispatcher(api.request); + module.exports.stream = makeDispatcher(api.stream); + module.exports.pipeline = makeDispatcher(api.pipeline); + module.exports.connect = makeDispatcher(api.connect); + module.exports.upgrade = makeDispatcher(api.upgrade); + module.exports.MockClient = MockClient; + module.exports.MockPool = MockPool; + module.exports.MockAgent = MockAgent; + module.exports.mockErrors = mockErrors; + var { EventSource } = require_eventsource(); + module.exports.EventSource = EventSource; + } +}); + // var require_proxy = __commonJS({ ""(exports) { @@ -434,7 +18810,7 @@ var require_lib = __commonJS({ var https = __importStar(__require("https")); var pm = __importStar(require_proxy()); var tunnel2 = __importStar(require_tunnel2()); - var undici_1 = __require("undici"); + var undici_1 = require_undici(); var HttpCodes2; (function(HttpCodes3) { HttpCodes3[HttpCodes3["OK"] = 200] = "OK"; @@ -1145,386 +19521,6 @@ var require_fast_content_type_parse = __commonJS({ } }); -// -var require_tmp = __commonJS({ - ""(exports, module) { - var fs2 = __require("fs"); - var os4 = __require("os"); - var path = __require("path"); - var crypto = __require("crypto"); - var _c2 = { fs: fs2.constants, os: os4.constants }; - var RANDOM_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - var TEMPLATE_PATTERN = /XXXXXX/; - var DEFAULT_TRIES = 3; - var CREATE_FLAGS = (_c2.O_CREAT || _c2.fs.O_CREAT) | (_c2.O_EXCL || _c2.fs.O_EXCL) | (_c2.O_RDWR || _c2.fs.O_RDWR); - var IS_WIN32 = os4.platform() === "win32"; - var EBADF = _c2.EBADF || _c2.os.errno.EBADF; - var ENOENT = _c2.ENOENT || _c2.os.errno.ENOENT; - var DIR_MODE = 448; - var FILE_MODE = 384; - var EXIT = "exit"; - var _removeObjects = []; - var FN_RMDIR_SYNC = fs2.rmdirSync.bind(fs2); - var _gracefulCleanup = false; - function rimraf(dirPath, callback) { - return fs2.rm(dirPath, { recursive: true }, callback); - } - function FN_RIMRAF_SYNC(dirPath) { - return fs2.rmSync(dirPath, { recursive: true }); - } - function tmpName(options, callback) { - const args = _parseArguments(options, callback), opts = args[0], cb = args[1]; - _assertAndSanitizeOptions(opts, function(err, sanitizedOptions) { - if (err) - return cb(err); - let tries = sanitizedOptions.tries; - (function _getUniqueName() { - try { - const name = _generateTmpName(sanitizedOptions); - fs2.stat(name, function(err2) { - if (!err2) { - if (tries-- > 0) - return _getUniqueName(); - return cb(new Error("Could not get a unique tmp filename, max tries reached " + name)); - } - cb(null, name); - }); - } catch (err2) { - cb(err2); - } - })(); - }); - } - function tmpNameSync(options) { - const args = _parseArguments(options), opts = args[0]; - const sanitizedOptions = _assertAndSanitizeOptionsSync(opts); - let tries = sanitizedOptions.tries; - do { - const name = _generateTmpName(sanitizedOptions); - try { - fs2.statSync(name); - } catch (e) { - return name; - } - } while (tries-- > 0); - throw new Error("Could not get a unique tmp filename, max tries reached"); - } - function file(options, callback) { - const args = _parseArguments(options, callback), opts = args[0], cb = args[1]; - tmpName(opts, function _tmpNameCreated(err, name) { - if (err) - return cb(err); - fs2.open(name, CREATE_FLAGS, opts.mode || FILE_MODE, function _fileCreated(err2, fd) { - if (err2) - return cb(err2); - if (opts.discardDescriptor) { - return fs2.close(fd, function _discardCallback(possibleErr) { - return cb(possibleErr, name, void 0, _prepareTmpFileRemoveCallback(name, -1, opts, false)); - }); - } else { - const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor; - cb(null, name, fd, _prepareTmpFileRemoveCallback(name, discardOrDetachDescriptor ? -1 : fd, opts, false)); - } - }); - }); - } - function fileSync2(options) { - const args = _parseArguments(options), opts = args[0]; - const discardOrDetachDescriptor = opts.discardDescriptor || opts.detachDescriptor; - const name = tmpNameSync(opts); - let fd = fs2.openSync(name, CREATE_FLAGS, opts.mode || FILE_MODE); - if (opts.discardDescriptor) { - fs2.closeSync(fd); - fd = void 0; - } - return { - name, - fd, - removeCallback: _prepareTmpFileRemoveCallback(name, discardOrDetachDescriptor ? -1 : fd, opts, true) - }; - } - function dir(options, callback) { - const args = _parseArguments(options, callback), opts = args[0], cb = args[1]; - tmpName(opts, function _tmpNameCreated(err, name) { - if (err) - return cb(err); - fs2.mkdir(name, opts.mode || DIR_MODE, function _dirCreated(err2) { - if (err2) - return cb(err2); - cb(null, name, _prepareTmpDirRemoveCallback(name, opts, false)); - }); - }); - } - function dirSync(options) { - const args = _parseArguments(options), opts = args[0]; - const name = tmpNameSync(opts); - fs2.mkdirSync(name, opts.mode || DIR_MODE); - return { - name, - removeCallback: _prepareTmpDirRemoveCallback(name, opts, true) - }; - } - function _removeFileAsync(fdPath, next) { - const _handler = function(err) { - if (err && !_isENOENT(err)) { - return next(err); - } - next(); - }; - if (0 <= fdPath[0]) - fs2.close(fdPath[0], function() { - fs2.unlink(fdPath[1], _handler); - }); - else - fs2.unlink(fdPath[1], _handler); - } - function _removeFileSync(fdPath) { - let rethrownException = null; - try { - if (0 <= fdPath[0]) - fs2.closeSync(fdPath[0]); - } catch (e) { - if (!_isEBADF(e) && !_isENOENT(e)) - throw e; - } finally { - try { - fs2.unlinkSync(fdPath[1]); - } catch (e) { - if (!_isENOENT(e)) - rethrownException = e; - } - } - if (rethrownException !== null) { - throw rethrownException; - } - } - function _prepareTmpFileRemoveCallback(name, fd, opts, sync) { - const removeCallbackSync = _prepareRemoveCallback(_removeFileSync, [fd, name], sync); - const removeCallback = _prepareRemoveCallback(_removeFileAsync, [fd, name], sync, removeCallbackSync); - if (!opts.keep) - _removeObjects.unshift(removeCallbackSync); - return sync ? removeCallbackSync : removeCallback; - } - function _prepareTmpDirRemoveCallback(name, opts, sync) { - const removeFunction = opts.unsafeCleanup ? rimraf : fs2.rmdir.bind(fs2); - const removeFunctionSync = opts.unsafeCleanup ? FN_RIMRAF_SYNC : FN_RMDIR_SYNC; - const removeCallbackSync = _prepareRemoveCallback(removeFunctionSync, name, sync); - const removeCallback = _prepareRemoveCallback(removeFunction, name, sync, removeCallbackSync); - if (!opts.keep) - _removeObjects.unshift(removeCallbackSync); - return sync ? removeCallbackSync : removeCallback; - } - function _prepareRemoveCallback(removeFunction, fileOrDirName, sync, cleanupCallbackSync) { - let called = false; - return function _cleanupCallback(next) { - if (!called) { - const toRemove = cleanupCallbackSync || _cleanupCallback; - const index = _removeObjects.indexOf(toRemove); - if (index >= 0) - _removeObjects.splice(index, 1); - called = true; - if (sync || removeFunction === FN_RMDIR_SYNC || removeFunction === FN_RIMRAF_SYNC) { - return removeFunction(fileOrDirName); - } else { - return removeFunction(fileOrDirName, next || function() { - }); - } - } - }; - } - function _garbageCollector() { - if (!_gracefulCleanup) - return; - while (_removeObjects.length) { - try { - _removeObjects[0](); - } catch (e) { - } - } - } - function _randomChars(howMany) { - let value = [], rnd = null; - try { - rnd = crypto.randomBytes(howMany); - } catch (e) { - rnd = crypto.pseudoRandomBytes(howMany); - } - for (let i = 0; i < howMany; i++) { - value.push(RANDOM_CHARS[rnd[i] % RANDOM_CHARS.length]); - } - return value.join(""); - } - function _isUndefined(obj) { - return typeof obj === "undefined"; - } - function _parseArguments(options, callback) { - if (typeof options === "function") { - return [{}, options]; - } - if (_isUndefined(options)) { - return [{}, callback]; - } - const actualOptions = {}; - for (const key of Object.getOwnPropertyNames(options)) { - actualOptions[key] = options[key]; - } - return [actualOptions, callback]; - } - function _resolvePath(name, tmpDir, cb) { - const pathToResolve = path.isAbsolute(name) ? name : path.join(tmpDir, name); - fs2.stat(pathToResolve, function(err) { - if (err) { - fs2.realpath(path.dirname(pathToResolve), function(err2, parentDir) { - if (err2) - return cb(err2); - cb(null, path.join(parentDir, path.basename(pathToResolve))); - }); - } else { - fs2.realpath(pathToResolve, cb); - } - }); - } - function _resolvePathSync(name, tmpDir) { - const pathToResolve = path.isAbsolute(name) ? name : path.join(tmpDir, name); - try { - fs2.statSync(pathToResolve); - return fs2.realpathSync(pathToResolve); - } catch (_err) { - const parentDir = fs2.realpathSync(path.dirname(pathToResolve)); - return path.join(parentDir, path.basename(pathToResolve)); - } - } - function _generateTmpName(opts) { - const tmpDir = opts.tmpdir; - if (!_isUndefined(opts.name)) { - return path.join(tmpDir, opts.dir, opts.name); - } - if (!_isUndefined(opts.template)) { - return path.join(tmpDir, opts.dir, opts.template).replace(TEMPLATE_PATTERN, _randomChars(6)); - } - const name = [ - opts.prefix ? opts.prefix : "tmp", - "-", - process.pid, - "-", - _randomChars(12), - opts.postfix ? "-" + opts.postfix : "" - ].join(""); - return path.join(tmpDir, opts.dir, name); - } - function _assertOptionsBase(options) { - if (!_isUndefined(options.name)) { - const name = options.name; - if (path.isAbsolute(name)) - throw new Error(`name option must not contain an absolute path, found "${name}".`); - const basename2 = path.basename(name); - if (basename2 === ".." || basename2 === "." || basename2 !== name) - throw new Error(`name option must not contain a path, found "${name}".`); - } - if (!_isUndefined(options.template) && !options.template.match(TEMPLATE_PATTERN)) { - throw new Error(`Invalid template, found "${options.template}".`); - } - if (!_isUndefined(options.tries) && isNaN(options.tries) || options.tries < 0) { - throw new Error(`Invalid tries, found "${options.tries}".`); - } - options.tries = _isUndefined(options.name) ? options.tries || DEFAULT_TRIES : 1; - options.keep = !!options.keep; - options.detachDescriptor = !!options.detachDescriptor; - options.discardDescriptor = !!options.discardDescriptor; - options.unsafeCleanup = !!options.unsafeCleanup; - options.prefix = _isUndefined(options.prefix) ? "" : options.prefix; - options.postfix = _isUndefined(options.postfix) ? "" : options.postfix; - } - function _getRelativePath(option, name, tmpDir, cb) { - if (_isUndefined(name)) - return cb(null); - _resolvePath(name, tmpDir, function(err, resolvedPath) { - if (err) - return cb(err); - const relativePath = path.relative(tmpDir, resolvedPath); - if (!resolvedPath.startsWith(tmpDir)) { - return cb(new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`)); - } - cb(null, relativePath); - }); - } - function _getRelativePathSync(option, name, tmpDir) { - if (_isUndefined(name)) - return; - const resolvedPath = _resolvePathSync(name, tmpDir); - const relativePath = path.relative(tmpDir, resolvedPath); - if (!resolvedPath.startsWith(tmpDir)) { - throw new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`); - } - return relativePath; - } - function _assertAndSanitizeOptions(options, cb) { - _getTmpDir(options, function(err, tmpDir) { - if (err) - return cb(err); - options.tmpdir = tmpDir; - try { - _assertOptionsBase(options, tmpDir); - } catch (err2) { - return cb(err2); - } - _getRelativePath("dir", options.dir, tmpDir, function(err2, dir2) { - if (err2) - return cb(err2); - options.dir = _isUndefined(dir2) ? "" : dir2; - _getRelativePath("template", options.template, tmpDir, function(err3, template) { - if (err3) - return cb(err3); - options.template = template; - cb(null, options); - }); - }); - }); - } - function _assertAndSanitizeOptionsSync(options) { - const tmpDir = options.tmpdir = _getTmpDirSync(options); - _assertOptionsBase(options, tmpDir); - const dir2 = _getRelativePathSync("dir", options.dir, tmpDir); - options.dir = _isUndefined(dir2) ? "" : dir2; - options.template = _getRelativePathSync("template", options.template, tmpDir); - return options; - } - function _isEBADF(error2) { - return _isExpectedError(error2, -EBADF, "EBADF"); - } - function _isENOENT(error2) { - return _isExpectedError(error2, -ENOENT, "ENOENT"); - } - function _isExpectedError(error2, errno, code) { - return IS_WIN32 ? error2.code === code : error2.code === code && error2.errno === errno; - } - function setGracefulCleanup() { - _gracefulCleanup = true; - } - function _getTmpDir(options, cb) { - return fs2.realpath(options && options.tmpdir || os4.tmpdir(), cb); - } - function _getTmpDirSync(options) { - return fs2.realpathSync(options && options.tmpdir || os4.tmpdir()); - } - process.addListener(EXIT, _garbageCollector); - Object.defineProperty(module.exports, "tmpdir", { - enumerable: true, - configurable: false, - get: function() { - return _getTmpDirSync(); - } - }); - module.exports.dir = dir; - module.exports.dirSync = dirSync; - module.exports.file = file; - module.exports.fileSync = fileSync2; - module.exports.tmpName = tmpName; - module.exports.tmpNameSync = tmpNameSync; - module.exports.setGracefulCleanup = setGracefulCleanup; - } -}); - // var require_lockfile = __commonJS({ ""(exports, module) { @@ -2772,9 +20768,9 @@ var require_lockfile = __commonJS({ /* 11 */ /***/ function(module2, exports2) { - var global = module2.exports = typeof window != "undefined" && window.Math == Math ? window : typeof self != "undefined" && self.Math == Math ? self : Function("return this")(); + var global2 = module2.exports = typeof window != "undefined" && window.Math == Math ? window : typeof self != "undefined" && self.Math == Math ? self : Function("return this")(); if (typeof __g == "number") - __g = global; + __g = global2; }, /* 12 */ /***/ @@ -3379,7 +21375,7 @@ var require_lockfile = __commonJS({ /* 41 */ /***/ function(module2, exports2, __webpack_require__) { - var global = __webpack_require__(11); + var global2 = __webpack_require__(11); var core = __webpack_require__(23); var ctx = __webpack_require__(48); var hide = __webpack_require__(31); @@ -3394,7 +21390,7 @@ var require_lockfile = __commonJS({ var IS_WRAP = type & $export.W; var exports3 = IS_GLOBAL ? core : core[name] || (core[name] = {}); var expProto = exports3[PROTOTYPE]; - var target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE]; + var target = IS_GLOBAL ? global2 : IS_STATIC ? global2[name] : (global2[name] || {})[PROTOTYPE]; var key, own, out; if (IS_GLOBAL) source = name; @@ -3403,7 +21399,7 @@ var require_lockfile = __commonJS({ if (own && has(exports3, key)) continue; out = own ? target[key] : source[key]; - exports3[key] = IS_GLOBAL && typeof target[key] != "function" ? source[key] : IS_BIND && own ? ctx(out, global) : IS_WRAP && target[key] == out ? function(C) { + exports3[key] = IS_GLOBAL && typeof target[key] != "function" ? source[key] : IS_BIND && own ? ctx(out, global2) : IS_WRAP && target[key] == out ? function(C) { var F = function(a, b, c) { if (this instanceof C) { switch (arguments.length) { @@ -5854,9 +23850,9 @@ var require_lockfile = __commonJS({ /***/ function(module2, exports2, __webpack_require__) { var core = __webpack_require__(23); - var global = __webpack_require__(11); + var global2 = __webpack_require__(11); var SHARED = "__core-js_shared__"; - var store = global[SHARED] || (global[SHARED] = {}); + var store = global2[SHARED] || (global2[SHARED] = {}); (module2.exports = function(key, value) { return store[key] || (store[key] = value !== void 0 ? value : {}); })("versions", []).push({ @@ -5884,12 +23880,12 @@ var require_lockfile = __commonJS({ var invoke = __webpack_require__(185); var html = __webpack_require__(102); var cel = __webpack_require__(68); - var global = __webpack_require__(11); - var process3 = global.process; - var setTask = global.setImmediate; - var clearTask = global.clearImmediate; - var MessageChannel = global.MessageChannel; - var Dispatch = global.Dispatch; + var global2 = __webpack_require__(11); + var process3 = global2.process; + var setTask = global2.setImmediate; + var clearTask = global2.clearImmediate; + var MessageChannel = global2.MessageChannel; + var Dispatch = global2.Dispatch; var counter = 0; var queue = {}; var ONREADYSTATECHANGE = "onreadystatechange"; @@ -5906,7 +23902,7 @@ var require_lockfile = __commonJS({ run.call(event.data); }; if (!setTask || !clearTask) { - setTask = function setImmediate(fn) { + setTask = function setImmediate2(fn) { var args = []; var i = 1; while (arguments.length > i) @@ -5917,7 +23913,7 @@ var require_lockfile = __commonJS({ defer(counter); return counter; }; - clearTask = function clearImmediate(id) { + clearTask = function clearImmediate2(id) { delete queue[id]; }; if (__webpack_require__(47)(process3) == "process") { @@ -5933,11 +23929,11 @@ var require_lockfile = __commonJS({ port = channel.port2; channel.port1.onmessage = listener; defer = ctx(port.postMessage, port, 1); - } else if (global.addEventListener && typeof postMessage == "function" && !global.importScripts) { + } else if (global2.addEventListener && typeof postMessage == "function" && !global2.importScripts) { defer = function(id) { - global.postMessage(id + "", "*"); + global2.postMessage(id + "", "*"); }; - global.addEventListener("message", listener, false); + global2.addEventListener("message", listener, false); } else if (ONREADYSTATECHANGE in cel("script")) { defer = function(id) { html.appendChild(cel("script"))[ONREADYSTATECHANGE] = function() { @@ -7342,11 +25338,11 @@ ${indent}`); /* 191 */ /***/ function(module2, exports2, __webpack_require__) { - var global = __webpack_require__(11); + var global2 = __webpack_require__(11); var macrotask = __webpack_require__(109).set; - var Observer = global.MutationObserver || global.WebKitMutationObserver; - var process3 = global.process; - var Promise2 = global.Promise; + var Observer = global2.MutationObserver || global2.WebKitMutationObserver; + var process3 = global2.process; + var Promise2 = global2.Promise; var isNode = __webpack_require__(47)(process3) == "process"; module2.exports = function() { var head, last, notify; @@ -7375,7 +25371,7 @@ ${indent}`); notify = function() { process3.nextTick(flush); }; - } else if (Observer && !(global.navigator && global.navigator.standalone)) { + } else if (Observer && !(global2.navigator && global2.navigator.standalone)) { var toggle = true; var node = document.createTextNode(""); new Observer(flush).observe(node, { characterData: true }); @@ -7389,7 +25385,7 @@ ${indent}`); }; } else { notify = function() { - macrotask.call(global, flush); + macrotask.call(global2, flush); }; } return function(fn) { @@ -7523,13 +25519,13 @@ ${indent}`); /***/ function(module2, exports2, __webpack_require__) { "use strict"; - var global = __webpack_require__(11); + var global2 = __webpack_require__(11); var core = __webpack_require__(23); var dP = __webpack_require__(50); var DESCRIPTORS = __webpack_require__(33); var SPECIES = __webpack_require__(13)("species"); module2.exports = function(KEY) { - var C = typeof core[KEY] == "function" ? core[KEY] : global[KEY]; + var C = typeof core[KEY] == "function" ? core[KEY] : global2[KEY]; if (DESCRIPTORS && C && !C[SPECIES]) dP.f(C, SPECIES, { configurable: true, @@ -7588,8 +25584,8 @@ ${indent}`); /* 202 */ /***/ function(module2, exports2, __webpack_require__) { - var global = __webpack_require__(11); - var navigator2 = global.navigator; + var global2 = __webpack_require__(11); + var navigator2 = global2.navigator; module2.exports = navigator2 && navigator2.userAgent || ""; }, /* 203 */ @@ -7643,7 +25639,7 @@ ${indent}`); function(module2, exports2, __webpack_require__) { "use strict"; var LIBRARY = __webpack_require__(69); - var global = __webpack_require__(11); + var global2 = __webpack_require__(11); var ctx = __webpack_require__(48); var classof = __webpack_require__(100); var $export = __webpack_require__(41); @@ -7659,11 +25655,11 @@ ${indent}`); var userAgent3 = __webpack_require__(202); var promiseResolve = __webpack_require__(105); var PROMISE = "Promise"; - var TypeError2 = global.TypeError; - var process3 = global.process; + var TypeError2 = global2.TypeError; + var process3 = global2.process; var versions = process3 && process3.versions; var v8 = versions && versions.v8 || ""; - var $Promise = global[PROMISE]; + var $Promise = global2[PROMISE]; var isNode = classof(process3) == "process"; var empty = function() { }; @@ -7739,7 +25735,7 @@ ${indent}`); }); }; var onUnhandled = function(promise) { - task.call(global, function() { + task.call(global2, function() { var value = promise._v; var unhandled = isUnhandled(promise); var result, handler3, console2; @@ -7747,9 +25743,9 @@ ${indent}`); result = perform(function() { if (isNode) { process3.emit("unhandledRejection", value, promise); - } else if (handler3 = global.onunhandledrejection) { + } else if (handler3 = global2.onunhandledrejection) { handler3({ promise, reason: value }); - } else if ((console2 = global.console) && console2.error) { + } else if ((console2 = global2.console) && console2.error) { console2.error("Unhandled promise rejection", value); } }); @@ -7764,11 +25760,11 @@ ${indent}`); return promise._h !== 1 && (promise._a || promise._c).length === 0; }; var onHandleUnhandled = function(promise) { - task.call(global, function() { + task.call(global2, function() { var handler3; if (isNode) { process3.emit("rejectionHandled", promise); - } else if (handler3 = global.onrejectionhandled) { + } else if (handler3 = global2.onrejectionhandled) { handler3({ promise, reason: promise._v }); } }); @@ -7954,11 +25950,11 @@ ${indent}`); "use strict"; var $export = __webpack_require__(41); var core = __webpack_require__(23); - var global = __webpack_require__(11); + var global2 = __webpack_require__(11); var speciesConstructor = __webpack_require__(108); var promiseResolve = __webpack_require__(105); $export($export.P + $export.R, "Promise", { "finally": function(onFinally) { - var C = speciesConstructor(this, core.Promise || global.Promise); + var C = speciesConstructor(this, core.Promise || global2.Promise); var isFunction2 = typeof onFinally == "function"; return this.then( isFunction2 ? function(x) { @@ -7992,14 +25988,14 @@ ${indent}`); /***/ function(module2, exports2, __webpack_require__) { __webpack_require__(204); - var global = __webpack_require__(11); + var global2 = __webpack_require__(11); var hide = __webpack_require__(31); var Iterators = __webpack_require__(35); var TO_STRING_TAG = __webpack_require__(13)("toStringTag"); var DOMIterables = "CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split(","); for (var i = 0; i < DOMIterables.length; i++) { var NAME = DOMIterables[i]; - var Collection3 = global[NAME]; + var Collection3 = global2[NAME]; var proto = Collection3 && Collection3.prototype; if (proto && !proto[TO_STRING_TAG]) hide(proto, TO_STRING_TAG, NAME); @@ -9534,7 +27530,7 @@ function escapeProperty(s) { // var tunnel = __toESM(require_tunnel2()); -import { ProxyAgent } from "undici"; +var import_undici = __toESM(require_undici()); var HttpCodes; (function(HttpCodes2) { HttpCodes2[HttpCodes2["OK"] = 200] = "OK"; @@ -9967,7 +27963,7 @@ var Context = class { // var httpClient = __toESM(require_lib()); -import { fetch as fetch2 } from "undici"; +var import_undici2 = __toESM(require_undici()); var __awaiter2 = function(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function(resolve5) { @@ -10006,7 +28002,7 @@ function getProxyAgentDispatcher(destinationUrl) { function getProxyFetch(destinationUrl) { const httpDispatcher = getProxyAgentDispatcher(destinationUrl); const proxyFetch = (url, opts) => __awaiter2(this, void 0, void 0, function* () { - return fetch2(url, Object.assign(Object.assign({}, opts), { dispatcher: httpDispatcher })); + return (0, import_undici2.fetch)(url, Object.assign(Object.assign({}, opts), { dispatcher: httpDispatcher })); }); return proxyFetch; } @@ -10271,7 +28267,7 @@ function isKeyOperator(operator) { function getValues(context3, operator, key, modifier) { var value = context3[key], result = []; if (isDefined(value) && value !== "") { - if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") { value = value.toString(); if (modifier && modifier !== "*") { value = value.substring(0, parseInt(modifier, 10)); @@ -10452,6 +28448,125 @@ var endpoint = withDefaults(null, DEFAULTS); // var import_fast_content_type_parse = __toESM(require_fast_content_type_parse()); +// +var intRegex = /^-?\d+$/; +var noiseValue = /^-?\d+n+$/; +var originalStringify = JSON.stringify; +var originalParse = JSON.parse; +var customFormat = /^-?\d+n$/; +var bigIntsStringify = /([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g; +var noiseStringify = /([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g; +var JSONStringify = (value, replacer, space) => { + if ("rawJSON" in JSON) { + return originalStringify( + value, + (key, value2) => { + if (typeof value2 === "bigint") + return JSON.rawJSON(value2.toString()); + if (typeof replacer === "function") + return replacer(key, value2); + if (Array.isArray(replacer) && replacer.includes(key)) + return value2; + return value2; + }, + space + ); + } + if (!value) + return originalStringify(value, replacer, space); + const convertedToCustomJSON = originalStringify( + value, + (key, value2) => { + const isNoise = typeof value2 === "string" && noiseValue.test(value2); + if (isNoise) + return value2.toString() + "n"; + if (typeof value2 === "bigint") + return value2.toString() + "n"; + if (typeof replacer === "function") + return replacer(key, value2); + if (Array.isArray(replacer) && replacer.includes(key)) + return value2; + return value2; + }, + space + ); + const processedJSON = convertedToCustomJSON.replace( + bigIntsStringify, + "$1$2$3" + ); + const denoisedJSON = processedJSON.replace(noiseStringify, "$1$2$3"); + return denoisedJSON; +}; +var featureCache = /* @__PURE__ */ new Map(); +var isContextSourceSupported = () => { + const parseFingerprint = JSON.parse.toString(); + if (featureCache.has(parseFingerprint)) { + return featureCache.get(parseFingerprint); + } + try { + const result = JSON.parse( + "1", + (_, __, context3) => !!context3?.source && context3.source === "1" + ); + featureCache.set(parseFingerprint, result); + return result; + } catch { + featureCache.set(parseFingerprint, false); + return false; + } +}; +var convertMarkedBigIntsReviver = (key, value, context3, userReviver) => { + const isCustomFormatBigInt = typeof value === "string" && customFormat.test(value); + if (isCustomFormatBigInt) + return BigInt(value.slice(0, -1)); + const isNoiseValue = typeof value === "string" && noiseValue.test(value); + if (isNoiseValue) + return value.slice(0, -1); + if (typeof userReviver !== "function") + return value; + return userReviver(key, value, context3); +}; +var JSONParseV2 = (text, reviver) => { + return JSON.parse(text, (key, value, context3) => { + const isBigNumber = typeof value === "number" && (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER); + const isInt = context3 && intRegex.test(context3.source); + const isBigInt = isBigNumber && isInt; + if (isBigInt) + return BigInt(context3.source); + if (typeof reviver !== "function") + return value; + return reviver(key, value, context3); + }); +}; +var MAX_INT = Number.MAX_SAFE_INTEGER.toString(); +var MAX_DIGITS = MAX_INT.length; +var stringsOrLargeNumbers = /"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g; +var noiseValueWithQuotes = /^"-?\d+n+"$/; +var JSONParse = (text, reviver) => { + if (!text) + return originalParse(text, reviver); + if (isContextSourceSupported()) + return JSONParseV2(text, reviver); + const serializedData = text.replace( + stringsOrLargeNumbers, + (text2, digits, fractional, exponential) => { + const isString = text2[0] === '"'; + const isNoise = isString && noiseValueWithQuotes.test(text2); + if (isNoise) + return text2.substring(0, text2.length - 1) + 'n"'; + const isFractionalOrExponential = fractional || exponential; + const isLessThanMaxSafeInt = digits && (digits.length < MAX_DIGITS || digits.length === MAX_DIGITS && digits <= MAX_INT); + if (isString || isFractionalOrExponential || isLessThanMaxSafeInt) + return text2; + return '"' + text2 + 'n"'; + } + ); + return originalParse( + serializedData, + (key, value, context3) => convertMarkedBigIntsReviver(key, value, context3, reviver) + ); +}; + // var RequestError = class extends Error { name; @@ -10492,7 +28607,7 @@ var RequestError = class extends Error { }; // -var VERSION2 = "10.0.7"; +var VERSION2 = "10.0.8"; var defaults_default = { headers: { "user-agent": `octokit-request.js/${VERSION2} ${getUserAgent()}` @@ -10519,7 +28634,7 @@ async function fetchWrapper(requestOptions) { } const log = requestOptions.request?.log || console; const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false; - const body = isPlainObject2(requestOptions.body) || Array.isArray(requestOptions.body) ? JSON.stringify(requestOptions.body) : requestOptions.body; + const body = isPlainObject2(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify(requestOptions.body) : requestOptions.body; const requestHeaders = Object.fromEntries( Object.entries(requestOptions.headers).map(([name, value]) => [ name, @@ -10618,7 +28733,7 @@ async function getResponseData(response) { let text = ""; try { text = await response.text(); - return JSON.parse(text); + return JSONParse(text); } catch (err) { return text; } @@ -13533,20 +31648,22 @@ var context2 = new Context(); // .github/actions/deploy-docs-site/lib/deploy.mjs import { mkdtemp, readFile, rm as rm2, writeFile as writeFile2 } from "node:fs/promises"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; +import { join as join2 } from "node:path"; +import { tmpdir as tmpdir2 } from "node:os"; import { spawnSync } from "node:child_process"; // .github/actions/deploy-docs-site/lib/credential.mjs -var import_tmp = __toESM(require_tmp(), 1); -import { writeSync } from "node:fs"; +import { writeFileSync, mkdtempSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; var credentialFilePath; function getCredentialFilePath() { if (credentialFilePath === void 0) { - const tmpFile = (0, import_tmp.fileSync)({ postfix: ".json" }); - writeSync(tmpFile.fd, getInput("serviceKey", { required: true })); - setSecret(tmpFile.name); - credentialFilePath = tmpFile.name; + const tmpDir = mkdtempSync(join(tmpdir(), "credential-")); + const filePath = join(tmpDir, "credential.json"); + writeFileSync(filePath, getInput("serviceKey", { required: true })); + setSecret(filePath); + credentialFilePath = filePath; } return credentialFilePath; } @@ -13561,7 +31678,7 @@ async function deployToFirebase(deployment, configPath, stagingDir) { return; } console.log("Preparing for deployment to firebase..."); - const deployConfigPath = join(stagingDir, "firebase.json"); + const deployConfigPath = join2(stagingDir, "firebase.json"); const config = JSON.parse(await readFile(configPath, { encoding: "utf-8" })); config["hosting"]["public"] = "./browser"; await writeFile2(deployConfigPath, JSON.stringify(config, null, 2)); @@ -13589,8 +31706,8 @@ async function setupRedirect(deployment) { ] } }, null, 2); - const tmpRedirectDir = await mkdtemp(join(tmpdir(), "redirect-directory")); - const redirectConfigPath = join(tmpRedirectDir, "firebase.json"); + const tmpRedirectDir = await mkdtemp(join2(tmpdir2(), "redirect-directory")); + const redirectConfigPath = join2(tmpRedirectDir, "firebase.json"); await writeFile2(redirectConfigPath, redirectConfig); spawnSync(`chmod 777 -R ${tmpRedirectDir}`, { encoding: "utf-8", shell: true }); firebase(`target:clear --config ${redirectConfigPath} --project angular-dev-site hosting angular-docs`, tmpRedirectDir); @@ -13834,7 +31951,7 @@ import { format } from "util"; import { normalize, resolve as resolve2 } from "path"; import { readFileSync as readFileSync2 } from "fs"; import { createRequire } from "node:module"; -import { basename, dirname as dirname2, extname, relative, resolve as resolve4, join as join2 } from "path"; +import { basename, dirname as dirname2, extname, relative, resolve as resolve4, join as join3 } from "path"; import { readFileSync as readFileSync22, statSync as statSync2, writeFile as writeFile3 } from "fs"; import { format as format2 } from "util"; import { resolve as resolve3 } from "path"; @@ -13844,7 +31961,7 @@ import { styleText } from "util"; import { spawn as _spawn, spawnSync as _spawnSync, exec as _exec } from "child_process"; import assert from "assert"; import { stripVTControlCharacters } from "util"; -import { join as join3 } from "path"; +import { join as join32 } from "path"; import { pathToFileURL } from "url"; var require4 = __cjsCompatRequire_ngDev3(import.meta.url); var require_get_caller_file = __commonJS2({ @@ -14135,17 +32252,75 @@ function stripAnsi(string) { if (typeof string !== "string") { throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``); } + if (!string.includes("\x1B") && !string.includes("\x9B")) { + return string; + } return string.replace(regex, ""); } -function isAmbiguous(x) { - return x === 161 || x === 164 || x === 167 || x === 168 || x === 170 || x === 173 || x === 174 || x >= 176 && x <= 180 || x >= 182 && x <= 186 || x >= 188 && x <= 191 || x === 198 || x === 208 || x === 215 || x === 216 || x >= 222 && x <= 225 || x === 230 || x >= 232 && x <= 234 || x === 236 || x === 237 || x === 240 || x === 242 || x === 243 || x >= 247 && x <= 250 || x === 252 || x === 254 || x === 257 || x === 273 || x === 275 || x === 283 || x === 294 || x === 295 || x === 299 || x >= 305 && x <= 307 || x === 312 || x >= 319 && x <= 322 || x === 324 || x >= 328 && x <= 331 || x === 333 || x === 338 || x === 339 || x === 358 || x === 359 || x === 363 || x === 462 || x === 464 || x === 466 || x === 468 || x === 470 || x === 472 || x === 474 || x === 476 || x === 593 || x === 609 || x === 708 || x === 711 || x >= 713 && x <= 715 || x === 717 || x === 720 || x >= 728 && x <= 731 || x === 733 || x === 735 || x >= 768 && x <= 879 || x >= 913 && x <= 929 || x >= 931 && x <= 937 || x >= 945 && x <= 961 || x >= 963 && x <= 969 || x === 1025 || x >= 1040 && x <= 1103 || x === 1105 || x === 8208 || x >= 8211 && x <= 8214 || x === 8216 || x === 8217 || x === 8220 || x === 8221 || x >= 8224 && x <= 8226 || x >= 8228 && x <= 8231 || x === 8240 || x === 8242 || x === 8243 || x === 8245 || x === 8251 || x === 8254 || x === 8308 || x === 8319 || x >= 8321 && x <= 8324 || x === 8364 || x === 8451 || x === 8453 || x === 8457 || x === 8467 || x === 8470 || x === 8481 || x === 8482 || x === 8486 || x === 8491 || x === 8531 || x === 8532 || x >= 8539 && x <= 8542 || x >= 8544 && x <= 8555 || x >= 8560 && x <= 8569 || x === 8585 || x >= 8592 && x <= 8601 || x === 8632 || x === 8633 || x === 8658 || x === 8660 || x === 8679 || x === 8704 || x === 8706 || x === 8707 || x === 8711 || x === 8712 || x === 8715 || x === 8719 || x === 8721 || x === 8725 || x === 8730 || x >= 8733 && x <= 8736 || x === 8739 || x === 8741 || x >= 8743 && x <= 8748 || x === 8750 || x >= 8756 && x <= 8759 || x === 8764 || x === 8765 || x === 8776 || x === 8780 || x === 8786 || x === 8800 || x === 8801 || x >= 8804 && x <= 8807 || x === 8810 || x === 8811 || x === 8814 || x === 8815 || x === 8834 || x === 8835 || x === 8838 || x === 8839 || x === 8853 || x === 8857 || x === 8869 || x === 8895 || x === 8978 || x >= 9312 && x <= 9449 || x >= 9451 && x <= 9547 || x >= 9552 && x <= 9587 || x >= 9600 && x <= 9615 || x >= 9618 && x <= 9621 || x === 9632 || x === 9633 || x >= 9635 && x <= 9641 || x === 9650 || x === 9651 || x === 9654 || x === 9655 || x === 9660 || x === 9661 || x === 9664 || x === 9665 || x >= 9670 && x <= 9672 || x === 9675 || x >= 9678 && x <= 9681 || x >= 9698 && x <= 9701 || x === 9711 || x === 9733 || x === 9734 || x === 9737 || x === 9742 || x === 9743 || x === 9756 || x === 9758 || x === 9792 || x === 9794 || x === 9824 || x === 9825 || x >= 9827 && x <= 9829 || x >= 9831 && x <= 9834 || x === 9836 || x === 9837 || x === 9839 || x === 9886 || x === 9887 || x === 9919 || x >= 9926 && x <= 9933 || x >= 9935 && x <= 9939 || x >= 9941 && x <= 9953 || x === 9955 || x === 9960 || x === 9961 || x >= 9963 && x <= 9969 || x === 9972 || x >= 9974 && x <= 9977 || x === 9979 || x === 9980 || x === 9982 || x === 9983 || x === 10045 || x >= 10102 && x <= 10111 || x >= 11094 && x <= 11097 || x >= 12872 && x <= 12879 || x >= 57344 && x <= 63743 || x >= 65024 && x <= 65039 || x === 65533 || x >= 127232 && x <= 127242 || x >= 127248 && x <= 127277 || x >= 127280 && x <= 127337 || x >= 127344 && x <= 127373 || x === 127375 || x === 127376 || x >= 127387 && x <= 127404 || x >= 917760 && x <= 917999 || x >= 983040 && x <= 1048573 || x >= 1048576 && x <= 1114109; -} -function isFullWidth(x) { - return x === 12288 || x >= 65281 && x <= 65376 || x >= 65504 && x <= 65510; -} -function isWide(x) { - return x >= 4352 && x <= 4447 || x === 8986 || x === 8987 || x === 9001 || x === 9002 || x >= 9193 && x <= 9196 || x === 9200 || x === 9203 || x === 9725 || x === 9726 || x === 9748 || x === 9749 || x >= 9776 && x <= 9783 || x >= 9800 && x <= 9811 || x === 9855 || x >= 9866 && x <= 9871 || x === 9875 || x === 9889 || x === 9898 || x === 9899 || x === 9917 || x === 9918 || x === 9924 || x === 9925 || x === 9934 || x === 9940 || x === 9962 || x === 9970 || x === 9971 || x === 9973 || x === 9978 || x === 9981 || x === 9989 || x === 9994 || x === 9995 || x === 10024 || x === 10060 || x === 10062 || x >= 10067 && x <= 10069 || x === 10071 || x >= 10133 && x <= 10135 || x === 10160 || x === 10175 || x === 11035 || x === 11036 || x === 11088 || x === 11093 || x >= 11904 && x <= 11929 || x >= 11931 && x <= 12019 || x >= 12032 && x <= 12245 || x >= 12272 && x <= 12287 || x >= 12289 && x <= 12350 || x >= 12353 && x <= 12438 || x >= 12441 && x <= 12543 || x >= 12549 && x <= 12591 || x >= 12593 && x <= 12686 || x >= 12688 && x <= 12773 || x >= 12783 && x <= 12830 || x >= 12832 && x <= 12871 || x >= 12880 && x <= 42124 || x >= 42128 && x <= 42182 || x >= 43360 && x <= 43388 || x >= 44032 && x <= 55203 || x >= 63744 && x <= 64255 || x >= 65040 && x <= 65049 || x >= 65072 && x <= 65106 || x >= 65108 && x <= 65126 || x >= 65128 && x <= 65131 || x >= 94176 && x <= 94180 || x >= 94192 && x <= 94198 || x >= 94208 && x <= 101589 || x >= 101631 && x <= 101662 || x >= 101760 && x <= 101874 || x >= 110576 && x <= 110579 || x >= 110581 && x <= 110587 || x === 110589 || x === 110590 || x >= 110592 && x <= 110882 || x === 110898 || x >= 110928 && x <= 110930 || x === 110933 || x >= 110948 && x <= 110951 || x >= 110960 && x <= 111355 || x >= 119552 && x <= 119638 || x >= 119648 && x <= 119670 || x === 126980 || x === 127183 || x === 127374 || x >= 127377 && x <= 127386 || x >= 127488 && x <= 127490 || x >= 127504 && x <= 127547 || x >= 127552 && x <= 127560 || x === 127568 || x === 127569 || x >= 127584 && x <= 127589 || x >= 127744 && x <= 127776 || x >= 127789 && x <= 127797 || x >= 127799 && x <= 127868 || x >= 127870 && x <= 127891 || x >= 127904 && x <= 127946 || x >= 127951 && x <= 127955 || x >= 127968 && x <= 127984 || x === 127988 || x >= 127992 && x <= 128062 || x === 128064 || x >= 128066 && x <= 128252 || x >= 128255 && x <= 128317 || x >= 128331 && x <= 128334 || x >= 128336 && x <= 128359 || x === 128378 || x === 128405 || x === 128406 || x === 128420 || x >= 128507 && x <= 128591 || x >= 128640 && x <= 128709 || x === 128716 || x >= 128720 && x <= 128722 || x >= 128725 && x <= 128728 || x >= 128732 && x <= 128735 || x === 128747 || x === 128748 || x >= 128756 && x <= 128764 || x >= 128992 && x <= 129003 || x === 129008 || x >= 129292 && x <= 129338 || x >= 129340 && x <= 129349 || x >= 129351 && x <= 129535 || x >= 129648 && x <= 129660 || x >= 129664 && x <= 129674 || x >= 129678 && x <= 129734 || x === 129736 || x >= 129741 && x <= 129756 || x >= 129759 && x <= 129770 || x >= 129775 && x <= 129784 || x >= 131072 && x <= 196605 || x >= 196608 && x <= 262141; +var ambiguousMinimalCodePoint = 161; +var ambiguousMaximumCodePoint = 1114109; +var ambiguousRanges = [161, 161, 164, 164, 167, 168, 170, 170, 173, 174, 176, 180, 182, 186, 188, 191, 198, 198, 208, 208, 215, 216, 222, 225, 230, 230, 232, 234, 236, 237, 240, 240, 242, 243, 247, 250, 252, 252, 254, 254, 257, 257, 273, 273, 275, 275, 283, 283, 294, 295, 299, 299, 305, 307, 312, 312, 319, 322, 324, 324, 328, 331, 333, 333, 338, 339, 358, 359, 363, 363, 462, 462, 464, 464, 466, 466, 468, 468, 470, 470, 472, 472, 474, 474, 476, 476, 593, 593, 609, 609, 708, 708, 711, 711, 713, 715, 717, 717, 720, 720, 728, 731, 733, 733, 735, 735, 768, 879, 913, 929, 931, 937, 945, 961, 963, 969, 1025, 1025, 1040, 1103, 1105, 1105, 8208, 8208, 8211, 8214, 8216, 8217, 8220, 8221, 8224, 8226, 8228, 8231, 8240, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8254, 8254, 8308, 8308, 8319, 8319, 8321, 8324, 8364, 8364, 8451, 8451, 8453, 8453, 8457, 8457, 8467, 8467, 8470, 8470, 8481, 8482, 8486, 8486, 8491, 8491, 8531, 8532, 8539, 8542, 8544, 8555, 8560, 8569, 8585, 8585, 8592, 8601, 8632, 8633, 8658, 8658, 8660, 8660, 8679, 8679, 8704, 8704, 8706, 8707, 8711, 8712, 8715, 8715, 8719, 8719, 8721, 8721, 8725, 8725, 8730, 8730, 8733, 8736, 8739, 8739, 8741, 8741, 8743, 8748, 8750, 8750, 8756, 8759, 8764, 8765, 8776, 8776, 8780, 8780, 8786, 8786, 8800, 8801, 8804, 8807, 8810, 8811, 8814, 8815, 8834, 8835, 8838, 8839, 8853, 8853, 8857, 8857, 8869, 8869, 8895, 8895, 8978, 8978, 9312, 9449, 9451, 9547, 9552, 9587, 9600, 9615, 9618, 9621, 9632, 9633, 9635, 9641, 9650, 9651, 9654, 9655, 9660, 9661, 9664, 9665, 9670, 9672, 9675, 9675, 9678, 9681, 9698, 9701, 9711, 9711, 9733, 9734, 9737, 9737, 9742, 9743, 9756, 9756, 9758, 9758, 9792, 9792, 9794, 9794, 9824, 9825, 9827, 9829, 9831, 9834, 9836, 9837, 9839, 9839, 9886, 9887, 9919, 9919, 9926, 9933, 9935, 9939, 9941, 9953, 9955, 9955, 9960, 9961, 9963, 9969, 9972, 9972, 9974, 9977, 9979, 9980, 9982, 9983, 10045, 10045, 10102, 10111, 11094, 11097, 12872, 12879, 57344, 63743, 65024, 65039, 65533, 65533, 127232, 127242, 127248, 127277, 127280, 127337, 127344, 127373, 127375, 127376, 127387, 127404, 917760, 917999, 983040, 1048573, 1048576, 1114109]; +var fullwidthMinimalCodePoint = 12288; +var fullwidthMaximumCodePoint = 65510; +var fullwidthRanges = [12288, 12288, 65281, 65376, 65504, 65510]; +var wideMinimalCodePoint = 4352; +var wideMaximumCodePoint = 262141; +var wideRanges = [4352, 4447, 8986, 8987, 9001, 9002, 9193, 9196, 9200, 9200, 9203, 9203, 9725, 9726, 9748, 9749, 9776, 9783, 9800, 9811, 9855, 9855, 9866, 9871, 9875, 9875, 9889, 9889, 9898, 9899, 9917, 9918, 9924, 9925, 9934, 9934, 9940, 9940, 9962, 9962, 9970, 9971, 9973, 9973, 9978, 9978, 9981, 9981, 9989, 9989, 9994, 9995, 10024, 10024, 10060, 10060, 10062, 10062, 10067, 10069, 10071, 10071, 10133, 10135, 10160, 10160, 10175, 10175, 11035, 11036, 11088, 11088, 11093, 11093, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12287, 12289, 12350, 12353, 12438, 12441, 12543, 12549, 12591, 12593, 12686, 12688, 12773, 12783, 12830, 12832, 12871, 12880, 42124, 42128, 42182, 43360, 43388, 44032, 55203, 63744, 64255, 65040, 65049, 65072, 65106, 65108, 65126, 65128, 65131, 94176, 94180, 94192, 94198, 94208, 101589, 101631, 101662, 101760, 101874, 110576, 110579, 110581, 110587, 110589, 110590, 110592, 110882, 110898, 110898, 110928, 110930, 110933, 110933, 110948, 110951, 110960, 111355, 119552, 119638, 119648, 119670, 126980, 126980, 127183, 127183, 127374, 127374, 127377, 127386, 127488, 127490, 127504, 127547, 127552, 127560, 127568, 127569, 127584, 127589, 127744, 127776, 127789, 127797, 127799, 127868, 127870, 127891, 127904, 127946, 127951, 127955, 127968, 127984, 127988, 127988, 127992, 128062, 128064, 128064, 128066, 128252, 128255, 128317, 128331, 128334, 128336, 128359, 128378, 128378, 128405, 128406, 128420, 128420, 128507, 128591, 128640, 128709, 128716, 128716, 128720, 128722, 128725, 128728, 128732, 128735, 128747, 128748, 128756, 128764, 128992, 129003, 129008, 129008, 129292, 129338, 129340, 129349, 129351, 129535, 129648, 129660, 129664, 129674, 129678, 129734, 129736, 129736, 129741, 129756, 129759, 129770, 129775, 129784, 131072, 196605, 196608, 262141]; +var isInRange = (ranges, codePoint) => { + let low = 0; + let high = Math.floor(ranges.length / 2) - 1; + while (low <= high) { + const mid = Math.floor((low + high) / 2); + const i = mid * 2; + if (codePoint < ranges[i]) { + high = mid - 1; + } else if (codePoint > ranges[i + 1]) { + low = mid + 1; + } else { + return true; + } + } + return false; +}; +var commonCjkCodePoint = 19968; +var [wideFastPathStart, wideFastPathEnd] = findWideFastPathRange(wideRanges); +function findWideFastPathRange(ranges) { + let fastPathStart = ranges[0]; + let fastPathEnd = ranges[1]; + for (let index = 0; index < ranges.length; index += 2) { + const start = ranges[index]; + const end = ranges[index + 1]; + if (commonCjkCodePoint >= start && commonCjkCodePoint <= end) { + return [start, end]; + } + if (end - start > fastPathEnd - fastPathStart) { + fastPathStart = start; + fastPathEnd = end; + } + } + return [fastPathStart, fastPathEnd]; } +var isAmbiguous = (codePoint) => { + if (codePoint < ambiguousMinimalCodePoint || codePoint > ambiguousMaximumCodePoint) { + return false; + } + return isInRange(ambiguousRanges, codePoint); +}; +var isFullWidth = (codePoint) => { + if (codePoint < fullwidthMinimalCodePoint || codePoint > fullwidthMaximumCodePoint) { + return false; + } + return isInRange(fullwidthRanges, codePoint); +}; +var isWide = (codePoint) => { + if (codePoint >= wideFastPathStart && codePoint <= wideFastPathEnd) { + return true; + } + if (codePoint < wideMinimalCodePoint || codePoint > wideMaximumCodePoint) { + return false; + } + return isInRange(wideRanges, codePoint); +}; function validate(codePoint) { if (!Number.isSafeInteger(codePoint)) { throw new TypeError(`Expected a code point, got \`${typeof codePoint}\`.`); @@ -15751,7 +33926,7 @@ var esm_default = { extname, relative, resolve: resolve4, - join: join2 + join: join3 }, process: { argv: () => process.argv, @@ -15891,8 +34066,8 @@ var GlobalMiddleware = class { this.frozens = []; this.yargs = yargs; } - addMiddleware(callback, applyBeforeValidation, global = true, mutates = false) { - argsert(" [boolean] [boolean] [boolean]", [callback, applyBeforeValidation, global], arguments.length); + addMiddleware(callback, applyBeforeValidation, global2 = true, mutates = false) { + argsert(" [boolean] [boolean] [boolean]", [callback, applyBeforeValidation, global2], arguments.length); if (Array.isArray(callback)) { for (let i = 0; i < callback.length; i++) { if (typeof callback[i] !== "function") { @@ -15900,13 +34075,13 @@ var GlobalMiddleware = class { } const m = callback[i]; m.applyBeforeValidation = applyBeforeValidation; - m.global = global; + m.global = global2; } Array.prototype.push.apply(this.globalMiddleware, callback); } else if (typeof callback === "function") { const m = callback; m.applyBeforeValidation = applyBeforeValidation; - m.global = global; + m.global = global2; m.mutates = mutates; this.globalMiddleware.push(callback); } @@ -17759,8 +35934,8 @@ var YargsInstance = class { this[kTrackManuallySetKeys](keys); return this; } - check(f, global) { - argsert(" [boolean]", [f, global], arguments.length); + check(f, global2) { + argsert(" [boolean]", [f, global2], arguments.length); this.middleware((argv, _yargs) => { return maybeAsyncResult(() => { return f(argv, _yargs.getOptions()); @@ -17775,7 +35950,7 @@ var YargsInstance = class { __classPrivateFieldGet(this, _YargsInstance_usage, "f").fail(err.message ? err.message : err.toString(), err); return argv; }); - }, false, global); + }, false, global2); return this; } choices(key, value) { @@ -18078,10 +36253,10 @@ var YargsInstance = class { getStrictOptions() { return __classPrivateFieldGet(this, _YargsInstance_strictOptions, "f"); } - global(globals, global) { - argsert(" [boolean]", [globals, global], arguments.length); + global(globals, global2) { + argsert(" [boolean]", [globals, global2], arguments.length); globals = [].concat(globals); - if (global !== false) { + if (global2 !== false) { __classPrivateFieldGet(this, _YargsInstance_options, "f").local = __classPrivateFieldGet(this, _YargsInstance_options, "f").local.filter((l) => globals.indexOf(l) === -1); } else { globals.forEach((g) => { @@ -18125,8 +36300,8 @@ var YargsInstance = class { __classPrivateFieldGet(this, _YargsInstance_shim, "f").y18n.setLocale(locale); return this; } - middleware(callback, applyBeforeValidation, global) { - return __classPrivateFieldGet(this, _YargsInstance_globalMiddleware, "f").addMiddleware(callback, !!applyBeforeValidation, global); + middleware(callback, applyBeforeValidation, global2) { + return __classPrivateFieldGet(this, _YargsInstance_globalMiddleware, "f").addMiddleware(callback, !!applyBeforeValidation, global2); } nargs(key, value) { argsert(" [number]", [key, value], arguments.length); @@ -19065,7 +37240,7 @@ var ChildProcess = class { return new Promise((resolve5, reject) => { const commandText = `${command2} ${args.join(" ")}`; Log.debug(`Executing command: ${commandText}`); - const childProcess = _spawn(command2, args, { ...options, shell: true, stdio: "inherit" }); + const childProcess = _spawn(command2, args, { ...options, stdio: "inherit" }); childProcess.on("close", (status) => status === 0 ? resolve5() : reject(status)); }); } @@ -19073,7 +37248,7 @@ var ChildProcess = class { const commandText = `${command2} ${args.join(" ")}`; const env22 = getEnvironmentForNonInteractiveCommand(options.env); Log.debug(`Executing command: ${commandText}`); - const { status: exitCode, signal, stdout, stderr } = _spawnSync(command2, args, { ...options, env: env22, encoding: "utf8", shell: true, stdio: "pipe" }); + const { status: exitCode, signal, stdout, stderr } = _spawnSync(command2, args, { ...options, env: env22, encoding: "utf8", stdio: "pipe" }); const status = statusFromExitCodeAndSignal(exitCode, signal); if (status === 0 || options.suppressErrorOnFailingExitCode) { return { status, stdout, stderr }; @@ -19083,7 +37258,7 @@ var ChildProcess = class { static spawn(command2, args, options = {}) { const commandText = `${command2} ${args.join(" ")}`; const env22 = getEnvironmentForNonInteractiveCommand(options.env); - return processAsyncCmd(commandText, options, _spawn(command2, args, { ...options, env: env22, shell: true, stdio: "pipe" })); + return processAsyncCmd(commandText, options, _spawn(command2, args, { ...options, env: env22, stdio: "pipe" })); } static exec(command2, options = {}) { const env22 = getEnvironmentForNonInteractiveCommand(options.env); @@ -19138,7 +37313,7 @@ ${logOutput}`); }); } function determineRepoBaseDirFromCwd() { - const { stdout, stderr, status } = ChildProcess.spawnSync("git", ["rev-parse --show-toplevel"]); + const { stdout, stderr, status } = ChildProcess.spawnSync("git", ["rev-parse", "--show-toplevel"]); if (status !== 0) { throw Error(`Unable to find the path to the base directory of the repository. Was the command run from inside of the repo? @@ -19219,7 +37394,7 @@ async function getConfig(baseDirOrAssertions) { } else { baseDir = determineRepoBaseDirFromCwd(); } - const configPath = join3(baseDir, CONFIG_FILE_PATH_MATCHER); + const configPath = join32(baseDir, CONFIG_FILE_PATH_MATCHER); cachedConfig2 = await readConfigFile(configPath); setCachedConfig(cachedConfig2); } @@ -19369,7 +37544,7 @@ var require_fast_content_type_parse2 = __commonJS2({ module.exports.defaultContentType = defaultContentType; } }); -var require_constants = __commonJS2({ +var require_constants6 = __commonJS2({ ""(exports, module) { "use strict"; var SEMVER_SPEC_VERSION = "2.0.0"; @@ -19414,7 +37589,7 @@ var require_re = __commonJS2({ MAX_SAFE_COMPONENT_LENGTH, MAX_SAFE_BUILD_LENGTH, MAX_LENGTH - } = require_constants(); + } = require_constants6(); var debug2 = require_debug(); exports = module.exports = {}; var re = exports.re = []; @@ -19537,7 +37712,7 @@ var require_semver = __commonJS2({ ""(exports, module) { "use strict"; var debug2 = require_debug(); - var { MAX_LENGTH, MAX_SAFE_INTEGER } = require_constants(); + var { MAX_LENGTH, MAX_SAFE_INTEGER } = require_constants6(); var { safeRe: re, t } = require_re(); var parseOptions = require_parse_options(); var { compareIdentifiers } = require_identifiers(); @@ -19806,7 +37981,7 @@ var require_semver = __commonJS2({ module.exports = SemVer; } }); -var require_parse = __commonJS2({ +var require_parse2 = __commonJS2({ ""(exports, module) { "use strict"; var SemVer = require_semver(); @@ -19829,7 +38004,7 @@ var require_parse = __commonJS2({ var require_valid = __commonJS2({ ""(exports, module) { "use strict"; - var parse22 = require_parse(); + var parse22 = require_parse2(); var valid = (version, options) => { const v = parse22(version, options); return v ? v.version : null; @@ -19840,7 +38015,7 @@ var require_valid = __commonJS2({ var require_clean = __commonJS2({ ""(exports, module) { "use strict"; - var parse22 = require_parse(); + var parse22 = require_parse2(); var clean = (version, options) => { const s = parse22(version.trim().replace(/^[=v]+/, ""), options); return s ? s.version : null; @@ -19873,7 +38048,7 @@ var require_inc = __commonJS2({ var require_diff = __commonJS2({ ""(exports, module) { "use strict"; - var parse22 = require_parse(); + var parse22 = require_parse2(); var diff = (version1, version2) => { const v1 = parse22(version1, null, true); const v2 = parse22(version2, null, true); @@ -19939,7 +38114,7 @@ var require_patch = __commonJS2({ var require_prerelease = __commonJS2({ ""(exports, module) { "use strict"; - var parse22 = require_parse(); + var parse22 = require_parse2(); var prerelease = (version, options) => { const parsed = parse22(version, options); return parsed && parsed.prerelease.length ? parsed.prerelease : null; @@ -20099,7 +38274,7 @@ var require_coerce = __commonJS2({ ""(exports, module) { "use strict"; var SemVer = require_semver(); - var parse22 = require_parse(); + var parse22 = require_parse2(); var { safeRe: re, t } = require_re(); var coerce = (version, options) => { if (version instanceof SemVer) { @@ -20331,7 +38506,7 @@ var require_range = __commonJS2({ tildeTrimReplace, caretTrimReplace } = require_re(); - var { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require_constants(); + var { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require_constants6(); var isNullSet = (c) => c.value === "<0.0.0-0"; var isAny = (c) => c.value === ""; var isSatisfiable = (comparators, options) => { @@ -21115,10 +39290,10 @@ var require_semver2 = __commonJS2({ ""(exports, module) { "use strict"; var internalRe = require_re(); - var constants3 = require_constants(); + var constants3 = require_constants6(); var SemVer = require_semver(); var identifiers = require_identifiers(); - var parse22 = require_parse(); + var parse22 = require_parse2(); var valid = require_valid(); var clean = require_clean(); var inc = require_inc(); @@ -22010,6 +40185,8 @@ var require_Alias = __commonJS2({ * instance of the `source` anchor before this node. */ resolve(doc, ctx) { + if (ctx?.maxAliasCount === 0) + throw new ReferenceError("Alias resolution is disabled"); let nodes; if (ctx?.aliasResolveCache) { nodes = ctx.aliasResolveCache; @@ -22795,6 +40972,7 @@ var require_stringify = __commonJS2({ nullStr: "null", simpleKeys: false, singleQuote: null, + trailingComma: false, trueStr: "true", verifyAliasOrder: true }, doc.schema.toStringOptions, options); @@ -23061,18 +41239,18 @@ var require_merge = __commonJS2({ }; var isMergeKey = (ctx, key) => (merge22.identify(key) || identity.isScalar(key) && (!key.type || key.type === Scalar.Scalar.PLAIN) && merge22.identify(key.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge22.tag && tag.default); function addMergeToJSMap(ctx, map, value) { - value = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value; - if (identity.isSeq(value)) - for (const it of value.items) + const source = resolveAliasValue(ctx, value); + if (identity.isSeq(source)) + for (const it of source.items) mergeValue(ctx, map, it); - else if (Array.isArray(value)) - for (const it of value) + else if (Array.isArray(source)) + for (const it of source) mergeValue(ctx, map, it); else - mergeValue(ctx, map, value); + mergeValue(ctx, map, source); } function mergeValue(ctx, map, value) { - const source = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value; + const source = resolveAliasValue(ctx, value); if (!identity.isMap(source)) throw new Error("Merge sources must be maps or map aliases"); const srcMap = source.toJSON(null, ctx, Map); @@ -23093,6 +41271,9 @@ var require_merge = __commonJS2({ } return map; } + function resolveAliasValue(ctx, value) { + return ctx && identity.isAlias(value) ? value.resolve(ctx.doc, ctx) : value; + } exports.addMergeToJSMap = addMergeToJSMap; exports.isMergeKey = isMergeKey; exports.merge = merge22; @@ -23300,12 +41481,19 @@ ${indent}${line}` : "\n"; if (comment) reqNewline = true; let str = stringify.stringify(item, itemCtx, () => comment = null); - if (i < items.length - 1) + reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n")); + if (i < items.length - 1) { str += ","; + } else if (ctx.options.trailingComma) { + if (ctx.options.lineWidth > 0) { + reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth); + } + if (reqNewline) { + str += ","; + } + } if (comment) str += stringifyComment.lineComment(str, itemIndent, commentString(comment)); - if (!reqNewline && (lines.length > linesAtValue || str.includes("\n"))) - reqNewline = true; lines.push(str); linesAtValue = lines.length; } @@ -23701,7 +41889,7 @@ var require_stringifyNumber = __commonJS2({ if (!isFinite(num)) return isNaN(num) ? ".nan" : num < 0 ? "-.inf" : ".inf"; let n = Object.is(value, -0) ? "-0" : JSON.stringify(value); - if (!format3 && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^\d/.test(n)) { + if (!format3 && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^-?\d/.test(n) && !n.includes("e")) { let i = n.indexOf("."); if (i < 0) { i = n.length; @@ -24986,7 +43174,7 @@ var require_Document = __commonJS2({ exports.Document = Document; } }); -var require_errors = __commonJS2({ +var require_errors2 = __commonJS2({ ""(exports) { "use strict"; var YAMLError = class extends Error { @@ -26011,7 +44199,7 @@ var require_resolve_flow_scalar = __commonJS2({ while (next === " " || next === " ") next = source[++i + 1]; } else if (next === "x" || next === "u" || next === "U") { - const length = { x: 2, u: 4, U: 8 }[next]; + const length = next === "x" ? 2 : next === "u" ? 4 : 8; res += parseCharCode(source, i + 1, length, onError); i += length; } else { @@ -26086,12 +44274,13 @@ var require_resolve_flow_scalar = __commonJS2({ const cc = source.substr(offset, length); const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc); const code = ok ? parseInt(cc, 16) : NaN; - if (isNaN(code)) { + try { + return String.fromCodePoint(code); + } catch { const raw = source.substr(offset - 2, length + 2); onError(offset - 2, "BAD_DQ_ESCAPE", `Invalid escape sequence ${raw}`); return raw; } - return String.fromCodePoint(code); } exports.resolveFlowScalar = resolveFlowScalar; } @@ -26235,17 +44424,22 @@ var require_compose_node = __commonJS2({ case "block-map": case "block-seq": case "flow-collection": - node = composeCollection.composeCollection(CN, ctx, token, props, onError); - if (anchor) - node.anchor = anchor.source.substring(1); + try { + node = composeCollection.composeCollection(CN, ctx, token, props, onError); + if (anchor) + node.anchor = anchor.source.substring(1); + } catch (error2) { + const message = error2 instanceof Error ? error2.message : String(error2); + onError(token, "RESOURCE_EXHAUSTION", message); + } break; default: { const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`; onError(token, "UNEXPECTED_TOKEN", message); - node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError); isSrcToken = false; } } + node ?? (node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError)); if (anchor && node.anchor === "") onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string"); if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) { @@ -26349,7 +44543,7 @@ var require_composer = __commonJS2({ var node_process = __require2("process"); var directives = require_directives(); var Document = require_Document(); - var errors = require_errors(); + var errors = require_errors2(); var identity = require_identity(); var composeDoc = require_compose_doc(); var resolveEnd = require_resolve_end(); @@ -26552,7 +44746,7 @@ var require_cst_scalar = __commonJS2({ "use strict"; var resolveBlockScalar = require_resolve_block_scalar(); var resolveFlowScalar = require_resolve_flow_scalar(); - var errors = require_errors(); + var errors = require_errors2(); var stringifyString = require_stringifyString(); function resolveAsScalar(token, strict = true, onError) { if (token) { @@ -28418,7 +46612,7 @@ var require_public_api = __commonJS2({ "use strict"; var composer = require_composer(); var Document = require_Document(); - var errors = require_errors(); + var errors = require_errors2(); var log = require_log(); var identity = require_identity(); var lineCounter = require_line_counter(); @@ -28514,7 +46708,7 @@ var require_dist = __commonJS2({ var composer = require_composer(); var Document = require_Document(); var Schema = require_Schema(); - var errors = require_errors(); + var errors = require_errors2(); var Alias = require_Alias(); var identity = require_identity(); var Pair = require_Pair(); @@ -28804,7 +46998,7 @@ function isKeyOperator2(operator) { function getValues2(context3, operator, key, modifier) { var value = context3[key], result = []; if (isDefined2(value) && value !== "") { - if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { + if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") { value = value.toString(); if (modifier && modifier !== "*") { value = value.substring(0, parseInt(modifier, 10)); @@ -28982,6 +47176,123 @@ function withDefaults4(oldDefaults, newDefaults) { } var endpoint2 = withDefaults4(null, DEFAULTS2); var import_fast_content_type_parse2 = __toESM2(require_fast_content_type_parse2()); +var intRegex2 = /^-?\d+$/; +var noiseValue2 = /^-?\d+n+$/; +var originalStringify2 = JSON.stringify; +var originalParse2 = JSON.parse; +var customFormat2 = /^-?\d+n$/; +var bigIntsStringify2 = /([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g; +var noiseStringify2 = /([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g; +var JSONStringify2 = (value, replacer, space) => { + if ("rawJSON" in JSON) { + return originalStringify2( + value, + (key, value2) => { + if (typeof value2 === "bigint") + return JSON.rawJSON(value2.toString()); + if (typeof replacer === "function") + return replacer(key, value2); + if (Array.isArray(replacer) && replacer.includes(key)) + return value2; + return value2; + }, + space + ); + } + if (!value) + return originalStringify2(value, replacer, space); + const convertedToCustomJSON = originalStringify2( + value, + (key, value2) => { + const isNoise = typeof value2 === "string" && noiseValue2.test(value2); + if (isNoise) + return value2.toString() + "n"; + if (typeof value2 === "bigint") + return value2.toString() + "n"; + if (typeof replacer === "function") + return replacer(key, value2); + if (Array.isArray(replacer) && replacer.includes(key)) + return value2; + return value2; + }, + space + ); + const processedJSON = convertedToCustomJSON.replace( + bigIntsStringify2, + "$1$2$3" + ); + const denoisedJSON = processedJSON.replace(noiseStringify2, "$1$2$3"); + return denoisedJSON; +}; +var featureCache2 = /* @__PURE__ */ new Map(); +var isContextSourceSupported2 = () => { + const parseFingerprint = JSON.parse.toString(); + if (featureCache2.has(parseFingerprint)) { + return featureCache2.get(parseFingerprint); + } + try { + const result = JSON.parse( + "1", + (_, __, context3) => !!context3?.source && context3.source === "1" + ); + featureCache2.set(parseFingerprint, result); + return result; + } catch { + featureCache2.set(parseFingerprint, false); + return false; + } +}; +var convertMarkedBigIntsReviver2 = (key, value, context3, userReviver) => { + const isCustomFormatBigInt = typeof value === "string" && customFormat2.test(value); + if (isCustomFormatBigInt) + return BigInt(value.slice(0, -1)); + const isNoiseValue = typeof value === "string" && noiseValue2.test(value); + if (isNoiseValue) + return value.slice(0, -1); + if (typeof userReviver !== "function") + return value; + return userReviver(key, value, context3); +}; +var JSONParseV22 = (text, reviver) => { + return JSON.parse(text, (key, value, context3) => { + const isBigNumber = typeof value === "number" && (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER); + const isInt = context3 && intRegex2.test(context3.source); + const isBigInt = isBigNumber && isInt; + if (isBigInt) + return BigInt(context3.source); + if (typeof reviver !== "function") + return value; + return reviver(key, value, context3); + }); +}; +var MAX_INT2 = Number.MAX_SAFE_INTEGER.toString(); +var MAX_DIGITS2 = MAX_INT2.length; +var stringsOrLargeNumbers2 = /"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g; +var noiseValueWithQuotes2 = /^"-?\d+n+"$/; +var JSONParse2 = (text, reviver) => { + if (!text) + return originalParse2(text, reviver); + if (isContextSourceSupported2()) + return JSONParseV22(text, reviver); + const serializedData = text.replace( + stringsOrLargeNumbers2, + (text2, digits, fractional, exponential) => { + const isString = text2[0] === '"'; + const isNoise = isString && noiseValueWithQuotes2.test(text2); + if (isNoise) + return text2.substring(0, text2.length - 1) + 'n"'; + const isFractionalOrExponential = fractional || exponential; + const isLessThanMaxSafeInt = digits && (digits.length < MAX_DIGITS2 || digits.length === MAX_DIGITS2 && digits <= MAX_INT2); + if (isString || isFractionalOrExponential || isLessThanMaxSafeInt) + return text2; + return '"' + text2 + 'n"'; + } + ); + return originalParse2( + serializedData, + (key, value, context3) => convertMarkedBigIntsReviver2(key, value, context3, reviver) + ); +}; var RequestError2 = class extends Error { name; /** @@ -29019,7 +47330,7 @@ var RequestError2 = class extends Error { this.request = requestCopy; } }; -var VERSION22 = "10.0.7"; +var VERSION22 = "10.0.8"; var defaults_default2 = { headers: { "user-agent": `octokit-request.js/${VERSION22} ${getUserAgent2()}` @@ -29046,7 +47357,7 @@ async function fetchWrapper2(requestOptions) { } const log = requestOptions.request?.log || console; const parseSuccessResponseBody = requestOptions.request?.parseSuccessResponseBody !== false; - const body = isPlainObject22(requestOptions.body) || Array.isArray(requestOptions.body) ? JSON.stringify(requestOptions.body) : requestOptions.body; + const body = isPlainObject22(requestOptions.body) || Array.isArray(requestOptions.body) ? JSONStringify2(requestOptions.body) : requestOptions.body; const requestHeaders = Object.fromEntries( Object.entries(requestOptions.headers).map(([name, value]) => [ name, @@ -29145,7 +47456,7 @@ async function getResponseData2(response) { let text = ""; try { text = await response.text(); - return JSON.parse(text); + return JSONParse2(text); } catch (err) { return text; } @@ -32269,6 +50580,54 @@ var types = ( return types2; }() ); +async function invokeWithRetry(fn, retries = 3, delay = 1e3) { + let attempt = 0; + while (attempt < retries) { + try { + return await fn(); + } catch (e) { + attempt++; + if (attempt >= retries) { + throw e; + } + if (isGithubApiError(e) && e.status < 500) { + throw e; + } + if (e instanceof GraphqlResponseError2) { + if (!e.errors) { + throw e; + } + if (e.errors.every((err) => ["NOT_FOUND", "FORBIDDEN", "BAD_USER_INPUT", "UNAUTHENTICATED"].includes(err.type))) { + throw e; + } + } + Log.warn(`GitHub API call failed (attempt ${attempt}/${retries}). Retrying in ${delay}ms...`); + await new Promise((resolve22) => setTimeout(resolve22, delay)); + } + } + throw new Error("Unreachable"); +} +function createRetryProxy(target) { + return new Proxy(target, { + get(targetObj, prop, receiver) { + const value = Reflect.get(targetObj, prop, receiver); + if (typeof value === "function") { + return new Proxy(value, { + apply(targetFn, thisArg, argArray) { + return invokeWithRetry(() => targetFn.apply(targetObj, argArray)); + } + }); + } + if (typeof value === "object" && value !== null) { + return createRetryProxy(value); + } + return value; + }, + apply(targetFn, thisArg, argArray) { + return invokeWithRetry(() => targetFn.apply(thisArg, argArray)); + } + }); +} var GithubClient = class { constructor(_octokitOptions) { this._octokitOptions = _octokitOptions; @@ -32281,18 +50640,18 @@ var GithubClient = class { }, ...this._octokitOptions }); - this.pulls = this._octokit.pulls; - this.orgs = this._octokit.orgs; - this.repos = this._octokit.repos; - this.issues = this._octokit.issues; - this.git = this._octokit.git; - this.rateLimit = this._octokit.rateLimit; - this.teams = this._octokit.teams; - this.search = this._octokit.search; - this.rest = this._octokit.rest; - this.paginate = this._octokit.paginate; - this.checks = this._octokit.checks; - this.users = this._octokit.users; + this.pulls = createRetryProxy(this._octokit.pulls); + this.orgs = createRetryProxy(this._octokit.orgs); + this.repos = createRetryProxy(this._octokit.repos); + this.issues = createRetryProxy(this._octokit.issues); + this.git = createRetryProxy(this._octokit.git); + this.rateLimit = createRetryProxy(this._octokit.rateLimit); + this.teams = createRetryProxy(this._octokit.teams); + this.search = createRetryProxy(this._octokit.search); + this.rest = createRetryProxy(this._octokit.rest); + this.paginate = createRetryProxy(this._octokit.paginate); + this.checks = createRetryProxy(this._octokit.checks); + this.users = createRetryProxy(this._octokit.users); } }; var AuthenticatedGithubClient = class extends GithubClient { @@ -32304,9 +50663,14 @@ var AuthenticatedGithubClient = class extends GithubClient { }); } async graphql(queryObject, params2 = {}) { - return await this._graphql(query(queryObject).toString(), params2); + return invokeWithRetry(async () => { + return await this._graphql(query(queryObject).toString(), params2); + }); } }; +function isGithubApiError(obj) { + return obj instanceof Error && obj.constructor.name === "RequestError" && obj.request !== void 0; +} function isDryRun() { return process.env["DRY_RUN"] !== void 0; } @@ -33020,24 +51384,28 @@ var requiresLabels = createTypedObject(RequiresLabel)({ description: "This PR requires a passing TGP before merging is allowed" } }); -var FeatureLabel = class extends Label { +var MiscLabel = class extends Label { }; -var featureLabels = createTypedObject(FeatureLabel)({ - FEATURE_IN_BACKLOG: { - name: "feature: in backlog", - description: "Feature request for which voting has completed and is now in the backlog" +var miscLabels = createTypedObject(MiscLabel)({ + FEATURE: { + name: "feature", + description: "Label used to distinguish feature request from other issues" }, - FEATURE_VOTES_REQUIRED: { - name: "feature: votes required", - description: "Feature request which is currently still in the voting phase" + GOOD_FIRST_ISSUE: { + name: "good first issue", + description: "Label noting a good first issue to be worked on by a community member" }, - FEATURE_UNDER_CONSIDERATION: { - name: "feature: under consideration", - description: "Feature request for which voting has completed and the request is now under consideration" + HELP_WANTED: { + name: "help wanted", + description: "Label noting an issue which the team is looking for contribution from the community to fix" }, - FEATURE_INSUFFICIENT_VOTES: { - name: "feature: insufficient votes", - description: "Label to add when the not a sufficient number of votes or comments from unique authors" + RENOVATE_MANAGED: { + name: "renovate managed", + description: "Label noting that a pull request will automatically be managed and rebased by renovate" + }, + GEMINI_TRIAGED: { + name: "gemini-triaged", + description: "Label noting that an issue has been triaged by gemini" } }); var allLabels = { @@ -33046,8 +51414,8 @@ var allLabels = { ...mergeLabels, ...targetLabels, ...priorityLabels, - ...featureLabels, - ...requiresLabels + ...requiresLabels, + ...miscLabels }; var import_which = __toESM2(require_lib2()); var import_yaml = __toESM2(require_dist()); @@ -33150,7 +51518,7 @@ function joinUrlParts(...parts) { // .github/actions/deploy-docs-site/lib/main.mts import { spawnSync as spawnSync3 } from "child_process"; import { cp, mkdtemp as mkdtemp2 } from "fs/promises"; -import { tmpdir as tmpdir2 } from "os"; +import { tmpdir as tmpdir3 } from "os"; import { join as join5 } from "path"; var refMatcher = /refs\/heads\/(.*)/; async function deployDocs() { @@ -33165,7 +51533,7 @@ async function deployDocs() { } const currentBranch = matchedRef[1]; const configPath = getInput("configPath"); - const stagingDir = await mkdtemp2(join5(tmpdir2(), "deploy-directory")); + const stagingDir = await mkdtemp2(join5(tmpdir3(), "deploy-directory")); await cp(getInput("distDir"), stagingDir, { recursive: true }); spawnSync3(`chmod 777 -R ${stagingDir}`, { encoding: "utf-8", shell: true }); const deployment = (await getDeployments()).get(currentBranch); @@ -33209,14 +51577,11 @@ if (context2.repo.owner === "angular") { } /*! Bundled license information: -tmp/lib/tmp.js: - (*! - * Tmp - * - * Copyright (c) 2011-2017 KARASZI Istvan - * - * MIT Licensed - *) +undici/lib/web/fetch/body.js: + (*! formdata-polyfill. MIT License. Jimmy Wärting *) + +undici/lib/web/websocket/frame.js: + (*! ws. MIT License. Einar Otto Stangvik *) @octokit/request-error/dist-src/index.js: (* v8 ignore else -- @preserve -- Bug with vitest coverage where it sees an else branch that doesn't exist *) @@ -33225,7 +51590,7 @@ tmp/lib/tmp.js: (* v8 ignore next -- @preserve *) (* v8 ignore else -- @preserve *) -@angular/ng-dev/bundles/chunk-ZTI2LCA3.mjs: +@angular/ng-dev/bundles/chunk-PVA34BB2.mjs: (*! Bundled license information: yargs-parser/build/lib/string-utils.js: @@ -33266,7 +51631,7 @@ tmp/lib/tmp.js: *) *) -@angular/ng-dev/bundles/chunk-BZKO77AS.mjs: +@angular/ng-dev/bundles/chunk-6BHVTSCA.mjs: (*! Bundled license information: @octokit/request-error/dist-src/index.js: diff --git a/.github/actions/saucelabs-legacy/action.yml b/.github/actions/saucelabs-legacy/action.yml deleted file mode 100644 index 8d824418ba84..000000000000 --- a/.github/actions/saucelabs-legacy/action.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: 'Saucelabs legacy test job' -description: 'Runs tests against Saucelabs (outside of Bazel)' - -runs: - using: 'composite' - steps: - - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc - - name: Setup Saucelabs Variables - uses: angular/dev-infra/github-actions/saucelabs@7c08ac2a4f396bad752829fba09dbaefbcded9fc - - name: Starting Saucelabs tunnel service - shell: bash - run: ./tools/saucelabs/sauce-service.sh run & - # Build test fixtures for a test that rely on Bazel-generated fixtures. Note that disabling - # specific tests which are reliant on such generated fixtures is not an option as SystemJS - # in the Saucelabs legacy job always fetches referenced files, even if the imports would be - # guarded by an check to skip in the Saucelabs legacy job. We should be good running such - # test in all supported browsers on Saucelabs anyway until this job can be removed. - - name: Preparing Bazel-generated fixtures required in legacy tests - shell: bash - run: | - # Locale files are needed for i18n tests running within Saucelabs. These are added - # directly as sources so that the TypeScript compilation of `/packages/tsconfig.json` - # can succeed. Note that the base locale and currencies files are checked-in, so - # we do not need to re-generate those through Bazel. - mkdir -p packages/common/locales/extra - cp dist/bin/packages/common/locales/*.ts packages/common/locales - cp dist/bin/packages/common/locales/extra/*.ts packages/common/locales/extra - - name: Build bundle of tests to run on Saucelabs - shell: bash - run: node tools/legacy-saucelabs/build-saucelabs-test-bundle.mjs - - name: Wait and confirm Saucelabs tunnel has connected - shell: bash - run: ./tools/saucelabs/sauce-service.sh ready-wait - - name: Running tests on Saucelabs. - shell: bash - run: KARMA_WEB_TEST_MODE=SL_REQUIRED pnpm karma start ./karma-js.conf.js --single-run - - name: Stop Saucelabs tunnel service - shell: bash - run: ./tools/saucelabs/sauce-service.sh stop diff --git a/.github/workflows/adev-preview-build.yml b/.github/workflows/adev-preview-build.yml index a80dbe389bb2..02d2c5399bb5 100644 --- a/.github/workflows/adev-preview-build.yml +++ b/.github/workflows/adev-preview-build.yml @@ -21,17 +21,17 @@ jobs: (github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'adev: preview')) steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Build adev # `snapshot-build` config is used to stamp the exact version with sha in the footer. run: pnpm bazel build //adev:build.production --config=snapshot-build - - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@7c08ac2a4f396bad752829fba09dbaefbcded9fc + - uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: workflow-artifact-name: 'adev-preview' pull-number: '${{github.event.pull_request.number}}' diff --git a/.github/workflows/adev-preview-deploy.yml b/.github/workflows/adev-preview-deploy.yml index 6cdc5fd02fda..f0eb58ed5ede 100644 --- a/.github/workflows/adev-preview-deploy.yml +++ b/.github/workflows/adev-preview-deploy.yml @@ -32,15 +32,17 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: token: '${{secrets.GITHUB_TOKEN}}' + persist-credentials: false - name: Configure Firebase deploy target working-directory: ./ run: | # We can use `npx` as the Firebase deploy actions uses it too. - npx -y firebase-tools@latest target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs - npx -y firebase-tools@latest target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}} + # Use stable version release + npx -y firebase-tools@15.15.0 target:clear --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs + npx -y firebase-tools@15.15.0 target:apply --config adev/firebase.json --project ${{env.PREVIEW_PROJECT}} hosting angular-docs ${{env.PREVIEW_SITE}} - - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@7c08ac2a4f396bad752829fba09dbaefbcded9fc + - uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: github-token: '${{secrets.GITHUB_TOKEN}}' workflow-artifact-name: 'adev-preview' diff --git a/.github/workflows/assistant-to-the-branch-manager.yml b/.github/workflows/assistant-to-the-branch-manager.yml index a6238fd64aa8..d392e616d01d 100644 --- a/.github/workflows/assistant-to-the-branch-manager.yml +++ b/.github/workflows/assistant-to-the-branch-manager.yml @@ -17,6 +17,6 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: angular/dev-infra/github-actions/branch-manager@7c08ac2a4f396bad752829fba09dbaefbcded9fc + - uses: angular/dev-infra/github-actions/branch-manager@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/benchmark-compare.yml b/.github/workflows/benchmark-compare.yml index ea84843b6e82..38bf2917ed5a 100644 --- a/.github/workflows/benchmark-compare.yml +++ b/.github/workflows/benchmark-compare.yml @@ -25,7 +25,7 @@ jobs: token: '${{secrets.BENCHMARK_POST_RESULTS_GITHUB_TOKEN}}' reactions: 'rocket' - - uses: alessbell/pull-request-comment-branch@aad01d65d6982b8eacabed5e9a684cd8ceb98da6 # v1.1 + - uses: alessbell/pull-request-comment-branch@ef3408c9757d05f89cb525036383033a313758a0 # v2.1.0 id: comment-branch - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -34,11 +34,11 @@ jobs: repository: ${{steps.comment-branch.outputs.head_owner}}/${{steps.comment-branch.outputs.head_repo}} # Checkout the pull request and assume it being trusted given we've checked # that the action was triggered by a team member. - ref: ${{steps.comment-branch.outputs.head_ref}} + ref: ${{steps.comment-branch.outputs.head_sha}} - run: pnpm install --frozen-lockfile - - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + - uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: bazelrc: ./.bazelrc.user @@ -49,7 +49,8 @@ jobs: COMMENT_BODY: ${{ github.event.comment.body }} run: pnpm benchmarks prepare-for-github-action "$COMMENT_BODY" - - run: pnpm benchmarks run-compare ${{steps.info.outputs.compareSha}} ${{steps.info.outputs.benchmarkTarget}} + - run: pnpm benchmarks run-compare ${{steps.info.outputs.compareSha}} "${{steps.info.outputs.benchmarkTarget}}" + id: benchmark name: Running benchmark diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4b6a34b41c9..7c800bddcbf6 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@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Check code lint @@ -32,6 +32,8 @@ jobs: run: pnpm ng-dev pullapprove verify - name: Validate angular robot configuration run: pnpm ng-dev ngbot verify + - name: Validate agent skills + run: pnpm ng-dev ai skills validate - name: Confirm code builds with typescript as expected run: pnpm check-tooling-setup @@ -39,13 +41,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: disable-package-manager-cache: true - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -55,7 +57,7 @@ jobs: - name: Test build run: pnpm devtools:build:chrome - name: Cypress run - uses: cypress-io/github-action@84d178e4bbce871e23f2ffa3085898cde0e4f0ec # v7.1.2 + uses: cypress-io/github-action@dace029018fcdf86e0df89a31bc3cfa5b32570d8 # v7.3.0 with: command: pnpm devtools:test:e2e start: pnpm bazel run //devtools/src:devserver @@ -67,11 +69,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel Remote Caching - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -83,11 +85,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel Remote Caching - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -100,11 +102,11 @@ jobs: labels: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -119,11 +121,11 @@ jobs: labels: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -136,11 +138,11 @@ jobs: labels: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials @@ -152,11 +154,11 @@ jobs: labels: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }} - name: Install node modules @@ -186,31 +188,21 @@ jobs: - run: pnpm -C packages/zone.js jest:test - run: pnpm -C packages/zone.js jest:nodetest - run: pnpm -C packages/zone.js vitest:test + - run: pnpm -C packages/zone.js vitest-globals:test - run: pnpm -C packages/zone.js electrontest - run: pnpm -C packages/zone.js/test/typings test - # saucelabs: - # runs-on: ubuntu-latest - # env: - # SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }} - # steps: - # - name: Initialize environment - # uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b5a3609f89c06eb4037dce22a93641213a5d1508 - # - name: Install node modules - # run: pnpm install --frozen-lockfile - # - uses: ./.github/actions/saucelabs-legacy - adev-deploy: needs: [adev] if: needs.adev.result == 'success' runs-on: ubuntu-latest-8core steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Build adev diff --git a/.github/workflows/cross-repo-adev-docs.yml b/.github/workflows/cross-repo-adev-docs.yml index b5788ea63366..9bbf7e2440b9 100644 --- a/.github/workflows/cross-repo-adev-docs.yml +++ b/.github/workflows/cross-repo-adev-docs.yml @@ -37,7 +37,7 @@ jobs: ANGULAR_READONLY_GITHUB_TOKEN: ${{ secrets.READONLY_GITHUB_TOKEN }} - name: Create a PR (if necessary) - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }} push-to-fork: 'angular-robot/angular' diff --git a/.github/workflows/dev-infra.yml b/.github/workflows/dev-infra.yml index e2099cb6a4d8..095296c4bcdd 100644 --- a/.github/workflows/dev-infra.yml +++ b/.github/workflows/dev-infra.yml @@ -3,23 +3,34 @@ name: DevInfra on: pull_request_target: types: [opened, synchronize, reopened] + issues: + types: [opened, reopened] # Declare default permissions as read only. permissions: contents: read jobs: - labels: + pull_request_labels: + if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: angular/dev-infra/github-actions/pull-request-labeling@7c08ac2a4f396bad752829fba09dbaefbcded9fc + - uses: angular/dev-infra/github-actions/labeling/pull-request@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} + labels: '{"requires: TGP": ["packages/core/primitives/**/{*,.*}"]}' post_approval_changes: + if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: angular/dev-infra/github-actions/post-approval-changes@7c08ac2a4f396bad752829fba09dbaefbcded9fc + - uses: angular/dev-infra/github-actions/post-approval-changes@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} + issue_labels: + if: github.event_name == 'issues' + runs-on: ubuntu-latest + steps: + - uses: angular/dev-infra/github-actions/labeling/issue@442c2fcbf06a321b5196b4c5fc70e78a49242958 + with: + angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} + google-generative-ai-key: ${{ secrets.GOOGLE_GENERATIVE_AI_KEY }} diff --git a/.github/workflows/google-internal-tests.yml b/.github/workflows/google-internal-tests.yml index 85aeba5fd314..e8c60e6ad7cf 100644 --- a/.github/workflows/google-internal-tests.yml +++ b/.github/workflows/google-internal-tests.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: angular/dev-infra/github-actions/google-internal-tests@7c08ac2a4f396bad752829fba09dbaefbcded9fc + - uses: angular/dev-infra/github-actions/google-internal-tests@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: run-tests-guide-url: http://go/angular-g3sync-start github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml deleted file mode 100644 index 4901a69e045b..000000000000 --- a/.github/workflows/manual.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Manual jobs - -on: - workflow_dispatch: - inputs: {} - -jobs: - # Bazel saucelabs job resides in `manual.yml` because it's currently unstable, but - # kept as "runnable" for debugging/stabilization effort purposes. - bazel-saucelabs: - runs-on: ubuntu-latest - env: - JOBS: 2 - steps: - - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc - - name: Install node modules - run: pnpm install --frozen-lockfile - - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc - - name: Setup Bazel Remote Caching - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc - - name: Setup Saucelabs Variables - uses: angular/dev-infra/github-actions/saucelabs@7c08ac2a4f396bad752829fba09dbaefbcded9fc - - name: Set up Sauce Tunnel Daemon - run: pnpm bazel run //tools/saucelabs-daemon/background-service -- $JOBS & - env: - SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }} - - name: Run all saucelabs bazel tests - run: | - TESTS=$(./node_modules/.bin/bazelisk query --output label '(kind(karma_web_test, ...) intersect attr("tags", "saucelabs", ...)) except attr("tags", "fixme-saucelabs", ...)') - pnpm bazel test --config=saucelabs --jobs=$JOBS ${TESTS} diff --git a/.github/workflows/merge-ready-status.yml b/.github/workflows/merge-ready-status.yml index 775b5ca6fae6..87d0fcb82a46 100644 --- a/.github/workflows/merge-ready-status.yml +++ b/.github/workflows/merge-ready-status.yml @@ -9,6 +9,6 @@ jobs: status: runs-on: ubuntu-latest steps: - - uses: angular/dev-infra/github-actions/unified-status-check@7c08ac2a4f396bad752829fba09dbaefbcded9fc + - uses: angular/dev-infra/github-actions/unified-status-check@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }} diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index ea71af9f177a..9539320b6801 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -21,7 +21,7 @@ jobs: workflows: ${{ steps.workflows.outputs.workflows }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - id: workflows @@ -36,9 +36,9 @@ jobs: workflow: ${{ fromJSON(needs.list.outputs.workflows) }} steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile # We utilize the google-github-actions/auth action to allow us to get an active credential using workflow diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ee5d250b307c..84a96f0c97a0 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@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Check code lint @@ -32,12 +32,14 @@ jobs: run: pnpm ng-dev ngbot verify - name: Confirm code builds with typescript as expected run: pnpm check-tooling-setup + - name: Validate agent skills + run: pnpm ng-dev ai skills validate - name: Check commit message run: pnpm ng-dev commit-message validate-range ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} - 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@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/linting/licenses@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: allow-dependencies-licenses: 'pkg:npm/google-protobuf@' @@ -45,13 +47,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 with: disable-package-manager-cache: true - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run unit tests @@ -59,7 +61,7 @@ jobs: - name: Test build run: pnpm devtools:build:chrome - name: Cypress run - uses: cypress-io/github-action@84d178e4bbce871e23f2ffa3085898cde0e4f0ec # v7.1.2 + uses: cypress-io/github-action@dace029018fcdf86e0df89a31bc3cfa5b32570d8 # v7.3.0 with: command: pnpm devtools:test:e2e start: pnpm bazel run //devtools/src:devserver @@ -71,11 +73,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel Remote Caching - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run CI tests for framework @@ -86,7 +88,7 @@ jobs: ASPECT_RULES_JS_FROZEN_PNPM_LOCK: '1' - name: Upload GRPC logs (for debugging of RBE issues) if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: path: /tmp/rbe-grpc.log retention-days: 1 @@ -95,11 +97,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel Remote Caching - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run integration CI tests for framework @@ -110,11 +112,11 @@ jobs: labels: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run tests @@ -127,11 +129,11 @@ jobs: labels: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - name: Run tests @@ -142,11 +144,11 @@ jobs: labels: ubuntu-latest steps: - name: Initialize environment - uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel - uses: angular/dev-infra/github-actions/bazel/setup@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/setup@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Setup Bazel RBE - uses: angular/dev-infra/github-actions/bazel/configure-remote@7c08ac2a4f396bad752829fba09dbaefbcded9fc + uses: angular/dev-infra/github-actions/bazel/configure-remote@442c2fcbf06a321b5196b4c5fc70e78a49242958 - name: Install node modules run: pnpm install --frozen-lockfile - run: | @@ -172,16 +174,6 @@ jobs: - run: pnpm -C packages/zone.js jest:test - run: pnpm -C packages/zone.js jest:nodetest - run: pnpm -C packages/zone.js vitest:test + - run: pnpm -C packages/zone.js vitest-globals:test - run: pnpm -C packages/zone.js electrontest - run: pnpm -C packages/zone.js/test/typings test - - # saucelabs: - # runs-on: ubuntu-latest - # env: - # SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }} - # steps: - # - name: Initialize environment - # uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b5a3609f89c06eb4037dce22a93641213a5d1508 - # - name: Install node modules - # run: pnpm install --frozen-lockfile - # - uses: ./.github/actions/saucelabs-legacy diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3598512789d3..6504b570e091 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -39,7 +39,7 @@ jobs: # Upload the results as artifacts. - name: 'Upload artifact' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif @@ -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@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4 with: sarif_file: results.sarif diff --git a/.ng-dev/github.mjs b/.ng-dev/github.mjs index 8df9dcd86ffa..ac2ec5582825 100644 --- a/.ng-dev/github.mjs +++ b/.ng-dev/github.mjs @@ -9,5 +9,5 @@ export const github = { name: 'angular', mainBranchName: 'main', mergeMode: 'caretaker-only', - requireReleaseModeForRelease: true, + requireReleaseModeForRelease: false, }; diff --git a/.ng-dev/google-sync-config.json b/.ng-dev/google-sync-config.json index 7262e9fbf0d2..a779d65d8120 100644 --- a/.ng-dev/google-sync-config.json +++ b/.ng-dev/google-sync-config.json @@ -10,6 +10,7 @@ "packages/compiler-cli/private/bazel.ts", "packages/compiler-cli/private/localize.ts", "packages/compiler-cli/private/tooling.ts", + "packages/compiler-cli/private/hybrid_analysis.ts", "packages/compiler-cli/private/babel.d.ts", "packages/compiler-cli/src/bin/**", "packages/core/schematics/utils/tsurge/helpers/angular_devkit/**", diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 7c7738880a15..000000000000 --- a/.npmrc +++ /dev/null @@ -1,12 +0,0 @@ -# Yarn Berry doesn't check engines at all, so pnpm shouldn't either. -engine-strict = false - -# Disabling pnpm [hoisting](https://pnpm.io/npmrc#hoist) by setting `hoist=false` is recommended on -# projects using rules_js so that pnpm outside of Bazel lays out a node_modules tree similar to what -# rules_js lays out under Bazel (without a hidden node_modules/.pnpm/node_modules) -hoist=false - -# Avoid pnpm auto-installing peer dependencies. We want to be explicit about our versions used -# for peer dependencies, avoiding potential mismatches. In addition, it ensures we can continue -# to rely on peer dependency placeholders substituted via Bazel. -auto-install-peers=false diff --git a/.nvmrc b/.nvmrc index 85e502778f62..941d7c071de8 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.22.0 +22.22.3 diff --git a/.prettierignore b/.prettierignore index 707f48036864..07f2d18df45a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -30,6 +30,9 @@ vscode-ng-language-service/syntaxes/test/data/*.html # Ignore goldens MD files goldens/**/*.api.md +# Ignore golden symbol json files +packages/**/*.golden_symbols.json + # adev generated files adev/src/content/aria/**/*.json adev/src/content/cli/**/*.json diff --git a/.pullapprove.yml b/.pullapprove.yml index 9cd37692e09a..4764984dbc49 100644 --- a/.pullapprove.yml +++ b/.pullapprove.yml @@ -36,8 +36,9 @@ version: 3 -#availability: -# users_unavailable: [] +availability: + users_unavailable: + - devversion # Meta field that goes unused by PullApprove to allow for defining aliases to be # used throughout the config. @@ -99,14 +100,13 @@ groups: reviewers: users: - ~alxhub - - AndrewKushnir + - ~AndrewKushnir - atscott - crisbeto - devversion - - thePunderWoman + - ~thePunderWoman - kirjs - JoostK - - mmalerba - ~amishne - ~leonsenft - ~mattrbeck @@ -146,14 +146,13 @@ groups: reviewers: users: - ~alxhub - - AndrewKushnir + - ~AndrewKushnir - atscott - crisbeto - devversion - kirjs - - thePunderWoman + - ~thePunderWoman - ~pkozlowski-opensource - - mmalerba - JeanMeche - ~amishne - ~leonsenft @@ -202,14 +201,13 @@ groups: users: - ~JiaLiPassion - ~alxhub - - AndrewKushnir + - ~AndrewKushnir - atscott - crisbeto - devversion - kirjs - - thePunderWoman + - ~thePunderWoman - ~pkozlowski-opensource - - mmalerba - ~amishne - ~leonsenft - ~mattrbeck @@ -246,24 +244,25 @@ groups: - > contains_any_globs(files, [ 'adev/**/{*,.*}', + 'tools/manual_api_docs/blocks/*.md', + 'tools/manual_api_docs/elements/*.md', ]) reviewers: users: - alan-agius4 - ~alxhub - - AndrewKushnir + - ~AndrewKushnir - atscott - bencodezen - crisbeto - kirjs - JeanMeche - - thePunderWoman + - ~thePunderWoman - devversion - josephperrott - ~pkozlowski-opensource - ~mgechev - MarkTechson - - mmalerba - ~hawkgs - ~amishne - ~leonsenft @@ -299,7 +298,11 @@ groups: <<: *defaults conditions: - > - contains_any_globs(files.exclude('.pullapprove.yml'), [ + contains_any_globs(files + .exclude('.pullapprove.yml') + .exclude('tools/manual_api_docs/blocks/*.md') + .exclude('tools/manual_api_docs/elements/*.md'), + [ '{*,.*}', '.agent/**/{*,.*}', '.devcontainer/**/{*,.*}', @@ -321,12 +324,9 @@ groups: 'third_party/**/{*,.*}', 'tools/contributing-stats/**/{*,.*}', 'tools/gulp-tasks/**/{*,.*}', - 'tools/legacy-saucelabs/**/{*,.*}', 'tools/manual_api_docs/**/{*,.*}', 'tools/pnpm-patches/**/{*,.*}', 'tools/rxjs/**/{*,.*}', - 'tools/saucelabs-daemon/**/{*,.*}', - 'tools/saucelabs/**/{*,.*}', 'tools/symbol-extractor/**/{*,.*}', 'tools/testing/**/{*,.*}', 'tools/tslint/**/{*,.*}', @@ -370,13 +370,12 @@ groups: ]) reviewers: users: - - AndrewKushnir + - ~AndrewKushnir - ~alxhub - atscott - - thePunderWoman + - ~thePunderWoman - ~pkozlowski-opensource - kirjs - - mmalerba - crisbeto - devversion - JeanMeche @@ -407,12 +406,11 @@ groups: reviewers: users: - ~alxhub - - AndrewKushnir + - ~AndrewKushnir - atscott - kirjs - - thePunderWoman + - ~thePunderWoman - ~pkozlowski-opensource - - mmalerba - ~amishne - ~leonsenft - ~mattrbeck @@ -434,7 +432,7 @@ groups: ]) reviewers: users: - - marktechson + - MarkTechson - kirjs - ~JeanMeche - ~dgp1130 @@ -456,10 +454,10 @@ groups: reviewers: users: - ~alxhub - - AndrewKushnir + - ~AndrewKushnir - andrewseguin - dgp1130 - - thePunderWoman + - ~thePunderWoman - josephperrott # ========================================================= @@ -478,20 +476,16 @@ groups: users: - ~pkozlowski-opensource # Pawel Kozlowski - ~alxhub # Alex Rickabaugh - - thePunderWoman # Jessica Janiuk - - AndrewKushnir # Andrew Kushnir + - ~thePunderWoman # Jessica Janiuk + - ~AndrewKushnir # Andrew Kushnir - atscott # Andrew Scott - labels: - pending: 'requires: TGP' - approved: 'requires: TGP' - rejected: 'requires: TGP' # External team required reviews primitives-shared: <<: *defaults conditions: - > - contains_any_globs(files, [ + contains_any_globs(files.exclude('packages/core/primitives/**/*spec.ts'), [ 'packages/core/primitives/**/{*,.*}', ]) reviewers: @@ -502,10 +496,6 @@ groups: - tbondwilkinson # Tom Wilkinson - rahatarmanahmed # Rahat Ahmed - ENAML # Ethan Cline - labels: - pending: 'requires: TGP' - approved: 'requires: TGP' - rejected: 'requires: TGP' #################################################################################### # Override managed result groups diff --git a/BUILD.bazel b/BUILD.bazel index cfdc620a5949..ba635a66edb1 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,3 @@ -load("@aspect_rules_js//js:defs.bzl", "js_library") load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") load("@devinfra//bazel/validation:defs.bzl", "validate_ts_version_matching") load("@npm//:defs.bzl", "npm_link_all_packages") @@ -8,8 +7,6 @@ package(default_visibility = ["//visibility:public"]) exports_files([ "LICENSE", - "karma-js.conf.js", - "browser-providers.conf.js", "package.json", ]) @@ -34,14 +31,6 @@ alias( actual = "//packages:tsconfig-build.json", ) -js_library( - name = "browser-providers", - srcs = [ - "browser-providers.conf.d.ts", - "browser-providers.conf.js", - ], -) - copy_to_bin( name = "angularjs_scripts", srcs = [ @@ -64,15 +53,6 @@ config_setting( values = {"stamp": "true"}, ) -alias( - name = "sauce_connect", - actual = select({ - "@devinfra//bazel/constraints:linux_x64": "@sauce_connect_linux_amd64//:bin/sc", - "@devinfra//bazel/constraints:macos_x64": "@sauce_connect_mac//:bin/sc", - "@devinfra//bazel/constraints:macos_arm64": "@sauce_connect_mac//:bin/sc", - }), -) - # When enabled, this flag substitutes dependency versions with snapshot repositories # for all packages in this repository. Note that this does not apply to peer # dependencies, as they must be installed directly. diff --git a/CHANGELOG.md b/CHANGELOG.md index 001c367879ea..4cc59553a3bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,178 +1,913 @@ - -# 21.1.5 (2026-02-18) + +# 22.0.0-rc.0 (2026-05-13) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [c7aef8ec5d](https://github.com/angular/angular/commit/c7aef8ec5dd12b5b1d4c703a61bd1dd43f998e18) | fix | enforce parentheses containing arguments for :host-context | +| [8a1533c9ad](https://github.com/angular/angular/commit/8a1533c9ad7c306e03d7c50676f87b56bade5bf6) | fix | preserve leading commas in animation definitions | +| [194f723f66](https://github.com/angular/angular/commit/194f723f6620ea3cdf490b762ecbef8df6bb2c8a) | fix | remove dedicated support for legacy shadow DOM selectors | +| [4c25a42e98](https://github.com/angular/angular/commit/4c25a42e988e7a59d4d4dc3121cd77f7b344c048) | fix | remove deprecated shadow CSS encapsulation polyfills | +| [7dc1017e51](https://github.com/angular/angular/commit/7dc1017e517c077a6aa8fd749392a2af1277e1b7) | fix | simplify handling of colon host with a selector list | +| [ccb7d427e4](https://github.com/angular/angular/commit/ccb7d427e4f07506c14c50ce0cbe87c57930ebb5) | fix | type check invalid for loops | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [119a19e604](https://github.com/angular/angular/commit/119a19e604a500f295867fc2cf2e6dbd43a9d807) | fix | forward BEFORE_APP_SERIALIZED errors to ErrorHandler | + + + + +# 21.2.13 (2026-05-13) +### core +| Commit | Type | Description | +| -- | -- | -- | +| [1c6553e97d](https://github.com/angular/angular/commit/1c6553e97d9655d8c48fbf625987fae86f9cd947) | fix | disallow event attribute bindings in host bindings unconditionally | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [629905d537](https://github.com/angular/angular/commit/629905d537f59dc3c264c49f6347e3599dea0215) | fix | add `allowedHosts` option to `renderModule` and `renderApplication` | +| [0b7192f441](https://github.com/angular/angular/commit/0b7192f4410d055191ac9b15bff57d1d0b9a644f) | fix | forward BEFORE_APP_SERIALIZED errors to ErrorHandler | + + + + +# 19.2.22 (2026-05-12) +### core +| Commit | Type | Description | +| -- | -- | -- | +| [83a640516f](https://github.com/angular/angular/commit/83a640516f7b1fff4dfb0fd0ed8b19876bdb00c4) | fix | disallow event attribute bindings in host bindings unconditionally ([#68469](https://github.com/angular/angular/pull/68469)) | +| [24a0103a98](https://github.com/angular/angular/commit/24a0103a9898b1547f5d1f57314e2bb6545a2c7a) | fix | validate security-sensitive attributes in i18n bindings ([#68469](https://github.com/angular/angular/pull/68469)) | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [8569db8875](https://github.com/angular/angular/commit/8569db88758d189544b03ec4474fd5334ff29346) | fix | add `allowedHosts` option to `renderModule` and `renderApplication` | +| [837a710217](https://github.com/angular/angular/commit/837a7102172502dd3d92793ec15b2d4e533a573d) | fix | ensure origin has a trailing slash when parsing url ([#68469](https://github.com/angular/angular/pull/68469)) | + + + + +# 20.3.21 (2026-05-12) +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [f584840e2e](https://github.com/angular/angular/commit/f584840e2e50f751397cf3fad5258e18e857427e) | fix | add `allowedHosts` option to `renderModule` and `renderApplication` | + + + + +# 22.0.0-next.12 (2026-05-08) +### core +| Commit | Type | Description | +| -- | -- | -- | +| [8ebae1de33](https://github.com/angular/angular/commit/8ebae1de330729f945391283e25661aada11b4ed) | fix | allow service with factory on abstract classes | +| [6f525245cd](https://github.com/angular/angular/commit/6f525245cd97a934b2b5ea888ee9d52c26c58cb5) | fix | disallow event attribute bindings in host bindings unconditionally | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [0f2160c410](https://github.com/angular/angular/commit/0f2160c4105a53ef6488d2c799dda9c0959ce7dc) | fix | remove compiler import from safe optional chaining migration | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [a451a1d66e](https://github.com/angular/angular/commit/a451a1d66ee4bf52b24b06dc9d35a7b7ae7b7eb5) | fix | add `allowedHosts` option to `renderModule` and `renderApplication` | + + + + +# 22.0.0-next.11 (2026-05-06) +## Breaking Changes +### forms +- `min` and `max` validation rules no longer support + string values. Bound values must be numbers or null. +## Deprecations +### http +- The `reportProgress` option is deprecated please use `reportUploadProgress` & `reportDownloadProgress` instead. +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [b225a5d902](https://github.com/angular/angular/commit/b225a5d902f0ee1f6f68cde42266748cb1f2b1f8) | fix | invalid type checking code if field name needs to be quoted | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [b8d3f36ed9](https://github.com/angular/angular/commit/b8d3f36ed962bd4f5abd6bf6e55078b56ce9fffa) | feat | add support for Node.js 26.0.0 | +| [2eae497a04](https://github.com/angular/angular/commit/2eae497a04a6a9b34397181dcd64dbd103f76c47) | feat | support external TCBs with copied content in specific mode | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [3b0ae5fef0](https://github.com/angular/angular/commit/3b0ae5fef0328477ee0f5d51980217e7c583a606) | feat | add `provideWebMcpTools` | +| [5a7c1e62dc](https://github.com/angular/angular/commit/5a7c1e62dc2a4fa199b85150eca66914c107a6f4) | feat | add ability to cache resources for SSR | +| [ef1810197b](https://github.com/angular/angular/commit/ef1810197b679bfcbf21a139b930984302cbe77f) | feat | export experimental `declareWebMcpTool` support | +| [1ab654cf28](https://github.com/angular/angular/commit/1ab654cf281559294bdd3b900ad81490cb91007f) | fix | allow explicit read generic with signal input transforms | +| [49748b5c79](https://github.com/angular/angular/commit/49748b5c7989b4e27686798ea7935e87d804eece) | fix | enforce return type for service factory | +| [6339d264eb](https://github.com/angular/angular/commit/6339d264eb2c00e956b504691842e49cfe365e80) | fix | i18n flags leaking on errors | +| [7aad302c3e](https://github.com/angular/angular/commit/7aad302c3ee6e9c711ab10ae0a9e8bc66d35291c) | fix | mark service decorator as stable | +| [4c9afb68a3](https://github.com/angular/angular/commit/4c9afb68a3447388f9ef1264888ea5dd6cb95dad) | fix | respect ngSkipHydration on components with projectable nodes in LContainers | +| [9d7a609458](https://github.com/angular/angular/commit/9d7a609458f9d9a3f988155c9481a862c4c51eb0) | fix | validate security-sensitive attributes in i18n bindings | +| [0ea27f4e65](https://github.com/angular/angular/commit/0ea27f4e652ddcf444b4c22a3b9643b7cc645926) | fix | visit ng-let expression value in signal migration schematics | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [7745365910](https://github.com/angular/angular/commit/7745365910771d97c91e9b640c2c26a99bfa5a6d) | feat | graduate signal forms APIs to public API | +| [3524de29f3](https://github.com/angular/angular/commit/3524de29f34bef5df941e08e88920dffe4f880c8) | fix | Add support for range type with outside of native bounds | +| [0ea50ffe5a](https://github.com/angular/angular/commit/0ea50ffe5adb07515867e8bf30d1abee49413003) | fix | ensure debounced async validators produce pending status during debounce | +| [3c44d7c90b](https://github.com/angular/angular/commit/3c44d7c90b2392f7307d1b1dd0734db10ede63f5) | fix | fix orphan field error on blur during array removal | +| [849dba6c65](https://github.com/angular/angular/commit/849dba6c6506c2696a43a3fad6ee459e17b4b6c8) | fix | implement custom control reset propagation | +| [5835a5e3a7](https://github.com/angular/angular/commit/5835a5e3a73686473ad064f53f93d9d9acb541a6) | fix | prevent orphan field crashes in debounceSync and async validation | +| [708631f2c4](https://github.com/angular/angular/commit/708631f2c48c146f2c6864c5edfec1d9ca4b0fe9) | fix | prohibit concurrent submits in signal forms | +| [68c3abbe09](https://github.com/angular/angular/commit/68c3abbe09f1937081b83af3c7d82ed1a044974f) | fix | synchronize controls with the model on reset | +| [e0536091f5](https://github.com/angular/angular/commit/e0536091f5f6c2033e377998eea3bf65b14f5ac6) | perf | optimize reactivity by using shallow array equality | +| [9b9769479b](https://github.com/angular/angular/commit/9b9769479b295bf34bae9a938ee758a256bd4b32) | perf | shortcut deepSignal writes if value is unchanged | +| [592a12d6c9](https://github.com/angular/angular/commit/592a12d6c947a0210020b00fd98ffa9fdaca2c20) | refactor | remove string support from min and max validation rules ([#68001](https://github.com/angular/angular/pull/68001)) | +### http +| Commit | Type | Description | +| -- | -- | -- | +| [7c8c3347ef](https://github.com/angular/angular/commit/7c8c3347efc1be2b5967b9481e3a2a3a23c24977) | refactor | Add `reportUploadProgress` & `reportDownloadProgress` options | +### language-service +| Commit | Type | Description | +| -- | -- | -- | +| [dc9c72da9b](https://github.com/angular/angular/commit/dc9c72da9b4ca499eebf6e78d7ccc31ea6f63580) | fix | Add support for `@Input` with transforms | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [c84642ac16](https://github.com/angular/angular/commit/c84642ac16bf3588c071bbdcc684daa8d4e494b3) | feat | add unmatchedInputBehavior option to componentInputBinding | + + + + +# 21.2.12 (2026-05-06) +### core +| Commit | Type | Description | +| -- | -- | -- | +| [fe13bb669d](https://github.com/angular/angular/commit/fe13bb669d2bfab4713623d17b41c430aa0a61d8) | fix | allow explicit read generic with signal input transforms | +| [3430251fef](https://github.com/angular/angular/commit/3430251fef93f6aec1fa9c7867e85df23f67c9a0) | fix | i18n flags leaking on errors | +| [1aeebbe304](https://github.com/angular/angular/commit/1aeebbe3048b5aa612dd0a5448de9883ed51e7e8) | fix | respect ngSkipHydration on components with projectable nodes in LContainers | +| [9e38ed7d57](https://github.com/angular/angular/commit/9e38ed7d5773a9193ba07afdba3f7a9f2fe02d18) | fix | sanitizer typings | +| [7a05a9a71a](https://github.com/angular/angular/commit/7a05a9a71a5ab75042ec5560c01526de6e61e062) | fix | validate security-sensitive attributes in i18n bindings | +| [c37f6ca42f](https://github.com/angular/angular/commit/c37f6ca42f263353cb9563fa90d7b31d3c7837ca) | fix | visit ng-let expression value in signal migration schematics | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [03ad53863b](https://github.com/angular/angular/commit/03ad53863bf3c368f0f02a4322d4141e8f70f674) | fix | prohibit concurrent submits in signal forms | - -# 21.2.0-next.3 (2026-02-11) + +# 20.3.20 (2026-05-06) +### core +| Commit | Type | Description | +| -- | -- | -- | +| [a9bcffdbc7](https://github.com/angular/angular/commit/a9bcffdbc7697715f3d4fa91d924a5b905d637b0) | fix | disallow event attribute bindings in host bindings unconditionally ([#68468](https://github.com/angular/angular/pull/68468)) | +| [97eeb45cfa](https://github.com/angular/angular/commit/97eeb45cfa5fbd89013d75b5d862095d34b8ba58) | fix | validate security-sensitive attributes in i18n bindings ([#68468](https://github.com/angular/angular/pull/68468)) | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [25e4e07238](https://github.com/angular/angular/commit/25e4e07238021a3641f96bb5f5648d74a83f1712) | fix | ensure origin has a trailing slash when parsing url ([#68468](https://github.com/angular/angular/pull/68468)) | + + + + +# 22.0.0-next.10 (2026-04-29) ### common | Commit | Type | Description | | -- | -- | -- | -| [18003a33bb](https://github.com/angular/angular/commit/18003a33bb0d6bb09def8a0e5939fa24069696eb) | feat | add an 'outlet' injector option for ngTemplateOutlet | -| [51cc914807](https://github.com/angular/angular/commit/51cc91480761b7275c15b5600381207f8ca00ee5) | feat | support height in ImageLoaderConfig and built-in loaders | +| [97cac1cf4d](https://github.com/angular/angular/commit/97cac1cf4d0efa49199fdd5736263d01316c7ff3) | fix | prevent focus from scrollToAnchor | +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [2896c93cc1](https://github.com/angular/angular/commit/2896c93cc1077e1306acd91f4ed62fed4204a26b) | feat | Angular expressions with optional chaining returns `undefined` | +| [6bd1721662](https://github.com/angular/angular/commit/6bd17216627978d68bb1c153af347b346a5aa503) | fix | let declaration span not including end character | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [444b024d49](https://github.com/angular/angular/commit/444b024d49725afc8b40aec67cfdb63a1f7f23ea) | feat | Add a `injectAsync` helper function | +| [8c11816490](https://github.com/angular/angular/commit/8c11816490074f9d7dbde2fb854d8225b775a9cb) | fix | fix ordering of view queries metadata in JIT mode | +| [3583c01bf9](https://github.com/angular/angular/commit/3583c01bf9a14f9e91d5173f8bae72a14ee99736) | fix | guard against non-object events and avoid listener wrapper identity mismatch | +| [d5fd51e956](https://github.com/angular/angular/commit/d5fd51e9569b44340274c3bd3b77993c0d21da9b) | fix | prevent event replay double-invocation when element hydrates before app stability | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [8f8972b0fd](https://github.com/angular/angular/commit/8f8972b0fdea2020800e7df5c6d85938602cb7e7) | feat | model + output migrations | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [2a6b6fafb0](https://github.com/angular/angular/commit/2a6b6fafb032b840797625590037bb4f1d8c6261) | fix | ensure origin has a trailing slash when parsing url | + + + + +# 21.2.11 (2026-04-29) +### common +| Commit | Type | Description | +| -- | -- | -- | +| [10ad3c0692](https://github.com/angular/angular/commit/10ad3c06923453ae0ec06b06e664ce05900a4ff6) | fix | prevent focus from scrollToAnchor | +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [4f5d8a2c0b](https://github.com/angular/angular/commit/4f5d8a2c0b5e38d4debc4293945270cea4a9590d) | fix | let declaration span not including end character | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [a40e2cebc8](https://github.com/angular/angular/commit/a40e2cebc878965c3e21bfb61658f3f80cbd2ebf) | fix | fix ordering of view queries metadata in JIT mode | +| [885a1a1d97](https://github.com/angular/angular/commit/885a1a1d9757adfa8766d9b369c848a277438c31) | fix | guard against non-object events and avoid listener wrapper identity mismatch | +| [7a64aff9b5](https://github.com/angular/angular/commit/7a64aff9b59999077ea915486a7fa0b97a286659) | fix | prevent event replay double-invocation when element hydrates before app stability | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [be1f80a253](https://github.com/angular/angular/commit/be1f80a253b8ee27ed7d8de2287d6895c4821909) | fix | ensure origin has a trailing slash when parsing url | + + + + +# 22.0.0-next.9 (2026-04-22) +## Breaking Changes +### router +- paramsInheritanceStrategy now defaults to 'always' + + The default value of paramsInheritanceStrategy has been changed from 'emptyOnly' to 'always'. This means that route parameters are inherited from all parent routes by default. To restore the previous behavior, set paramsInheritanceStrategy to 'emptyOnly' in your router configuration. +### core +| Commit | Type | Description | +| -- | -- | -- | +| [8f3d0b9d97](https://github.com/angular/angular/commit/8f3d0b9d97424e058eb7bce57d80833fb68dec4a) | feat | introduce `@Service` decorator | +| [9f479ae964](https://github.com/angular/angular/commit/9f479ae9641a5c928f8eeab9c7846245002b3eff) | feat | Update Testability to use PendingTasks for stability indicator | +### docs +| Commit | Type | Description | +| -- | -- | -- | +| [b24b4cb699](https://github.com/angular/angular/commit/b24b4cb699c325fc2ce40681724341baaabf277b) | fix | link formatting in "Animating your Application with CSS" | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [b395173cf2](https://github.com/angular/angular/commit/b395173cf206b8c04c5ab74298e640c9086d0bac) | fix | fix NgClass leaving trailing comma after removal | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [6eff439546](https://github.com/angular/angular/commit/6eff4395467de51a46656d79d957b448b32dde0c) | fix | restore internal URL on popstate when `browserUrl` is used | +| [17d10f7a99](https://github.com/angular/angular/commit/17d10f7a9921429d0192df6925d20d7236425c9a) | fix | set default paramsInheritanceStrategy to 'always' | + + + + +# 21.2.10 (2026-04-22) +### docs +| Commit | Type | Description | +| -- | -- | -- | +| [0d5ee9ae1b](https://github.com/angular/angular/commit/0d5ee9ae1ba4b7acd8f27a059a778f0b4bd8a5bd) | fix | link formatting in "Animating your Application with CSS" | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [5533ab4f56](https://github.com/angular/angular/commit/5533ab4f56f574bc9365cf0573c4a34a3ab5aaf1) | fix | fix NgClass leaving trailing comma after removal | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [580212c995](https://github.com/angular/angular/commit/580212c995751c4bf4ce8a49df4167498743e0ea) | fix | restore internal URL on popstate when `browserUrl` is used | + + + + +# 19.2.21 (2026-04-15) +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [f3a5bfb949](https://github.com/angular/angular/commit/f3a5bfb949cb4d2de960b962a53aa16d8435b8e4) | fix | prevent SSRF bypasses via protocol-relative and backslash URLs | + + + + +# 20.3.19 (2026-04-15) +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [303d4cd580](https://github.com/angular/angular/commit/303d4cd580dec38bfaa71a0a34965f151bab3ba8) | fix | prevent SSRF bypasses via protocol-relative and backslash URLs | + + + + +# 22.0.0-next.8 (2026-04-15) +## Breaking Changes +### compiler +- This change will trigger the `nullishCoalescingNotNullable` and `optionalChainNotNullable` diagnostics on exisiting projects. + You might want to disable those 2 diagnotiscs in your `tsconfig` temporarily. +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [47fcbc4704](https://github.com/angular/angular/commit/47fcbc470462192c4f9e273d8dce8b353d5baaa2) | feat | allow safe navigation to correctly narrow down nullables | +| [2c5aabb9da](https://github.com/angular/angular/commit/2c5aabb9daf5da3ad539381ef1e430c77583e3bf) | fix | don't escape dollar sign in literal expression | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [e5f96c2d88](https://github.com/angular/angular/commit/e5f96c2d8813f95c91761ae3080065575ca3b536) | fix | animation events not type checked properly when bound through HostListener decorator | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [4e331062e8](https://github.com/angular/angular/commit/4e331062e8385e066102c3bbb8be439eabfdf8c9) | feat | allow synchronous values for stream Resources | +| [2f5ab541ea](https://github.com/angular/angular/commit/2f5ab541eafba72bc0079a8650d0b96b0ddfde2f) | feat | enhance profiling with documentation URLs | +| [75f2cb8f56](https://github.com/angular/angular/commit/75f2cb8f566de43a5f2fd27bb2982c796b93490d) | feat | implement Angular DI graph in-page AI tool | +| [8ce9cc4f6b](https://github.com/angular/angular/commit/8ce9cc4f6b10d60300dedb6571822ce77a96f2ce) | feat | register AI runtime debugging tools | +| [cdda51a3b2](https://github.com/angular/angular/commit/cdda51a3b2f48d5623acef0c6f54afb7af921b58) | feat | support bootstrapping Angular applications underneath shadow roots | +| [3c7641151c](https://github.com/angular/angular/commit/3c7641151cc50011340d791849954d51399570f5) | fix | escape forward slashes in transfer state to prevent crawler indexing | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [f9f24fc669](https://github.com/angular/angular/commit/f9f24fc6699b762d17127d0412343041ecdea70e) | feat | shim legacy NG_VALIDATORS into parseErrors for CVA mode ([#67943](https://github.com/angular/angular/pull/67943)) | +| [72d3ace03c](https://github.com/angular/angular/commit/72d3ace03c1292ba9d6fdf7b418ba3287bf54316) | fix | use controlValue in NgControl for CVA interop ([#67943](https://github.com/angular/angular/pull/67943)) | +### http +| Commit | Type | Description | +| -- | -- | -- | +| [39e382a756](https://github.com/angular/angular/commit/39e382a756b552d2b7bd3ce2c364daee9d7a0056) | fix | add CSP nonce support to JsonpClientBackend | +| [d1cd97648a](https://github.com/angular/angular/commit/d1cd97648a943717fe42a174ab8006e06c757fde) | fix | Don't on Passthru outside of reactive context | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [6a435658e2](https://github.com/angular/angular/commit/6a435658e25f9c81ddeaaa72d9c9694fc02bbef1) | feat | Disabling nullishCoalescingNotNullable & optionalChainNotNullable on ng update | +| [1415d86980](https://github.com/angular/angular/commit/1415d869804729e50ed4bcdc829da870b4a70206) | fix | Fix typo for strict-template migration | +### platform-browser +| Commit | Type | Description | +| -- | -- | -- | +| [68628dd45b](https://github.com/angular/angular/commit/68628dd45bfcf4ea33bc00798bab1e4ab9da804c) | feat | make incremental hydration default behavior | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [ede7c58a2a](https://github.com/angular/angular/commit/ede7c58a2aa13fdccc8f0b67ce93ba1c11749412) | fix | prevent SSRF bypasses via protocol-relative and backslash URLs | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [c90b6b398e](https://github.com/angular/angular/commit/c90b6b398ead0727a5e22668a4617e687258d466) | fix | normalize multiple leading slashes in URL parser | +### service-worker +| Commit | Type | Description | +| -- | -- | -- | +| [836094c072](https://github.com/angular/angular/commit/836094c072cb0f6cdbd35469ee02158667a9ba51) | fix | resolve TS 6.0 compatibility for messageerror listener | + + + + +# 21.2.9 (2026-04-15) +### core +| Commit | Type | Description | +| -- | -- | -- | +| [f603d4714f](https://github.com/angular/angular/commit/f603d4714fa184aad34a6f7f9ea4e79c8af3afac) | fix | escape forward slashes in transfer state to prevent crawler indexing | +### http +| Commit | Type | Description | +| -- | -- | -- | +| [540536c386](https://github.com/angular/angular/commit/540536c386f2c735a700c2c9e2697a88dcb3d4ec) | fix | add CSP nonce support to JsonpClientBackend | +| [63a857b874](https://github.com/angular/angular/commit/63a857b874172766451aa75ed3347ba50f0ee229) | fix | Don't on Passthru outside of reactive context | +### platform-server +| Commit | Type | Description | +| -- | -- | -- | +| [e0b5078cf2](https://github.com/angular/angular/commit/e0b5078cf2ebe79a6de85e9123148ae948b3d81d) | fix | prevent SSRF bypasses via protocol-relative and backslash URLs | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [684e9fd53d](https://github.com/angular/angular/commit/684e9fd53daacb9e910f42d98c6017f9e5cb4180) | fix | normalize multiple leading slashes in URL parser | + + + + +# 22.0.0-next.7 (2026-04-08) +## Breaking Changes +### core +- The second argument of appRef.bootstrap does not accept `any` anymore. Make sure the element you pass is not nullable. +- * TypeScript versions older than 6.0 are no longer supported. +- `ComponentFactoryResolver` and `ComponentFactory` are no longer available. Pass the component class directly to APIs that previously required a factory, such as `ViewContainerRef.createComponent` or use the standalone `createComponentFunction`. +- `ComponentFactoryResolver` and `ComponentFactory` are no longer available. Pass the component class directly to APIs that previously required a factory, such as `ViewContainerRef.createComponent` or use the standalone `createComponent` function. +### platform-browser +- This removes styles when they appear to no longer be used by an associated `host`. However other DOM on the page may still be affected by those styles if not leveraging `ViewEncapsulation.Emulated` or if those styles are used by elements outside of Angular, potentially causing other DOM to appear unstyled. +### router +- The `currentSnapshot` parameter in `CanMatchFn` and the `canMatch` method of the `CanMatch` interface is now required. While this was already the behavior of the Router at runtime, existing class implementations of `CanMatch` must now include the third argument to satisfy the interface. +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [2ce0e98f79](https://github.com/angular/angular/commit/2ce0e98f79a02ddc550d00580e8e232cfed3bfb2) | fix | handle nested brackets in host object bindings | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [7f9450219f](https://github.com/angular/angular/commit/7f9450219f5c30d1ce0a90061864e8c844c8807c) | feat | Adds warning for prefetch without main defer trigger | +| [ab061a7610](https://github.com/angular/angular/commit/ab061a7610bfcc5aad15fdc2d812085ae3e8d9b1) | fix | error for type parameter declarations | +| [9218140348](https://github.com/angular/angular/commit/9218140348cb2e3ad301c1e7f37db4b0cdad4f9d) | fix | resolve TCB mapping failure for safe property reads with as any | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [a0aa8304cd](https://github.com/angular/angular/commit/a0aa8304cd78a58a990c3b648e41f6888b50b1b3) | feat | bootstrap via `ApplicationRef` with config | +| [9c55fcb3e6](https://github.com/angular/angular/commit/9c55fcb3e65ffcde32d7ac438ea40a69ffc2b3b6) | feat | de-duplicate host directives | +| [8fe025f514](https://github.com/angular/angular/commit/8fe025f5149d7eb460e784a5a17bb467f85b9080) | feat | drop support for TypeScript 5.9 | +| [77f1ca08e4](https://github.com/angular/angular/commit/77f1ca08e4aa1d1ddd1a8062c602eac0d1044d5a) | fix | handle missing serialized container hydration data | +| [a24179e125](https://github.com/angular/angular/commit/a24179e125147b2b608a291e8f93da79668f68ec) | fix | remove obsolete iOS cursor pointer hack in event delegation | +| [9d76ac8229](https://github.com/angular/angular/commit/9d76ac82290e047f1481fb38bd95233e951a77de) | refactor | remove ComponentFactoryResolver & ComponentFactory from the api surface | +| [b1f5181ffd](https://github.com/angular/angular/commit/b1f5181ffd8e9906affd486d9e2f655eb144f175) | refactor | remove ComponentFactoryResolver & ComponentFactory from the api surface"" | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [de56d74da3](https://github.com/angular/angular/commit/de56d74da39178308b81a2d94c8eb4488cb0cbab) | fix | align FormField CVA selection priority with standard forms | +| [394ad0c2a2](https://github.com/angular/angular/commit/394ad0c2a26eec8a8f7136b1b7971420b30a117e) | fix | allow late-bound input types for signals forms | +| [2e9aeea0fe](https://github.com/angular/angular/commit/2e9aeea0fed1a2eae261b95cb1479519d0428b83) | fix | deduplicate writeValue calls in CVA interop | +### language-service +| Commit | Type | Description | +| -- | -- | -- | +| [75ac120493](https://github.com/angular/angular/commit/75ac1204936dcf1f5646ec918732bee9ade22f99) | fix | get quick info at local var location to align with TS semantics and support type narrowing | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [8216d34976](https://github.com/angular/angular/commit/8216d349768687ed0cf9ef6e1d737e7db9c9e28b) | feat | Add migration for CanMatchFn snapshot parameter ([#67452](https://github.com/angular/angular/pull/67452)) | +### platform-browser +| Commit | Type | Description | +| -- | -- | -- | +| [d45b7a91f9](https://github.com/angular/angular/commit/d45b7a91f961ee40e3ea0f0ae837bf543bddb520) | fix | remove unused styles when associated `host` is dropped | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [579440170b](https://github.com/angular/angular/commit/579440170b372f8348cf3e5b5ce9f9f430093947) | fix | make currentSnapshot required in CanMatchFn ([#67452](https://github.com/angular/angular/pull/67452)) | + + + + +# 21.2.8 (2026-04-08) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [e40d378f3e](https://github.com/angular/angular/commit/e40d378f3e3e7e57a45c8fbd9565ee06a3a6a13f) | fix | handle nested brackets in host object bindings | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [2c6781071f](https://github.com/angular/angular/commit/2c6781071f52d6378a002fba6611bb283fbb2fde) | fix | error for type parameter declarations | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [82192deda9](https://github.com/angular/angular/commit/82192deda9c07113835e6c85af3f2c8c8218cda0) | fix | handle missing serialized container hydration data | +| [057cc6d09d](https://github.com/angular/angular/commit/057cc6d09d234f401a810cfdd3ad14127652b88b) | fix | remove obsolete iOS cursor pointer hack in event delegation | +### language-service +| Commit | Type | Description | +| -- | -- | -- | +| [7797671257](https://github.com/angular/angular/commit/7797671257350665e8b3ceb2bc6a0201829dd338) | fix | get quick info at local var location to align with TS semantics and support type narrowing | + + + + +# 22.0.0-next.6 (2026-04-01) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [08d36599d7](https://github.com/angular/angular/commit/08d36599d724d6b3dbe2c9891c32f783bef4e157) | fix | register SVG animation attributes in URL security context ([#67797](https://github.com/angular/angular/pull/67797)) | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [fcd0bb0db8](https://github.com/angular/angular/commit/fcd0bb0db83576ef0bc13c5c32f158d95efbedd5) | fix | prevent recursive scope checks for invalid NgModule imports | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [e84e35cdd6](https://github.com/angular/angular/commit/e84e35cdd60696d8670421189e4aa02c1db583a6) | fix | prevent binding unsafe attributes on SVG animation elements ([#67797](https://github.com/angular/angular/pull/67797)) | +| [8fa6617352](https://github.com/angular/angular/commit/8fa66173523bb275d33de4bd43ce23947812922e) | fix | resolve component import by exact specifier in route lazy-loading schematic | +| [028e1d3ce0](https://github.com/angular/angular/commit/028e1d3ce0ed3a33d3b7730a51b549a5cf013b29) | fix | treat `object[data]` as resource URL context ([#67797](https://github.com/angular/angular/pull/67797)) | +### localize +| Commit | Type | Description | +| -- | -- | -- | +| [7871093822](https://github.com/angular/angular/commit/78710938225d2c29c6e3666e431c2952bb5560db) | fix | validate locale in getOutputPathFn to prevent path traversal | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [682aaf943f](https://github.com/angular/angular/commit/682aaf943fea3d99f9f834b0bad4d165b4b28071) | feat | add strictTemplates to tsconfig during ng update | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [daa9b2a9d6](https://github.com/angular/angular/commit/daa9b2a9d67557ae9246559f74396f43b240581c) | fix | pass outlet context to split to fix empty path named outlets | + + + + +# 21.2.7 (2026-04-01) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [fea25d1a60](https://github.com/angular/angular/commit/fea25d1a60ecaba1599d9cd9b8df27109ed195c5) | fix | register SVG animation attributes in URL security context ([#67797](https://github.com/angular/angular/pull/67797)) | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [bba5ed8e64](https://github.com/angular/angular/commit/bba5ed8e643b9c3f680e7e539c3d744ad6905e59) | fix | prevent recursive scope checks for invalid NgModule imports | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [d04ddd73df](https://github.com/angular/angular/commit/d04ddd73dfc03f420afbdde964c5119f338af135) | fix | prevent binding unsafe attributes on SVG animation elements ([#67797](https://github.com/angular/angular/pull/67797)) | +| [8fd896e99a](https://github.com/angular/angular/commit/8fd896e99a13855c6569f29efe7e578c301e13ee) | fix | resolve component import by exact specifier in route lazy-loading schematic | +| [b682c62873](https://github.com/angular/angular/commit/b682c628731b86a4884e50abb2f5fa73ac0ad057) | fix | treat `object[data]` as resource URL context ([#67797](https://github.com/angular/angular/pull/67797)) | +### localize +| Commit | Type | Description | +| -- | -- | -- | +| [3c41e74fdd](https://github.com/angular/angular/commit/3c41e74fdd279f683156b654699a9312a850add0) | fix | validate locale in getOutputPathFn to prevent path traversal | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [0960592d3d](https://github.com/angular/angular/commit/0960592d3d4fad110d5598144fda9f2488520826) | fix | pass outlet context to split to fix empty path named outlets | + + + + +# 22.0.0-next.5 (2026-03-25) +## Breaking Changes +### compiler-cli +- Elements with multiple matching selectors will now throw at compile time. +### core +- Component with undefined `changeDetection` property are now `OnPush` by default. Specify `changeDetection: ChangeDetectionStrategy.Eager` to keep the previous behavior. +### platform-browser +- Hammer.js integration has been removed. Use your own implementation. +### common +| Commit | Type | Description | +| -- | -- | -- | +| [c1312da183](https://github.com/angular/angular/commit/c1312da1832e2b59ce18edae8fae13d3f562d9b0) | fix | avoid redundant image fetch on destroy with auto sizes | +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [e850643b1b](https://github.com/angular/angular/commit/e850643b1b8dca8cfdc12705be51441197cd987a) | feat | Support comments in html element. | +| [96be4f429b](https://github.com/angular/angular/commit/96be4f429ba316c75d2d4a39ececcc529ec10943) | fix | abstract emitter producing incorrect code for dynamic imports | +| [5a712d42d1](https://github.com/angular/angular/commit/5a712d42d161c93f6315d6462543c26c4e4a4490) | fix | prevent shimCssText from adding extra blank lines per CSS comment | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [ca67828ee2](https://github.com/angular/angular/commit/ca67828ee247bdff46736661e51f43f2ca736a24) | refactor | introduce NG8023 compile-time diagnostic for duplicate selectors | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [8bc31a515f](https://github.com/angular/angular/commit/8bc31a515ff6e8edda6ea5786a47ae5a788acd36) | feat | Allow other expression for exhaustive typechecking | +| [eae8f7e30b](https://github.com/angular/angular/commit/eae8f7e30b9f8bebdcdb535bd86260199c34274b) | feat | Set default Component changeDetection strategy to OnPush | +| [16adbbf423](https://github.com/angular/angular/commit/16adbbf4234cc67507f578e588a8500fc5d31013) | fix | ensure custom controls resolve transitive host directives | +| [dfa149dc68](https://github.com/angular/angular/commit/dfa149dc68c83c8d7d765aee435b940a8e89a235) | fix | fixes a regression with animate.leave and reordering | +| [50e599e73e](https://github.com/angular/angular/commit/50e599e73ec5bb8f483e749d76fff579e33b1670) | fix | lazy-initialize debounced state to prevent computation cycle | +| [22f8b0a500](https://github.com/angular/angular/commit/22f8b0a500807e69b323378b843465a949e08abf) | fix | resolver function not matching expected type | +| [5e99ae9f00](https://github.com/angular/angular/commit/5e99ae9f00fb119cac93a19bbf36aee71299cae1) | fix | widen type for directive inputs/outputs | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [74f76d8075](https://github.com/angular/angular/commit/74f76d8075d03b1271aef37b974c9e15f9c7d3af) | feat | add `reloadValidation` to Signal Forms to manually trigger async validation | +| [24e52d450d](https://github.com/angular/angular/commit/24e52d450d201e3da90bb64f84358f9eccd7877d) | feat | add debounce option to validateAsync and validateHttp | +| [709f5a390c](https://github.com/angular/angular/commit/709f5a390ca0de04f8066012a5cb36999f2fd4a6) | feat | add FieldState.getError() | +| [41b1410cb8](https://github.com/angular/angular/commit/41b1410cb8a333a2ce6569483cd10866effc154d) | feat | support binding `number|null` to `` | +| [0eeb1b5f03](https://github.com/angular/angular/commit/0eeb1b5f03395ea0ddb047790af4cf1440655a07) | fix | allow `FormRoot` to be used without submission options ([#67727](https://github.com/angular/angular/pull/67727)) | +| [ee8d2098cb](https://github.com/angular/angular/commit/ee8d2098cb3cdce1589c462cd9a66eae490477f9) | fix | change FieldState optional properties to non-optional | undefined | +| [df8b020299](https://github.com/angular/angular/commit/df8b020299b5e579956578d9137cab93a8065045) | fix | clear native date inputs correctly in signal forms when changed via native UI | +| [98c5afdb02](https://github.com/angular/angular/commit/98c5afdb02192f99c886fc3fda13ec6f39018f23) | perf | lazily instantiate signal form fields | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [a73b4b7c30](https://github.com/angular/angular/commit/a73b4b7c30ae943966ad6deecf5a284cddb1f3fd) | fix | inject migration not work in multi-project workspace with option path | +### platform-browser +| Commit | Type | Description | +| -- | -- | -- | +| [f99e7ed20f](https://github.com/angular/angular/commit/f99e7ed20f0b1a26fd275fcf5befd589bb4e5d31) | refactor | remove Hammer integration | + + + + +# 21.2.6 (2026-03-25) +### common +| Commit | Type | Description | +| -- | -- | -- | +| [b4ab6ba2e8](https://github.com/angular/angular/commit/b4ab6ba2e84a18309b0bb5dd68311ff1776b1cb4) | fix | avoid redundant image fetch on destroy with auto sizes | +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [880a57d4b3](https://github.com/angular/angular/commit/880a57d4b34af5aa27cd5bee11fa218ade6444bb) | fix | prevent shimCssText from adding extra blank lines per CSS comment | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [ad0156e056](https://github.com/angular/angular/commit/ad0156e056e60ffebfeb804fda70dce88d9475a8) | fix | fixes a regression with animate.leave and reordering | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [73d6b01b47](https://github.com/angular/angular/commit/73d6b01b47bb6762d182f1cd891f8ad4d7f688e1) | fix | inject migration not work in multi-project workspace with option path | + + + + +# 22.0.0-next.4 (2026-03-18) +## Breaking Changes +### core +- Leave animations are no longer limited to the element being removed. +- `ChangeDetectorRef.checkNoChanges` was removed. In tests use `fixture.detectChanges()` instead. +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [412788fac9](https://github.com/angular/angular/commit/412788fac9c82ddf94b3f5ad0b141098c8101b0b) | fix | ensure generated code compiles | +| [75560ce43d](https://github.com/angular/angular/commit/75560ce43d7422a131ae86f2312c478754d035aa) | fix | parse named HTML entities containing digits | +| [d99ab0e040](https://github.com/angular/angular/commit/d99ab0e0400d256021d6cc601e2a6e16f784a406) | fix | stop generating unused field | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [2bd708fb6b](https://github.com/angular/angular/commit/2bd708fb6bc3ada19c5dd21077a7f609996f6346) | fix | escape template literal in TCB | +| [9769560da7](https://github.com/angular/angular/commit/9769560da73efee4793dfdc1459c8b1ac10981de) | fix | generic types not filled out correctly in type check block | +| [7a0d6b8df2](https://github.com/angular/angular/commit/7a0d6b8df21ca6a407e5c63dc0af753bc39c90c5) | fix | transform dropping exclamationToken from properties | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [df659b8d0c](https://github.com/angular/angular/commit/df659b8d0cf64eeed418c60bc16cae5630086401) | feat | re-introduce nested leave animations scoped to component boundaries | +| [dc3131c639](https://github.com/angular/angular/commit/dc3131c639542ad6a463bff3da5ca84c6f8ecb6f) | feat | TestBed.getFixture -> TestBed.getLastFixture and update implementation | +| [dc0446552a](https://github.com/angular/angular/commit/dc0446552af76bfcac4642c975b293d4dcb93d46) | fix | clean up dehydrated views during HMR component replacement | +| [523d69a768](https://github.com/angular/angular/commit/523d69a7685a3437f1f6ef8fedfd26c52d2d3bb1) | fix | run linked signal equality check without reactive consumer | +| [69fb1614ef](https://github.com/angular/angular/commit/69fb1614eff6e40bb7dcca81f275ac32b9cbd28a) | refactor | remove `checkNoChanges` from the public API. | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [3983080236](https://github.com/angular/angular/commit/3983080236e348ecc17ab4e65a6a5cc0a16aa315) | feat | support ngNoCva as an opt-out for ControlValueAccessors | +| [c4ce3f345f](https://github.com/angular/angular/commit/c4ce3f345fdb14595f0991dff488c4043a0fc71c) | feat | template & reactive support for FVC | +| [83032e3605](https://github.com/angular/angular/commit/83032e36059ad0fc61cde2ac26c1eb0cede14e8c) | fix | support generic unions in signal form schemas | +### language-service +| Commit | Type | Description | +| -- | -- | -- | +| [cfd0f9950c](https://github.com/angular/angular/commit/cfd0f9950c08324e1c56f16d98a2e3081feeda58) | feat | add Document Symbols support for Angular templates | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [730684b9ce](https://github.com/angular/angular/commit/730684b9ce8335b91ff224422fb12b7eafeaec1d) | fix | prevent trailing comma syntax errors after removing NgStyle | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [89c9a4de30](https://github.com/angular/angular/commit/89c9a4de308a087ce95246ee259f32c8a927e39e) | feat | Add `options` optional parameter for `withComponentInputBinding` | +### service-worker +| Commit | Type | Description | +| -- | -- | -- | +| [07abfbcc6c](https://github.com/angular/angular/commit/07abfbcc6c59d9b4949fdad2a975005d0f28eea7) | fix | preserve redirect policy on reconstructed asset requests | + + + + +# 21.2.5 (2026-03-18) ### compiler | Commit | Type | Description | | -- | -- | -- | -| [11834a4274](https://github.com/angular/angular/commit/11834a42745e62830a83a4c14eea9d203baec680) | fix | add geolocation element to schema | +| [334ae10168](https://github.com/angular/angular/commit/334ae10168fdad15cd1390180e2994b4eb65349b) | fix | ensure generated code compiles | +| [23ea431c4e](https://github.com/angular/angular/commit/23ea431c4ec45cbb4a7db9839969e7cb23b07f58) | fix | parse named HTML entities containing digits | ### compiler-cli | Commit | Type | Description | | -- | -- | -- | -| [2ea6dfc6c9](https://github.com/angular/angular/commit/2ea6dfc6c9ca11e96a2654510c980419899f8d04) | fix | update diagnostic to flag no-op arrow functions in listeners | +| [26c43d14ba](https://github.com/angular/angular/commit/26c43d14baad1a6b3629a77825e702a97a4f8482) | fix | escape template literal in TCB | +| [67e0ba7e03](https://github.com/angular/angular/commit/67e0ba7e03bb940639f0eafb3af45015e9727eac) | fix | generic types not filled out correctly in type check block | ### core | Commit | Type | Description | | -- | -- | -- | -| [ea2016a6dc](https://github.com/angular/angular/commit/ea2016a6dce58f95ecab7c773d5dcde274354e1a) | feat | add support for nested animations | -| [bd2868e915](https://github.com/angular/angular/commit/bd2868e915e78fb60583c00a11c778e3abf3ed8d) | fix | capture animation dependencies eagerly to avoid destroyed injector | -| [a7e8abbb7e](https://github.com/angular/angular/commit/a7e8abbb7e738ba338c3f50c76934c99925954e5) | fix | correctly handle SkipSelf when resolving from embedded view injector | -| [e53c8abaf9](https://github.com/angular/angular/commit/e53c8abaf9f09ca66b55f8169b9d723d1ffb640a) | fix | Fix flakey test due to document injection | -### forms +| [1890c3008b](https://github.com/angular/angular/commit/1890c3008bbb41b7143b7ede09bed1f7704744fb) | fix | clean up dehydrated views during HMR component replacement | +| [bf948be4c2](https://github.com/angular/angular/commit/bf948be4c2c88c604e428cba35e3b9e532bfe5b0) | fix | run linked signal equality check without reactive consumer | +### migrations | Commit | Type | Description | | -- | -- | -- | -| [f56bb07d83](https://github.com/angular/angular/commit/f56bb07d83a015b0ac12e74fdb0cf1550ff36b97) | feat | add field param to submit action and onInvalid | -| [ba009b6031](https://github.com/angular/angular/commit/ba009b603119299a03f9d844f93882d42d47d150) | feat | add form directive | -| [24c0c5a180](https://github.com/angular/angular/commit/24c0c5a180fab31438b1939e501f01b75e2d2760) | feat | support signal-based schemas in validateStandardSchema | -| [adfb83146b](https://github.com/angular/angular/commit/adfb83146b0c149734f43961563b389e00cc1d85) | fix | simplify design of parse errors | -### http +| [076d41c3f6](https://github.com/angular/angular/commit/076d41c3f6496eb6c6f84b54e2d2ca85c1b35e64) | fix | prevent trailing comma syntax errors after removing NgStyle | +### service-worker | Commit | Type | Description | | -- | -- | -- | -| [cb1163e5e5](https://github.com/angular/angular/commit/cb1163e5e5434d5d96897180f6f0eb86d3992964) | fix | correctly parse ArrayBuffer and Blob in transfer cache | +| [e19150d2b5](https://github.com/angular/angular/commit/e19150d2b596e87c69bee61f478c3e9c7cbc8f67) | fix | preserve redirect policy on reconstructed asset requests | - -# 21.1.4 (2026-02-11) + +# 19.2.20 (2026-03-12) ### compiler | Commit | Type | Description | | -- | -- | -- | -| [caab23dfe6](https://github.com/angular/angular/commit/caab23dfe6acf06c3b859af091f5e078b08f1c4c) | fix | add geolocation element to schema | +| [5be912eb55](https://github.com/angular/angular/commit/5be912eb55fe88e8621e2ce82470d51b7d950ceb) | fix | disallow translations of iframe src | ### core | Commit | Type | Description | | -- | -- | -- | -| [2b99eaa019](https://github.com/angular/angular/commit/2b99eaa019b5551a2e2fcf9ff8cd0a796e1e857b) | fix | capture animation dependencies eagerly to avoid destroyed injector | -| [d6aeac504c](https://github.com/angular/angular/commit/d6aeac504c6181f15e5d8afdca3d9c3e3b32652c) | fix | Fix flakey test due to document injection | -### forms +| [b89b0a83a4](https://github.com/angular/angular/commit/b89b0a83a4d21bbb6f8534bbf56aece12af24595) | fix | sanitize translated attribute bindings with interpolations | +| [621c7071ad](https://github.com/angular/angular/commit/621c7071adffbe5dd45a5c954b6b6138e0870844) | fix | sanitize translated form attributes | + + + + +# 20.3.18 (2026-03-12) +### compiler | Commit | Type | Description | | -- | -- | -- | -| [0d1acd0165](https://github.com/angular/angular/commit/0d1acd0165511b57ce853f29486d9b92d0215959) | feat | support signal-based schemas in validateStandardSchema | -### http +| [02fbf08890](https://github.com/angular/angular/commit/02fbf08890ec6ac2efb6c2ec4f17e56497cb81d2) | fix | disallow translations of iframe src | +### core | Commit | Type | Description | | -- | -- | -- | -| [3905015ccc](https://github.com/angular/angular/commit/3905015ccc53399a606dd8e4f3c4d0cce628a08e) | fix | correctly parse ArrayBuffer and Blob in transfer cache | +| [72126f9a08](https://github.com/angular/angular/commit/72126f9a08c185a9b93461bab67841c4e84c9b17) | fix | sanitize translated attribute bindings with interpolations | +| [626bc8bc20](https://github.com/angular/angular/commit/626bc8bc20e485cad2094c4a5d9417fb9a71dda8) | fix | sanitize translated form attributes | - -# 21.2.0-next.2 (2026-02-04) + +# 22.0.0-next.3 (2026-03-12) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [78dea55351](https://github.com/angular/angular/commit/78dea55351fb305b33a919c43a6b363137eca166) | fix | disallow translations of iframe src | ### core | Commit | Type | Description | | -- | -- | -- | -| [8d5210c9fe](https://github.com/angular/angular/commit/8d5210c9fedd8abdd810d7a89ec7ee9a1234f5c1) | feat | add ChangeDetectionStrategy.Eager alias for Default | -| [aff9e36a98](https://github.com/angular/angular/commit/aff9e36a988ad7f11bc7a1ca903f75140d14c02c) | fix | `linkedSignal.update` should propagate errors | -| [8ab433abdd](https://github.com/angular/angular/commit/8ab433abdd4c8984b6f9a16219b10af0c54b31f7) | fix | export DirectiveWithBindings | -| [cab5ddd526](https://github.com/angular/angular/commit/cab5ddd526f5a6ba44b042ba0391f0526877cde6) | fix | hold constructors weakly in DepsTracker cache | -| [c66a19f0de](https://github.com/angular/angular/commit/c66a19f0de1a4f8d0f50079b26f8bb52852be6e1) | fix | prevent element duplication with dynamic components | -### forms +| [999c14eaab](https://github.com/angular/angular/commit/999c14eaab981d12bf2b1d9b1fd6766157f7b1cc) | fix | reverts "feat(core): add support for nested animations" | +| [de0eb4c656](https://github.com/angular/angular/commit/de0eb4c6566011e1a34d529a273ec3d5b6bf17d5) | fix | sanitize translated form attributes | + + + + +# 21.2.4 (2026-03-12) +### compiler | Commit | Type | Description | | -- | -- | -- | -| [95ecce8334](https://github.com/angular/angular/commit/95ecce8334299defe55fb2b74264e5258ffd137c) | feat | allow setting submit options at form-level | -| [3937afc316](https://github.com/angular/angular/commit/3937afc3167ce409eebb06d91d5fb122eea4e33d) | feat | introduce SignalFormControl for Reactive Forms compatibility | -| [dd208ca259](https://github.com/angular/angular/commit/dd208ca2595258fcd1e289374f812ce0b56c7011) | feat | update submit function to accept options object | -| [b1bf535f8e](https://github.com/angular/angular/commit/b1bf535f8e5cbeaec23e9dbb98c1a1bb99deb62a) | fix | Resolves debounce promise on abort in debounceForDuration | -### language-service +| [ed2d324f9c](https://github.com/angular/angular/commit/ed2d324f9cc12aab6cfa0569ef10b73243a62c65) | fix | disallow translations of iframe src | +### core | Commit | Type | Description | | -- | -- | -- | -| [496967e7b1](https://github.com/angular/angular/commit/496967e7b13dfe1ebdde69724cd62880914beb60) | feat | add JSON schema for angularCompilerOptions | -| [8c21866f49](https://github.com/angular/angular/commit/8c21866f49ff74344551395ae0a5df1841d54c0d) | feat | add linked editing ranges for HTML tag synchronization | -### localize +| [abbd8797bb](https://github.com/angular/angular/commit/abbd8797bbd3ae53a10033c39bd895b5b85a4fae) | fix | reverts "feat(core): add support for nested animations" | +| [d1dcd16c5b](https://github.com/angular/angular/commit/d1dcd16c5b40291aa3fa2dc84d22842cd657b201) | fix | sanitize translated form attributes | + + + + +# 22.0.0-next.2 (2026-03-11) +## Breaking Changes +### core +- `createNgModuleRef` was removed, use `createNgModule` instead +### core | Commit | Type | Description | | -- | -- | -- | -| [1c3b1cf18d](https://github.com/angular/angular/commit/1c3b1cf18d14eb795f949ed05c4a6741dc582485) | fix | add support for unit-test builder in ng-add schematic | -### router +| [b918beda32](https://github.com/angular/angular/commit/b918beda323eefef17bf1de03fde3d402a3d4af0) | feat | allow debouncing signals | +| [f9ede9ec98](https://github.com/angular/angular/commit/f9ede9ec98ad233c4bbddf268cce8a647333ebfc) | fix | ensure definitions compile | +| [b401c18674](https://github.com/angular/angular/commit/b401c18674f16bceeaf7c9babcb4b4d70f29be4f) | fix | include signal debug names in their `toString()` representation | +| [8630319f74](https://github.com/angular/angular/commit/8630319f74c9575a21693d875cc7d5252516146d) | fix | sanitize translated attribute bindings with interpolations | +| [36936872c9](https://github.com/angular/angular/commit/36936872c962b2073c8f44080684701068866691) | refactor | remove `createNgModuleRef` | +### forms | Commit | Type | Description | | -- | -- | -- | -| [cf9620f7d0](https://github.com/angular/angular/commit/cf9620f7d072897f13b7f281b7bca6f51f69cfd0) | feat | Make match options optional in isActive | -| [907a94dcec](https://github.com/angular/angular/commit/907a94dcec2926a5c7d0c4d36249bd62e31a2ae3) | feat | Update `IsActiveMatchOptions` APIs to accept a Partial | -| [458bc4a2c8](https://github.com/angular/angular/commit/458bc4a2c88aa6f9f955c8f2bd58dd73ae35b0ad) | fix | limit UrlParser recursion depth to prevent stack overflow | -| [3867cd8554](https://github.com/angular/angular/commit/3867cd85545bfb5bde9e92d46467651337b2b7ae) | perf | Use .bind to avoid holding other closures in memory | +| [3e7ce0dafc](https://github.com/angular/angular/commit/3e7ce0dafcf1c0b9ed7a8c528f7120f5c796a668) | fix | restrict `SignalFormsConfig` to a readonly API | +### language-service +| Commit | Type | Description | +| -- | -- | -- | +| [5a6d88626b](https://github.com/angular/angular/commit/5a6d88626b604db937287a501cb723c088412a7e) | feat | add angular template inlay hints support | - -# 21.1.3 (2026-02-04) + +# 21.2.3 (2026-03-11) ### core | Commit | Type | Description | | -- | -- | -- | -| [2b254bc050](https://github.com/angular/angular/commit/2b254bc0508b73aab8991c3b1a9a703c339cb735) | fix | `linkedSignal.update` should propagate errors | -| [e5110b4fa1](https://github.com/angular/angular/commit/e5110b4fa155e4669ed507f3460d2d29026a28ab) | fix | export DirectiveWithBindings | -| [2cf4da0ea1](https://github.com/angular/angular/commit/2cf4da0ea11f5746eb7ae4dfd775f757576e4d98) | fix | hold constructors weakly in DepsTracker cache | -| [70a5b651be](https://github.com/angular/angular/commit/70a5b651be29f1421eb25150b560bfe154aad6bc) | fix | prevent element duplication with dynamic components | +| [62a97f7e4b](https://github.com/angular/angular/commit/62a97f7e4b896b4b03a1ef25764db387ffecebe1) | fix | ensure definitions compile | +| [21b1c3b2ee](https://github.com/angular/angular/commit/21b1c3b2ee2c8423782b111b93bd60eb6b453259) | fix | include signal debug names in their `toString()` representation | +| [224e60ecb1](https://github.com/angular/angular/commit/224e60ecb1b90115baa702f1c06edc1d64d86187) | fix | sanitize translated attribute bindings with interpolations | + + + + +# 21.2.2 (2026-03-09) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [1df1697c6e](https://github.com/angular/angular/commit/1df1697c6e3a6b1d302f7692b495146943faa12f) | fix | prevent mutation of children array in RecursiveVisitor | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [c822bf8e76](https://github.com/angular/angular/commit/c822bf8e76611afde332b6625f5e7bae2fe9c3f3) | fix | always parenthesize object literals in TCB | +| [05d022d5e6](https://github.com/angular/angular/commit/05d022d5e61cca7ac90d5b2b2ba3fc738b364ad9) | fix | ignore generated ngDevMode signal branch for code coverage | ### forms | Commit | Type | Description | | -- | -- | -- | -| [6f75b6e3f6](https://github.com/angular/angular/commit/6f75b6e3f60dc2a4f33e13562649931dc95eb52b) | fix | Resolves debounce promise on abort in debounceForDuration | -### localize +| [670d1660c4](https://github.com/angular/angular/commit/670d1660c40504e3f55e094c3ebbcccad14163f3) | feat | add 'blur' option to debounce rule | + + + + +# 22.0.0-next.1 (2026-03-05) +### compiler | Commit | Type | Description | | -- | -- | -- | -| [4c7126d23b](https://github.com/angular/angular/commit/4c7126d23be3e43b1d5bd6f2fb13119d185c3682) | fix | add support for unit-test builder in ng-add schematic | -### router +| [72a17afaf3](https://github.com/angular/angular/commit/72a17afaf32194d42e4b5c090d4f75bad875930b) | fix | prevent mutation of children array in RecursiveVisitor | +### compiler-cli | Commit | Type | Description | | -- | -- | -- | -| [d6268c0bbb](https://github.com/angular/angular/commit/d6268c0bbbdc92abaaaeb8eebee3bc45decab9c9) | fix | limit UrlParser recursion depth to prevent stack overflow | -| [49a36f4cc7](https://github.com/angular/angular/commit/49a36f4cc7254420bc34fff4e0f0242e00970280) | perf | Use .bind to avoid holding other closures in memory | +| [dc4cf649b6](https://github.com/angular/angular/commit/dc4cf649b62fd47e0e1bd255ec954d88585899c4) | fix | ignore generated ngDevMode signal branch for code coverage | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [c767d678cf](https://github.com/angular/angular/commit/c767d678cff65a89f380b4512590fa732db072c8) | feat | add 'blur' option to debounce rule | +### migrations +| Commit | Type | Description | +| -- | -- | -- | +| [f01901d766](https://github.com/angular/angular/commit/f01901d7668ab926bd7a786f43dbb18f2bb8a5b7) | fix | avoid generating invalid code in ChangeDetectionStrategy.Eager migration | - -# 21.2.0-next.1 (2026-01-28) -### common + +# 22.0.0-next.0 (2026-03-04) +## Breaking Changes +### +- Node.js v20 is no longer supported. The minimum supported Node.js versions are now v22.22.0 and v24.13.1. +### compiler +- data prefixed attribute no-longer bind inputs nor outputs. +- The compiler will throw when there a when inputs, outputs or model are binding to the same input/outputs. +- `in` variables will throw in template expressions. +### core +- change AnimationCallbackEvent.animationComplete signature +### http +- Use the `HttpXhrBackend` with `provideHttpClient(withXhr)` if you want to keep supporting upload progress reports. +### router +- `provideRoutes()` has been removed. Use `provideRouter()` or `ROUTES` as multi token if necessary. +### upgrade +- Deprecated `getAngularLib`/`setAngularLib` have been removed use `getAngularJSGlobal`/`setAngularJSGlobal` instead. +## Deprecations +### http +- `withFetch` is now deprecated, it can be safely removed. +### | Commit | Type | Description | | -- | -- | -- | -| [8bbe6dc46c](https://github.com/angular/angular/commit/8bbe6dc46c9dc13bafa81a60c7613b84b5ca3761) | feat | Add Location strategies to manage trailing slash on write | +| [d550bf713a](https://github.com/angular/angular/commit/d550bf713a5f558a85cc85639ce96de3677f324a) | build | update minimum supported Node.js versions | +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [488d962bc7](https://github.com/angular/angular/commit/488d962bc700fb7189749c63ba63eac50a54e363) | fix | Don't bind inputs/outputs for `data-` attributes | +| [03db2aefaa](https://github.com/angular/angular/commit/03db2aefaa88bc73b6af6ed1c9c722b65079ab3b) | fix | throw on duplicate input/outputs | +| [786ef8261f](https://github.com/angular/angular/commit/786ef8261f4faca0693ef73938d3a6275b5baf7f) | fix | throw on invalid in expressions | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [2c87f21abe](https://github.com/angular/angular/commit/2c87f21abe902f5ef04396994e351762b96836b1) | fix | always parenthesize object literals in TCB | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [17d3ea44e2](https://github.com/angular/angular/commit/17d3ea44e25e077b18178aa8108828f36eb821f4) | feat | add `IdleRequestOptions` support to `IdleService` | +| [3bc095d508](https://github.com/angular/angular/commit/3bc095d508653982a48b337afd51bfedbbde1f87) | feat | Add a schematics to migrate `provideHttpClient` to keep using the `HttpXhrBackend` implementation. | +| [cb4cb77053](https://github.com/angular/angular/commit/cb4cb77053a817fe800af6395783720761e29ada) | feat | Add migration to add `ChangeDetectionStrategy.Eager` where applicable | +| [2206efa55f](https://github.com/angular/angular/commit/2206efa55fc1de160333d62680f8893c47525335) | feat | add special return statuses for resource params | +| [246a984a5d](https://github.com/angular/angular/commit/246a984a5df0006bc5f4025baf918345aa38499c) | feat | add TestBed.getFixture | +| [a5981b83a6](https://github.com/angular/angular/commit/a5981b83a60577d9068d2429bcbed969edca581b) | feat | support customization of @defer's on idle behavior | +| [98eb24cea0](https://github.com/angular/angular/commit/98eb24cea0498382cc7cf7d7b85cd9ead5ad99ad) | feat | Support optional timeout for idle deferred triggers | +| [f9d8da6924](https://github.com/angular/angular/commit/f9d8da69243ae1cd0eb1ab197fdd80e9a34107c1) | fix | bind global context to idle callback shims in @defer's idle service | +| [b9b5c279b4](https://github.com/angular/angular/commit/b9b5c279b444ab2684fe911982930dc7c31ed43c) | refactor | enhance AnimationCallbackEvent.animationComplete signature | ### forms | Commit | Type | Description | | -- | -- | -- | -| [ebae211add](https://github.com/angular/angular/commit/ebae211add37700858adeb8fc5d87bf503a59721) | feat | introduce parse errors in signal forms | -| [e682e53113](https://github.com/angular/angular/commit/e682e53113601b967881cf3d19ba8cffff637c0f) | fix | only touch visible, interactive fields on submit | -| [fb05fc86d0](https://github.com/angular/angular/commit/fb05fc86d0f12ffafd94c7c1420118d8a79f7e59) | fix | sort error summary by DOM order | -### language-server +| [fb166772d2](https://github.com/angular/angular/commit/fb166772d2e987c0145bdd5bbe83b2a29d74f31c) | fix | split the `touched` model into an input and `touch` output | +| [2061fd8253](https://github.com/angular/angular/commit/2061fd8253882a46336aae8d73a79a1b176449e0) | fix | Untrack `setValue` in reactive forms | +### http | Commit | Type | Description | | -- | -- | -- | -| [6fb39d9b62](https://github.com/angular/angular/commit/6fb39d9b62cbb634e95ec00fe5ef85d84da3bdbd) | feat | Support client-side file watching via `onDidChangeWatchedFiles` | +| [5c432fb8bb](https://github.com/angular/angular/commit/5c432fb8bb69343ef2633811c37c0c6c0fd65126) | feat | Use `FetchBackend` as default for the `HttpBackend` implementation | ### language-service | Commit | Type | Description | | -- | -- | -- | -| [8a7cbd4668](https://github.com/angular/angular/commit/8a7cbd46685874f4500c52629d09c5f7fd309080) | fix | Detect local project version on creation | +| [c6f98c723c](https://github.com/angular/angular/commit/c6f98c723cdd2c209092927855f8cbaf63ecce30) | feat | Add support for idle timeout in defer blocks | ### router | Commit | Type | Description | | -- | -- | -- | -| [b51bab583d](https://github.com/angular/angular/commit/b51bab583d84e38f16dea489e4119edc34e2a491) | feat | Add partial ActivatedRouteSnapshot information to `canMatch` params | -| [dbd50be7f7](https://github.com/angular/angular/commit/dbd50be7f7151c567b3caa72c3f21083e7022b74) | fix | Do not intercept reload events with Navigation integration | +| [3683902234](https://github.com/angular/angular/commit/3683902234acf74c7047337bda4db937e93f93d7) | feat | adds browserUrl input support to router links | +| [bdb6ae9dbc](https://github.com/angular/angular/commit/bdb6ae9dbc080cd6ce4f5058c65f6b2bd853beda) | refactor | remove deprecated `provideRoutes` function. | +### upgrade +| Commit | Type | Description | +| -- | -- | -- | +| [01a179577b](https://github.com/angular/angular/commit/01a179577b5a250f5801f6d9a04378aea73c4251) | refactor | remove `getAngularLib`/`setAngularLib` | - -# 21.1.2 (2026-01-28) -### forms + +# 21.2.1 (2026-03-04) +### core | Commit | Type | Description | | -- | -- | -- | -| [9f99b14882](https://github.com/angular/angular/commit/9f99b14882bc4f883aa33295856010a8bca900fa) | fix | only touch visible, interactive fields on submit | -### language-service +| [e2e9a9a531](https://github.com/angular/angular/commit/e2e9a9a531c9e9a69701e549f28354cc5d5edd77) | fix | adds transfer cache to httpResource to fix hydration | +| [b4ec3cc4e4](https://github.com/angular/angular/commit/b4ec3cc4e41f2948ad0830eb14aa05d14fa3a9ed) | fix | prevent child animation elements from being orphaned | +| [e923d88398](https://github.com/angular/angular/commit/e923d8839868c79989502ab3503e13d93c78516a) | fix | Prevent removal of elements during drag and drop | +### http | Commit | Type | Description | | -- | -- | -- | -| [c57b0355b5](https://github.com/angular/angular/commit/c57b0355b51f5aee5abd822f203fc3bcc3e85acd) | fix | Detect local project version on creation | -### router +| [277ade97ac](https://github.com/angular/angular/commit/277ade97ac2a3a7f2a5b513acaa93e7663cdc55f) | fix | correctly cache blob responses in transfer cache ([#67002](https://github.com/angular/angular/pull/67002)) | + + + + +# 19.2.19 (2026-02-25) +## Breaking Changes +### core +- Angular now only applies known attributes from HTML in translated ICU content. Unknown attributes are dropped and not rendered. + + (cherry picked from commit 03da204b6daa5e4583e0d0968c2107390bbd8235) +### core | Commit | Type | Description | | -- | -- | -- | -| [21ecdc036a](https://github.com/angular/angular/commit/21ecdc036a46c487d6c5b6bd25c2bbc3e53a60f9) | fix | Do not intercept reload events with Navigation integration | +| [747548721d](https://github.com/angular/angular/commit/747548721d051c21e388a302d20d53fb3ab16367) | fix | block creation of sensitive URI attributes from ICU messages | + + + + +# 20.3.17 (2026-02-25) +## Breaking Changes +### core +- Angular now only applies known attributes from HTML in translated ICU content. Unknown attributes are dropped and not rendered. + + (cherry picked from commit 03da204b6daa5e4583e0d0968c2107390bbd8235) +### core +| Commit | Type | Description | +| -- | -- | -- | +| [7f9de3c118](https://github.com/angular/angular/commit/7f9de3c118383c09fa8851708c66ec94453a9680) | fix | block creation of sensitive URI attributes from ICU messages | - -# 21.2.0-next.0 (2026-01-22) + +# 21.2.0 (2026-02-25) +### common +| Commit | Type | Description | +| -- | -- | -- | +| [18003a33bb](https://github.com/angular/angular/commit/18003a33bb0d6bb09def8a0e5939fa24069696eb) | feat | add an 'outlet' injector option for ngTemplateOutlet | +| [8bbe6dc46c](https://github.com/angular/angular/commit/8bbe6dc46c9dc13bafa81a60c7613b84b5ca3761) | feat | Add Location strategies to manage trailing slash on write | +| [51cc914807](https://github.com/angular/angular/commit/51cc91480761b7275c15b5600381207f8ca00ee5) | feat | support height in ImageLoaderConfig and built-in loaders | ### compiler | Commit | Type | Description | | -- | -- | -- | | [72534e2a34](https://github.com/angular/angular/commit/72534e2a3458df4e1bb097973872f00bbb92be42) | feat | Add support for the `instanceof` binary operator | +| [95b3f37d4a](https://github.com/angular/angular/commit/95b3f37d4a7d9a38f616d56df746dfcda3c2139b) | feat | Exhaustive checks for switch blocks | | [04ba09a8d9](https://github.com/angular/angular/commit/04ba09a8d9454013bebdd643eacb737642161952) | feat | support `AstVisitor.visitEmptyExpr()` | | [ce80136e7b](https://github.com/angular/angular/commit/ce80136e7b9f0024d49fce835cffa024c4505855) | fix | optimize away unnecessary restore/reset view calls | | [3242a61bae](https://github.com/angular/angular/commit/3242a61bae02253d13abb510b666376c665e61ac) | fix | variable counter visiting some expressions twice | @@ -181,23 +916,150 @@ | -- | -- | -- | | [473dd3e1cb](https://github.com/angular/angular/commit/473dd3e1cbd4fe3fa88ae4d5358eee35c11acb1b) | fix | attach source spans to object literal keys in TCB | | [a904d9f77b](https://github.com/angular/angular/commit/a904d9f77b56feab407f75f8d0527fa512d5dafb) | fix | support nested component declaration | +| [2ea6dfc6c9](https://github.com/angular/angular/commit/2ea6dfc6c9ca11e96a2654510c980419899f8d04) | fix | update diagnostic to flag no-op arrow functions in listeners | ### core | Commit | Type | Description | | -- | -- | -- | +| [8d5210c9fe](https://github.com/angular/angular/commit/8d5210c9fedd8abdd810d7a89ec7ee9a1234f5c1) | feat | add ChangeDetectionStrategy.Eager alias for Default | | [92d2498910](https://github.com/angular/angular/commit/92d2498910caed06c182b67e39726e1441418698) | feat | add host node to DeferBlockData ([#66546](https://github.com/angular/angular/pull/66546)) | +| [ea2016a6dc](https://github.com/angular/angular/commit/ea2016a6dce58f95ecab7c773d5dcde274354e1a) | feat | add support for nested animations | +| [81cabc1477](https://github.com/angular/angular/commit/81cabc14777a3b4966c29d60e1505aca8c29b71c) | feat | add support for TypeScript 6 | | [1ba9b7ac50](https://github.com/angular/angular/commit/1ba9b7ac5001b315cc9df78c518964dbf479d647) | feat | resource composition via snapshots | | [d9923b72a2](https://github.com/angular/angular/commit/d9923b72a20972ba6bf728d78f1afac6936ade18) | feat | support arrow functions in expressions | +| [a7e8abbb7e](https://github.com/angular/angular/commit/a7e8abbb7e738ba338c3f50c76934c99925954e5) | fix | correctly handle SkipSelf when resolving from embedded view injector | +| [0806ee3826](https://github.com/angular/angular/commit/0806ee38269b664f535e10d4d501b88370d3b44c) | fix | prevent animated element duplication with dynamic components in zoneless mode | | [ed78fa05c7](https://github.com/angular/angular/commit/ed78fa05c710ebafb355ae00a85b190a118b6cc4) | fix | Remove note to skip arrow functions in best practices | ### forms | Commit | Type | Description | | -- | -- | -- | +| [f56bb07d83](https://github.com/angular/angular/commit/f56bb07d83a015b0ac12e74fdb0cf1550ff36b97) | feat | add field param to submit action and onInvalid | +| [ba009b6031](https://github.com/angular/angular/commit/ba009b603119299a03f9d844f93882d42d47d150) | feat | add form directive | +| [22afbb2f36](https://github.com/angular/angular/commit/22afbb2f36be89c2ae575df343571a918dec5985) | feat | add parsing support to native inputs ([#66917](https://github.com/angular/angular/pull/66917)) | | [95c386469c](https://github.com/angular/angular/commit/95c386469c7a2f09dd731601c2061bdb10d25717) | feat | Add passing focus options to form field | +| [95ecce8334](https://github.com/angular/angular/commit/95ecce8334299defe55fb2b74264e5258ffd137c) | feat | allow setting submit options at form-level | +| [ebae211add](https://github.com/angular/angular/commit/ebae211add37700858adeb8fc5d87bf503a59721) | feat | introduce parse errors in signal forms | +| [3937afc316](https://github.com/angular/angular/commit/3937afc3167ce409eebb06d91d5fb122eea4e33d) | feat | introduce SignalFormControl for Reactive Forms compatibility | +| [30f0914754](https://github.com/angular/angular/commit/30f09147545b67185f93efb9796e37c1db76733a) | feat | support binding null to number input ([#66917](https://github.com/angular/angular/pull/66917)) | +| [dd208ca259](https://github.com/angular/angular/commit/dd208ca2595258fcd1e289374f812ce0b56c7011) | feat | update submit function to accept options object | +| [27397b3f4f](https://github.com/angular/angular/commit/27397b3f4f3182ce00d6e2f8690285c316e2a274) | fix | clear parse errors when model updates ([#66917](https://github.com/angular/angular/pull/66917)) | +| [63d8005703](https://github.com/angular/angular/commit/63d80057039928b3e878b59c1fe6b93ef1c6b701) | fix | preserve custom-control focus context in signal forms | +| [631f60d1f9](https://github.com/angular/angular/commit/631f60d1f9be72cb68330308a6ff18cc195babb8) | fix | preserve parse errors when parse returns value | +| [adfb83146b](https://github.com/angular/angular/commit/adfb83146b0c149734f43961563b389e00cc1d85) | fix | simplify design of parse errors | +| [fb05fc86d0](https://github.com/angular/angular/commit/fb05fc86d0f12ffafd94c7c1420118d8a79f7e59) | fix | sort error summary by DOM order | +| [567f292e8e](https://github.com/angular/angular/commit/567f292e8e0f9d2b5ddebadfa1c6d6dd6c456f39) | fix | support custom controls as host directives | +| [bdfb60f3e3](https://github.com/angular/angular/commit/bdfb60f3e33065e047183dc1890c36e527e2b304) | fix | use consistent error format returned from parse | +| [d75046bc09](https://github.com/angular/angular/commit/d75046bc091699bbadcb5f2032be627e983ee6fa) | fix | warn when showing hidden field state | ### language-server | Commit | Type | Description | | -- | -- | -- | | [ebc90c26f5](https://github.com/angular/angular/commit/ebc90c26f5ff1ba1e0ca9b775a44e301ebfb9473) | feat | Add completions and hover info for inline styles | | [26fd0839c3](https://github.com/angular/angular/commit/26fd0839c32d2ebeaa5e3ecc10ed70ab9ca17749) | feat | Add folding range support for inline styles | | [573aadef7e](https://github.com/angular/angular/commit/573aadef7eb8b6b5e83b82a16f95d2a556f27c01) | feat | Add quick info for inline styles | +| [6fb39d9b62](https://github.com/angular/angular/commit/6fb39d9b62cbb634e95ec00fe5ef85d84da3bdbd) | feat | Support client-side file watching via `onDidChangeWatchedFiles` | +### language-service +| Commit | Type | Description | +| -- | -- | -- | +| [496967e7b1](https://github.com/angular/angular/commit/496967e7b13dfe1ebdde69724cd62880914beb60) | feat | add JSON schema for angularCompilerOptions | +| [8c21866f49](https://github.com/angular/angular/commit/8c21866f49ff74344551395ae0a5df1841d54c0d) | feat | add linked editing ranges for HTML tag synchronization | +| [d2137928e8](https://github.com/angular/angular/commit/d2137928e8f075527016a3c011dd8efc4d4e1ebd) | perf | use lightweight project warmup for Angular analysis | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [b51bab583d](https://github.com/angular/angular/commit/b51bab583d84e38f16dea489e4119edc34e2a491) | feat | Add partial ActivatedRouteSnapshot information to `canMatch` params | +| [cf9620f7d0](https://github.com/angular/angular/commit/cf9620f7d072897f13b7f281b7bca6f51f69cfd0) | feat | Make match options optional in isActive | +| [907a94dcec](https://github.com/angular/angular/commit/907a94dcec2926a5c7d0c4d36249bd62e31a2ae3) | feat | Update `IsActiveMatchOptions` APIs to accept a Partial | + + + + +# 21.1.6 (2026-02-25) +## Breaking Changes +### core +- Angular now only applies known attributes from HTML in translated ICU content. Unknown attributes are dropped and not rendered. + + (cherry picked from commit 306f367899dfc2e04238fecd3455547b5d54075d) +### common +| Commit | Type | Description | +| -- | -- | -- | +| [31d3d56496](https://github.com/angular/angular/commit/31d3d564961b701bda96d94731fbed72c01975fa) | fix | fix LCP image detection with duplicate URLs | +### compiler-cli +| Commit | Type | Description | +| -- | -- | -- | +| [24b578ce90](https://github.com/angular/angular/commit/24b578ce90ed50022f62584671aef01d4c5dd7b2) | fix | detect uninvoked functions in defer trigger expressions | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [b858309532](https://github.com/angular/angular/commit/b85830953281ff3a1a77bbfe69019d352d509c93) | fix | block creation of sensitive URI attributes from ICU messages | + + + + +# 21.1.5 (2026-02-18) +No user facing changes in this release + + + + +# 21.1.4 (2026-02-11) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [caab23dfe6](https://github.com/angular/angular/commit/caab23dfe6acf06c3b859af091f5e078b08f1c4c) | fix | add geolocation element to schema | +### core +| Commit | Type | Description | +| -- | -- | -- | +| [2b99eaa019](https://github.com/angular/angular/commit/2b99eaa019b5551a2e2fcf9ff8cd0a796e1e857b) | fix | capture animation dependencies eagerly to avoid destroyed injector | +| [d6aeac504c](https://github.com/angular/angular/commit/d6aeac504c6181f15e5d8afdca3d9c3e3b32652c) | fix | Fix flakey test due to document injection | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [0d1acd0165](https://github.com/angular/angular/commit/0d1acd0165511b57ce853f29486d9b92d0215959) | feat | support signal-based schemas in validateStandardSchema | +### http +| Commit | Type | Description | +| -- | -- | -- | +| [3905015ccc](https://github.com/angular/angular/commit/3905015ccc53399a606dd8e4f3c4d0cce628a08e) | fix | correctly parse ArrayBuffer and Blob in transfer cache | + + + + +# 21.1.3 (2026-02-04) +### core +| Commit | Type | Description | +| -- | -- | -- | +| [2b254bc050](https://github.com/angular/angular/commit/2b254bc0508b73aab8991c3b1a9a703c339cb735) | fix | `linkedSignal.update` should propagate errors | +| [e5110b4fa1](https://github.com/angular/angular/commit/e5110b4fa155e4669ed507f3460d2d29026a28ab) | fix | export DirectiveWithBindings | +| [2cf4da0ea1](https://github.com/angular/angular/commit/2cf4da0ea11f5746eb7ae4dfd775f757576e4d98) | fix | hold constructors weakly in DepsTracker cache | +| [70a5b651be](https://github.com/angular/angular/commit/70a5b651be29f1421eb25150b560bfe154aad6bc) | fix | prevent element duplication with dynamic components | +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [6f75b6e3f6](https://github.com/angular/angular/commit/6f75b6e3f60dc2a4f33e13562649931dc95eb52b) | fix | Resolves debounce promise on abort in debounceForDuration | +### localize +| Commit | Type | Description | +| -- | -- | -- | +| [4c7126d23b](https://github.com/angular/angular/commit/4c7126d23be3e43b1d5bd6f2fb13119d185c3682) | fix | add support for unit-test builder in ng-add schematic | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [d6268c0bbb](https://github.com/angular/angular/commit/d6268c0bbbdc92abaaaeb8eebee3bc45decab9c9) | fix | limit UrlParser recursion depth to prevent stack overflow | +| [49a36f4cc7](https://github.com/angular/angular/commit/49a36f4cc7254420bc34fff4e0f0242e00970280) | perf | Use .bind to avoid holding other closures in memory | + + + + +# 21.1.2 (2026-01-28) +### forms +| Commit | Type | Description | +| -- | -- | -- | +| [9f99b14882](https://github.com/angular/angular/commit/9f99b14882bc4f883aa33295856010a8bca900fa) | fix | only touch visible, interactive fields on submit | +### language-service +| Commit | Type | Description | +| -- | -- | -- | +| [c57b0355b5](https://github.com/angular/angular/commit/c57b0355b51f5aee5abd822f203fc3bcc3e85acd) | fix | Detect local project version on creation | +### router +| Commit | Type | Description | +| -- | -- | -- | +| [21ecdc036a](https://github.com/angular/angular/commit/21ecdc036a46c487d6c5b6bd25c2bbc3e53a60f9) | fix | Do not intercept reload events with Navigation integration | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 11cbd8a3e636..77bce4cca3c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -71,7 +71,7 @@ You can file new issues by selecting from our [new issue templates](https://gith ### Contribution Quality -We strongly value open source contribution and pull requests from community contributors. Please note that every pull request is reviewed and merged by an actual person on the team, which does take time and effort. That is time and effort that does take away from other valuable work. With that in mind we have an minimum set of expectations that are required of any community contribution pull request that is opened. +We strongly value open source contribution and pull requests from community contributors. Please note that every pull request is reviewed and merged by an actual person on the team, which does take time and effort. That is time and effort that does take away from other valuable work. With that in mind we have a minimum set of expectations that are required of any community contribution pull request that is opened. 1. Search [GitHub](https://github.com/angular/angular/pulls) for an open or closed PR that relates to your submission. - You don't want to duplicate existing efforts. @@ -238,7 +238,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise - For corporations, we'll need you to [print, sign and one of scan+email, fax or mail the form][corporate-cla]. -If you have more than one GitHub accounts, or multiple email addresses associated with a single GitHub account, you must sign the CLA using the primary email address of the GitHub account used to author Git commits and send pull requests. +If you have more than one GitHub account, or multiple email addresses associated with a single GitHub account, you must sign the CLA using the primary email address of the GitHub account used to author Git commits and send pull requests. The following documents can help you sort out issues with GitHub accounts and multiple email addresses: diff --git a/MODULE.bazel b/MODULE.bazel index b666c9e274ed..d8506ca7e634 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -5,42 +5,42 @@ module( ) bazel_dep(name = "rules_pkg", version = "1.2.0") -bazel_dep(name = "rules_nodejs", version = "6.7.3") -bazel_dep(name = "aspect_rules_ts", version = "3.8.4") -bazel_dep(name = "aspect_rules_js", version = "2.9.2") -bazel_dep(name = "aspect_rules_esbuild", version = "0.25.0") -bazel_dep(name = "aspect_rules_jasmine", version = "2.0.2") +bazel_dep(name = "rules_nodejs", version = "6.7.4") +bazel_dep(name = "aspect_rules_ts", version = "3.8.9") +bazel_dep(name = "aspect_rules_js", version = "3.1.1") +bazel_dep(name = "aspect_rules_esbuild", version = "0.25.1") +bazel_dep(name = "aspect_rules_jasmine", version = "2.0.4") bazel_dep(name = "aspect_rules_rollup", version = "2.0.1") bazel_dep(name = "bazel_skylib", version = "1.9.0") -bazel_dep(name = "aspect_bazel_lib", version = "2.22.5") -bazel_dep(name = "tar.bzl", version = "0.8.1") -bazel_dep(name = "yq.bzl", version = "0.3.4") +bazel_dep(name = "bazel_lib", version = "3.3.1") +bazel_dep(name = "tar.bzl", version = "0.10.4") +bazel_dep(name = "yq.bzl", version = "0.3.6") bazel_dep(name = "rules_angular") git_override( module_name = "rules_angular", - commit = "d746c4f75e42cffe389d1ab077f4639be2bc78d1", - remote = "https://github.com/devversion/rules_angular.git", + commit = "045f98407a299ffaeeeafa275d8490d4507513f8", + remote = "https://github.com/angular/rules_angular.git", ) bazel_dep(name = "devinfra") git_override( module_name = "devinfra", - commit = "7c08ac2a4f396bad752829fba09dbaefbcded9fc", + commit = "eaa9aaa98de612484109350f710fdb02e92c84ae", remote = "https://github.com/angular/dev-infra.git", ) bazel_dep(name = "rules_sass") git_override( module_name = "rules_sass", - commit = "1184a80751a21af8348f308abc5b38a41f26850e", - remote = "https://github.com/devversion/rules_sass.git", + commit = "846437db57b03761ac6af22415919a9763b7be65", + remote = "https://github.com/angular/rules_sass.git", ) bazel_dep(name = "rules_browsers") git_override( module_name = "rules_browsers", - commit = "e08ae33c679d07b3b2fcc136658b787a81995bc5", - remote = "https://github.com/devversion/rules_browsers.git", + commit = "bf27ea46fdbb0209526ca821f1500d4337eb8299", + remote = "https://github.com/angular/rules_browsers.git", ) yq = use_extension("@yq.bzl//yq:extensions.bzl", "yq") @@ -49,15 +49,15 @@ use_repo(yq, "yq_toolchains") node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") node.toolchain( node_repositories = { - "22.22.0-darwin_arm64": ("node-v22.22.0-darwin-arm64.tar.gz", "node-v22.22.0-darwin-arm64", "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640"), - "22.22.0-darwin_amd64": ("node-v22.22.0-darwin-x64.tar.gz", "node-v22.22.0-darwin-x64", "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce"), - "22.22.0-linux_arm64": ("node-v22.22.0-linux-arm64.tar.xz", "node-v22.22.0-linux-arm64", "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f"), - "22.22.0-linux_ppc64le": ("node-v22.22.0-linux-ppc64le.tar.xz", "node-v22.22.0-linux-ppc64le", "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865"), - "22.22.0-linux_s390x": ("node-v22.22.0-linux-s390x.tar.xz", "node-v22.22.0-linux-s390x", "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4"), - "22.22.0-linux_amd64": ("node-v22.22.0-linux-x64.tar.xz", "node-v22.22.0-linux-x64", "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37"), - "22.22.0-windows_amd64": ("node-v22.22.0-win-x64.zip", "node-v22.22.0-win-x64", "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a"), + "22.22.3-darwin_arm64": ("node-v22.22.3-darwin-arm64.tar.gz", "node-v22.22.3-darwin-arm64", "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207"), + "22.22.3-darwin_amd64": ("node-v22.22.3-darwin-x64.tar.gz", "node-v22.22.3-darwin-x64", "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec"), + "22.22.3-linux_arm64": ("node-v22.22.3-linux-arm64.tar.xz", "node-v22.22.3-linux-arm64", "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7"), + "22.22.3-linux_ppc64le": ("node-v22.22.3-linux-ppc64le.tar.xz", "node-v22.22.3-linux-ppc64le", "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754"), + "22.22.3-linux_s390x": ("node-v22.22.3-linux-s390x.tar.xz", "node-v22.22.3-linux-s390x", "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617"), + "22.22.3-linux_amd64": ("node-v22.22.3-linux-x64.tar.xz", "node-v22.22.3-linux-x64", "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f"), + "22.22.3-windows_amd64": ("node-v22.22.3-win-x64.zip", "node-v22.22.3-win-x64", "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33"), }, - node_version = "22.22.0", + node_version = "22.22.3", ) use_repo(node, "nodejs_toolchains") use_repo(node, "nodejs_darwin_amd64") @@ -71,8 +71,8 @@ use_repo(node, "nodejs_windows_amd64") pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm") pnpm.pnpm( name = "pnpm", - pnpm_version = "10.30.0", - pnpm_version_integrity = "sha512-K1dT3gFdSA7riPW1th4AUfBbQwGAioLsi4QMnSrfd0jrNSyD9cFZPKcD/xAXKVvD/dMRmruWhu/Ja5/LGCAJNw==", + pnpm_version = "11.1.1", + pnpm_version_integrity = "sha512-0f319zxhe2T6GlaoHDyN/g6WbjOmAQqiVrUXrne+Idk+Ba/8DeGoOw5PKdVp9otEaujwaM1yR8C7PfD7TXvfmg==", ) use_repo(pnpm, "pnpm") @@ -86,6 +86,7 @@ npm.npm_translate_lock( "//adev:package.json", "//adev/shared-docs:package.json", "//adev/shared-docs/pipeline/api-gen:package.json", + "//devtools:package.json", "//integration:package.json", "//modules:package.json", "//packages/animations:package.json", @@ -109,7 +110,11 @@ npm.npm_translate_lock( "//tools/bazel/rules_angular_store:package.json", "//vscode-ng-language-service/integration/project:package.json", ], - npmrc = "//:.npmrc", + lifecycle_hooks = { + # sleep requires node-gyp rebuild but the native module is not needed in Bazel; + # disable the install hook to avoid failing builds when node-gyp is unavailable. + "sleep": [], + }, pnpm_lock = "//:pnpm-lock.yaml", ) use_repo(npm, "npm") @@ -117,22 +122,20 @@ use_repo(npm, "npm") rules_ts_ext = use_extension("@aspect_rules_ts//ts:extensions.bzl", "ext") rules_ts_ext.deps( name = "angular_npm_typescript", - # Obtained by: curl --silent https://registry.npmjs.org/typescript/6.0.0-beta | jq -r '.dist.integrity' - ts_integrity = "sha512-CldZdztDpQRLM1HC6WDQjQkQN5Ub5zRau737a1diGh3lPmb9oRsaWHk1y5iqK0o7+1bNJ0oXfEGRkAogFZBL+Q==", - ts_version = "6.0.0-beta", + # Obtained by: curl --silent https://registry.npmjs.org/typescript/6.0.2 | jq -r '.dist.integrity' + ts_integrity = "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + ts_version = "6.0.3", ) use_repo(rules_ts_ext, **{"npm_typescript": "angular_npm_typescript"}) # TODO: Figure out how to make ng_project update whenever the packages/core::pkg target changes. rules_angular = use_extension("@rules_angular//setup:extensions.bzl", "rules_angular") - -use_repo_rule("@rules_angular//setup:repositories.bzl", "configurable_deps_repo")( - name = "rules_angular_configurable_deps", - angular_compiler_cli = "@angular//:node_modules/@angular/compiler-cli", - typescript = "@angular//:node_modules/typescript", +rules_angular.setup( + name = "angular_rules_angular_configurable_deps", + angular_compiler_cli = "//:node_modules/@angular/compiler-cli", + typescript = "//:node_modules/typescript", ) - -override_repo(rules_angular, "rules_angular_configurable_deps") +use_repo(rules_angular, rules_angular_configurable_deps = "angular_rules_angular_configurable_deps") register_toolchains( "@devinfra//bazel/git-toolchain:git_linux_toolchain", @@ -160,22 +163,3 @@ cldr_xml_data( "https://github.com/unicode-org/cldr/releases/download/release-%s/core.zip" % CLDR_VERSION: "d5ee2abac64158c04884a722f8ef4830ea22b6c74aac20185be2838db8eda788", }, ) - -http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -# Fetch sauce connect (tool to open Saucelabs tunnel for Saucelabs browser tests) -http_archive( - name = "sauce_connect_linux_amd64", - build_file_content = """exports_files(["bin/sc"], visibility = ["//visibility:public"])""", - sha256 = "26b9c3630f441b47854b6032f7eca6f1d88d3f62e50ee44c27015d71a5155c36", - strip_prefix = "sc-4.8.2-linux", - url = "https://saucelabs.com/downloads/sc-4.8.2-linux.tar.gz", -) - -http_archive( - name = "sauce_connect_mac", - build_file_content = """exports_files(["bin/sc"], visibility = ["//visibility:public"])""", - sha256 = "28277ce81ef9ab84f5b87b526258920a8ead44789a5034346e872629bbf38089", - strip_prefix = "sc-4.8.2-osx", - url = "https://saucelabs.com/downloads/sc-4.8.2-osx.zip", -) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index f2afca489234..cc93583eac13 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -19,23 +19,17 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", "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.25.0/MODULE.bazel": "5fef5ec709c837312823f9bcf0f276661e2cb48ad52f17c4e01176bbf1e9bf58", - "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.25.0/source.json": "5e42968c6d23ab8bd95c02634b16864d866334347827cb6a8425b86c11cc4363", - "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.2/MODULE.bazel": "45f054400ff242c4433f6d7f20f6123a9a72739cb7a1f44247d738db1644f46c", - "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.2/source.json": "3ed399a5654259a822448f9cdbf21f6c738f16ccd7f89249c7507e374cbdd1e3", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.25.1/MODULE.bazel": "9b931b3e483bd8eedb6966bda6df07d801f70ccb4896231b4e5e711b5130f3aa", + "https://bcr.bazel.build/modules/aspect_rules_esbuild/0.25.1/source.json": "a0b72e23ed06113f3878cb635d586b4045ef37750983467af72fe0315c3a2fcd", + "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.4/MODULE.bazel": "fbb819eb8b7e5d7f67fdd38f7cecb413e287594cd666ce192c72c8828527775a", + "https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.4/source.json": "81ffb708333cd98ec3c0b4cc004f4d5cf92a16914b5196a2892c45141bba7cff", "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.9.2/MODULE.bazel": "93fd5b85e6e912fb0712cbab453c43271d4ea33a093f84fd587638fbc9f8c145", - "https://bcr.bazel.build/modules/aspect_rules_js/2.9.2/source.json": "4bff7c03ab387b60deb15649ba575688e62f2a71a7544cbc7a660b19ec473808", + "https://bcr.bazel.build/modules/aspect_rules_js/3.1.1/MODULE.bazel": "b83cf3ee44837345f1c926d70b96453deb5e244de43d08dcd7acad8d381c275a", + "https://bcr.bazel.build/modules/aspect_rules_js/3.1.1/source.json": "2806c2d7ce5993f68b74df5f3e2de45d4b2a5798afedd459d88e37c75562da97", "https://bcr.bazel.build/modules/aspect_rules_rollup/2.0.1/MODULE.bazel": "296e3a053658c2af989ba9bd62a205e6d1fa84bdd6dd5249196546e6b84770ec", "https://bcr.bazel.build/modules/aspect_rules_rollup/2.0.1/source.json": "2fe8ac1ccb4de74bf884761e070010280b272d94e3997205b361b91c75409726", - "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.8.4/MODULE.bazel": "a50254ac3add6232d0f9f93103836f9afaf614315589a13abf74183982c4101d", - "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.4/source.json": "f786e0763f3ea5de7ea6d4c4e38fccb48bf4d9c5eafaf95091c0e1590502510e", - "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.3/MODULE.bazel": "20f53b145f40957a51077ae90b37b7ce83582a1daf9350349f0f86179e19dd0d", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.9/MODULE.bazel": "bd5f9ebf517cfcd377eaa7ce1cb16035d167f00774b77789909590c53bc6f20c", + "https://bcr.bazel.build/modules/aspect_rules_ts/3.8.9/source.json": "59e66656561571ed82ccff56c75c43d0bc79f0065ca8d17be2752d4f648d40c9", "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", "https://bcr.bazel.build/modules/aspect_tools_telemetry/0.3.3/MODULE.bazel": "37c764292861c2f70314efa9846bb6dbb44fc0308903b3285da6528305450183", @@ -51,19 +45,21 @@ "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.34.0/MODULE.bazel": "e8475ad7c8965542e0c7aac8af68eb48c4af904be3d614b6aa6274c092c2ea1e", "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d", - "https://bcr.bazel.build/modules/bazel_features/1.39.0/source.json": "f63cbeb4c602098484d57001e5a07d31cb02bbccde9b5e2c9bf0b29d05283e93", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.41.0/MODULE.bazel": "6e0f87fafed801273c371d41e22a15a6f8abf83fdd7f87d5e44ad317b94433d0", + "https://bcr.bazel.build/modules/bazel_features/1.41.0/source.json": "8fd525b31b0883c47e0593443cdd10219b94a7556b3195fc02d75c86c66cfe30", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "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-rc.0/MODULE.bazel": "d6e00979a98ac14ada5e31c8794708b41434d461e7e7ca39b59b765e6d233b18", "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_lib/3.2.2/MODULE.bazel": "e2c890c8a515d6bca9c66d47718aa9e44b458fde64ec7204b8030bf2d349058c", + "https://bcr.bazel.build/modules/bazel_lib/3.3.1/MODULE.bazel": "732a0d516cf6400d9b3136e4356258aef1bf91de8d5240f87f0112f098920c1d", + "https://bcr.bazel.build/modules/bazel_lib/3.3.1/source.json": "8e5175d7b4125a39b8941d01e38039934d058e03804f46a2b8fd7ae6316b1ce2", "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", "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.0/MODULE.bazel": "2ab127ef8d56a739a99bb2ce00ec4c7d1ecc7977d4370c0ca6efd0d8f03d6d99", "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", @@ -77,19 +73,23 @@ "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", - "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/MODULE.bazel": "f1b7bb2dd53e8f2ef984b39485ec8a44e9076dda5c4b8efd2fb4c6a6e856a31d", + "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.3/source.json": "ebe931bfe362e4b41e59ee00a528db6074157ff2ced92eb9e970acab2e1089c9", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "https://bcr.bazel.build/modules/jq.bzl/0.1.0/MODULE.bazel": "2ce69b1af49952cd4121a9c3055faa679e748ce774c7f1fda9657f936cae902f", - "https://bcr.bazel.build/modules/jq.bzl/0.1.0/source.json": "746bf13cac0860f091df5e4911d0c593971cd8796b5ad4e809b2f8e133eee3d5", + "https://bcr.bazel.build/modules/jq.bzl/0.4.0/MODULE.bazel": "a7b39b37589f2b0dad53fd6c1ccaabbdb290330caa920d7ef3e6aad068cd4ab2", + "https://bcr.bazel.build/modules/jq.bzl/0.6.1/MODULE.bazel": "f30c46e0a08a9f7566a8bf60a43d48abea960cd7f57b315b01e2762f1537eb52", + "https://bcr.bazel.build/modules/jq.bzl/0.6.1/source.json": "9ca9e2f90baa6a5bb0a49626ed9528554ec83165adf47b39792673ecc7feda22", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", - "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", + "https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83", + "https://bcr.bazel.build/modules/package_metadata/0.0.3/source.json": "742075a428ad12a3fa18a69014c2f57f01af910c6d9d18646c990200853e641a", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", @@ -98,7 +98,8 @@ "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", - "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/platforms/1.1.0/MODULE.bazel": "1c0c09f5bdcf4b3f924720d2478a3711cb39f4977019ca5988685e5b7e18b3d2", + "https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", @@ -107,6 +108,7 @@ "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", @@ -126,6 +128,7 @@ "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", "https://bcr.bazel.build/modules/rules_cc/0.2.16/source.json": "d03d5cde49376d87e14ec14b666c56075e5e3926930327fd5d0484a1ff2ac1cc", + "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", @@ -159,12 +162,10 @@ "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", "https://bcr.bazel.build/modules/rules_nodejs/6.2.0/MODULE.bazel": "ec27907f55eb34705adb4e8257952162a2d4c3ed0f0b3b4c3c1aad1fac7be35e", - "https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d", - "https://bcr.bazel.build/modules/rules_nodejs/6.3.3/MODULE.bazel": "b66eadebd10f1f1b25f52f95ab5213a57e82c37c3f656fcd9a57ad04d2264ce7", "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.7.3/MODULE.bazel": "c22a48b2a0dbf05a9dc5f83837bbc24c226c1f6e618de3c3a610044c9f336056", - "https://bcr.bazel.build/modules/rules_nodejs/6.7.3/source.json": "a3f966f4415a8a6545e560ee5449eac95cc633f96429d08e87c87775c72f5e09", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.4/MODULE.bazel": "e6a241a55c82e999145553d2e00a08fc6ebadf62b63d108fb5e984696ffd0bd2", + "https://bcr.bazel.build/modules/rules_nodejs/6.7.4/source.json": "34e7a8a3b4c8d630ac0e0492b3fed9dba41fe008a0edf220b7d88fa38ac53698", "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.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e", @@ -196,17 +197,18 @@ "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/MODULE.bazel": "e8f9ff79199e8d9eaad7f1b0a77ad74b30bb82d794b87d8ca942bead5de83ae9", + "https://bcr.bazel.build/modules/tar.bzl/0.10.4/source.json": "20143442376c03426f6135292ba02d825cb75308aa47e6bf42dd4cc5a435c2ff", "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.8.1/MODULE.bazel": "6ffe8907ed4c555bc94bd35a5a01411cc4470c6dace84f9cf487815409e077d1", - "https://bcr.bazel.build/modules/tar.bzl/0.8.1/source.json": "835f83b482facf6205ad8708cf2b2f6524d1d7b1075a90fe9bb540da761d6d2e", + "https://bcr.bazel.build/modules/tar.bzl/0.6.0/MODULE.bazel": "a3584b4edcfafcabd9b0ef9819808f05b372957bbdff41601429d5fd0aac2e7c", "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.2/MODULE.bazel": "0384efa70e8033d842ea73aa4b7199fa099709e236a7264345c03937166670b6", - "https://bcr.bazel.build/modules/yq.bzl/0.3.4/MODULE.bazel": "d3a270662f5d766cd7229732d65a5a5bc485240c3007343dd279edfb60c9ae27", - "https://bcr.bazel.build/modules/yq.bzl/0.3.4/source.json": "786dafdc2843722da3416e4343ee1a05237227f068590779a6e8496a2064c0f9", + "https://bcr.bazel.build/modules/yq.bzl/0.3.6/MODULE.bazel": "985c2a0cb4ad9994bb0e33cc7fae931c91105eeefe3faa355b8f4c258d0607c0", + "https://bcr.bazel.build/modules/yq.bzl/0.3.6/source.json": "678aaf6e291164f3cd761bb3e872e8a151248f413dbb63c5524a50b82a5bc890", "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.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" @@ -215,8 +217,8 @@ "moduleExtensions": { "@@aspect_rules_esbuild+//esbuild:extensions.bzl%esbuild": { "general": { - "bzlTransitiveDigest": "c4i5gawrp4Au9UMb55EzQCePYwkrFqD9tFBN7GdHG5g=", - "usagesDigest": "ToTaCONCN/E05krnHXLM1kpV1zrHNxHrGpUip973II4=", + "bzlTransitiveDigest": "yEdNDVnymA4zAhL3PLavB/cRtK1c5FZ3PMxhjTrwbBw=", + "usagesDigest": "6We6zwGoawD9YXqMI0KPaxEKJTnamXBsuOekhFS2D40=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -271,11 +273,11 @@ "npm__esbuild_0.19.9": { "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", "attributes": { + "key": "npm__esbuild_0.19.9", "package": "esbuild", "version": "0.19.9", "root_package": "", "link_workspace": "", - "link_packages": {}, "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", "url": "", "commit": "", @@ -290,20 +292,23 @@ "npm_auth_password": "", "lifecycle_hooks": [], "extra_build_content": "", + "generate_package_json_bzl": false, "generate_bzl_library_targets": false, "extract_full_archive": false, - "exclude_package_contents": [] + "exclude_package_contents": [], + "exclude_package_contents_presets": [] } }, "npm__esbuild_0.19.9__links": { - "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", + "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links_rule", "attributes": { + "key": "npm__esbuild_0.19.9", "package": "esbuild", "version": "0.19.9", - "dev": false, "root_package": "", - "link_packages": {}, "deps": {}, + "deps_oss": {}, + "deps_cpus": {}, "transitive_closure": {}, "lifecycle_build_target": false, "lifecycle_hooks_env": [], @@ -316,31 +321,12 @@ "//visibility:public" ], "replace_package": "", - "exclude_package_contents": [] + "exclude_package_contents": [], + "exclude_package_contents_presets": [] } } }, "recordedRepoMappingEntries": [ - [ - "aspect_bazel_lib+", - "bazel_lib", - "bazel_lib+" - ], - [ - "aspect_bazel_lib+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "aspect_bazel_lib+", - "bazel_tools", - "bazel_tools" - ], - [ - "aspect_bazel_lib+", - "tar.bzl", - "tar.bzl+" - ], [ "aspect_rules_esbuild+", "aspect_rules_js", @@ -356,11 +342,6 @@ "bazel_skylib", "bazel_skylib+" ], - [ - "aspect_rules_js+", - "aspect_bazel_lib", - "aspect_bazel_lib+" - ], [ "aspect_rules_js+", "aspect_rules_js", @@ -373,162 +354,33 @@ ], [ "aspect_rules_js+", - "bazel_lib", - "bazel_lib+" - ], - [ - "aspect_rules_js+", - "bazel_skylib", - "bazel_skylib+" + "bazel_features", + "bazel_features+" ], [ "aspect_rules_js+", - "bazel_tools", - "bazel_tools" - ], - [ - "bazel_lib+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "bazel_lib+", - "bazel_tools", - "bazel_tools" - ], - [ - "tar.bzl+", - "bazel_lib", - "bazel_lib+" - ], - [ - "tar.bzl+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "tar.bzl+", - "tar.bzl", - "tar.bzl+" - ] - ] - } - }, - "@@aspect_rules_js+//npm:extensions.bzl%pnpm": { - "general": { - "bzlTransitiveDigest": "HC+l+mTivq1p/KbcVQ+iV5QwYR+oKESJh827FY68SH8=", - "usagesDigest": "i/ja+7Le/9VdqJB4MTzD11aQl7qURG7BbkP/sBkILdc=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "pnpm": { - "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", - "attributes": { - "package": "pnpm", - "version": "10.30.0", - "root_package": "", - "link_workspace": "", - "link_packages": {}, - "integrity": "sha512-K1dT3gFdSA7riPW1th4AUfBbQwGAioLsi4QMnSrfd0jrNSyD9cFZPKcD/xAXKVvD/dMRmruWhu/Ja5/LGCAJNw==", - "url": "", - "commit": "", - "patch_args": [ - "-p0" - ], - "patches": [], - "custom_postinstall": "", - "npm_auth": "", - "npm_auth_basic": "", - "npm_auth_username": "", - "npm_auth_password": "", - "lifecycle_hooks": [], - "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": [] - } - }, - "pnpm__links": { - "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", - "attributes": { - "package": "pnpm", - "version": "10.30.0", - "dev": false, - "root_package": "", - "link_packages": {}, - "deps": {}, - "transitive_closure": {}, - "lifecycle_build_target": false, - "lifecycle_hooks_env": [], - "lifecycle_hooks_execution_requirements": [ - "no-sandbox" - ], - "lifecycle_hooks_use_default_shell_env": false, - "bins": {}, - "package_visibility": [ - "//visibility:public" - ], - "replace_package": "", - "exclude_package_contents": [] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "aspect_bazel_lib+", "bazel_lib", "bazel_lib+" ], [ - "aspect_bazel_lib+", + "aspect_rules_js+", "bazel_skylib", "bazel_skylib+" ], [ - "aspect_bazel_lib+", + "aspect_rules_js+", "bazel_tools", "bazel_tools" ], - [ - "aspect_bazel_lib+", - "tar.bzl", - "tar.bzl+" - ], - [ - "aspect_rules_js+", - "aspect_bazel_lib", - "aspect_bazel_lib+" - ], - [ - "aspect_rules_js+", - "aspect_rules_js", - "aspect_rules_js+" - ], - [ - "aspect_rules_js+", - "aspect_tools_telemetry_report", - "aspect_tools_telemetry++telemetry+aspect_tools_telemetry_report" - ], [ "aspect_rules_js+", - "bazel_features", - "bazel_features+" - ], - [ - "aspect_rules_js+", - "bazel_lib", - "bazel_lib+" - ], - [ - "aspect_rules_js+", - "bazel_skylib", - "bazel_skylib+" + "protobuf", + "protobuf+" ], [ "aspect_rules_js+", - "bazel_tools", - "bazel_tools" + "tar.bzl", + "tar.bzl+" ], [ "bazel_features+", @@ -540,6 +392,11 @@ "bazel_features_version", "bazel_features++version_extension+bazel_features_version" ], + [ + "bazel_lib+", + "bazel_lib", + "bazel_lib+" + ], [ "bazel_lib+", "bazel_skylib", @@ -550,6 +407,11 @@ "bazel_tools", "bazel_tools" ], + [ + "protobuf+", + "proto_bazel_features", + "bazel_features+" + ], [ "tar.bzl+", "bazel_lib", @@ -570,19 +432,17 @@ }, "@@aspect_rules_ts+//ts:extensions.bzl%ext": { "general": { - "bzlTransitiveDigest": "QDTi1Wl/eEY4IgbXjRhegUQfHj+bB8ZEVyiSGLZc6qo=", - "usagesDigest": "Ub6gt3aBo5SL9ZAwi9DMMSkmtsKSvApxOfgVfF478B4=", - "recordedFileInputs": { - "@@rules_browsers+//package.json": "84dc1ba9b1c667a25894e97218bd8f247d54f24bb694efb397a881be3c06a4c5" - }, + "bzlTransitiveDigest": "oXZdaO5AFNj463wHR4NRAWU9XCc9wkl3rcZxqQoNWHQ=", + "usagesDigest": "8mf2+xwO+CO397XBbbqOVFqyfTb2jWbfICPzgzPbSEk=", + "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { "angular_npm_typescript": { "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", "attributes": { - "version": "6.0.0-beta", - "integrity": "sha512-CldZdztDpQRLM1HC6WDQjQkQN5Ub5zRau737a1diGh3lPmb9oRsaWHk1y5iqK0o7+1bNJ0oXfEGRkAogFZBL+Q==", + "version": "6.0.3", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "urls": [ "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" ] @@ -591,8 +451,8 @@ "rules_angular_npm_typescript": { "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", "attributes": { - "version": "5.9.2", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "6.0.3", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "urls": [ "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" ] @@ -601,8 +461,8 @@ "npm_typescript": { "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", "attributes": { - "version": "5.9.3", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "urls": [ "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" ] @@ -611,9 +471,8 @@ "npm_rules_browsers_typescript": { "repoRuleId": "@@aspect_rules_ts+//ts/private:npm_repositories.bzl%http_archive_version", "attributes": { - "version": "", - "version_from": "@@rules_browsers+//:package.json", - "integrity": "", + "version": "6.0.3", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "urls": [ "https://registry.npmjs.org/typescript/-/typescript-{}.tgz" ] @@ -637,7 +496,7 @@ "@@aspect_tools_telemetry+//:extension.bzl%telemetry": { "general": { "bzlTransitiveDigest": "cl5A2O84vDL6Tt+Qga8FCj1DUDGqn+e7ly5rZ+4xvcc=", - "usagesDigest": "A5MqWjvPgjA8QQhNFYoF+X5KGGymOKBKewnYtqYqU24=", + "usagesDigest": "ZZb1xIaM7MwIk9oEUnD4lvRBuRPBGUaaRCajg1kTm5c=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -646,10 +505,10 @@ "repoRuleId": "@@aspect_tools_telemetry+//:extension.bzl%tel_repository", "attributes": { "deps": { - "aspect_rules_ts": "3.8.4", - "aspect_rules_js": "2.9.2", - "aspect_rules_esbuild": "0.25.0", - "aspect_rules_jasmine": "2.0.2", + "aspect_rules_ts": "3.8.9", + "aspect_rules_js": "3.1.1", + "aspect_rules_esbuild": "0.25.1", + "aspect_rules_jasmine": "2.0.4", "aspect_tools_telemetry": "0.3.3" } } @@ -671,7 +530,7 @@ }, "@@pybind11_bazel+//:python_configure.bzl%extension": { "general": { - "bzlTransitiveDigest": "c9ZWWeXeu6bctL4/SsY2otFWyeFN0JJ20+ymGyJZtWk=", + "bzlTransitiveDigest": "VhEtmxw1yzb9rBZVsKTdti7p+nDM/Fv1p9TmKdO45+Q=", "usagesDigest": "fycyB39YnXIJkfWCIXLUKJMZzANcuLy9ZE73hRucjFk=", "recordedFileInputs": { "@@pybind11_bazel+//MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e" @@ -705,24 +564,24 @@ }, "@@rules_angular+//setup:extensions.bzl%rules_angular": { "general": { - "bzlTransitiveDigest": "fkaH7HMicL3g7/NDaFzlq39kcLopMyQ3KdbDn+5CRzA=", - "usagesDigest": "RWY3uqO/f0lDK8WZtS5QwE7S7IrkU3PrHr2/Vje4M3s=", + "bzlTransitiveDigest": "aS7Uud1IzoU7PPLzH3s6IfFS4b2fa0SRWDi2/fS4bQU=", + "usagesDigest": "PaJB/TvnSzJTbqGUeIfiFAEjGkG4FEW7es6f6MFMtq8=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { - "rules_angular_configurable_deps": { + "angular_rules_angular_configurable_deps": { "repoRuleId": "@@rules_angular+//setup:repositories.bzl%configurable_deps_repo", "attributes": { - "angular_compiler_cli": "@@rules_angular+//:node_modules/@angular/compiler-cli", - "typescript": "@@rules_angular+//:node_modules/typescript-local" + "angular_compiler_cli": "@@//:node_modules/@angular/compiler-cli", + "typescript": "@@//:node_modules/typescript" } }, - "dev_infra_rules_angular_configurable_deps": { + "rules_angular_configurable_deps": { "repoRuleId": "@@rules_angular+//setup:repositories.bzl%configurable_deps_repo", "attributes": { - "angular_compiler_cli": "@@rules_angular+//:node_modules/@angular/compiler-cli", - "typescript": "@@rules_angular+//:node_modules/typescript" + "angular_compiler_cli": "@@//:node_modules/@angular/compiler-cli", + "typescript": "@@//:node_modules/typescript" } } }, @@ -731,8 +590,8 @@ }, "@@rules_browsers+//browsers:extensions.bzl%browsers": { "general": { - "bzlTransitiveDigest": "agkaLQ8wE1r/5IX6pkERzFxI/z0M42Em+ICNO6TXsVo=", - "usagesDigest": "FS7q5WaIwg3KirS3njhuPFkTIBYvDaTInVGrlzu0XL8=", + "bzlTransitiveDigest": "Bm6fiKpWy96aLohOlLCP36ARVxRLZm/R+smhsb2HzmI=", + "usagesDigest": "FmXYJVoVJlnfUU8x8gObSvu4qWcco/9Faw61aC/wBF0=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -740,9 +599,9 @@ "rules_browsers_chrome_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "0a2ff0fc9eb5958b7b420f20e3968f424be7423fef89739e71565a48aa073a57", + "sha256": "1ac33f89306327af43be159c03ca4a26486de0858f42fe52394acdef50364143", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/linux64/chrome-headless-shell-linux64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/linux64/chrome-headless-shell-linux64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-linux64/chrome-headless-shell" @@ -758,9 +617,9 @@ "rules_browsers_chrome_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "e6076b1201d86f74c5eab982a239d5af83e66b1aa4d780bcb792698790e01d87", + "sha256": "169ff49c465cfda52931395e61861e146dfc5013e92c01ca792db5acea858d0b", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/mac-x64/chrome-headless-shell-mac-x64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/mac-x64/chrome-headless-shell-mac-x64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-x64/chrome-headless-shell" @@ -776,9 +635,9 @@ "rules_browsers_chrome_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "b74dbcf5543d916b02d0a133e2e7c6a4de251f06733f72c2c15ea8c42213f63b", + "sha256": "aeaaaaa4d68193a21bed04c44ddeb1230232707b4ea1d845a92925787509cd8e", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/mac-arm64/chrome-headless-shell-mac-arm64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/mac-arm64/chrome-headless-shell-mac-arm64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-arm64/chrome-headless-shell" @@ -794,9 +653,9 @@ "rules_browsers_chrome_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "df1e612dc3b1615e182a1f11821052995913c39df37caa52699de21a68d030d2", + "sha256": "4d6d79bcbcb22084df6e3a3d3a2caff67d6c0fa488d63f0c7ec1526f9553db8c", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/win64/chrome-headless-shell-win64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/win64/chrome-headless-shell-win64.zip" ], "named_files": { "CHROME-HEADLESS-SHELL": "chrome-headless-shell-win64/chrome-headless-shell.exe" @@ -812,9 +671,9 @@ "rules_browsers_chromedriver_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "69c504306399d979a2766fea603c3fb9d3d87d46c75bddc9f2a049b4f636d57c", + "sha256": "0607ccf6810a07ae08cac6443beac8b23f88dd53c7f1e0299e22d65f7cd2d020", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/linux64/chromedriver-linux64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/linux64/chromedriver-linux64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-linux64/chromedriver" @@ -828,9 +687,9 @@ "rules_browsers_chromedriver_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "5fc9d6f594fc5f2568a15145f25116dd8e9c9a60baa8da4bb21a17650fb00e7e", + "sha256": "0f512a9dd683ed4c41e609d8d02c07807497dbad3ab2f95f0d583486be7b8cff", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/mac-x64/chromedriver-mac-x64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/mac-x64/chromedriver-mac-x64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-mac-x64/chromedriver" @@ -844,9 +703,9 @@ "rules_browsers_chromedriver_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "14e92294c2c3639ca4e7d27e850588b619d698e2f8905cee368f07db2e1bf1e9", + "sha256": "7d6fc6d17de1733eb6739d1ea16d085c8df1568bcf9fa0d130c2784b27f38268", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/mac-arm64/chromedriver-mac-arm64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/mac-arm64/chromedriver-mac-arm64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-mac-arm64/chromedriver" @@ -860,9 +719,9 @@ "rules_browsers_chromedriver_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "cf641d2e176db95bcc158cd90eafd347ad4928fa0458a5f3bfd56c6d983e70db", + "sha256": "f4e9fb7bbf692fde7979b24e8d737b3cef4baafbc7a370e5d0abc4a8450fd830", "urls": [ - "https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/win64/chromedriver-win64.zip" + "https://storage.googleapis.com/chrome-for-testing-public/147.0.7687.0/win64/chromedriver-win64.zip" ], "named_files": { "CHROMEDRIVER": "chromedriver-win64/chromedriver.exe" @@ -876,9 +735,9 @@ "rules_browsers_firefox_linux": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "8d56f479cc398a537a60a3fa20dca92d8a41925113d3a67f534881a4e4d7e344", + "sha256": "f055b9c0d7346a10d22edc7f10e08679af2ea495367381ab2be9cab3ec6add97", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/146.0/linux-x86_64/en-US/firefox-146.0.tar.xz" + "https://archive.mozilla.org/pub/firefox/releases/147.0/linux-x86_64/en-US/firefox-147.0.tar.xz" ], "named_files": { "FIREFOX": "firefox/firefox" @@ -892,9 +751,9 @@ "rules_browsers_firefox_mac": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "4b1645313887972d466cd82166ea571485c2c40a167f84624e3f3ca739993cc9", + "sha256": "48485e2068bc726e2f30cf5855fc2da1fc75c1272bc243a5394f428ffae3ba35", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/146.0/mac/en-US/Firefox%20146.0.dmg" + "https://archive.mozilla.org/pub/firefox/releases/147.0/mac/en-US/Firefox%20147.0.dmg" ], "named_files": { "FIREFOX": "Firefox.app/Contents/MacOS/firefox" @@ -908,9 +767,9 @@ "rules_browsers_firefox_mac_arm": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "4b1645313887972d466cd82166ea571485c2c40a167f84624e3f3ca739993cc9", + "sha256": "48485e2068bc726e2f30cf5855fc2da1fc75c1272bc243a5394f428ffae3ba35", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/146.0/mac/en-US/Firefox%20146.0.dmg" + "https://archive.mozilla.org/pub/firefox/releases/147.0/mac/en-US/Firefox%20147.0.dmg" ], "named_files": { "FIREFOX": "Firefox.app/Contents/MacOS/firefox" @@ -924,9 +783,9 @@ "rules_browsers_firefox_win64": { "repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo", "attributes": { - "sha256": "216870c89648f32450cfefb5cec417fcd66d480d5dc83f894bf99f5fd7f38dbb", + "sha256": "36ff9e150875aa48a0af9eec3eb67f66dddd8efac5c743265371a72ae3e796c4", "urls": [ - "https://archive.mozilla.org/pub/firefox/releases/146.0/win64/en-US/Firefox%20Setup%20146.0.exe" + "https://archive.mozilla.org/pub/firefox/releases/147.0/win64/en-US/Firefox%20Setup%20147.0.exe" ], "named_files": { "FIREFOX": "core/firefox.exe" @@ -943,7 +802,7 @@ }, "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { "general": { - "bzlTransitiveDigest": "WHRlQQnxW7e7XMRBhq7SARkDarLDOAbg6iLaJpk5QYM=", + "bzlTransitiveDigest": "CYUiFDCnL2VGx3uotIu/VuGabgObnZra2zzRUJCX0sU=", "usagesDigest": "wy6ISK6UOcBEjj/mvJ/S3WeXoO67X+1llb9yPyFtPgc=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1026,7 +885,7 @@ }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=", + "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1090,8 +949,8 @@ }, "@@rules_nodejs+//nodejs:extensions.bzl%node": { "general": { - "bzlTransitiveDigest": "4pUxCNc22K4I+6+4Nxu52Hur12tFRfa1JMsN5mdDv60=", - "usagesDigest": "lL5Z7wPurP4zJReKSAmwR5GGk3IL1B1vaFgAu5Vm8fs=", + "bzlTransitiveDigest": "oZFClfRhTTwsYzpxVPkOpOt/r0+OzEfEV37au0jFZ0s=", + "usagesDigest": "U4CYOQYDtSR3kZ0TZ9Z5qIPfS3Gb2y5IERDI0XgHptc=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -1101,46 +960,46 @@ "attributes": { "node_download_auth": {}, "node_repositories": { - "22.22.0-darwin_arm64": [ - "node-v22.22.0-darwin-arm64.tar.gz", - "node-v22.22.0-darwin-arm64", - "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + "22.22.3-darwin_arm64": [ + "node-v22.22.3-darwin-arm64.tar.gz", + "node-v22.22.3-darwin-arm64", + "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207" ], - "22.22.0-darwin_amd64": [ - "node-v22.22.0-darwin-x64.tar.gz", - "node-v22.22.0-darwin-x64", - "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + "22.22.3-darwin_amd64": [ + "node-v22.22.3-darwin-x64.tar.gz", + "node-v22.22.3-darwin-x64", + "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec" ], - "22.22.0-linux_arm64": [ - "node-v22.22.0-linux-arm64.tar.xz", - "node-v22.22.0-linux-arm64", - "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + "22.22.3-linux_arm64": [ + "node-v22.22.3-linux-arm64.tar.xz", + "node-v22.22.3-linux-arm64", + "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7" ], - "22.22.0-linux_ppc64le": [ - "node-v22.22.0-linux-ppc64le.tar.xz", - "node-v22.22.0-linux-ppc64le", - "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + "22.22.3-linux_ppc64le": [ + "node-v22.22.3-linux-ppc64le.tar.xz", + "node-v22.22.3-linux-ppc64le", + "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754" ], - "22.22.0-linux_s390x": [ - "node-v22.22.0-linux-s390x.tar.xz", - "node-v22.22.0-linux-s390x", - "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + "22.22.3-linux_s390x": [ + "node-v22.22.3-linux-s390x.tar.xz", + "node-v22.22.3-linux-s390x", + "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617" ], - "22.22.0-linux_amd64": [ - "node-v22.22.0-linux-x64.tar.xz", - "node-v22.22.0-linux-x64", - "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + "22.22.3-linux_amd64": [ + "node-v22.22.3-linux-x64.tar.xz", + "node-v22.22.3-linux-x64", + "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f" ], - "22.22.0-windows_amd64": [ - "node-v22.22.0-win-x64.zip", - "node-v22.22.0-win-x64", - "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + "22.22.3-windows_amd64": [ + "node-v22.22.3-win-x64.zip", + "node-v22.22.3-win-x64", + "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33" ] }, "node_urls": [ "https://nodejs.org/dist/v{version}/{filename}" ], - "node_version": "22.22.0", + "node_version": "22.22.3", "include_headers": false, "platform": "linux_amd64" } @@ -1150,46 +1009,46 @@ "attributes": { "node_download_auth": {}, "node_repositories": { - "22.22.0-darwin_arm64": [ - "node-v22.22.0-darwin-arm64.tar.gz", - "node-v22.22.0-darwin-arm64", - "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + "22.22.3-darwin_arm64": [ + "node-v22.22.3-darwin-arm64.tar.gz", + "node-v22.22.3-darwin-arm64", + "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207" ], - "22.22.0-darwin_amd64": [ - "node-v22.22.0-darwin-x64.tar.gz", - "node-v22.22.0-darwin-x64", - "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + "22.22.3-darwin_amd64": [ + "node-v22.22.3-darwin-x64.tar.gz", + "node-v22.22.3-darwin-x64", + "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec" ], - "22.22.0-linux_arm64": [ - "node-v22.22.0-linux-arm64.tar.xz", - "node-v22.22.0-linux-arm64", - "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + "22.22.3-linux_arm64": [ + "node-v22.22.3-linux-arm64.tar.xz", + "node-v22.22.3-linux-arm64", + "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7" ], - "22.22.0-linux_ppc64le": [ - "node-v22.22.0-linux-ppc64le.tar.xz", - "node-v22.22.0-linux-ppc64le", - "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + "22.22.3-linux_ppc64le": [ + "node-v22.22.3-linux-ppc64le.tar.xz", + "node-v22.22.3-linux-ppc64le", + "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754" ], - "22.22.0-linux_s390x": [ - "node-v22.22.0-linux-s390x.tar.xz", - "node-v22.22.0-linux-s390x", - "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + "22.22.3-linux_s390x": [ + "node-v22.22.3-linux-s390x.tar.xz", + "node-v22.22.3-linux-s390x", + "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617" ], - "22.22.0-linux_amd64": [ - "node-v22.22.0-linux-x64.tar.xz", - "node-v22.22.0-linux-x64", - "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + "22.22.3-linux_amd64": [ + "node-v22.22.3-linux-x64.tar.xz", + "node-v22.22.3-linux-x64", + "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f" ], - "22.22.0-windows_amd64": [ - "node-v22.22.0-win-x64.zip", - "node-v22.22.0-win-x64", - "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + "22.22.3-windows_amd64": [ + "node-v22.22.3-win-x64.zip", + "node-v22.22.3-win-x64", + "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33" ] }, "node_urls": [ "https://nodejs.org/dist/v{version}/{filename}" ], - "node_version": "22.22.0", + "node_version": "22.22.3", "include_headers": false, "platform": "linux_arm64" } @@ -1199,46 +1058,46 @@ "attributes": { "node_download_auth": {}, "node_repositories": { - "22.22.0-darwin_arm64": [ - "node-v22.22.0-darwin-arm64.tar.gz", - "node-v22.22.0-darwin-arm64", - "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + "22.22.3-darwin_arm64": [ + "node-v22.22.3-darwin-arm64.tar.gz", + "node-v22.22.3-darwin-arm64", + "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207" ], - "22.22.0-darwin_amd64": [ - "node-v22.22.0-darwin-x64.tar.gz", - "node-v22.22.0-darwin-x64", - "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + "22.22.3-darwin_amd64": [ + "node-v22.22.3-darwin-x64.tar.gz", + "node-v22.22.3-darwin-x64", + "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec" ], - "22.22.0-linux_arm64": [ - "node-v22.22.0-linux-arm64.tar.xz", - "node-v22.22.0-linux-arm64", - "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + "22.22.3-linux_arm64": [ + "node-v22.22.3-linux-arm64.tar.xz", + "node-v22.22.3-linux-arm64", + "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7" ], - "22.22.0-linux_ppc64le": [ - "node-v22.22.0-linux-ppc64le.tar.xz", - "node-v22.22.0-linux-ppc64le", - "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + "22.22.3-linux_ppc64le": [ + "node-v22.22.3-linux-ppc64le.tar.xz", + "node-v22.22.3-linux-ppc64le", + "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754" ], - "22.22.0-linux_s390x": [ - "node-v22.22.0-linux-s390x.tar.xz", - "node-v22.22.0-linux-s390x", - "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + "22.22.3-linux_s390x": [ + "node-v22.22.3-linux-s390x.tar.xz", + "node-v22.22.3-linux-s390x", + "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617" ], - "22.22.0-linux_amd64": [ - "node-v22.22.0-linux-x64.tar.xz", - "node-v22.22.0-linux-x64", - "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + "22.22.3-linux_amd64": [ + "node-v22.22.3-linux-x64.tar.xz", + "node-v22.22.3-linux-x64", + "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f" ], - "22.22.0-windows_amd64": [ - "node-v22.22.0-win-x64.zip", - "node-v22.22.0-win-x64", - "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + "22.22.3-windows_amd64": [ + "node-v22.22.3-win-x64.zip", + "node-v22.22.3-win-x64", + "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33" ] }, "node_urls": [ "https://nodejs.org/dist/v{version}/{filename}" ], - "node_version": "22.22.0", + "node_version": "22.22.3", "include_headers": false, "platform": "linux_s390x" } @@ -1248,46 +1107,46 @@ "attributes": { "node_download_auth": {}, "node_repositories": { - "22.22.0-darwin_arm64": [ - "node-v22.22.0-darwin-arm64.tar.gz", - "node-v22.22.0-darwin-arm64", - "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + "22.22.3-darwin_arm64": [ + "node-v22.22.3-darwin-arm64.tar.gz", + "node-v22.22.3-darwin-arm64", + "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207" ], - "22.22.0-darwin_amd64": [ - "node-v22.22.0-darwin-x64.tar.gz", - "node-v22.22.0-darwin-x64", - "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + "22.22.3-darwin_amd64": [ + "node-v22.22.3-darwin-x64.tar.gz", + "node-v22.22.3-darwin-x64", + "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec" ], - "22.22.0-linux_arm64": [ - "node-v22.22.0-linux-arm64.tar.xz", - "node-v22.22.0-linux-arm64", - "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + "22.22.3-linux_arm64": [ + "node-v22.22.3-linux-arm64.tar.xz", + "node-v22.22.3-linux-arm64", + "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7" ], - "22.22.0-linux_ppc64le": [ - "node-v22.22.0-linux-ppc64le.tar.xz", - "node-v22.22.0-linux-ppc64le", - "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + "22.22.3-linux_ppc64le": [ + "node-v22.22.3-linux-ppc64le.tar.xz", + "node-v22.22.3-linux-ppc64le", + "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754" ], - "22.22.0-linux_s390x": [ - "node-v22.22.0-linux-s390x.tar.xz", - "node-v22.22.0-linux-s390x", - "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + "22.22.3-linux_s390x": [ + "node-v22.22.3-linux-s390x.tar.xz", + "node-v22.22.3-linux-s390x", + "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617" ], - "22.22.0-linux_amd64": [ - "node-v22.22.0-linux-x64.tar.xz", - "node-v22.22.0-linux-x64", - "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + "22.22.3-linux_amd64": [ + "node-v22.22.3-linux-x64.tar.xz", + "node-v22.22.3-linux-x64", + "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f" ], - "22.22.0-windows_amd64": [ - "node-v22.22.0-win-x64.zip", - "node-v22.22.0-win-x64", - "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + "22.22.3-windows_amd64": [ + "node-v22.22.3-win-x64.zip", + "node-v22.22.3-win-x64", + "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33" ] }, "node_urls": [ "https://nodejs.org/dist/v{version}/{filename}" ], - "node_version": "22.22.0", + "node_version": "22.22.3", "include_headers": false, "platform": "linux_ppc64le" } @@ -1297,46 +1156,46 @@ "attributes": { "node_download_auth": {}, "node_repositories": { - "22.22.0-darwin_arm64": [ - "node-v22.22.0-darwin-arm64.tar.gz", - "node-v22.22.0-darwin-arm64", - "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + "22.22.3-darwin_arm64": [ + "node-v22.22.3-darwin-arm64.tar.gz", + "node-v22.22.3-darwin-arm64", + "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207" ], - "22.22.0-darwin_amd64": [ - "node-v22.22.0-darwin-x64.tar.gz", - "node-v22.22.0-darwin-x64", - "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + "22.22.3-darwin_amd64": [ + "node-v22.22.3-darwin-x64.tar.gz", + "node-v22.22.3-darwin-x64", + "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec" ], - "22.22.0-linux_arm64": [ - "node-v22.22.0-linux-arm64.tar.xz", - "node-v22.22.0-linux-arm64", - "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + "22.22.3-linux_arm64": [ + "node-v22.22.3-linux-arm64.tar.xz", + "node-v22.22.3-linux-arm64", + "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7" ], - "22.22.0-linux_ppc64le": [ - "node-v22.22.0-linux-ppc64le.tar.xz", - "node-v22.22.0-linux-ppc64le", - "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + "22.22.3-linux_ppc64le": [ + "node-v22.22.3-linux-ppc64le.tar.xz", + "node-v22.22.3-linux-ppc64le", + "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754" ], - "22.22.0-linux_s390x": [ - "node-v22.22.0-linux-s390x.tar.xz", - "node-v22.22.0-linux-s390x", - "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + "22.22.3-linux_s390x": [ + "node-v22.22.3-linux-s390x.tar.xz", + "node-v22.22.3-linux-s390x", + "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617" ], - "22.22.0-linux_amd64": [ - "node-v22.22.0-linux-x64.tar.xz", - "node-v22.22.0-linux-x64", - "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + "22.22.3-linux_amd64": [ + "node-v22.22.3-linux-x64.tar.xz", + "node-v22.22.3-linux-x64", + "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f" ], - "22.22.0-windows_amd64": [ - "node-v22.22.0-win-x64.zip", - "node-v22.22.0-win-x64", - "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + "22.22.3-windows_amd64": [ + "node-v22.22.3-win-x64.zip", + "node-v22.22.3-win-x64", + "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33" ] }, "node_urls": [ "https://nodejs.org/dist/v{version}/{filename}" ], - "node_version": "22.22.0", + "node_version": "22.22.3", "include_headers": false, "platform": "darwin_amd64" } @@ -1346,46 +1205,46 @@ "attributes": { "node_download_auth": {}, "node_repositories": { - "22.22.0-darwin_arm64": [ - "node-v22.22.0-darwin-arm64.tar.gz", - "node-v22.22.0-darwin-arm64", - "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + "22.22.3-darwin_arm64": [ + "node-v22.22.3-darwin-arm64.tar.gz", + "node-v22.22.3-darwin-arm64", + "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207" ], - "22.22.0-darwin_amd64": [ - "node-v22.22.0-darwin-x64.tar.gz", - "node-v22.22.0-darwin-x64", - "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + "22.22.3-darwin_amd64": [ + "node-v22.22.3-darwin-x64.tar.gz", + "node-v22.22.3-darwin-x64", + "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec" ], - "22.22.0-linux_arm64": [ - "node-v22.22.0-linux-arm64.tar.xz", - "node-v22.22.0-linux-arm64", - "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + "22.22.3-linux_arm64": [ + "node-v22.22.3-linux-arm64.tar.xz", + "node-v22.22.3-linux-arm64", + "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7" ], - "22.22.0-linux_ppc64le": [ - "node-v22.22.0-linux-ppc64le.tar.xz", - "node-v22.22.0-linux-ppc64le", - "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + "22.22.3-linux_ppc64le": [ + "node-v22.22.3-linux-ppc64le.tar.xz", + "node-v22.22.3-linux-ppc64le", + "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754" ], - "22.22.0-linux_s390x": [ - "node-v22.22.0-linux-s390x.tar.xz", - "node-v22.22.0-linux-s390x", - "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + "22.22.3-linux_s390x": [ + "node-v22.22.3-linux-s390x.tar.xz", + "node-v22.22.3-linux-s390x", + "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617" ], - "22.22.0-linux_amd64": [ - "node-v22.22.0-linux-x64.tar.xz", - "node-v22.22.0-linux-x64", - "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + "22.22.3-linux_amd64": [ + "node-v22.22.3-linux-x64.tar.xz", + "node-v22.22.3-linux-x64", + "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f" ], - "22.22.0-windows_amd64": [ - "node-v22.22.0-win-x64.zip", - "node-v22.22.0-win-x64", - "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + "22.22.3-windows_amd64": [ + "node-v22.22.3-win-x64.zip", + "node-v22.22.3-win-x64", + "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33" ] }, "node_urls": [ "https://nodejs.org/dist/v{version}/{filename}" ], - "node_version": "22.22.0", + "node_version": "22.22.3", "include_headers": false, "platform": "darwin_arm64" } @@ -1395,46 +1254,46 @@ "attributes": { "node_download_auth": {}, "node_repositories": { - "22.22.0-darwin_arm64": [ - "node-v22.22.0-darwin-arm64.tar.gz", - "node-v22.22.0-darwin-arm64", - "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + "22.22.3-darwin_arm64": [ + "node-v22.22.3-darwin-arm64.tar.gz", + "node-v22.22.3-darwin-arm64", + "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207" ], - "22.22.0-darwin_amd64": [ - "node-v22.22.0-darwin-x64.tar.gz", - "node-v22.22.0-darwin-x64", - "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + "22.22.3-darwin_amd64": [ + "node-v22.22.3-darwin-x64.tar.gz", + "node-v22.22.3-darwin-x64", + "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec" ], - "22.22.0-linux_arm64": [ - "node-v22.22.0-linux-arm64.tar.xz", - "node-v22.22.0-linux-arm64", - "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + "22.22.3-linux_arm64": [ + "node-v22.22.3-linux-arm64.tar.xz", + "node-v22.22.3-linux-arm64", + "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7" ], - "22.22.0-linux_ppc64le": [ - "node-v22.22.0-linux-ppc64le.tar.xz", - "node-v22.22.0-linux-ppc64le", - "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + "22.22.3-linux_ppc64le": [ + "node-v22.22.3-linux-ppc64le.tar.xz", + "node-v22.22.3-linux-ppc64le", + "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754" ], - "22.22.0-linux_s390x": [ - "node-v22.22.0-linux-s390x.tar.xz", - "node-v22.22.0-linux-s390x", - "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + "22.22.3-linux_s390x": [ + "node-v22.22.3-linux-s390x.tar.xz", + "node-v22.22.3-linux-s390x", + "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617" ], - "22.22.0-linux_amd64": [ - "node-v22.22.0-linux-x64.tar.xz", - "node-v22.22.0-linux-x64", - "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + "22.22.3-linux_amd64": [ + "node-v22.22.3-linux-x64.tar.xz", + "node-v22.22.3-linux-x64", + "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f" ], - "22.22.0-windows_amd64": [ - "node-v22.22.0-win-x64.zip", - "node-v22.22.0-win-x64", - "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + "22.22.3-windows_amd64": [ + "node-v22.22.3-win-x64.zip", + "node-v22.22.3-win-x64", + "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33" ] }, "node_urls": [ "https://nodejs.org/dist/v{version}/{filename}" ], - "node_version": "22.22.0", + "node_version": "22.22.3", "include_headers": false, "platform": "windows_amd64" } @@ -1444,46 +1303,46 @@ "attributes": { "node_download_auth": {}, "node_repositories": { - "22.22.0-darwin_arm64": [ - "node-v22.22.0-darwin-arm64.tar.gz", - "node-v22.22.0-darwin-arm64", - "5ed4db0fcf1eaf84d91ad12462631d73bf4576c1377e192d222e48026a902640" + "22.22.3-darwin_arm64": [ + "node-v22.22.3-darwin-arm64.tar.gz", + "node-v22.22.3-darwin-arm64", + "0da7ff74ef8611328c8212f17943368713a2ad953fb7d89a8c8a0eae87c23207" ], - "22.22.0-darwin_amd64": [ - "node-v22.22.0-darwin-x64.tar.gz", - "node-v22.22.0-darwin-x64", - "5ea50c9d6dea3dfa3abb66b2656f7a4e1c8cef23432b558d45fb538c7b5dedce" + "22.22.3-darwin_amd64": [ + "node-v22.22.3-darwin-x64.tar.gz", + "node-v22.22.3-darwin-x64", + "45830ba752fa0d892c6dcd640946669801293cac820a33591ded40ac075198ec" ], - "22.22.0-linux_arm64": [ - "node-v22.22.0-linux-arm64.tar.xz", - "node-v22.22.0-linux-arm64", - "1bf1eb9ee63ffc4e5d324c0b9b62cf4a289f44332dfef9607cea1a0d9596ba6f" + "22.22.3-linux_arm64": [ + "node-v22.22.3-linux-arm64.tar.xz", + "node-v22.22.3-linux-arm64", + "1c4a9933a5e45bc88f54f70b5f91232c127ec49f1a5989d23fb85824c7adf9b7" ], - "22.22.0-linux_ppc64le": [ - "node-v22.22.0-linux-ppc64le.tar.xz", - "node-v22.22.0-linux-ppc64le", - "d83b9957431cc18e1fc143a4b99f89cde7b8a18f53ef392231b4336afd058865" + "22.22.3-linux_ppc64le": [ + "node-v22.22.3-linux-ppc64le.tar.xz", + "node-v22.22.3-linux-ppc64le", + "edb5478071bd1375e80195ca52f72823998bb5141b1a09e68bc54b3e2eb67754" ], - "22.22.0-linux_s390x": [ - "node-v22.22.0-linux-s390x.tar.xz", - "node-v22.22.0-linux-s390x", - "5aa0e520689448c4233e8d73f284e8e0634fdcd32b479735698494be5641f3e4" + "22.22.3-linux_s390x": [ + "node-v22.22.3-linux-s390x.tar.xz", + "node-v22.22.3-linux-s390x", + "ce398c057830d57a24c458177279a17bc51742d5c22dd4cbe97b10dbd43f2617" ], - "22.22.0-linux_amd64": [ - "node-v22.22.0-linux-x64.tar.xz", - "node-v22.22.0-linux-x64", - "9aa8e9d2298ab68c600bd6fb86a6c13bce11a4eca1ba9b39d79fa021755d7c37" + "22.22.3-linux_amd64": [ + "node-v22.22.3-linux-x64.tar.xz", + "node-v22.22.3-linux-x64", + "2e5d13569282d016861fae7c8f935e741693c269101a5bebcf761a5376d1f99f" ], - "22.22.0-windows_amd64": [ - "node-v22.22.0-win-x64.zip", - "node-v22.22.0-win-x64", - "c97fa376d2becdc8863fcd3ca2dd9a83a9f3468ee7ccf7a6d076ec66a645c77a" + "22.22.3-windows_amd64": [ + "node-v22.22.3-win-x64.zip", + "node-v22.22.3-win-x64", + "6c8d54f635feff4df76c2ca80f45332eb2ff57d25226edce36592e51a177ee33" ] }, "node_urls": [ "https://nodejs.org/dist/v{version}/{filename}" ], - "node_version": "22.22.0", + "node_version": "22.22.3", "include_headers": false, "platform": "windows_arm64" } @@ -1512,7 +1371,7 @@ }, "@@rules_python+//python/extensions:pip.bzl%pip": { "general": { - "bzlTransitiveDigest": "d3ENjFH8qMwmOrkcb3c9JYqQ5hJ6owjfbSr24KY0Ugg=", + "bzlTransitiveDigest": "weEvgIUjy7e5WRM5OvjzsfGuVlHNMpaVlfYXco8WUIk=", "usagesDigest": "/9NP3RV6/DWuNdYAsIU/8UCgCX0TdPUJr0X6O+0lrtk=", "recordedFileInputs": { "@@protobuf+//python/requirements.txt": "983be60d3cec4b319dcab6d48aeb3f5b2f7c3350f26b3a9e97486c37967c73c5", @@ -4250,7 +4109,7 @@ }, "@@rules_sass+//src/toolchain:extensions.bzl%sass": { "general": { - "bzlTransitiveDigest": "RA58Nyrsn03Z5YmQnpmBw3mqlVck++XIrx34amsqU/E=", + "bzlTransitiveDigest": "mOfuR8PsNuUWEq7JZ4MpIRbwyAGAqrCvkXXGaRNnlPQ=", "usagesDigest": "R0KshhzIouLWuexMUCrl4HY+FUDwlVVgF9Z7UnwyUWA=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -4306,8 +4165,8 @@ }, "@@yq.bzl+//yq:extensions.bzl%yq": { "general": { - "bzlTransitiveDigest": "tDqk+ntWTdxNAWPDjRY1uITgHbti2jcXR5ZdinltBs0=", - "usagesDigest": "soFLPyR23RWNxgPKSnibWfWF51i2rx0N0Vs4WyvVNpE=", + "bzlTransitiveDigest": "UfFMy8CWK4/dVo/tfaSAIYUiDGNAPes5eRllx9O9Q9Q=", + "usagesDigest": "S0iKr407bxb958DX9nvFIy8pPC6eCZJYS5Ok45r9U2g=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/README.md b/README.md index baf7d7a7d94c..c1528944d08b 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Install the Angular CLI globally: npm install -g @angular/cli ``` -Create workspace: +Create a workspace: ``` ng new [PROJECT NAME] diff --git a/adev/BUILD.bazel b/adev/BUILD.bazel index a0f5864bbd70..3f6665762c81 100644 --- a/adev/BUILD.bazel +++ b/adev/BUILD.bazel @@ -97,6 +97,7 @@ APPLICATION_DEPS = [ ":node_modules/@shikijs/langs", ":node_modules/@shikijs/engine-oniguruma", ":node_modules/@shikijs/core", + ":node_modules/@shikijs/primitive", ":node_modules/@shikijs/engine-javascript", ":node_modules/@shikijs/types", ":node_modules/@shikijs/vscode-textmate", diff --git a/adev/angular.json b/adev/angular.json index de7df65a4b1e..018db2f5e94e 100644 --- a/adev/angular.json +++ b/adev/angular.json @@ -7,9 +7,7 @@ "projectType": "application", "schematics": { "@schematics/angular:component": { - "style": "scss", - "standalone": true, - "changeDetection": "OnPush" + "style": "scss" } }, "root": ".", @@ -77,12 +75,10 @@ }, "test": { "builder": "@angular/build:unit-test", - "options": { + "options": { "runner": "karma", "browsers": ["ChromeHeadlessNoSandbox"], - "include": [ - "src/app/**/*.spec.ts" - ] + "include": ["src/app/**/*.spec.ts"] } } } diff --git a/adev/package.json b/adev/package.json index 6129f3d829e0..183c15799801 100644 --- a/adev/package.json +++ b/adev/package.json @@ -1,104 +1,105 @@ { "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/client-search": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-node-http": "5.48.0", + "@algolia/client-common": "5.52.1", + "@algolia/client-search": "5.52.1", + "@algolia/requester-browser-xhr": "5.52.1", + "@algolia/requester-node-http": "5.52.1", "@angular/animations": "workspace:*", - "@angular/aria": "21.2.0-rc.0", - "@angular/build": "21.2.0-next.2", - "@angular/cdk": "21.2.0-rc.0", - "@angular/cli": "21.2.0-next.2", + "@angular/aria": "22.0.0-rc.0", + "@angular/build": "22.0.0-rc.0", + "@angular/cdk": "22.0.0-rc.0", + "@angular/cli": "22.0.0-rc.0", "@angular/common": "workspace:*", "@angular/compiler": "workspace:*", "@angular/compiler-cli": "workspace:*", "@angular/core": "workspace:*", "@angular/docs": "workspace:*", "@angular/forms": "workspace:*", - "@angular/material": "21.2.0-rc.0", + "@angular/material": "22.0.0-rc.0", "@angular/platform-browser": "workspace:*", "@angular/platform-server": "workspace:*", "@angular/router": "workspace:*", - "@angular/ssr": "21.2.0-next.2", - "@codemirror/autocomplete": "6.20.0", - "@codemirror/commands": "6.10.1", + "@angular/ssr": "22.0.0-rc.0", + "@codemirror/autocomplete": "6.20.2", + "@codemirror/commands": "6.10.3", "@codemirror/lang-angular": "0.1.4", "@codemirror/lang-css": "6.3.1", "@codemirror/lang-html": "6.4.11", - "@codemirror/lang-javascript": "6.2.4", + "@codemirror/lang-javascript": "6.2.5", "@codemirror/lang-sass": "6.0.2", - "@codemirror/language": "6.12.1", - "@codemirror/lint": "6.9.3", - "@codemirror/search": "6.6.0", - "@codemirror/state": "6.5.4", - "@codemirror/view": "6.39.12", - "@lezer/common": "1.5.1", - "@lezer/css": "1.3.0", + "@codemirror/language": "6.12.3", + "@codemirror/lint": "6.9.6", + "@codemirror/search": "6.7.0", + "@codemirror/state": "6.6.0", + "@codemirror/view": "6.43.0", + "@lezer/common": "1.5.2", + "@lezer/css": "1.3.3", "@lezer/highlight": "1.2.3", "@lezer/html": "1.3.13", "@lezer/javascript": "1.5.4", - "@lezer/lr": "1.4.8", + "@lezer/lr": "1.4.10", "@lezer/sass": "1.1.0", "@marijn/find-cluster-break": "1.0.2", - "@shikijs/core": "^3.21.0", - "@shikijs/engine-javascript": "^3.21.0", - "@shikijs/engine-oniguruma": "^3.21.0", - "@shikijs/langs": "^3.21.0", - "@shikijs/themes": "^3.21.0", - "@shikijs/types": "^3.21.0", + "@shikijs/core": "^4.0.0", + "@shikijs/engine-javascript": "^4.0.0", + "@shikijs/engine-oniguruma": "^4.0.0", + "@shikijs/langs": "^4.0.0", + "@shikijs/primitive": "^4.0.0", + "@shikijs/themes": "^4.0.0", + "@shikijs/types": "^4.0.0", "@shikijs/vscode-textmate": "^10.0.2", "@stackblitz/sdk": "1.11.0", - "@types/dom-navigation": "1.0.6", + "@types/dom-navigation": "1.0.7", "@types/jasmine": "6.0.0", - "@types/jsdom": "27.0.0", - "@types/node": "24.10.11", - "@typescript/vfs": "1.6.2", - "@webcontainer/api": "1.6.1", + "@types/jsdom": "28.0.3", + "@types/node": "24.12.4", + "@typescript/vfs": "1.6.4", + "@webcontainer/api": "1.6.4", "@xterm/addon-fit": "0.11.0", "@xterm/xterm": "6.0.0", - "algoliasearch": "5.48.0", + "algoliasearch": "5.52.1", "angular-split": "20.0.0", "ccount": "^2.0.1", "character-entities-html4": "^2.1.0", "character-entities-legacy": "^3.0.0", "comma-separated-tokens": "^2.0.3", "crelt": "1.0.6", - "diff": "8.0.3", + "diff": "9.0.0", "emoji-regex": "10.6.0", "fflate": "0.8.2", "hast-util-to-html": "^9.0.5", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", - "jsdom": "28.0.0", - "jasmine-core": "6.0.0", + "jsdom": "29.1.1", + "jasmine-core": "6.2.0", "karma-chrome-launcher": "3.2.0", "karma-coverage": "2.2.1", "karma-jasmine": "5.1.0", "karma-jasmine-html-reporter": "2.2.0", - "marked": "17.0.1", - "mermaid": "11.12.2", + "marked": "18.0.3", + "mermaid": "11.15.0", "ngx-progressbar": "14.0.0", "open-in-idx": "0.1.1", - "playwright-core": "1.58.1", - "preact": "10.28.3", - "preact-render-to-string": "6.6.5", - "prettier": "3.8.1", + "playwright-core": "1.60.0", + "preact": "10.29.1", + "preact-render-to-string": "6.6.7", + "prettier": "3.8.3", "property-information": "^7.1.0", "rxjs": "7.8.2", - "shiki": "3.22.0", + "shiki": "4.0.2", "space-separated-tokens": "^2.0.2", "stringify-entities": "^4.0.4", "style-mod": "4.1.3", - "tinyglobby": "0.2.15", + "tinyglobby": "0.2.16", "tslib": "2.8.1", - "typescript": "6.0.0-beta", + "typescript": "6.0.3", "w3c-keyname": "2.2.8", "zwitch": "^2.0.4" }, "devDependencies": { - "autoprefixer": "10.4.24", + "autoprefixer": "10.5.0", "karma": "~6.4.4", - "postcss": "8.5.6", + "postcss": "8.5.14", "tailwindcss": "3.4.19" } } diff --git a/adev/scripts/routes/BUILD.bazel b/adev/scripts/routes/BUILD.bazel index 387d65897cc3..573d7aeca2a7 100644 --- a/adev/scripts/routes/BUILD.bazel +++ b/adev/scripts/routes/BUILD.bazel @@ -29,6 +29,7 @@ ts_project( deps = [ "//adev:node_modules/@angular/docs", "//adev:node_modules/@types/node", + "//adev/shared-docs/pipeline/shared:heading", "//adev/src/app/routing/navigation-entries", "//adev/src/content/reference/errors:route-nav-items", "//adev/src/content/reference/extended-diagnostics:route-nav-items", diff --git a/adev/scripts/routes/generate-routes.mts b/adev/scripts/routes/generate-routes.mts index 3cb1b64d64d9..f6cd639f9710 100644 --- a/adev/scripts/routes/generate-routes.mts +++ b/adev/scripts/routes/generate-routes.mts @@ -7,6 +7,7 @@ */ import {ALL_ITEMS} from '../../src/app/routing/navigation-entries/index.js'; +import {getIdFromHeading} from '../../shared-docs/pipeline/shared/heading.mjs'; import {NavigationItem} from '@angular/docs'; import {writeFileSync, readFileSync} from 'fs'; import {join, resolve} from 'path'; @@ -72,11 +73,3 @@ function main() { } main(); - -// TODO: refactor so this function is shared with the generation pipeline (adev/shared-docs/pipeline/shared/marked/transformations/heading.mts) -function getIdFromHeading(heading: string): string { - return heading - .toLowerCase() - .replace(/\s|\//g, '-') // replace spaces and slashes with dashes - .replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes -} diff --git a/adev/shared-docs/components/algolia-icon/algolia-icon.component.ts b/adev/shared-docs/components/algolia-icon/algolia-icon.component.ts index 270612ada955..4f439f07d904 100644 --- a/adev/shared-docs/components/algolia-icon/algolia-icon.component.ts +++ b/adev/shared-docs/components/algolia-icon/algolia-icon.component.ts @@ -6,11 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'docs-algolia-icon', - changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './algolia-icon.component.html', }) export class AlgoliaIcon {} diff --git a/adev/shared-docs/components/breadcrumb/breadcrumb.component.ts b/adev/shared-docs/components/breadcrumb/breadcrumb.component.ts index 1dba0a5210e6..9c31c14eb7d4 100644 --- a/adev/shared-docs/components/breadcrumb/breadcrumb.component.ts +++ b/adev/shared-docs/components/breadcrumb/breadcrumb.component.ts @@ -6,17 +6,16 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, inject, computed} from '@angular/core'; -import {NavigationState} from '../../services/index'; -import {NavigationItem} from '../../interfaces/index'; +import {Component, computed, inject} from '@angular/core'; import {RouterLink} from '@angular/router'; +import {NavigationItem} from '../../interfaces/index'; +import {NavigationState} from '../../services/index'; @Component({ selector: 'docs-breadcrumb', imports: [RouterLink], templateUrl: './breadcrumb.component.html', styleUrls: ['./breadcrumb.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class Breadcrumb { private readonly navigationState = inject(NavigationState); diff --git a/adev/shared-docs/components/cookie-popup/cookie-popup.component.ts b/adev/shared-docs/components/cookie-popup/cookie-popup.component.ts index dd881dfd539a..a1aeb670a0ba 100644 --- a/adev/shared-docs/components/cookie-popup/cookie-popup.component.ts +++ b/adev/shared-docs/components/cookie-popup/cookie-popup.component.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core'; +import {Component, inject, signal} from '@angular/core'; import {LOCAL_STORAGE} from '../../providers/index'; import {setCookieConsent} from '../../utils'; @@ -21,7 +21,6 @@ export const STORAGE_KEY = 'docs-accepts-cookies'; selector: 'docs-cookie-popup', templateUrl: './cookie-popup.component.html', styleUrls: ['./cookie-popup.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CookiePopup { private readonly localStorage = inject(LOCAL_STORAGE); diff --git a/adev/shared-docs/components/copy-link-anchor/copy-link-anchor.component.ts b/adev/shared-docs/components/copy-link-anchor/copy-link-anchor.component.ts index d7ff132a3e3c..e0317ce2532f 100644 --- a/adev/shared-docs/components/copy-link-anchor/copy-link-anchor.component.ts +++ b/adev/shared-docs/components/copy-link-anchor/copy-link-anchor.component.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, inject, input, signal} from '@angular/core'; import {Clipboard} from '@angular/cdk/clipboard'; +import {Component, inject, input, signal} from '@angular/core'; import {MatTooltip} from '@angular/material/tooltip'; import {IconComponent} from '../icon/icon.component'; @@ -45,7 +45,6 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 1000; '[class.docs-copy-link-success]': 'showCopySuccess()', }, imports: [IconComponent], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CopyLinkButton { readonly href = input.required(); diff --git a/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.spec.ts b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.spec.ts index a3d4f1e612ab..8485588947e7 100644 --- a/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.spec.ts +++ b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.spec.ts @@ -8,13 +8,13 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; +import {Clipboard} from '@angular/cdk/clipboard'; +import {Component, signal} from '@angular/core'; +import {By} from '@angular/platform-browser'; import { CONFIRMATION_DISPLAY_TIME_MS, CopySourceCodeButton, } from './copy-source-code-button.component'; -import {ChangeDetectionStrategy, Component, signal} from '@angular/core'; -import {By} from '@angular/platform-browser'; -import {Clipboard} from '@angular/cdk/clipboard'; const SUCCESSFULLY_COPY_CLASS_NAME = 'docs-copy-source-code-button-success'; const FAILED_COPY_CLASS_NAME = 'docs-copy-source-code-button-failed'; @@ -99,7 +99,6 @@ describe('CopySourceCodeButton', () => { `, imports: [CopySourceCodeButton], - changeDetection: ChangeDetectionStrategy.OnPush, }) class CodeSnippetWrapper { code = signal(''); diff --git a/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.ts b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.ts index 9301090d29c2..9664603a05ae 100644 --- a/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.ts +++ b/adev/shared-docs/components/copy-source-code-button/copy-source-code-button.component.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ +import {Clipboard} from '@angular/cdk/clipboard'; import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, @@ -15,7 +15,6 @@ import { inject, signal, } from '@angular/core'; -import {Clipboard} from '@angular/cdk/clipboard'; import {IconComponent} from '../icon/icon.component'; export const REMOVED_LINE_CLASS_NAME = '.line.remove'; @@ -33,7 +32,6 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 2000; '[class.docs-copy-source-code-button-success]': 'showCopySuccess()', '[class.docs-copy-source-code-button-failed]': 'showCopyFailure()', }, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CopySourceCodeButton { private readonly changeDetector = inject(ChangeDetectorRef); diff --git a/adev/shared-docs/components/icon/icon.component.ts b/adev/shared-docs/components/icon/icon.component.ts index 187529a67122..8f0f9805254b 100644 --- a/adev/shared-docs/components/icon/icon.component.ts +++ b/adev/shared-docs/components/icon/icon.component.ts @@ -7,18 +7,10 @@ */ import {DOCUMENT} from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - afterNextRender, - computed, - inject, - signal, -} from '@angular/core'; +import {Component, afterNextRender, computed, inject, signal} from '@angular/core'; @Component({ selector: 'docs-icon', - changeDetection: ChangeDetectionStrategy.OnPush, host: { 'class': 'material-symbols-outlined', '[style.font-size.px]': 'fontSize()', diff --git a/adev/shared-docs/components/navigation-list/navigation-list.component.html b/adev/shared-docs/components/navigation-list/navigation-list.component.html index 2a8d892950f9..e37ae38ebf96 100644 --- a/adev/shared-docs/components/navigation-list/navigation-list.component.html +++ b/adev/shared-docs/components/navigation-list/navigation-list.component.html @@ -53,7 +53,7 @@ matrixParams: 'ignored', fragment: 'ignored', }" - (click)="emitClickOnLink()" + (click)="emitClickOnLink(item)" [matTooltip]="item.label" [matTooltipDisabled]="itemLabel.length < 27" matTooltipPosition="after" diff --git a/adev/shared-docs/components/navigation-list/navigation-list.component.scss b/adev/shared-docs/components/navigation-list/navigation-list.component.scss index 16c11ab97d37..c2eb98aaf97c 100644 --- a/adev/shared-docs/components/navigation-list/navigation-list.component.scss +++ b/adev/shared-docs/components/navigation-list/navigation-list.component.scss @@ -6,7 +6,7 @@ list-style: none; overflow-y: auto; overflow-x: hidden; - height: 100vh; + height: 100dvh; padding: 0; margin: 0; padding-block: 1.5rem; diff --git a/adev/shared-docs/components/navigation-list/navigation-list.component.spec.ts b/adev/shared-docs/components/navigation-list/navigation-list.component.spec.ts index a92995269da8..dee63071a9c7 100644 --- a/adev/shared-docs/components/navigation-list/navigation-list.component.spec.ts +++ b/adev/shared-docs/components/navigation-list/navigation-list.component.spec.ts @@ -153,5 +153,6 @@ describe('NavigationList', () => { class FakeNavigationListState { isOpened = signal(true); activeNavigationItem = signal(navigationItems.at(1)); + crossCategoryOrigin = signal(undefined); toggleItem(item: NavigationItem) {} } diff --git a/adev/shared-docs/components/navigation-list/navigation-list.component.ts b/adev/shared-docs/components/navigation-list/navigation-list.component.ts index a26ce4f9d7e7..3dbdd57ab35e 100644 --- a/adev/shared-docs/components/navigation-list/navigation-list.component.ts +++ b/adev/shared-docs/components/navigation-list/navigation-list.component.ts @@ -7,7 +7,7 @@ */ import {NgTemplateOutlet} from '@angular/common'; -import {ChangeDetectionStrategy, Component, inject, input, output} from '@angular/core'; +import {Component, inject, input, output} from '@angular/core'; import {MatTooltip} from '@angular/material/tooltip'; import {RouterLink, RouterLinkActive} from '@angular/router'; import {NavigationItem} from '../../interfaces/index'; @@ -27,7 +27,6 @@ import {IconComponent} from '../icon/icon.component'; ], templateUrl: './navigation-list.component.html', styleUrls: ['./navigation-list.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class NavigationList { readonly navigationItems = input.required(); @@ -40,6 +39,7 @@ export class NavigationList { readonly linkClicked = output(); private readonly navigationState = inject(NavigationState); + private readonly crossCategoryOrigin = this.navigationState.crossCategoryOrigin; readonly activeItem = this.navigationState.activeNavigationItem; @@ -51,10 +51,19 @@ export class NavigationList { ) { return; } + const prevParentItem = this.crossCategoryOrigin(); + if (prevParentItem) { + this.crossCategoryOrigin.set(undefined); + this.navigationState.toggleItem(prevParentItem); + return; + } this.navigationState.toggleItem(item); } - emitClickOnLink(): void { + emitClickOnLink(item: NavigationItem): void { + if (item.isCrossReferenced) { + this.crossCategoryOrigin.set(item.parent); + } this.linkClicked.emit(); } diff --git a/adev/shared-docs/components/search-dialog/BUILD.bazel b/adev/shared-docs/components/search-dialog/BUILD.bazel index 66335a6d2f89..24ad709978d5 100644 --- a/adev/shared-docs/components/search-dialog/BUILD.bazel +++ b/adev/shared-docs/components/search-dialog/BUILD.bazel @@ -29,6 +29,7 @@ ng_project( "//adev/shared-docs/pipes", "//adev/shared-docs/providers", "//adev/shared-docs/services", + "//adev/shared-docs/utils", ], ) diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.html b/adev/shared-docs/components/search-dialog/search-dialog.component.html index 783b5d957f98..b9ff5129ed3f 100644 --- a/adev/shared-docs/components/search-dialog/search-dialog.component.html +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.html @@ -27,14 +27,16 @@ - - - @if (result.package) { - - } + + + + @if (result.package) { + + } +

@if (result.subLabelHtml) { diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.scss b/adev/shared-docs/components/search-dialog/search-dialog.component.scss index fb4db397a8c7..ac9c543518e1 100644 --- a/adev/shared-docs/components/search-dialog/search-dialog.component.scss +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.scss @@ -46,6 +46,7 @@ dialog { .docs-search-result-icon { display: inline-block; + flex-shrink: 0; i { display: flex; @@ -91,7 +92,20 @@ dialog { &__label { font-weight: 600; - flex-wrap: wrap; + + &__text { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 0.25rem 0.5rem; + flex: 1; + min-width: 0; + overflow-wrap: break-word; + + > * { + min-width: 0; + } + } &__package { font-size: 0.75rem; @@ -137,14 +151,14 @@ dialog { .docs-search-results__start-typing, .docs-search-results__no-results { padding: 0.75rem; - color: var(--gray-400); + color: var(--quaternary-contrast); } .docs-search-footer { display: flex; align-items: center; justify-content: space-between; - color: var(--gray-400); + color: var(--quaternary-contrast); padding: 1rem; font-size: 0.75rem; font-weight: 500; diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.spec.ts b/adev/shared-docs/components/search-dialog/search-dialog.component.spec.ts index e1e18eb32af3..dc6bcf5e257c 100644 --- a/adev/shared-docs/components/search-dialog/search-dialog.component.spec.ts +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.spec.ts @@ -14,7 +14,7 @@ import {Router, provideRouter} from '@angular/router'; import {SearchDialog} from './search-dialog.component'; import {ENVIRONMENT, WINDOW} from '../../providers'; import {ALGOLIA_CLIENT, Search} from '../../services'; -import {FakeEventTarget} from '../../testing/index'; +import {FakeEventTarget, timeout, useAutoTick} from '../../testing/index'; import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component'; import {SearchResult} from '../../interfaces'; @@ -27,8 +27,10 @@ describe('SearchDialog', () => { let search: Search; + useAutoTick(); + beforeEach(async () => { - searchResults.and.returnValue([]); + searchResults.and.returnValue(Promise.resolve({results: [{hits: []}]})); TestBed.configureTestingModule({ imports: [SearchDialog], @@ -55,6 +57,9 @@ describe('SearchDialog', () => { // Fire the request TestBed.inject(ApplicationRef).tick(); + // The delay from debounced (200ms) + await timeout(300); + // Wait for the resource to resolve await TestBed.inject(ApplicationRef).whenStable(); @@ -85,6 +90,9 @@ describe('SearchDialog', () => { // Fire the request TestBed.inject(ApplicationRef).tick(); + // The delay from debounced (200ms) + await timeout(300); + // Wait for the resource to resolve await TestBed.inject(ApplicationRef).whenStable(); @@ -96,7 +104,7 @@ describe('SearchDialog', () => { }); it('should display `Start typing to see results` message when there are no provided query', () => { - searchResults.and.returnValue(undefined); + searchResults.and.returnValue(Promise.resolve(undefined)); const startTypingContainer = fixture.debugElement.query( By.css('.docs-search-results__start-typing'), @@ -112,6 +120,9 @@ describe('SearchDialog', () => { // Fire the request TestBed.inject(ApplicationRef).tick(); + // The delay from debounced (200ms) + await timeout(300); + // Wait for the resource to resolve await TestBed.inject(ApplicationRef).whenStable(); diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.ts b/adev/shared-docs/components/search-dialog/search-dialog.component.ts index dedff0ed2b45..947d82f998a1 100644 --- a/adev/shared-docs/components/search-dialog/search-dialog.component.ts +++ b/adev/shared-docs/components/search-dialog/search-dialog.component.ts @@ -8,7 +8,6 @@ import { afterNextRender, - ChangeDetectionStrategy, Component, DestroyRef, effect, @@ -33,10 +32,10 @@ import {RelativeLink} from '../../pipes'; import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component'; import {SearchHistoryComponent} from '../search-history/search-history.component'; import {TextField} from '../text-field/text-field.component'; +import {getRelativeUrl} from '../../utils'; @Component({ selector: 'docs-search-dialog', - changeDetection: ChangeDetectionStrategy.OnPush, imports: [ ClickOutside, TextField, @@ -57,7 +56,7 @@ export class SearchDialog { readonly history = inject(SearchHistory); private readonly search = inject(Search); - private readonly relativeLink = new RelativeLink(); + private readonly router = inject(Router); private readonly window = inject(WINDOW); private readonly injector = inject(Injector); @@ -122,7 +121,7 @@ export class SearchDialog { return; } - this.router.navigateByUrl(this.relativeLink.transform(activeItemLink)); + this.router.navigateByUrl(getRelativeUrl(activeItemLink)); this.onClose.emit(); } } diff --git a/adev/shared-docs/components/search-history/BUILD.bazel b/adev/shared-docs/components/search-history/BUILD.bazel index 67e58bd31bed..8e728f3249fb 100644 --- a/adev/shared-docs/components/search-history/BUILD.bazel +++ b/adev/shared-docs/components/search-history/BUILD.bazel @@ -28,6 +28,7 @@ ng_project( "//adev/shared-docs/directives", "//adev/shared-docs/pipes", "//adev/shared-docs/services", + "//adev/shared-docs/utils", ], ) diff --git a/adev/shared-docs/components/search-history/search-history.component.ts b/adev/shared-docs/components/search-history/search-history.component.ts index 2d038b7b6178..4830df053fe7 100644 --- a/adev/shared-docs/components/search-history/search-history.component.ts +++ b/adev/shared-docs/components/search-history/search-history.component.ts @@ -6,9 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ +import {ActiveDescendantKeyManager} from '@angular/cdk/a11y'; +import {NgTemplateOutlet} from '@angular/common'; import { afterNextRender, - ChangeDetectionStrategy, Component, DestroyRef, effect, @@ -16,14 +17,13 @@ import { Injector, viewChildren, } from '@angular/core'; -import {Router, RouterLink} from '@angular/router'; import {toSignal} from '@angular/core/rxjs-interop'; -import {ActiveDescendantKeyManager} from '@angular/cdk/a11y'; -import {NgTemplateOutlet} from '@angular/common'; +import {Router, RouterLink} from '@angular/router'; -import {SearchHistory} from '../../services'; -import {RelativeLink} from '../../pipes'; import {SearchItem} from '../../directives'; +import {RelativeLink} from '../../pipes'; +import {SearchHistory} from '../../services'; +import {getRelativeUrl} from '../../utils'; @Component({ selector: 'docs-search-history', @@ -34,7 +34,6 @@ import {SearchItem} from '../../directives'; '(document:keydown)': 'onKeydown($event)', '(document:mousemove)': 'onMouseMove($event)', }, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class SearchHistoryComponent { protected readonly items = viewChildren(SearchItem); @@ -43,7 +42,6 @@ export class SearchHistoryComponent { private readonly injector = inject(Injector); private readonly router = inject(Router); - private readonly relativeLink = new RelativeLink(); private readonly keyManager = new ActiveDescendantKeyManager( this.items, this.injector, @@ -101,7 +99,7 @@ export class SearchHistoryComponent { const activeItemLink = this.keyManager.activeItem?.item()?.url; if (activeItemLink) { - const url = this.relativeLink.transform(activeItemLink); + const url = getRelativeUrl(activeItemLink); this.router.navigateByUrl(url); } } diff --git a/adev/shared-docs/components/select/BUILD.bazel b/adev/shared-docs/components/select/BUILD.bazel index 9ee74aaa8c50..48e5d609162b 100644 --- a/adev/shared-docs/components/select/BUILD.bazel +++ b/adev/shared-docs/components/select/BUILD.bazel @@ -16,6 +16,7 @@ ng_project( ], deps = [ "//adev:node_modules/@angular/aria", + "//adev:node_modules/@angular/cdk", "//adev:node_modules/@angular/common", "//adev:node_modules/@angular/core", "//adev:node_modules/@angular/forms", diff --git a/adev/shared-docs/components/select/select.component.html b/adev/shared-docs/components/select/select.component.html index 71427ab9184c..da98bd4a0b38 100644 --- a/adev/shared-docs/components/select/select.component.html +++ b/adev/shared-docs/components/select/select.component.html @@ -1,61 +1,93 @@ -
-
+
+
- - -
-
- - -
+ + +
+
+
+
+ + +
- - @if (filteredOptions().length === 0) { -
No results found
- } +
+ {{ filteredOptions().length === 0 ? 'No results found for ' + searchString() : '' }} +
-
- @for (option of filteredOptions(); track option.value) { -
- {{ option.label }} - +
+ @if (filteredOptions().length === 0) { +
No results found
+ } +
+ @for (option of filteredOptions(); track option.value) { +
+ {{ option.label }} + +
+ } +
- } +
- +
-
+
diff --git a/adev/shared-docs/components/select/select.component.scss b/adev/shared-docs/components/select/select.component.scss index cc9d81910c4f..90a88ae091f5 100644 --- a/adev/shared-docs/components/select/select.component.scss +++ b/adev/shared-docs/components/select/select.component.scss @@ -1,8 +1,6 @@ :host { --border-color: color-mix(in srgb, var(--full-contrast) 20%, var(--page-background)); -} -[ngCombobox] { position: relative; width: 100%; display: flex; @@ -16,15 +14,25 @@ position: relative; align-items: center; border-radius: 0.25rem; -} - -[ngComboboxInput] { - border-radius: 0.25rem; -} -[ngComboboxInput][readonly='true'] { - cursor: pointer; - padding: 0.7rem 1rem; + input { + width: 100%; + border: none; + outline: none; + font-size: 1rem; + padding: 0.7rem 1rem 0.7rem; + background-color: var(--septenary-contrast); + color: var(--primary-contrast); + + &[readonly] { + cursor: pointer; + padding: 0.7rem 1rem; + } + + &[aria-expanded='true'] + .docs-select-arrow { + transform: rotate(180deg); + } + } } [ngCombobox]:focus-within [ngComboboxInput]:not(.docs-select-search-input) { @@ -32,6 +40,10 @@ box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent); } +.docs-select-popover { + width: 100%; +} + .docs-select-arrow { width: 24px; height: 24px; @@ -46,16 +58,11 @@ transition: transform 0.2s ease; } -[ngComboboxInput][aria-expanded='true'] + .docs-select-arrow { - transform: rotate(180deg); -} - -[ngComboboxInput] { - width: 100%; +[ngComboboxInput] [ngCombobox] { border: none; outline: none; font-size: 1rem; - padding: 0.7rem 1rem 0.7rem 2.5rem; + padding: 0.7rem 1rem 0.7rem; background-color: var(--septenary-contrast); color: var(--primary-contrast); } diff --git a/adev/shared-docs/components/select/select.component.ts b/adev/shared-docs/components/select/select.component.ts index 142a1a69f3b4..99b0cf00a6a9 100644 --- a/adev/shared-docs/components/select/select.component.ts +++ b/adev/shared-docs/components/select/select.component.ts @@ -1,4 +1,4 @@ -/*! +/** * @license * Copyright Google LLC All Rights Reserved. * @@ -6,28 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - Combobox, - ComboboxDialog, - ComboboxInput, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; +import {Combobox, ComboboxPopup, ComboboxWidget} from '@angular/aria/combobox'; import {Listbox, Option} from '@angular/aria/listbox'; +import {OverlayModule} from '@angular/cdk/overlay'; import { afterRenderEffect, - ChangeDetectionStrategy, Component, computed, input, model, signal, - untracked, viewChild, } from '@angular/core'; import {FormValueControl} from '@angular/forms/signals'; -import {FormsModule} from '@angular/forms'; -type SelectOptionValue = string | number | boolean; +type SelectOptionValue = string; export interface SelectOption { label: string; @@ -36,18 +29,9 @@ export interface SelectOption { @Component({ selector: 'docs-select', - changeDetection: ChangeDetectionStrategy.OnPush, - imports: [ - Combobox, - ComboboxDialog, - ComboboxInput, - ComboboxPopupContainer, - FormsModule, - Listbox, - Option, - ], - templateUrl: './select.component.html', - styleUrls: ['./select.component.scss'], + templateUrl: 'select.component.html', + styleUrl: 'select.component.css', + imports: [Combobox, ComboboxPopup, ComboboxWidget, Listbox, Option, OverlayModule], }) export class Select implements FormValueControl { readonly value = model(null); @@ -57,12 +41,12 @@ export class Select implements FormValueControl { readonly options = input.required(); readonly disabled = input(false); - readonly dialog = viewChild(ComboboxDialog); - readonly listbox = viewChild>(Listbox); - readonly combobox = viewChild>(Combobox); + readonly listbox = viewChild(Listbox); + readonly combobox = viewChild(Combobox); readonly searchString = signal(''); + readonly popupExpanded = signal(false); readonly filteredOptions = computed(() => { const search = this.searchString().toLowerCase(); if (!search) { @@ -84,39 +68,36 @@ export class Select implements FormValueControl { constructor() { afterRenderEffect(() => { - if (this.dialog() && this.combobox()?.expanded()) { - untracked(() => this.listbox()?.gotoFirst()); - this.positionDialog(); - } + this.listbox()?.scrollActiveItemIntoView(); }); - - afterRenderEffect(() => { - const selected = this.selectedValues(); - if (selected.length > 0) { - untracked(() => this.dialog()?.close()); - this.value.set(selected[0] as string); - this.searchString.set(''); - } - }); - - afterRenderEffect(() => this.listbox()?.scrollActiveItemIntoView()); } - // TODO: Improve once CDK overlay is fixed https://github.com/angular/components/issues/32504 - private positionDialog(): void { - const dialog = this.dialog(); - const combobox = this.combobox(); - if (!dialog || !combobox) { - return; + onCommit() { + const values = this.selectedValues(); + if (values.length) { + this.value.set(values[0]); + //this.popupExpanded.set(false); } + } - const comboboxRect = combobox.inputElement()?.getBoundingClientRect(); - const scrollY = window.scrollY; + /** Dismisses the dialog overlay on Escape key. */ + onSearchEscape(event: Event) { + this.popupExpanded.set(false); + this.combobox()?.element.focus(); + } - if (comboboxRect) { - dialog.element.style.width = `${comboboxRect.width}px`; - dialog.element.style.top = `${comboboxRect.bottom + scrollY + 4}px`; - dialog.element.style.left = `${comboboxRect.left}px`; + /** Handles keydown events on the clear button. */ + onKeydown(event: KeyboardEvent): void { + if (event.key === 'Enter') { + this.clear(); + this.popupExpanded.set(false); + event.stopPropagation(); } } + + /** Clears the search query and all selected options. */ + clear(): void { + this.searchString.set(''); + this.value.set(null); + } } diff --git a/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts b/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts index 92375a0dd52f..591f0df77dc6 100644 --- a/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts +++ b/adev/shared-docs/components/slide-toggle/slide-toggle.component.ts @@ -6,12 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, input, model} from '@angular/core'; +import {Component, input, model} from '@angular/core'; import {FormCheckboxControl} from '@angular/forms/signals'; @Component({ selector: 'docs-slide-toggle', - changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './slide-toggle.component.html', styleUrls: ['./slide-toggle.component.scss'], }) diff --git a/adev/shared-docs/components/tab-group/tab-group.component.ts b/adev/shared-docs/components/tab-group/tab-group.component.ts index 96d2809c5c8c..5a4ce5bd62e0 100644 --- a/adev/shared-docs/components/tab-group/tab-group.component.ts +++ b/adev/shared-docs/components/tab-group/tab-group.component.ts @@ -8,7 +8,6 @@ import { afterRenderEffect, - ChangeDetectionStrategy, Component, computed, ElementRef, @@ -33,7 +32,6 @@ let idCounter = 0; host: { class: 'docs-tab-group', }, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class TabGroup { private readonly _renderer = inject(Renderer2); diff --git a/adev/shared-docs/components/table-of-contents/table-of-contents.component.ts b/adev/shared-docs/components/table-of-contents/table-of-contents.component.ts index baa3b114f716..170e9938062a 100644 --- a/adev/shared-docs/components/table-of-contents/table-of-contents.component.ts +++ b/adev/shared-docs/components/table-of-contents/table-of-contents.component.ts @@ -6,23 +6,14 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - ChangeDetectionStrategy, - Component, - DestroyRef, - input, - inject, - afterNextRender, - signal, -} from '@angular/core'; import {Location, ViewportScroller} from '@angular/common'; +import {afterNextRender, Component, DestroyRef, inject, input, signal} from '@angular/core'; import {TableOfContentsLevel} from '../../interfaces/index'; import {TableOfContentsLoader} from '../../services'; import {IconComponent} from '../icon/icon.component'; @Component({ selector: 'docs-table-of-contents', - changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './table-of-contents.component.html', styleUrls: ['./table-of-contents.component.scss'], imports: [IconComponent], diff --git a/adev/shared-docs/components/text-field/text-field.component.ts b/adev/shared-docs/components/text-field/text-field.component.ts index abffbfc82535..e955e2bebb5a 100644 --- a/adev/shared-docs/components/text-field/text-field.component.ts +++ b/adev/shared-docs/components/text-field/text-field.component.ts @@ -6,21 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - afterNextRender, - ChangeDetectionStrategy, - Component, - ElementRef, - input, - model, - viewChild, -} from '@angular/core'; +import {afterNextRender, Component, ElementRef, input, model, viewChild} from '@angular/core'; import {FormValueControl} from '@angular/forms/signals'; import {IconComponent} from '../icon/icon.component'; @Component({ selector: 'docs-text-field', - changeDetection: ChangeDetectionStrategy.OnPush, imports: [IconComponent], templateUrl: './text-field.component.html', styleUrls: ['./text-field.component.scss'], diff --git a/adev/shared-docs/components/top-level-banner/top-level-banner.component.ts b/adev/shared-docs/components/top-level-banner/top-level-banner.component.ts index 35209b7ea164..25c12591d6b3 100644 --- a/adev/shared-docs/components/top-level-banner/top-level-banner.component.ts +++ b/adev/shared-docs/components/top-level-banner/top-level-banner.component.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, inject, input, linkedSignal} from '@angular/core'; +import {Component, inject, input, linkedSignal} from '@angular/core'; import {ExternalLink} from '../../directives'; import {LOCAL_STORAGE} from '../../providers'; import {IconComponent} from '../icon/icon.component'; @@ -18,7 +18,6 @@ export const STORAGE_KEY_PREFIX = 'docs-was-closed-top-banner-'; imports: [ExternalLink, IconComponent], templateUrl: './top-level-banner.component.html', styleUrl: './top-level-banner.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class TopLevelBannerComponent { private readonly localStorage = inject(LOCAL_STORAGE); diff --git a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.scss b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.scss index b6baa173991f..750efd753f0f 100644 --- a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.scss +++ b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.scss @@ -143,7 +143,7 @@ height: fit-content; docs-icon { - color: var(--gray-400); + color: var(--quaternary-contrast); transition: color 0.3s ease; } diff --git a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts index f6ae2d647c58..b9c22eb174cb 100644 --- a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts +++ b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts @@ -9,7 +9,6 @@ import {DOCUMENT, isPlatformBrowser, Location} from '@angular/common'; import { ApplicationRef, - ChangeDetectionStrategy, Component, ComponentRef, createComponent, @@ -38,10 +37,10 @@ import {TableOfContents} from '../../table-of-contents/table-of-contents.compone import {DomSanitizer} from '@angular/platform-browser'; import {Breadcrumb} from '../../breadcrumb/breadcrumb.component'; -import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component'; import {CopyLinkButton} from '../../copy-link-anchor/copy-link-anchor.component'; -import {ExampleViewer} from '../example-viewer/example-viewer.component'; +import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component'; import {TabGroup} from '../../tab-group/tab-group.component'; +import {ExampleViewer} from '../example-viewer/example-viewer.component'; const TOC_HOST_ELEMENT_NAME = 'docs-table-of-contents'; export const ASSETS_EXAMPLES_PATH = 'assets/content/examples'; @@ -56,7 +55,6 @@ const GITHUB_CONTENT_URL = 'https://github.com/angular/angular/blob/{{BUILD_SCM_ selector: DOCS_VIEWER_SELECTOR, template: '', styleUrls: ['docs-viewer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: { '[class.docs-animate-content]': 'animateContent', diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.html b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.html index f3f2e252128f..b95b642f2bc2 100644 --- a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.html +++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.html @@ -87,7 +87,7 @@ fill="none" > diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.scss b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.scss index 400756da6bff..4d06a9b66527 100644 --- a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.scss +++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.scss @@ -2,11 +2,11 @@ .docs-example-viewer-preview { .docs-dark-mode & { color: var(--primary-contrast); - background: var(--gray-900); + background: var(--primary-constrast); } @media screen and (prefers-color-scheme: dark) { color: var(--primary-contrast); - background: var(--gray-900); + background: var(--primary-constrast); } .docs-light-mode & { background: var(--page-background); @@ -70,7 +70,7 @@ cursor: pointer; height: 24px; width: 24px; - color: var(--gray-400); + color: var(--quaternary-contrast); path { transition: fill 0.3s ease; diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts index edf7be0884bb..54251f2461b9 100644 --- a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts +++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts @@ -6,9 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ +import {Clipboard} from '@angular/cdk/clipboard'; +import {DOCUMENT, NgComponentOutlet, NgTemplateOutlet} from '@angular/common'; import { afterNextRender, - ChangeDetectionStrategy, Component, computed, ElementRef, @@ -18,14 +19,12 @@ import { signal, Type, } from '@angular/core'; -import {DOCUMENT, NgComponentOutlet, NgTemplateOutlet} from '@angular/common'; import {MatTab, MatTabGroup} from '@angular/material/tabs'; -import {Clipboard} from '@angular/cdk/clipboard'; -import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component'; -import {IconComponent} from '../../icon/icon.component'; +import {MatTooltip} from '@angular/material/tooltip'; import {ExampleMetadata, Snippet} from '../../../interfaces/index'; import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index'; -import {MatTooltip} from '@angular/material/tooltip'; +import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component'; +import {IconComponent} from '../../icon/icon.component'; export const CODE_LINE_NUMBER_CLASS_NAME = 'shiki-ln-number'; export const CODE_LINE_CLASS_NAME = 'line'; @@ -45,7 +44,6 @@ export const HIDDEN_CLASS_NAME = 'hidden'; ], templateUrl: './example-viewer.component.html', styleUrls: ['./example-viewer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class ExampleViewer { readonly exampleMetadata = input(null, {alias: 'metadata'}); diff --git a/adev/shared-docs/defaults.bzl b/adev/shared-docs/defaults.bzl index 4474528baace..db75d0b60775 100644 --- a/adev/shared-docs/defaults.bzl +++ b/adev/shared-docs/defaults.bzl @@ -1,6 +1,6 @@ -load("@aspect_bazel_lib//lib:copy_to_bin.bzl", _copy_to_bin = "copy_to_bin") -load("@aspect_bazel_lib//lib:copy_to_directory.bzl", _copy_to_directory = "copy_to_directory") load("@aspect_rules_esbuild//esbuild:defs.bzl", _esbuild = "esbuild") +load("@bazel_lib//lib:copy_to_bin.bzl", _copy_to_bin = "copy_to_bin") +load("@bazel_lib//lib:copy_to_directory.bzl", _copy_to_directory = "copy_to_directory") load("@devinfra//bazel/private:path_relative_to_label.bzl", _path_relative_to_label = "path_relative_to_label") load( "//tools:defaults.bzl", diff --git a/adev/shared-docs/directives/click-outside/click-outside.directive.spec.ts b/adev/shared-docs/directives/click-outside/click-outside.directive.spec.ts index cc2b8bff6180..ed5ab178b22d 100644 --- a/adev/shared-docs/directives/click-outside/click-outside.directive.spec.ts +++ b/adev/shared-docs/directives/click-outside/click-outside.directive.spec.ts @@ -43,15 +43,6 @@ describe('ClickOutside', () => { expect(clickedOutsideSpy).not.toHaveBeenCalled(); }); - - it('should not docsClickOutside be emitted when user click inside `content` element', () => { - const clickedOutsideSpy = spyOn(component, 'clickedOutside'); - const button = fixture.debugElement.query(By.css('button[id="ignoreThisButton"]')); - - button.nativeElement.click(); - - expect(clickedOutsideSpy).not.toHaveBeenCalled(); - }); }); @Component({ diff --git a/adev/shared-docs/directives/external-link/external-link.directive.ts b/adev/shared-docs/directives/external-link/external-link.directive.ts index 349befe123fd..7c455c8d7fd1 100644 --- a/adev/shared-docs/directives/external-link/external-link.directive.ts +++ b/adev/shared-docs/directives/external-link/external-link.directive.ts @@ -22,7 +22,7 @@ import {isExternalLink} from '../../utils/index'; }, }) export class ExternalLink { - private readonly anchor: ElementRef = inject(ElementRef); + private readonly anchor = inject>(ElementRef); private readonly platformId = inject(PLATFORM_ID); target?: '_blank' | '_self' | '_parent' | '_top' | ''; diff --git a/adev/shared-docs/directives/search-item/search-item.directive.ts b/adev/shared-docs/directives/search-item/search-item.directive.ts index d577c0712294..1e7280747654 100644 --- a/adev/shared-docs/directives/search-item/search-item.directive.ts +++ b/adev/shared-docs/directives/search-item/search-item.directive.ts @@ -23,7 +23,7 @@ export class SearchItem implements Highlightable { readonly item = input(); - private readonly elementRef = inject(ElementRef); + private readonly elementRef = inject>(ElementRef); private _isActive = signal(false); diff --git a/adev/shared-docs/interfaces/navigation-item.ts b/adev/shared-docs/interfaces/navigation-item.ts index 72b580d2545e..bd3a6042c0f8 100644 --- a/adev/shared-docs/interfaces/navigation-item.ts +++ b/adev/shared-docs/interfaces/navigation-item.ts @@ -17,5 +17,6 @@ export interface NavigationItem { contentPath?: string; status?: 'new' | 'updated'; category?: string; + isCrossReferenced?: boolean; preserveOtherCategoryOrder?: boolean; // true by default } diff --git a/adev/shared-docs/package.json b/adev/shared-docs/package.json index d7d53e61a036..24f8cb28938c 100644 --- a/adev/shared-docs/package.json +++ b/adev/shared-docs/package.json @@ -2,26 +2,26 @@ "name": "@angular/docs", "version": "0.0.0-PLACEHOLDER", "peerDependencies": { - "@angular/cdk": "^21.2.0-next", - "@angular/common": "^21.2.0-next", - "@angular/core": "^21.2.0-next", - "@angular/forms": "^21.2.0-next", - "@angular/material": "^21.2.0-next", - "@angular/platform-browser": "^21.2.0-next", - "@angular/router": "^21.2.0-next", - "@angular/ssr": "^21.2.0-next", + "@angular/cdk": "^22.0.0-next", + "@angular/common": "^22.0.0-next", + "@angular/core": "^22.0.0-next", + "@angular/forms": "^22.0.0-next", + "@angular/material": "^22.0.0-next", + "@angular/platform-browser": "^22.0.0-next", + "@angular/router": "^22.0.0-next", + "@angular/ssr": "^22.0.0-next", "algoliasearch": "^5.0.0", "rxjs": "^7.8.1" }, "dependencies": { "@webcontainer/api": "^1.1.8", - "diff": "~8.0.0", + "diff": "~9.0.0", "emoji-regex": "~10.6.0", "fflate": "^0.8.2", - "jsdom": "~28.0.0", - "marked": "~17.0.0", + "jsdom": "~29.1.0", + "marked": "~18.0.0", "mermaid": "^11.0.0", - "shiki": "^3.0.0", + "shiki": "^4.0.0", "tinyglobby": "^0.2.12" }, "exports": { diff --git a/adev/shared-docs/pipeline/api-gen/rendering/templates/class-member.tsx b/adev/shared-docs/pipeline/api-gen/rendering/templates/class-member.tsx index 684b34025af0..5ba9a99b88ad 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/templates/class-member.tsx +++ b/adev/shared-docs/pipeline/api-gen/rendering/templates/class-member.tsx @@ -59,10 +59,16 @@ export function ClassMember(props: {member: MemberEntryRenderable}) { const memberName = member.name; const displayName = member.displayName; const returnType = getMemberType(member); + const label = displayName ?? memberName; + return (
-

{displayName ?? memberName}

+

+ + {label} + +

{isClassMethodEntry(member) && member.signatures.length > 1 ? ( {member.signatures.length} overloads ) : returnType ? ( diff --git a/adev/shared-docs/pipeline/api-gen/rendering/templates/cli-reference.tsx b/adev/shared-docs/pipeline/api-gen/rendering/templates/cli-reference.tsx index 842212a39aa5..8740b4dfdbf4 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/templates/cli-reference.tsx +++ b/adev/shared-docs/pipeline/api-gen/rendering/templates/cli-reference.tsx @@ -27,14 +27,14 @@ export function CliCommandReference(entry: CliCommandRenderable) {
ng {commandName(entry, command)} {entry.argumentsLabel && ( - + )} {entry.optionsLabel && ( - + )}
diff --git a/adev/shared-docs/pipeline/api-gen/rendering/test/renderable.spec.mts b/adev/shared-docs/pipeline/api-gen/rendering/test/renderable.spec.mts index b869906e5c0d..5329c8666111 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/test/renderable.spec.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/test/renderable.spec.mts @@ -7,8 +7,10 @@ */ import {readFile} from 'fs/promises'; +import {JSDOM} from 'jsdom'; import {DocEntryRenderable} from '../entities/renderables.mjs'; import {getRenderable} from '../processing.mjs'; +import {renderEntry} from '../rendering.mjs'; import {setSymbols} from '../symbol-context.mjs'; import {resolve} from 'path'; import {initHighlighter} from '../../../shared/shiki.mjs'; @@ -60,4 +62,21 @@ describe('renderable', () => { expect(linkedSignal!.experimental).toBe(undefined); expect(linkedSignal!.stable).toBe(undefined); }); + + it('should render docs-anchor links in class member card headers', () => { + const viewRef = entries.get('ViewRef')!; + expect(viewRef).toBeDefined(); + + const html = renderEntry(viewRef); + const fragment = JSDOM.fragment(html); + + const memberCards = fragment.querySelectorAll('.docs-reference-member-card'); + expect(memberCards.length).toBeGreaterThan(0); + + for (const card of Array.from(memberCards)) { + const id = card.getAttribute('id')!; + const anchor = card.querySelector('h3 a.docs-anchor') as HTMLAnchorElement; + expect(anchor.getAttribute('href')).toBe(`#${id}`); + } + }); }); diff --git a/adev/shared-docs/pipeline/api-gen/rendering/test/transforms/jsdoc-transforms.spec.mts b/adev/shared-docs/pipeline/api-gen/rendering/test/transforms/jsdoc-transforms.spec.mts index 285345badc64..fd57b323d22b 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/test/transforms/jsdoc-transforms.spec.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/test/transforms/jsdoc-transforms.spec.mts @@ -199,6 +199,23 @@ describe('jsdoc transforms', () => { expect(entryFn).toThrowError(/Forbidden relative link: cli\/build ng build/); }); + + it('should throw on a miscased absolute @link to a known API symbol', () => { + setSymbols({RouterModule: 'router'}); + + const entryFn = () => + addHtmlAdditionalLinks({ + jsdocTags: [ + { + name: 'see', + comment: '{@link /api/router/routerModule#forRoot forRoot}', + }, + ], + moduleName: 'test', + }); + + expect(entryFn).toThrowError(/Broken @link.*Did you mean \/api\/router\/RouterModule/); + }); }); describe('addHtmlDescription', () => { diff --git a/adev/shared-docs/pipeline/api-gen/rendering/transforms/code-transforms.mts b/adev/shared-docs/pipeline/api-gen/rendering/transforms/code-transforms.mts index cfb15a34338d..eec73cdccd2b 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/transforms/code-transforms.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/transforms/code-transforms.mts @@ -95,6 +95,7 @@ export async function addRenderableCodeToc( { language: 'typescript', apiEntries: getSymbolsAsApiEntries(), + removeWhitespace: false, }, ); diff --git a/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts b/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts index 38fbe90b85df..5f067da7bb0c 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts @@ -223,6 +223,27 @@ function parseAtLink(link: string): {label: string; url: string} | undefined { ); } + // Validate absolute `/api/...` links against the known symbol registry. This catches + // miscased symbol names (e.g. `/api/router/routerModule` instead of + // `/api/router/RouterModule`) at build time. + if (rawSymbol.startsWith('/api/')) { + const [pathPart] = rawSymbol.split('#'); + const segments = pathPart.split('/').filter((s) => s.length > 0); + const symbolName = segments[segments.length - 1]; + // Case-insensitive lookup: find the canonical symbol name in the registry. + const knownSymbols = Object.keys(getSymbolsAsApiEntries()); + const canonicalSymbol = knownSymbols.find( + (s) => s.toLowerCase() === symbolName.toLowerCase(), + ); + if (canonicalSymbol && canonicalSymbol !== symbolName) { + const expectedUrl = getSymbolUrl(canonicalSymbol); + throw Error( + `Broken @link: ${link}. Did you mean ${expectedUrl}? ` + + `Symbol names in API URLs are case-sensitive.`, + ); + } + } + return { url: rawSymbol, label: description ?? rawSymbol.split('/').pop()!, diff --git a/adev/shared-docs/pipeline/shared/BUILD.bazel b/adev/shared-docs/pipeline/shared/BUILD.bazel index 5c6015a1e857..3b09685c954a 100644 --- a/adev/shared-docs/pipeline/shared/BUILD.bazel +++ b/adev/shared-docs/pipeline/shared/BUILD.bazel @@ -10,6 +10,15 @@ ts_project( ], ) +ts_project( + name = "heading", + srcs = ["heading.mts"], + visibility = [ + "//adev/scripts/routes:__subpackages__", + "//adev/shared-docs/pipeline:__subpackages__", + ], +) + ts_project( name = "shiki", srcs = ["shiki.mts"], @@ -36,3 +45,17 @@ zoneless_jasmine_test( name = "linking_test", data = [":linking_test_lib"], ) + +ts_project( + name = "heading_test_lib", + testonly = True, + srcs = ["test/heading.spec.mts"], + deps = [ + ":heading", + ], +) + +zoneless_jasmine_test( + name = "heading_test", + data = [":heading_test_lib"], +) diff --git a/adev/shared-docs/pipeline/shared/heading.mts b/adev/shared-docs/pipeline/shared/heading.mts new file mode 100644 index 000000000000..9f051bca1130 --- /dev/null +++ b/adev/shared-docs/pipeline/shared/heading.mts @@ -0,0 +1,29 @@ +/*! + * @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 + */ + +/** + * Extracts the ID from a heading text. + * Supports custom ID syntax: `## My Heading {#custom-id}` + */ +export function getIdFromHeading(heading: string): string { + // extract the extended markdown heading id + // ex: ## MyHeading {# myId} + // This is recommended in case we end up having duplicate Ids but we still want the same heading text. + // We don't want to make Id generation stateful/too complex to handle duplicates automatically. + const customIdRegex = /{#\s*([\w-]+)\s*}/g; + const customId = customIdRegex.exec(heading)?.[1]; + + if (customId) { + return customId; + } + + return heading + .toLowerCase() + .replace(/\s|\//g, '-') // replace spaces and slashes with dashes + .replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes +} diff --git a/adev/shared-docs/pipeline/shared/linking.mts b/adev/shared-docs/pipeline/shared/linking.mts index 3c01b92983c8..2c8bbd62aa0d 100644 --- a/adev/shared-docs/pipeline/shared/linking.mts +++ b/adev/shared-docs/pipeline/shared/linking.mts @@ -30,6 +30,15 @@ const LINK_EXEMPT = new Set([ 'Event', 'form', 'type', + 'Host', + 'filter', + 'required', + 'pattern', + 'min', + 'max', + 'minLength', + 'maxLength', + 'query', ]); export function shouldLinkSymbol(symbol: string): boolean { diff --git a/adev/shared-docs/pipeline/shared/marked/BUILD.bazel b/adev/shared-docs/pipeline/shared/marked/BUILD.bazel index b0993bbd2d5b..bc4b9c2361a7 100644 --- a/adev/shared-docs/pipeline/shared/marked/BUILD.bazel +++ b/adev/shared-docs/pipeline/shared/marked/BUILD.bazel @@ -18,6 +18,7 @@ ts_project( "//adev:node_modules/mermaid", "//adev:node_modules/playwright-core", "//adev:node_modules/shiki", + "//adev/shared-docs/pipeline/shared:heading", "//adev/shared-docs/pipeline/shared:linking", "//adev/shared-docs/pipeline/shared:shiki", "//adev/shared-docs/pipeline/shared/regions", diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-card/docs-card-container.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-card/docs-card-container.mts index 3d8cfc0fe013..2c7096ff9324 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-card/docs-card-container.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-card/docs-card-container.mts @@ -21,7 +21,7 @@ interface DocsCardContainerToken extends Tokens.Generic { // Capture group 1: all attributes on the opening tag // Capture group 2: all content between the open and close tags const cardContainerRule = - /^[^<]*]*))?>((?:.(?!\/docs-card-container))*)<\/docs-card-container>/s; + /^\s*]*))?>((?:.(?!\/docs-card-container))*)<\/docs-card-container>/s; const headerTitleRule = /headerTitle="([^"]*)"/; const headerImgSrcRule = /headerImgSrc="([^"]*)"/; diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-card/docs-card.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-card/docs-card.mts index f21fa8469bc8..ebd74453da10 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-card/docs-card.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-card/docs-card.mts @@ -18,18 +18,20 @@ interface DocsCardToken extends Tokens.Generic { href?: string; imgSrc?: string; iconImgSrc?: string; // Need image since icons are custom + titleInline?: boolean; tokens: Token[]; } // Capture group 1: all attributes on the opening tag // Capture group 2: all content between the open and close tags -const cardRule = /^[^<]*]*))?>((?:.(?!\/docs-card))*)<\/docs-card>/s; +const cardRule = /^\s*]*))?>((?:.(?!\/docs-card))*)<\/docs-card>/s; const titleRule = /title="([^"]*)"/; const linkRule = /link="([^"]*)"/; const hrefRule = /href="([^"]*)"/; const imgSrcRule = /imgSrc="([^"]*)"/; const iconImgSrcRule = /iconImgSrc="([^"]*)"/; +const titleInlineRule = /(?:^|\s)titleInline(?=\s|$|=)/; export const docsCardExtension = { name: 'docs-card', @@ -47,6 +49,7 @@ export const docsCardExtension = { const href = hrefRule.exec(attr); const imgSrc = imgSrcRule.exec(attr); const iconImgSrc = iconImgSrcRule.exec(attr); + const titleInline = titleInlineRule.test(attr); const body = match[2].trim(); @@ -59,6 +62,7 @@ export const docsCardExtension = { link: link ? link[1] : undefined, imgSrc: imgSrc ? imgSrc[1] : undefined, iconImgSrc: iconImgSrc ? iconImgSrc[1] : undefined, + titleInline, tokens: [], }; this.lexer.blockTokens(token.body, token.tokens); @@ -79,12 +83,14 @@ function getStandardCard(renderer: AdevDocsRenderer, token: DocsCardToken) { // We need to read svg content, instead of renering svg with `img`, // cause we would like to use CSS variables to support dark and light mode. const icon = loadWorkspaceRelativeFile(token.iconImgSrc); + const header = token.titleInline + ? `
${icon}

${token.title}

` + : `${icon}

${token.title}

`; return `
- ${icon} -

${token.title}

+ ${header} ${renderer.parser.parse(token.tokens)}
${token.link ? token.link : 'Learn more'} diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-block.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-block.mts index 75678a67deeb..cccf135afde9 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-block.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-block.mts @@ -41,6 +41,7 @@ export const docsCodeBlockExtension = { const headerRule = /header\s*:\s*(['"`])([^'"`]+)\1/; // The 2nd capture matters here const highlightRule = /highlight\s*:\s*(.*)([^,])/; const hideCopyRule = /hideCopy/; + const hideDollarRule = /hideDollar/; const preferRule = /\b(prefer|avoid)\b/; const linenumsRule = /linenums/; @@ -52,6 +53,7 @@ export const docsCodeBlockExtension = { header: headerRule.exec(metadataStr)?.[2], highlight: highlightRule.exec(metadataStr)?.[1], hideCopy: hideCopyRule.test(metadataStr), + hideDollar: hideDollarRule.test(metadataStr), style: preferRule.exec(metadataStr)?.[1] as 'prefer' | 'avoid' | undefined, linenums: linenumsRule.test(metadataStr), }; diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts index 2bb5797f07d0..d0981a9cdee6 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts @@ -32,6 +32,7 @@ const visibleLinesRule = /visibleLines="([^"]*)"/; const regionRule = /region="([^"]*)"/; const previewRule = /preview/; const hideCodeRule = /hideCode/; +const hideDollarRule = /hideDollar/; const preferRule = /\b(prefer|avoid)\b/; export const docsCodeExtension = { @@ -52,8 +53,9 @@ export const docsCodeExtension = { const language = languageRule.exec(attr); const visibleLines = visibleLinesRule.exec(attr); const region = regionRule.exec(attr); - const preview = previewRule.exec(attr) ? true : false; - const hideCode = hideCodeRule.exec(attr) ? true : false; + const preview = previewRule.test(attr); + const hideCode = hideCodeRule.test(attr); + const hideDollar = hideDollarRule.test(attr); const classes = classRule.exec(attr); const style = preferRule.exec(attr); @@ -78,6 +80,7 @@ export const docsCodeExtension = { region: region?.[1], preview: preview, hideCode, + hideDollar, style: style?.[1] as 'prefer' | 'avoid' | undefined, classes: classes?.[1]?.split(' '), }; diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts index d077de171fd2..21957368737c 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts @@ -39,6 +39,8 @@ export interface CodeToken extends Tokens.Generic { highlight?: string; /** Whether to hide the copy button */ hideCopy?: boolean; + /** Whether to hide the dollar sign in the shell code */ + hideDollar?: boolean; // additional classes for the element classes?: string[]; @@ -132,6 +134,10 @@ function applyContainerAttributesAndClasses(el: Element, token: CodeToken) { if (token.hideCopy) { el.setAttribute('hideCopy', 'true'); } + if (token.hideDollar) { + el.setAttribute('hideDollar', 'true'); + } + const language = token.language; if (language === 'mermaid') { diff --git a/adev/shared-docs/pipeline/shared/marked/mermaid/index.mts b/adev/shared-docs/pipeline/shared/marked/mermaid/index.mts index 84baa0ea1475..94c3e4391085 100644 --- a/adev/shared-docs/pipeline/shared/marked/mermaid/index.mts +++ b/adev/shared-docs/pipeline/shared/marked/mermaid/index.mts @@ -11,7 +11,7 @@ import {chromium} from 'playwright-core'; import {Mermaid, MermaidConfig} from 'mermaid'; import {resolve, join} from 'path'; -const runfilesDir = process.env['RUNFILES']!; +const runfilesDir = process.env['JS_BINARY__RUNFILES']!; // Declare mermarid in the context of this file so that typescript doesn't get upset when we // access it within the `page.evaluate` function. At runtime the context in with the method // is run difference than this file, but this makes typescript happy. diff --git a/adev/shared-docs/pipeline/shared/marked/test/docs-card-container/docs-card-container.md b/adev/shared-docs/pipeline/shared/marked/test/docs-card-container/docs-card-container.md index 29511a710957..bfdc38e74388 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/docs-card-container/docs-card-container.md +++ b/adev/shared-docs/pipeline/shared/marked/test/docs-card-container/docs-card-container.md @@ -1,4 +1,39 @@ +Introductory paragraph that should be preserved before any card containers. + +## First Section + +This section contains important information about the feature status. + +### Available features + +- Feature one +- Feature two +- Feature three + +### In progress + +The following items are currently being developed: + +1. Item A with description +2. Item B with description + - - + + + +### Additional context + +More text after the first card container that should also be preserved. + +## Second Section + +Another paragraph before the second card container. + + + + + +## Final Section + +Concluding remarks at the end of the document. diff --git a/adev/shared-docs/pipeline/shared/marked/test/docs-card-container/docs-card-container.spec.mts b/adev/shared-docs/pipeline/shared/marked/test/docs-card-container/docs-card-container.spec.mts index 3b8363b56ecc..86d39c258229 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/docs-card-container/docs-card-container.spec.mts +++ b/adev/shared-docs/pipeline/shared/marked/test/docs-card-container/docs-card-container.spec.mts @@ -20,10 +20,60 @@ describe('markdown to html', () => { markdownDocument = JSDOM.fragment(await parseMarkdown(markdownContent, rendererContext)); }); - it('creates card containers containing multiple cards', () => { - const containerEl = markdownDocument.querySelector('.docs-card-grid'); + it('creates multiple card containers with correct card counts', () => { + const containers = markdownDocument.querySelectorAll('.docs-card-grid'); - expect(containerEl!.children.length).toBe(2); - expect(containerEl!.classList.contains('docs-card-grid')).toBeTrue(); + expect(containers.length).toBe(2); + expect(containers[0].children.length).toBe(2); + expect(containers[1].children.length).toBe(1); + }); + + it('preserves all h2 section headings', () => { + const h2Elements = markdownDocument.querySelectorAll('h2'); + + expect(h2Elements.length).toBe(3); + expect(h2Elements[0].textContent).toContain('First Section'); + expect(h2Elements[1].textContent).toContain('Second Section'); + expect(h2Elements[2].textContent).toContain('Final Section'); + }); + + it('preserves all h3 subsection headings outside card containers', () => { + // Card titles also render as h3, so we filter to only h3s not inside card containers + const allH3s = Array.from(markdownDocument.querySelectorAll('h3')); + const sectionH3s = allH3s.filter((h3) => !h3.closest('.docs-card-grid')); + + expect(sectionH3s.length).toBe(3); + expect(sectionH3s[0].textContent).toContain('Available features'); + expect(sectionH3s[1].textContent).toContain('In progress'); + expect(sectionH3s[2].textContent).toContain('Additional context'); + }); + + it('preserves unordered lists before card containers', () => { + const ulElements = markdownDocument.querySelectorAll('ul'); + + expect(ulElements.length).toBeGreaterThanOrEqual(1); + expect(ulElements[0].children.length).toBe(3); + expect(ulElements[0].textContent).toContain('Feature one'); + }); + + it('preserves ordered lists before card containers', () => { + const olElements = markdownDocument.querySelectorAll('ol'); + + expect(olElements.length).toBe(1); + expect(olElements[0].children.length).toBe(2); + expect(olElements[0].textContent).toContain('Item A'); + }); + + it('preserves paragraphs throughout the document', () => { + const paragraphs = markdownDocument.querySelectorAll('p'); + + expect(paragraphs.length).toBeGreaterThanOrEqual(5); + + const paragraphTexts = Array.from(paragraphs).map((p) => p.textContent); + expect(paragraphTexts.some((t) => t?.includes('Introductory paragraph'))).toBeTrue(); + expect(paragraphTexts.some((t) => t?.includes('important information'))).toBeTrue(); + expect(paragraphTexts.some((t) => t?.includes('More text after'))).toBeTrue(); + expect(paragraphTexts.some((t) => t?.includes('Another paragraph'))).toBeTrue(); + expect(paragraphTexts.some((t) => t?.includes('Concluding remarks'))).toBeTrue(); }); }); diff --git a/adev/shared-docs/pipeline/shared/marked/test/docs-code-block/docs-code-block.md b/adev/shared-docs/pipeline/shared/marked/test/docs-code-block/docs-code-block.md index 507ed4546ea4..4091ceb4b797 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/docs-code-block/docs-code-block.md +++ b/adev/shared-docs/pipeline/shared/marked/test/docs-code-block/docs-code-block.md @@ -11,3 +11,7 @@ this is a code block ``` code block without language ``` + +```shell {hideDollar} +echo "hello" +``` diff --git a/adev/shared-docs/pipeline/shared/marked/test/docs-code-block/docs-code-block.spec.mts b/adev/shared-docs/pipeline/shared/marked/test/docs-code-block/docs-code-block.spec.mts index c544411c8963..6bc9e27ddf5f 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/docs-code-block/docs-code-block.spec.mts +++ b/adev/shared-docs/pipeline/shared/marked/test/docs-code-block/docs-code-block.spec.mts @@ -27,10 +27,10 @@ describe('markdown to html', () => { expect(codeBlock?.textContent?.trim()).toBe('this is a code block'); }); - it('should parse all 3 code blocks', () => { + it('should parse all 4 code blocks', () => { const codeBlocks = markdownDocument.querySelectorAll('.docs-code'); - expect(codeBlocks.length).toBe(3); + expect(codeBlocks.length).toBe(4); }); it('should deindent code blocks correctly', () => { @@ -42,4 +42,9 @@ describe('markdown to html', () => { const codeBlock = markdownDocument.querySelectorAll('.docs-code')[2]; expect(codeBlock).toBeDefined(); }); + + it('should parse the hideDollar attribute', () => { + const codeBlock = markdownDocument.querySelectorAll('.docs-code')[3]; + expect(codeBlock.getAttribute('hideDollar')).toBe('true'); + }); }); diff --git a/adev/shared-docs/pipeline/shared/marked/test/docs-code/docs-code.md b/adev/shared-docs/pipeline/shared/marked/test/docs-code/docs-code.md index fa52fce3c58c..0be343a07f63 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/docs-code/docs-code.md +++ b/adev/shared-docs/pipeline/shared/marked/test/docs-code/docs-code.md @@ -12,3 +12,5 @@ const form = { state: [''] }; + + diff --git a/adev/shared-docs/pipeline/shared/marked/test/docs-code/docs-code.spec.mts b/adev/shared-docs/pipeline/shared/marked/test/docs-code/docs-code.spec.mts index f008639a8d7d..38dd735f6c4f 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/docs-code/docs-code.spec.mts +++ b/adev/shared-docs/pipeline/shared/marked/test/docs-code/docs-code.spec.mts @@ -52,4 +52,9 @@ describe('markdown to html', () => { const codeBlock = markdownDocument.querySelectorAll('code')[4]; expect(codeBlock?.innerHTML).not.toContain('
state'); }); + + it('should parse the hideDollar attribute', () => { + const codeBlock = markdownDocument.querySelectorAll('.docs-code')[5]; + expect(codeBlock.getAttribute('hideDollar')).toBe('true'); + }); }); diff --git a/adev/shared-docs/pipeline/shared/marked/test/heading/heading.spec.mts b/adev/shared-docs/pipeline/shared/marked/test/heading/heading.spec.mts index ec8511baffe0..223740aca657 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/heading/heading.spec.mts +++ b/adev/shared-docs/pipeline/shared/marked/test/heading/heading.spec.mts @@ -135,4 +135,29 @@ describe('markdown to html', () => { // We ensure that we still style the heading content expect(markdownDocument.querySelector('strong')).toBeDefined(); }); + + it('should strip all HTML tags from the aria-label attribute', () => { + const markdownDocument = JSDOM.fragment( + parseMarkdown( + '## **Phase 5: Experimental Signal Forms (⚠️ WARNING: Subject to Change)**', + rendererContext, + ), + ); + const h2Anchor = markdownDocument.querySelector('h2 > a'); + + expect(h2Anchor!.innerHTML).toBe( + 'Phase 5: Experimental Signal Forms (⚠️ WARNING: Subject to Change)', + ); + expect(h2Anchor!.getAttribute('aria-label')).toBe( + 'Link to Phase 5: Experimental Signal Forms (⚠️ WARNING: Subject to Change)', + ); + }); + + it('should escape double quotes from the aria-label attribute', () => { + const markdownDocument = JSDOM.fragment(parseMarkdown('## "Special" heading', rendererContext)); + const h2Anchor = markdownDocument.querySelector('h2 > a'); + + expect(h2Anchor!.innerHTML).toBe('"Special" heading'); + expect(h2Anchor!.getAttribute('aria-label')).toBe('Link to "Special" heading'); + }); }); diff --git a/adev/shared-docs/pipeline/shared/marked/test/mermaid/mermaid.spec.mts b/adev/shared-docs/pipeline/shared/marked/test/mermaid/mermaid.spec.mts index 7a4e00823549..36c15b015650 100644 --- a/adev/shared-docs/pipeline/shared/marked/test/mermaid/mermaid.spec.mts +++ b/adev/shared-docs/pipeline/shared/marked/test/mermaid/mermaid.spec.mts @@ -66,11 +66,17 @@ describe('markdown to html', () => { expect(marker?.tagName).toBe('marker'); }); - it('should have a structure .statediagram-state > g > path', () => { + it('should have a structure .statediagram-state > rect', () => { const stateDiagram = markdownDocument.querySelectorAll('svg')[2]; - const path = stateDiagram.querySelector('.statediagram-state > g > path'); - expect(path).not.toBeNull(); - expect(path?.tagName).toBe('path'); + const rect = stateDiagram.querySelector('.statediagram-state > rect.label-container'); + expect(rect).not.toBeNull(); + expect(rect?.tagName).toBe('rect'); + + const nodeLabel = stateDiagram.querySelector( + '.statediagram-state > g > foreignObject .nodeLabel', + ); + expect(nodeLabel).not.toBeNull(); + expect(nodeLabel?.tagName).toBe('SPAN'); }); }); }); diff --git a/adev/shared-docs/pipeline/shared/marked/transformations/heading.mts b/adev/shared-docs/pipeline/shared/marked/transformations/heading.mts index 1b00e9bd62d2..6492644c9b06 100644 --- a/adev/shared-docs/pipeline/shared/marked/transformations/heading.mts +++ b/adev/shared-docs/pipeline/shared/marked/transformations/heading.mts @@ -8,6 +8,7 @@ import {Tokens} from 'marked'; import {AdevDocsRenderer} from '../renderer.mjs'; +import {getIdFromHeading} from '../../heading.mjs'; export function headingRender( this: AdevDocsRenderer, @@ -25,19 +26,12 @@ export function headingRender( `; } - // extract the extended markdown heading id - // ex: ## MyHeading {# myId} - // This is recommended in case we end up having duplicate Ids but we still want the same heading text. - // We don't want to make Id generation stateful/too complex to handle duplicates automatically. - const customIdRegex = /{#\s*([\w-]+)\s*}/g; - const customId = customIdRegex.exec(headingText)?.[1]; - - const link = customId ?? getIdFromHeading(headingText); + const link = getIdFromHeading(headingText); // Replace code backticks and remove custom ID syntax from the displayed label let label = parsedText.replace(/`(.*?)`/g, '$1'); label = label.replace(/{#\s*[\w-]+\s*}/g, '').trim(); - const normalizedLabel = label.replace(/<\/?code>/g, ''); + const normalizedLabel = label.replace(/<[^>]+>/g, '').replace(/"/g, '"'); return ` @@ -67,10 +61,3 @@ export function getPageTitle(text: string, filePath?: string): string { }
`; } - -function getIdFromHeading(heading: string): string { - return heading - .toLowerCase() - .replace(/\s|\//g, '-') // replace spaces and slashes with dashes - .replace(/[^\p{L}\d\-]/gu, ''); // only keep letters, digits & dashes -} diff --git a/adev/shared-docs/pipeline/shared/shiki.mts b/adev/shared-docs/pipeline/shared/shiki.mts index 1a55ebc776cf..3cc8b7a0def9 100644 --- a/adev/shared-docs/pipeline/shared/shiki.mts +++ b/adev/shared-docs/pipeline/shared/shiki.mts @@ -42,6 +42,7 @@ export function codeToHtml( apiEntries?: ApiEntries; language?: string; highlight?: Set; + removeWhitespace?: boolean; }, ): string { const html = highlighter.codeToHtml(code, { @@ -53,7 +54,7 @@ export function codeToHtml( cssVariablePrefix: '--shiki-', defaultColor: false, transformers: [ - removeWhitespaceTransformer(), + ...(config.removeWhitespace ? [removeWhitespaceTransformer()] : []), highlightTransformer(config.highlight), linkApiEntriesTransformer(config.apiEntries), ], diff --git a/adev/shared-docs/pipeline/shared/test/heading.spec.mts b/adev/shared-docs/pipeline/shared/test/heading.spec.mts new file mode 100644 index 000000000000..15c952c67eaf --- /dev/null +++ b/adev/shared-docs/pipeline/shared/test/heading.spec.mts @@ -0,0 +1,31 @@ +/*! + * @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 {getIdFromHeading} from '../heading.mjs'; + +describe('getIdFromHeading', () => { + it('should generate id from simple text', () => { + expect(getIdFromHeading('My Heading')).toBe('my-heading'); + }); + + it('should generate id from text with special characters', () => { + expect(getIdFromHeading('Step 2 - Add component')).toBe('step-2---add-component'); + }); + + it('should extract custom id when present', () => { + expect(getIdFromHeading('My Heading {#custom-id}')).toBe('custom-id'); + }); + + it('should extract custom id ignoring surrounding spaces', () => { + expect(getIdFromHeading('My Heading {# custom-id }')).toBe('custom-id'); + }); + + it('should prioritize custom id over text content', () => { + expect(getIdFromHeading('Duplicate Heading {#unique-id}')).toBe('unique-id'); + }); +}); diff --git a/adev/shared-docs/pipes/relative-link.pipe.ts b/adev/shared-docs/pipes/relative-link.pipe.ts index 7c491ff22e35..19e84e86187a 100644 --- a/adev/shared-docs/pipes/relative-link.pipe.ts +++ b/adev/shared-docs/pipes/relative-link.pipe.ts @@ -7,21 +7,11 @@ */ import {Pipe, PipeTransform} from '@angular/core'; -import {normalizePath, removeTrailingSlash} from '../utils/index'; +import {getRelativeUrl} from '../utils/index'; @Pipe({ name: 'relativeLink', }) export class RelativeLink implements PipeTransform { - transform(absoluteUrl: string, result: 'relative' | 'pathname' | 'hash' = 'relative'): string { - const url = new URL(normalizePath(absoluteUrl)); - - if (result === 'hash') { - return url.hash?.substring(1) ?? ''; - } - if (result === 'pathname') { - return `${removeTrailingSlash(normalizePath(url.pathname))}`; - } - return `${removeTrailingSlash(normalizePath(url.pathname))}${url.hash ?? ''}`; - } + transform = getRelativeUrl; } diff --git a/adev/shared-docs/services/navigation-state.service.ts b/adev/shared-docs/services/navigation-state.service.ts index c021190f3acf..040d29de5ed5 100644 --- a/adev/shared-docs/services/navigation-state.service.ts +++ b/adev/shared-docs/services/navigation-state.service.ts @@ -6,13 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Injectable, inject, linkedSignal, signal} from '@angular/core'; -import {NavigationItem} from '../interfaces/index'; +import {Service, inject, linkedSignal, signal} from '@angular/core'; import {Router} from '@angular/router'; +import {NavigationItem} from '../interfaces/index'; -@Injectable({ - providedIn: 'root', -}) +@Service() export class NavigationState { private readonly router = inject(Router); @@ -21,6 +19,7 @@ export class NavigationState { private readonly _isMobileNavVisible = signal(false); private readonly _level = linkedSignal(() => this._expandedItems().length); + readonly crossCategoryOrigin = signal(undefined); readonly primaryActiveRouteItem = signal(null); activeNavigationItem = this._activeNavigationItem.asReadonly(); expandedItems = this._expandedItems.asReadonly(); diff --git a/adev/shared-docs/services/search-history.service.ts b/adev/shared-docs/services/search-history.service.ts index 03f3e6d059e3..3a3874146012 100644 --- a/adev/shared-docs/services/search-history.service.ts +++ b/adev/shared-docs/services/search-history.service.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {computed, inject, Injectable, signal} from '@angular/core'; +import {computed, inject, signal, Service} from '@angular/core'; import {LOCAL_STORAGE} from '../providers'; import {SearchResultItem} from '../interfaces'; @@ -29,7 +29,7 @@ function cleanUpHtml(label: string | null): string { return (label || '').replace(/<\/?mark>/g, ''); } -@Injectable({providedIn: 'root'}) +@Service() export class SearchHistory { private readonly localStorage = inject(LOCAL_STORAGE); private readonly history = signal>(new Map()); diff --git a/adev/shared-docs/services/search.service.ts b/adev/shared-docs/services/search.service.ts index eb44dfa42f87..589d26ea9bcd 100644 --- a/adev/shared-docs/services/search.service.ts +++ b/adev/shared-docs/services/search.service.ts @@ -7,22 +7,23 @@ */ import { - Injectable, InjectionToken, Provider, + Service, + debounced, inject, linkedSignal, resource, signal, } from '@angular/core'; -import {ENVIRONMENT} from '../providers/index'; -import type {Environment, SearchResult, SearchResultItem, SnippetResult} from '../interfaces/index'; import { + SearchResult as AlgoliaSearchResult, LiteClient, - liteClient as algoliasearch, SearchResponses, - SearchResult as AlgoliaSearchResult, + liteClient as algoliasearch, } from 'algoliasearch/lite'; +import type {Environment, SearchResult, SearchResultItem, SnippetResult} from '../interfaces/index'; +import {ENVIRONMENT} from '../providers/index'; export const SEARCH_DELAY = 200; // Maximum number of facet values to return for each facet during a regular search. @@ -39,22 +40,18 @@ export const provideAlgoliaSearchClient = (config: Environment): Provider => { }; }; -@Injectable({ - providedIn: 'root', -}) +@Service() export class Search { readonly searchQuery = signal(''); private readonly config = inject(ENVIRONMENT); private readonly client = inject(ALGOLIA_CLIENT); + debounceParams = debounced(this.searchQuery, SEARCH_DELAY); + readonly resultsResource = resource({ - params: () => this.searchQuery() || undefined, // coerces empty string to undefined - loader: async ({params: query, abortSignal}) => { - // Until we have a better alternative we debounce by awaiting for a short delay. - await wait(SEARCH_DELAY, abortSignal); - return this.searchWithQuery(query); - }, + params: () => this.debounceParams.value() || undefined, // coerces empty string to undefined + loader: async ({params}) => this.searchWithQuery(params), }); readonly searchResults = linkedSignal({ @@ -223,27 +220,6 @@ function matched(snippet: SnippetResult | undefined): boolean { return snippet?.matchLevel !== undefined && snippet.matchLevel !== 'none'; } -/** - * Temporary helper to implement the debounce functionality on the search resource - */ -function wait(ms: number, signal: AbortSignal): Promise { - return new Promise((resolve, reject) => { - let timeout: ReturnType | undefined; - - const onAbort = () => { - clearTimeout(timeout); - reject(new Error('Operation aborted')); - }; - - timeout = setTimeout(() => { - signal.removeEventListener('abort', onAbort); - resolve(); - }, ms); - - signal.addEventListener('abort', onAbort, {once: true}); - }); -} - function extractPackageNameFromUrl(url: string): string | null { const extractedSegment = url.match(/\/api\/(.*)\/.*#?/); if (extractedSegment == null) { diff --git a/adev/shared-docs/services/table-of-contents-loader.service.ts b/adev/shared-docs/services/table-of-contents-loader.service.ts index a67c8a2fd496..e0eb1c598e6d 100644 --- a/adev/shared-docs/services/table-of-contents-loader.service.ts +++ b/adev/shared-docs/services/table-of-contents-loader.service.ts @@ -7,7 +7,7 @@ */ import {DOCUMENT} from '@angular/common'; -import {inject, signal, Injectable, DestroyRef} from '@angular/core'; +import {inject, signal, DestroyRef, Service} from '@angular/core'; import {TableOfContentsItem, TableOfContentsLevel} from '../interfaces/index'; @@ -19,7 +19,7 @@ import {TableOfContentsItem, TableOfContentsLevel} from '../interfaces/index'; */ export const TOC_SKIP_CONTENT_MARKER = 'toc-skip-content'; -@Injectable({providedIn: 'root'}) +@Service() export class TableOfContentsLoader { // There are some cases when default browser anchor scrolls a little above the // heading In that cases wrong item was selected. The value found by trial and diff --git a/adev/shared-docs/styles/_reference.scss b/adev/shared-docs/styles/_reference.scss index dbd98a8c359f..fe251c4e0796 100644 --- a/adev/shared-docs/styles/_reference.scss +++ b/adev/shared-docs/styles/_reference.scss @@ -51,7 +51,7 @@ } .docs-reference-category { - color: var(--gray-400); + color: var(--quaternary-contrast); font-size: 0.875rem; font-weight: 500; line-height: 1.4rem; @@ -426,6 +426,14 @@ .docs-ref-content { padding: 1rem 0; + .docs-ref-option-and-description { + .docs-reference-option { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + } + &:not(:first-child) { border-block-start: 1px solid var(--senary-contrast); } diff --git a/adev/shared-docs/styles/docs/_card.scss b/adev/shared-docs/styles/docs/_card.scss index a844e7b3dc68..dedda443844c 100644 --- a/adev/shared-docs/styles/docs/_card.scss +++ b/adev/shared-docs/styles/docs/_card.scss @@ -1,78 +1,92 @@ // Card Grid @mixin docs-card() { + $compact-breakpoint: 550px; + .docs-card-container-wrapper { border: 1px solid var(--senary-contrast); border-radius: 0.25rem; margin: 1rem 0; - .docs-card-container-header { - align-items: flex-end; - border-bottom: 1px solid var(--senary-contrast); - display: flex; - justify-content: space-between; + .docs-card-container-header { + align-items: flex-end; + border-bottom: 1px solid var(--senary-contrast); + display: flex; + justify-content: space-between; - h2 { - padding: 2.5rem 1rem 2.5rem 2.5rem; - min-width: 350px; - } + h2 { + padding: 2.5rem 1rem 2.5rem 2.5rem; + min-width: 350px; - .theme-fill-bg { - fill: var(--page-background); - stroke: var(--primary-contrast); - } - .theme-fill-accent-bg { - fill: var(--septenary-contrast); - stroke: var(--primary-contrast); - } - .theme-stroke-primary { - stroke: var(--primary-contrast); - } - .theme-fill-primary { - fill: var(--primary-contrast); - } - .theme-fill-secondary { - fill: var(--octonary-contrast); - } - .theme-stroke-accent { - stroke: var(--vivid-pink); - } - .theme-fill-accent { - fill: var(--vivid-pink); - stroke: var(--primary-contrast); - } - .theme-stroke-secondary { - stroke: var(--senary-contrast); - } - .theme-fill-secondary { - fill: var(--senary-contrast); + @container docs-content (max-width: $compact-breakpoint) { + min-width: auto; + padding: 1.5rem; } + } + + .theme-fill-bg { + fill: var(--page-background); + stroke: var(--primary-contrast); + } + .theme-fill-accent-bg { + fill: var(--septenary-contrast); + stroke: var(--primary-contrast); + } + .theme-stroke-primary { + stroke: var(--primary-contrast); + } + .theme-fill-primary { + fill: var(--primary-contrast); + } + .theme-fill-secondary { + fill: var(--octonary-contrast); + } + .theme-stroke-accent { + stroke: var(--vivid-pink); + } + .theme-fill-accent { + fill: var(--vivid-pink); + stroke: var(--primary-contrast); + } + .theme-stroke-secondary { + stroke: var(--senary-contrast); + } + .theme-fill-secondary { + fill: var(--senary-contrast); + } + .header-img { + margin-top: 5px; + overflow: hidden; + margin-bottom: -5px; + } + svg { + fill-opacity: 1; - .header-img { - margin-top: 5px; - overflow: hidden; - margin-bottom: -5px; + @container header (max-width: $compact-breakpoint) { + display: none; } - svg { - fill-opacity: 1; - - @container header (max-width: 550px) { - display: none; - } + @container docs-content (max-width: $compact-breakpoint) { + display: none; + } - rect { - fill: transparent; - } + rect { + fill: transparent; } } + } .docs-card-container-content { margin: 1rem; padding: 1.5rem; + @container docs-content (max-width: $compact-breakpoint) { + margin: 0.5rem; + padding: 0.75rem; + } + .docs-card { margin: 0; @@ -145,6 +159,21 @@ margin-block: 1.5rem; } + .docs-card-header-inline { + display: flex; + align-items: center; + gap: 0.5rem; + + svg { + margin-block-end: 0; + flex-shrink: 0; + } + + h3 { + margin-block: 0; + } + } + &.docs-card-with-svg { padding: 0; @@ -214,7 +243,10 @@ &:before { content: ''; position: absolute; - top: 0; right: 0; bottom: 0; left: 0; + top: 0; + right: 0; + bottom: 0; + left: 0; z-index: -1; margin: -1px; border-radius: inherit; @@ -223,7 +255,7 @@ .docs-nav-card-title { background: var(--pink-to-highlight-to-purple-to-blue-horizontal-gradient); - padding: 1.5rem 1.5rem .5rem; + padding: 1.5rem 1.5rem 0.5rem; -webkit-background-clip: text; background-clip: text; color: transparent; @@ -251,7 +283,7 @@ .docs-nav-card-svg { width: 350px; - @container header (max-width: 550px) { + @container header (max-width: $compact-breakpoint) { display: none; } } @@ -264,11 +296,11 @@ gap: 8px; h3 { - margin-block-end: .5rem; + margin-block-end: 0.5rem; } svg path { - fill: var(--gray-400); + fill: var(--quaternary-contrast); } } diff --git a/adev/shared-docs/styles/docs/_code.scss b/adev/shared-docs/styles/docs/_code.scss index 556318e4fed3..f0d95a957bb2 100644 --- a/adev/shared-docs/styles/docs/_code.scss +++ b/adev/shared-docs/styles/docs/_code.scss @@ -36,6 +36,15 @@ $code-font-size: 0.875rem; } } + .shiki.line.cli { + span { + color: var(--quaternary-contrast); + font-family: var(--inter-font); + font-weight: 600; + font-size: 13px; + } + } + .docs-light-mode .shiki { color: var(--shiki-light); background-color: var(--shiki-light-bg); @@ -46,7 +55,6 @@ $code-font-size: 0.875rem; /* Optional, if you also want font styles */ font-style: var(--shiki-light-font-style); font-weight: var(--shiki-light-font-weight); - text-decoration: var(--shiki-light-text-decoration); } .shiki-ln-line-highlighted, @@ -204,15 +212,24 @@ $code-font-size: 0.875rem; white-space: nowrap; } - .shiki .line { - &::before { - content: '$'; - padding-inline-end: 0.5rem; + &:not([hideDollar]) { + .shiki { + .line { + &::before { + content: '$'; + padding-inline-end: 0.5rem; + } + } } - display: block; + } - &.hidden { - display: none; + .shiki { + .line { + display: block; + + &.hidden { + display: none; + } } } } @@ -239,7 +256,7 @@ $code-font-size: 0.875rem; opacity: 1; path { transition: fill 0.3s ease; - fill: var(--gray-400); + fill: var(--quaternary-contrast); } } .docs-check { diff --git a/adev/shared-docs/styles/docs/_table.scss b/adev/shared-docs/styles/docs/_table.scss index ff3c033f31fe..451966efe714 100644 --- a/adev/shared-docs/styles/docs/_table.scss +++ b/adev/shared-docs/styles/docs/_table.scss @@ -35,6 +35,9 @@ vertical-align: top; min-width: 10ch; } + td:has(code) { + min-width: 8ch; + } &:not(:last-child) { border-block-end: 1px solid var(--senary-contrast); } diff --git a/adev/shared-docs/testing/BUILD.bazel b/adev/shared-docs/testing/BUILD.bazel index fc29ce309edf..410cab637861 100644 --- a/adev/shared-docs/testing/BUILD.bazel +++ b/adev/shared-docs/testing/BUILD.bazel @@ -21,8 +21,10 @@ ts_project( "index.ts", ], ), + tsconfig = "//adev/shared-docs:tsconfig_test", deps = [ "//adev:node_modules/@angular/core", + "//adev:node_modules/@types/jasmine", "//adev:node_modules/@webcontainer/api", ], ) diff --git a/adev/shared-docs/testing/testing-helper.ts b/adev/shared-docs/testing/testing-helper.ts index 819cd0aee5f9..ad9b882119cc 100644 --- a/adev/shared-docs/testing/testing-helper.ts +++ b/adev/shared-docs/testing/testing-helper.ts @@ -181,3 +181,46 @@ export class FakeWebContainerProcess implements WebContainerProcess { kill(): void {} resize(dimensions: {cols: number; rows: number}): void {} } + +// Copy from utils in packages/private/testing + +/** + * Returns a promise that resolves after the specified time. + * + * @param ms - Time to wait in milliseconds. Defaults to 0. + * + * @example + * ```ts + * await timeout(100); // Wait 100ms + * ``` + */ +export async function timeout(ms?: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + +/** + * Installs Jasmine's fake clock with auto-tick enabled for all tests in the describe block. + * Call at the top level of a describe block to automatically advance time for async operations. + * + * @example + * ```ts + * describe('MyComponent', () => { + * useAutoTick(); + * + * it('should handle timers', () => { + * // setTimeout, setInterval, etc. will execute synchronously + * }); + * }); + * ``` + */ +export function useAutoTick() { + beforeEach(() => { + jasmine.clock().install(); + jasmine.clock().autoTick(); + }); + afterEach(() => { + jasmine.clock().uninstall(); + }); +} diff --git a/adev/shared-docs/utils/analytics.utils.ts b/adev/shared-docs/utils/analytics.utils.ts index 8f53b58069d5..12a4f284a930 100644 --- a/adev/shared-docs/utils/analytics.utils.ts +++ b/adev/shared-docs/utils/analytics.utils.ts @@ -41,3 +41,6 @@ export const setCookieConsent = (state: 'denied' | 'granted'): void => { } } }; + +// This fixes the RollupError: Exported variable "global" is not defined. +export {}; diff --git a/adev/shared-docs/utils/url.utils.ts b/adev/shared-docs/utils/url.utils.ts index 14bee4d5411c..003620bfa382 100644 --- a/adev/shared-docs/utils/url.utils.ts +++ b/adev/shared-docs/utils/url.utils.ts @@ -6,6 +6,23 @@ * found in the LICENSE file at https://angular.dev/license */ +import {normalizePath} from './navigation.utils'; + +export function getRelativeUrl( + absoluteUrl: string, + result: 'relative' | 'pathname' | 'hash' = 'relative', +): string { + const url = new URL(normalizePath(absoluteUrl)); + + if (result === 'hash') { + return url.hash?.substring(1) ?? ''; + } + if (result === 'pathname') { + return `${removeTrailingSlash(normalizePath(url.pathname))}`; + } + return `${removeTrailingSlash(normalizePath(url.pathname))}${url.hash ?? ''}`; +} + export const removeTrailingSlash = (url: string): string => { if (url.endsWith('/')) { return url.slice(0, -1); diff --git a/adev/src/app/app-scroller.ts b/adev/src/app/app-scroller.ts index d5827ac81bc7..894818c60b0e 100644 --- a/adev/src/app/app-scroller.ts +++ b/adev/src/app/app-scroller.ts @@ -7,7 +7,6 @@ */ import {isPlatformBrowser, ViewportScroller} from '@angular/common'; import { - Injectable, inject, ApplicationRef, afterNextRender, @@ -15,11 +14,12 @@ import { Injector, DestroyRef, PLATFORM_ID, + Service, } from '@angular/core'; import {Scroll, Router} from '@angular/router'; import {filter, firstValueFrom, map, switchMap, tap} from 'rxjs'; -@Injectable({providedIn: 'root'}) +@Service() export class AppScroller { private readonly router = inject(Router); private readonly viewportScroller = inject(ViewportScroller); diff --git a/adev/src/app/app.component.scss b/adev/src/app/app.component.scss index 10e0c8232b61..b4760cd53cc9 100644 --- a/adev/src/app/app.component.scss +++ b/adev/src/app/app.component.scss @@ -40,6 +40,13 @@ } } + // Lock the page behind the mobile primary-nav drawer. `clip` preserves the primary nav's sticky context. + @include mq.for-phone-only { + &:has(.adev-nav-primary--open) { + overflow: clip; + } + } + &:has(.adev-home) { .adev-nav { width: 0; @@ -91,7 +98,7 @@ width: 100vw; height: 100vh; backdrop-filter: blur(2px); - background-color: color-mix(in srgb, var(--gray-1000) 5%, transparent); + background-color: color-mix(in srgb, var(--page-background) 5%, transparent); z-index: 50; visibility: hidden; opacity: 0; diff --git a/adev/src/app/app.component.spec.ts b/adev/src/app/app.component.spec.ts index 1b5360966333..1b698ab66925 100644 --- a/adev/src/app/app.component.spec.ts +++ b/adev/src/app/app.component.spec.ts @@ -6,20 +6,20 @@ * found in the LICENSE file at https://angular.dev/license */ +import {provideHttpClient} from '@angular/common/http'; +import {provideHttpClientTesting} from '@angular/common/http/testing'; import {TestBed} from '@angular/core/testing'; -import {AppComponent} from './app.component'; +import {Search, WINDOW} from '@angular/docs'; import {provideRouter, withComponentInputBinding} from '@angular/router'; +import {AppComponent} from './app.component'; import {routes} from './routing/routes'; -import {Search, WINDOW} from '@angular/docs'; -import {provideHttpClient} from '@angular/common/http'; -import {provideHttpClientTesting} from '@angular/common/http/testing'; describe('AppComponent', () => { const fakeSearch = {}; const fakeWindow = {location: {hostname: 'angular.dev'}}; - it('should create the app', async () => { - await TestBed.configureTestingModule({ + it('should create the app', () => { + TestBed.configureTestingModule({ imports: [AppComponent], providers: [ provideHttpClient(), @@ -34,7 +34,7 @@ describe('AppComponent', () => { useValue: fakeSearch, }, ], - }).compileComponents(); + }); const fixture = TestBed.createComponent(AppComponent); const app = fixture.componentInstance; diff --git a/adev/src/app/app.component.ts b/adev/src/app/app.component.ts index 37dd27d79c46..cbf33466accb 100644 --- a/adev/src/app/app.component.ts +++ b/adev/src/app/app.component.ts @@ -7,16 +7,7 @@ */ import {DOCUMENT, isPlatformBrowser} from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - inject, - PLATFORM_ID, - signal, - isDevMode, -} from '@angular/core'; -import {NavigationEnd, NavigationSkipped, Router, RouterOutlet} from '@angular/router'; -import {filter, map} from 'rxjs/operators'; +import {Component, inject, isDevMode, PLATFORM_ID, signal} from '@angular/core'; import { CookiePopup, getActivatedRouteSnapshotFromRouter, @@ -24,16 +15,17 @@ import { SearchDialog, TopLevelBannerComponent, } from '@angular/docs'; +import {NavigationEnd, NavigationSkipped, Router, RouterOutlet} from '@angular/router'; +import {filter, map} from 'rxjs/operators'; +import {ESCAPE, SEARCH_TRIGGER_KEY} from './core/constants/keys'; import {Footer} from './core/layout/footer/footer.component'; import {Navigation} from './core/layout/navigation/navigation.component'; -import {SecondaryNavigation} from './core/layout/secondary-navigation/secondary-navigation.component'; import {ProgressBarComponent} from './core/layout/progress-bar/progress-bar.component'; -import {ESCAPE, SEARCH_TRIGGER_KEY} from './core/constants/keys'; +import {SecondaryNavigation} from './core/layout/secondary-navigation/secondary-navigation.component'; import {HeaderService} from './core/services/header.service'; @Component({ selector: 'adev-root', - changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CookiePopup, Navigation, diff --git a/adev/src/app/app.config.ts b/adev/src/app/app.config.ts index bd370768a871..6eafe1c71ad2 100644 --- a/adev/src/app/app.config.ts +++ b/adev/src/app/app.config.ts @@ -7,14 +7,13 @@ */ import {DOCUMENT} from '@angular/common'; -import {provideHttpClient, withFetch} from '@angular/common/http'; import { ApplicationConfig, ErrorHandler, inject, - provideZonelessChangeDetection, provideEnvironmentInitializer, } from '@angular/core'; +import {UrlSerializer} from '@angular/router'; import { DOCS_CONTENT_LOADER, ENVIRONMENT, @@ -33,13 +32,12 @@ import {CustomErrorHandler} from './core/services/errors-handling/error-handler' import {ExampleContentLoader} from './core/services/example-content-loader.service'; import {routerProviders} from './routing/router_providers'; import {TYPESCRIPT_VFS_WORKER_PROVIDER} from './editor/code-editor/workers/factory-provider'; +import {AdevUrlSerializer} from './core/services/routing/adev-url-serializer'; export const appConfig: ApplicationConfig = { providers: [ routerProviders, - provideZonelessChangeDetection(), provideClientHydration(), - provideHttpClient(withFetch()), provideEnvironmentInitializer(() => inject(AnalyticsService)), provideAlgoliaSearchClient(environment), {provide: ENVIRONMENT, useValue: environment}, @@ -53,5 +51,9 @@ export const appConfig: ApplicationConfig = { deps: [DOCUMENT], }, TYPESCRIPT_VFS_WORKER_PROVIDER, + { + provide: UrlSerializer, + useClass: AdevUrlSerializer, + }, ], }; diff --git a/adev/src/app/core/layout/footer/footer.component.html b/adev/src/app/core/layout/footer/footer.component.html index 7b2627bdceeb..a5cd7ed9cd60 100644 --- a/adev/src/app/core/layout/footer/footer.component.html +++ b/adev/src/app/core/layout/footer/footer.component.html @@ -58,7 +58,7 @@

Community

  • Report Issues @@ -95,20 +95,27 @@

    Resources

    diff --git a/adev/src/app/core/layout/footer/footer.component.ts b/adev/src/app/core/layout/footer/footer.component.ts index 264febb3a666..1a231b100007 100644 --- a/adev/src/app/core/layout/footer/footer.component.ts +++ b/adev/src/app/core/layout/footer/footer.component.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, VERSION} from '@angular/core'; +import {Component, VERSION} from '@angular/core'; import {ExternalLink} from '@angular/docs'; import {RouterLink} from '@angular/router'; import {ANGULAR_LINKS} from '../../constants/links'; @@ -16,7 +16,6 @@ import {ANGULAR_LINKS} from '../../constants/links'; imports: [ExternalLink, RouterLink], templateUrl: './footer.component.html', styleUrls: ['./footer.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class Footer { protected angularVersion = VERSION.full; diff --git a/adev/src/app/core/layout/navigation/navigation.component.html b/adev/src/app/core/layout/navigation/navigation.component.html index 69d0e73526af..685f66bb002f 100644 --- a/adev/src/app/core/layout/navigation/navigation.component.html +++ b/adev/src/app/core/layout/navigation/navigation.component.html @@ -392,11 +392,11 @@ - +
  • +
  • + + + + + + +
  • diff --git a/adev/src/app/core/layout/navigation/navigation.component.scss b/adev/src/app/core/layout/navigation/navigation.component.scss index e002da6ac523..d774377db877 100644 --- a/adev/src/app/core/layout/navigation/navigation.component.scss +++ b/adev/src/app/core/layout/navigation/navigation.component.scss @@ -76,7 +76,6 @@ background-color 0.3s ease, border-color 0.3s ease; height: 100dvh; - padding-block-start: 1rem; padding-block-end: 2rem; box-sizing: border-box; // Add a subtle border for the home page @@ -95,6 +94,7 @@ width: 100%; padding-inline: calc(var(--layout-padding) - 1.25rem); height: auto; + scrollbar-width: none; padding-block: 0; } @@ -108,6 +108,19 @@ color-mix(in srgb, var(--electric-violet), transparent 70%) 25%, color-mix(in srgb, var(--bright-blue), transparent 60%) 90% ); + + &::before { + content: ''; + position: absolute; + opacity: 0.8; + width: 100%; + height: 100%; + background-color: var(--octonary-contrast); + z-index: -1; + } + .adev-version-button { + border: 1px solid var(--quinary-contrast); + } } &.adev-nav-primary--deprecated { @@ -127,7 +140,7 @@ position: absolute; top: 0; background-color: var(--page-background); - box-shadow: 10px 4px 3px 0 rgba(0, 0, 0, 0.001); + box-shadow: 2px 0 0 0 var(--page-background); transform: translateX(-100%); // primary nav: exit delayed @@ -153,9 +166,11 @@ list-style: none; display: flex; flex-direction: column; + padding-block-start: 1rem; @include mq.for-tablet { flex-direction: row; + padding-block-start: 0; } // version dropdown button @@ -220,7 +235,9 @@ @include mq.for-tablet { flex-direction: row !important; + align-items: center; margin-inline-end: 1.25rem; + margin-top: 0.25rem; gap: 0.75rem; } @@ -276,6 +293,8 @@ @include mq.for-phone-only { display: block; + position: relative; + top: 1rem; } } diff --git a/adev/src/app/core/layout/navigation/navigation.component.ts b/adev/src/app/core/layout/navigation/navigation.component.ts index 2cf4ffe86913..e411c2c385dd 100644 --- a/adev/src/app/core/layout/navigation/navigation.component.ts +++ b/adev/src/app/core/layout/navigation/navigation.component.ts @@ -7,34 +7,27 @@ */ import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu'; +import {ConnectionPositionPair} from '@angular/cdk/overlay'; import {DOCUMENT, Location, isPlatformBrowser} from '@angular/common'; -import { - ChangeDetectionStrategy, - Component, - DestroyRef, - PLATFORM_ID, - inject, - signal, -} from '@angular/core'; +import {Component, PLATFORM_ID, inject, signal} from '@angular/core'; import {takeUntilDestroyed, toObservable} from '@angular/core/rxjs-interop'; import { ClickOutside, - NavigationState, + IS_SEARCH_DIALOG_OPEN, IconComponent, + NavigationState, getBaseUrlAfterRedirects, isApple, - IS_SEARCH_DIALOG_OPEN, } from '@angular/docs'; import {NavigationEnd, Router, RouterLink} from '@angular/router'; import {filter, map, startWith} from 'rxjs/operators'; import {DOCS_ROUTES, REFERENCE_ROUTES, TUTORIALS_ROUTES} from '../../../routing/routes'; -import {Theme, ThemeManager} from '../../services/theme-manager.service'; -import {VersionManager} from '../../services/version-manager.service'; -import {ConnectionPositionPair} from '@angular/cdk/overlay'; -import {ANGULAR_LINKS} from '../../constants/links'; import {PRIMARY_NAV_ID, SECONDARY_NAV_ID} from '../../constants/element-ids'; import {COMMAND, CONTROL, SEARCH_TRIGGER_KEY} from '../../constants/keys'; +import {ANGULAR_LINKS} from '../../constants/links'; import {PAGE_PREFIX} from '../../constants/pages'; +import {Theme, ThemeManager} from '../../services/theme-manager.service'; +import {VersionManager} from '../../services/version-manager.service'; type MenuType = 'social' | 'theme-picker' | 'version-picker'; @@ -43,10 +36,8 @@ type MenuType = 'social' | 'theme-picker' | 'version-picker'; imports: [RouterLink, ClickOutside, CdkMenu, CdkMenuItem, CdkMenuTrigger, IconComponent], templateUrl: './navigation.component.html', styleUrls: ['./navigation.component.scss', './mini-menu.scss', './nav-item.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class Navigation { - private readonly destroyRef = inject(DestroyRef); private readonly document = inject(DOCUMENT); private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID)); private readonly navigationState = inject(NavigationState); @@ -142,7 +133,7 @@ export class Navigation { } private closeMobileNavOnPrimaryRouteChange(): void { - this.primaryRouteChanged$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => { + this.primaryRouteChanged$.pipe(takeUntilDestroyed()).subscribe(() => { this.closeMobileNav(); }); } @@ -154,7 +145,7 @@ export class Navigation { map((event) => (event as NavigationEnd).urlAfterRedirects), ) .pipe( - takeUntilDestroyed(this.destroyRef), + takeUntilDestroyed(), //using location because router.url will only return "/" here startWith(this.location.path()), ) @@ -197,7 +188,7 @@ export class Navigation { } private preventToScrollContentWhenSecondaryNavIsOpened(): void { - this.isMobileNavigationOpened$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((opened) => { + this.isMobileNavigationOpened$.pipe(takeUntilDestroyed()).subscribe((opened) => { if (opened) { this.document.body.style.overflowY = 'hidden'; } else { diff --git a/adev/src/app/core/layout/progress-bar/progress-bar.component.ts b/adev/src/app/core/layout/progress-bar/progress-bar.component.ts index c6645dfaebd3..3dd6311d5ca5 100644 --- a/adev/src/app/core/layout/progress-bar/progress-bar.component.ts +++ b/adev/src/app/core/layout/progress-bar/progress-bar.component.ts @@ -6,9 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, inject, PLATFORM_ID, viewChild} from '@angular/core'; import {isPlatformServer} from '@angular/common'; -import {NgProgressbar, NgProgressRef} from 'ngx-progressbar'; +import {Component, inject, PLATFORM_ID, viewChild} from '@angular/core'; import { NavigationCancel, NavigationEnd, @@ -17,6 +16,7 @@ import { NavigationStart, Router, } from '@angular/router'; +import {NgProgressbar, NgProgressRef} from 'ngx-progressbar'; import {filter, map, switchMap, take} from 'rxjs/operators'; /** Time to wait after navigation starts before showing the progress bar. This delay allows a small amount of time to skip showing the progress bar when a navigation is effectively immediate. 30ms is approximately the amount of time we can wait before a delay is perceptible.*/ @@ -26,7 +26,6 @@ export const PROGRESS_BAR_DELAY = 30; selector: 'adev-progress-bar', imports: [NgProgressbar], template: ``, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProgressBarComponent { private readonly router = inject(Router); diff --git a/adev/src/app/core/layout/secondary-navigation/secondary-navigation.component.scss b/adev/src/app/core/layout/secondary-navigation/secondary-navigation.component.scss index fd655ebcd789..a793427d9db6 100644 --- a/adev/src/app/core/layout/secondary-navigation/secondary-navigation.component.scss +++ b/adev/src/app/core/layout/secondary-navigation/secondary-navigation.component.scss @@ -35,6 +35,7 @@ @include mq.for-tablet-landscape-down { position: absolute; + height: 100dvh; } @media (prefers-reduced-motion: no-preference) { diff --git a/adev/src/app/core/layout/secondary-navigation/secondary-navigation.component.ts b/adev/src/app/core/layout/secondary-navigation/secondary-navigation.component.ts index a480b2df6910..0221bfcf87fe 100644 --- a/adev/src/app/core/layout/secondary-navigation/secondary-navigation.component.ts +++ b/adev/src/app/core/layout/secondary-navigation/secondary-navigation.component.ts @@ -6,15 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - ChangeDetectionStrategy, - Component, - DestroyRef, - PLATFORM_ID, - computed, - inject, - signal, -} from '@angular/core'; +import {isPlatformBrowser} from '@angular/common'; +import {Component, PLATFORM_ID, computed, inject, signal} from '@angular/core'; import {takeUntilDestroyed, toObservable} from '@angular/core/rxjs-interop'; import { ClickOutside, @@ -27,10 +20,9 @@ import { markExternalLinks, shouldReduceMotion, } from '@angular/docs'; +import {ActivatedRouteSnapshot, NavigationEnd, Router, RouterStateSnapshot} from '@angular/router'; import {distinctUntilChanged, filter, map, skip, startWith} from 'rxjs/operators'; import {SUB_NAVIGATION_DATA} from '../../../routing/sub-navigation-data'; -import {ActivatedRouteSnapshot, NavigationEnd, Router, RouterStateSnapshot} from '@angular/router'; -import {isPlatformBrowser} from '@angular/common'; import {PRIMARY_NAV_ID, SECONDARY_NAV_ID} from '../../constants/element-ids'; import {PAGE_PREFIX} from '../../constants/pages'; @@ -41,10 +33,8 @@ export const ANIMATION_DURATION = 500; imports: [NavigationList, ClickOutside], templateUrl: './secondary-navigation.component.html', styleUrls: ['./secondary-navigation.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class SecondaryNavigation { - private readonly destroyRef = inject(DestroyRef); private readonly navigationState = inject(NavigationState); private readonly platformId = inject(PLATFORM_ID); private readonly router = inject(Router); @@ -78,15 +68,15 @@ export class SecondaryNavigation { private readonly primaryActiveRouteChanged$ = toObservable(this.primaryActiveRouteItem).pipe( distinctUntilChanged(), - takeUntilDestroyed(this.destroyRef), + takeUntilDestroyed(), ); private readonly urlAfterRedirects$ = this.router.events.pipe( filter((event) => event instanceof NavigationEnd), - map((event) => (event as NavigationEnd).urlAfterRedirects), - filter((url): url is string => url !== undefined), + map((event) => event.urlAfterRedirects), + filter((url) => url !== undefined), startWith(this.getInitialPath(this.router.routerState.snapshot)), - takeUntilDestroyed(this.destroyRef), + takeUntilDestroyed(), ); constructor() { diff --git a/adev/src/app/core/services/a-dev-title-strategy.ts b/adev/src/app/core/services/a-dev-title-strategy.ts index d023d95b9407..60662a00dd19 100644 --- a/adev/src/app/core/services/a-dev-title-strategy.ts +++ b/adev/src/app/core/services/a-dev-title-strategy.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Injectable, inject} from '@angular/core'; +import {inject, Service} from '@angular/core'; import {NavigationItem} from '@angular/docs'; import {Meta, Title} from '@angular/platform-browser'; import {ActivatedRouteSnapshot, RouterStateSnapshot, TitleStrategy} from '@angular/router'; @@ -22,7 +22,7 @@ export const TITLE_TWITTER_META_TAG = 'twitter:title'; export const ALL_TITLE_META_TAGS = [TITLE_OG_META_TAG, TITLE_TWITTER_META_TAG]; -@Injectable({providedIn: 'root'}) +@Service() export class ADevTitleStrategy extends TitleStrategy { private readonly title = inject(Title); private readonly meta = inject(Meta); diff --git a/adev/src/app/core/services/analytics/analytics.service.ts b/adev/src/app/core/services/analytics/analytics.service.ts index 7ffbeac2660e..f4a80c85f6b2 100644 --- a/adev/src/app/core/services/analytics/analytics.service.ts +++ b/adev/src/app/core/services/analytics/analytics.service.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {inject, Injectable, PLATFORM_ID} from '@angular/core'; +import {inject, PLATFORM_ID, Service} from '@angular/core'; import {isPlatformBrowser} from '@angular/common'; import {WINDOW, ENVIRONMENT, LOCAL_STORAGE, STORAGE_KEY, setCookieConsent} from '@angular/docs'; @@ -19,7 +19,7 @@ interface WindowWithAnalytics extends Window { gtag?(...args: any[]): void; } -@Injectable({providedIn: 'root'}) +@Service() /** * Google Analytics Service - captures app behaviors and sends them to Google Analytics. * diff --git a/adev/src/app/core/services/content-loader.service.ts b/adev/src/app/core/services/content-loader.service.ts index ac08bd695e00..f488e348463f 100644 --- a/adev/src/app/core/services/content-loader.service.ts +++ b/adev/src/app/core/services/content-loader.service.ts @@ -7,12 +7,12 @@ */ import {HttpClient, HttpErrorResponse} from '@angular/common/http'; -import {Injectable, inject} from '@angular/core'; +import {Service, inject} from '@angular/core'; import {DocContent, DocsContentLoader} from '@angular/docs'; import {firstValueFrom} from 'rxjs'; import {map} from 'rxjs/operators'; -@Injectable() +@Service({autoProvided: false}) export class ContentLoader implements DocsContentLoader { private readonly cache = new Map>(); private readonly httpClient = inject(HttpClient); diff --git a/adev/src/app/core/services/errors-handling/error-snack-bar.ts b/adev/src/app/core/services/errors-handling/error-snack-bar.ts index fc717b12fbb3..16ba426cec8a 100644 --- a/adev/src/app/core/services/errors-handling/error-snack-bar.ts +++ b/adev/src/app/core/services/errors-handling/error-snack-bar.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ChangeDetectionStrategy, Component, inject} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {MAT_SNACK_BAR_DATA, MatSnackBarAction, MatSnackBarRef} from '@angular/material/snack-bar'; export interface ErrorSnackBarData { @@ -16,7 +16,6 @@ export interface ErrorSnackBarData { @Component({ selector: 'error-snack-bar', - changeDetection: ChangeDetectionStrategy.OnPush, template: ` {{ message }} \n```", + "description": "The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n\nThis directive requires the `panel` input be set to the template reference of the `ngAccordionPanel`\nit controls. When clicked, it will expand or collapse the panel. It also handles keyboard\ninteractions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n`aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\nThe `disabled` input can be used to disable the trigger.\n\n```html\n\n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Accordion](guide/aria/accordion)" } ], - "rawComment": "/**\n * The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n *\n * This directive requires a `panelId` that must match the `panelId` of the `ngAccordionPanel` it\n * controls. When clicked, it will expand or collapse the panel. It also handles keyboard\n * interactions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n * `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\n * The `disabled` input can be used to disable the trigger.\n *\n * ```html\n * \n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */", - "implements": [], + "rawComment": "/**\n * The trigger that toggles the visibility of its associated `ngAccordionPanel`.\n *\n * This directive requires the `panel` input be set to the template reference of the `ngAccordionPanel`\n * it controls. When clicked, it will expand or collapse the panel. It also handles keyboard\n * interactions for navigation within the `ngAccordionGroup`. It applies `role=\"button\"` and manages\n * `aria-expanded`, `aria-controls`, and `aria-disabled` attributes for accessibility.\n * The `disabled` input can be used to disable the trigger.\n *\n * ```html\n * \n * ```\n *\n * @see [Accordion](guide/aria/accordion)\n */", + "implements": [ + "OnInit", + "OnDestroy" + ], "isStandalone": true, "selector": "[ngAccordionTrigger]", "exportAs": [ @@ -387,8 +454,8 @@ ], "source": { "filePath": "/src/aria/accordion/accordion-trigger.ts", - "startLine": 42, - "endLine": 107 + "startLine": 43, + "endLine": 145 } }, { @@ -468,6 +535,39 @@ "inputAlias": "wrap", "isRequiredInput": false }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, { "name": "expandAll", "signatures": [ @@ -536,19 +636,17 @@ } ], "generics": [], - "description": "A container for a group of accordion items. It manages the overall state and\ninteractions of the accordion, such as keyboard navigation and expansion mode.\n\nThe `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\ncoordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\nIt supports both single and multiple expansion modes.\n\n```html\n
    \n
    \n

    \n \n

    \n
    \n \n

    Content for Item 1.

    \n
    \n
    \n
    \n
    \n

    \n \n

    \n
    \n \n

    Content for Item 2.

    \n
    \n
    \n
    \n
    \n```", + "description": "A container for a group of accordion items. It manages the overall state and\ninteractions of the accordion, such as keyboard navigation and expansion mode.\n\nThe `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\ncoordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\nIt supports both single and multiple expansion modes.\n\n```html\n
    \n
    \n

    \n \n

    \n
    \n \n

    Content for Item 1.

    \n
    \n
    \n
    \n
    \n

    \n \n

    \n
    \n \n

    Content for Item 2.

    \n
    \n
    \n
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Accordion](guide/aria/accordion)" } ], - "rawComment": "/**\n * A container for a group of accordion items. It manages the overall state and\n * interactions of the accordion, such as keyboard navigation and expansion mode.\n *\n * The `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\n * coordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\n * It supports both single and multiple expansion modes.\n *\n * ```html\n *
    \n *
    \n *

    \n * \n *

    \n *
    \n * \n *

    Content for Item 1.

    \n *
    \n *
    \n *
    \n *
    \n *

    \n * \n *

    \n *
    \n * \n *

    Content for Item 2.

    \n *
    \n *
    \n *
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n * @see [Accordion](guide/aria/accordion)\n */", - "implements": [], + "rawComment": "/**\n * A container for a group of accordion items. It manages the overall state and\n * interactions of the accordion, such as keyboard navigation and expansion mode.\n *\n * The `ngAccordionGroup` serves as the root of a group of accordion triggers and panels,\n * coordinating the behavior of the `ngAccordionTrigger` and `ngAccordionPanel` elements within it.\n * It supports both single and multiple expansion modes.\n *\n * ```html\n *
    \n *
    \n *

    \n * \n *

    \n *
    \n * \n *

    Content for Item 1.

    \n *
    \n *
    \n *
    \n *
    \n *

    \n * \n *

    \n *
    \n * \n *

    Content for Item 2.

    \n *
    \n *
    \n *
    \n *
    \n * ```\n *\n * @see [Accordion](guide/aria/accordion)\n */", + "implements": [ + "OnDestroy" + ], "isStandalone": true, "selector": "[ngAccordionGroup]", "exportAs": [ @@ -559,8 +657,8 @@ ], "source": { "filePath": "src/aria/accordion/accordion-group.ts", - "startLine": 62, - "endLine": 158 + "startLine": 61, + "endLine": 141 } } ], @@ -570,27 +668,27 @@ "@angular/core" ], [ - "input", + "ElementRef", "@angular/core" ], [ - "inject", + "afterRenderEffect", "@angular/core" ], [ - "afterRenderEffect", + "computed", "@angular/core" ], [ - "signal", + "contentChild", "@angular/core" ], [ - "computed", + "inject", "@angular/core" ], [ - "WritableSignal", + "input", "@angular/core" ], [ @@ -598,11 +696,11 @@ "@angular/cdk/a11y" ], [ - "ElementRef", + "OnDestroy", "@angular/core" ], [ - "model", + "OnInit", "@angular/core" ], [ @@ -610,7 +708,15 @@ "@angular/core" ], [ - "contentChildren", + "model", + "@angular/core" + ], + [ + "signal", + "@angular/core" + ], + [ + "afterNextRender", "@angular/core" ], [ @@ -642,11 +748,11 @@ "@angular/aria/accordion" ], [ - "AccordionPanel.id", + "AccordionPanel.element", "@angular/aria/accordion" ], [ - "AccordionPanel.panelId", + "AccordionPanel.id", "@angular/aria/accordion" ], [ @@ -673,6 +779,10 @@ "AccordionTrigger.element", "@angular/aria/accordion" ], + [ + "AccordionTrigger.panel", + "@angular/aria/accordion" + ], [ "AccordionTrigger.id", "@angular/aria/accordion" @@ -693,6 +803,14 @@ "AccordionTrigger.active", "@angular/aria/accordion" ], + [ + "AccordionTrigger.ngOnInit", + "@angular/aria/accordion" + ], + [ + "AccordionTrigger.ngOnDestroy", + "@angular/aria/accordion" + ], [ "AccordionTrigger.expand", "@angular/aria/accordion" @@ -733,6 +851,10 @@ "AccordionGroup.wrap", "@angular/aria/accordion" ], + [ + "AccordionGroup.ngOnDestroy", + "@angular/aria/accordion" + ], [ "AccordionGroup.expandAll", "@angular/aria/accordion" diff --git a/adev/src/content/aria/aria-combobox.json b/adev/src/content/aria/aria-combobox.json index 8f720d3dc9b2..a585240a4c35 100755 --- a/adev/src/content/aria/aria-combobox.json +++ b/adev/src/content/aria/aria-combobox.json @@ -5,44 +5,7 @@ "normalizedModuleName": "angular_aria_combobox", "entries": [ { - "name": "ComboboxPopupContainer", - "isAbstract": false, - "entryType": "undecorated_class", - "members": [], - "generics": [], - "description": "A structural directive that marks the `ng-template` to be used as the popup\nfor a combobox. This content is conditionally rendered.\n\nThe content of the popup can be a `ngListbox`, `ngTree`, or `role=\"dialog\"`, allowing for\nflexible and complex combobox implementations. The consumer is responsible for\nimplementing the filtering logic based on the `ngComboboxInput`'s value.\n\n```html\n\n
    \n \n
    \n
    \n```\n\nWhen using CdkOverlay, this directive can be replaced by `cdkConnectedOverlay`.\n\n```html\n\n
    \n \n
    \n\n```", - "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, - { - "name": "see", - "comment": "[Combobox](guide/aria/combobox)" - }, - { - "name": "see", - "comment": "[Select](guide/aria/select)" - }, - { - "name": "see", - "comment": "[Multiselect](guide/aria/multiselect)" - }, - { - "name": "see", - "comment": "[Autocomplete](guide/aria/autocomplete)" - } - ], - "rawComment": "/**\n * A structural directive that marks the `ng-template` to be used as the popup\n * for a combobox. This content is conditionally rendered.\n *\n * The content of the popup can be a `ngListbox`, `ngTree`, or `role=\"dialog\"`, allowing for\n * flexible and complex combobox implementations. The consumer is responsible for\n * implementing the filtering logic based on the `ngComboboxInput`'s value.\n *\n * ```html\n * \n *
    \n * \n *
    \n *
    \n * ```\n *\n * When using CdkOverlay, this directive can be replaced by `cdkConnectedOverlay`.\n *\n * ```html\n * \n *
    \n * \n *
    \n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */", - "implements": [], - "source": { - "filePath": "src/aria/combobox/combobox-popup-container.ts", - "startLine": 47, - "endLine": 52 - } - }, - { - "name": "ComboboxDialog", + "name": "ComboboxWidget", "isAbstract": false, "entryType": "directive", "members": [ @@ -53,24 +16,37 @@ "memberTags": [ "readonly" ], - "description": "A reference to the dialog element.", + "description": "A reference to the popup widget element.", "jsdocTags": [] }, { - "name": "combobox", - "type": "Combobox", + "name": "popupId", + "type": "WritableSignal", "memberType": "property", "memberTags": [ "readonly" ], - "description": "The combobox that the dialog belongs to.", + "description": "The ID of the popup widget.", "jsdocTags": [] }, { - "name": "close", + "name": "activeDescendant", + "type": "InputSignal", + "memberType": "property", + "memberTags": [ + "readonly", + "input" + ], + "description": "The ID of the active descendant in the widget.", + "jsdocTags": [], + "inputAlias": "activeDescendant", + "isRequiredInput": false + }, + { + "name": "ngOnInit", "signatures": [ { - "name": "close", + "name": "ngOnInit", "entryType": "function", "description": "", "generics": [], @@ -86,7 +62,7 @@ "isNewType": false, "returnType": "void", "generics": [], - "name": "close", + "name": "ngOnInit", "description": "", "entryType": "function", "jsdocTags": [], @@ -98,176 +74,283 @@ "rawComment": "", "memberType": "method", "memberTags": [] - } - ], - "generics": [], - "description": "Integrates a native `` element with the combobox, allowing for\na modal or non-modal popup experience. It handles the opening and closing of the dialog\nbased on the combobox's expanded state.\n\n```html\n\n \n \n \n\n```", - "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, - { - "name": "see", - "comment": "[Combobox](guide/aria/combobox)" }, { - "name": "see", - "comment": "[Select](guide/aria/select)" + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] }, { - "name": "see", - "comment": "[Multiselect](guide/aria/multiselect)" + "name": "onFocusin", + "signatures": [ + { + "name": "onFocusin", + "entryType": "function", + "description": "Handles focus in events for the widget.", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "/** Handles focus in events for the widget. */", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "onFocusin", + "description": "Handles focus in events for the widget.", + "entryType": "function", + "jsdocTags": [], + "rawComment": "/** Handles focus in events for the widget. */" + }, + "entryType": "function", + "description": "Handles focus in events for the widget.", + "jsdocTags": [], + "rawComment": "/** Handles focus in events for the widget. */", + "memberType": "method", + "memberTags": [] }, { - "name": "see", - "comment": "[Autocomplete](guide/aria/autocomplete)" + "name": "onFocusout", + "signatures": [ + { + "name": "onFocusout", + "entryType": "function", + "description": "Handles focus out events for the widget.", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [ + { + "name": "event", + "description": "", + "type": "FocusEvent", + "isOptional": false, + "isRestParam": false + } + ], + "rawComment": "/** Handles focus out events for the widget. */", + "returnType": "void" + } + ], + "implementation": { + "params": [ + { + "name": "event", + "description": "", + "type": "FocusEvent", + "isOptional": false, + "isRestParam": false + } + ], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "onFocusout", + "description": "Handles focus out events for the widget.", + "entryType": "function", + "jsdocTags": [], + "rawComment": "/** Handles focus out events for the widget. */" + }, + "entryType": "function", + "description": "Handles focus out events for the widget.", + "jsdocTags": [], + "rawComment": "/** Handles focus out events for the widget. */", + "memberType": "method", + "memberTags": [] } ], - "rawComment": "/**\n * Integrates a native `` element with the combobox, allowing for\n * a modal or non-modal popup experience. It handles the opening and closing of the dialog\n * based on the combobox's expanded state.\n *\n * ```html\n * \n * \n * \n * \n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */", - "implements": [], + "generics": [], + "description": "Identifies an element as a widget within a combobox popup.\n\nThis directive should be applied to the element that contains the options or content\nof the popup. It handles the communication of ID and active descendant information\nto the combobox.", + "jsdocTags": [], + "rawComment": "/**\n * Identifies an element as a widget within a combobox popup.\n *\n * This directive should be applied to the element that contains the options or content\n * of the popup. It handles the communication of ID and active descendant information\n * to the combobox.\n */", + "implements": [ + "OnInit", + "OnDestroy" + ], "isStandalone": true, - "selector": "dialog[ngComboboxDialog]", + "selector": "[ngComboboxWidget]", "exportAs": [ - "ngComboboxDialog" + "ngComboboxWidget" ], "aliases": [ - "ngComboboxDialog" + "ngComboboxWidget" ], "source": { - "filePath": "src/aria/combobox/combobox-dialog.ts", - "startLine": 34, - "endLine": 84 + "filePath": "/src/aria/combobox/combobox-widget.ts", + "startLine": 19, + "endLine": 78 } }, { - "name": "ComboboxInput", + "name": "ComboboxPopup", "isAbstract": false, "entryType": "directive", "members": [ { - "name": "element", - "type": "HTMLElement", + "name": "combobox", + "type": "InputSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], - "description": "A reference to the input element.", - "jsdocTags": [] + "description": "The combobox that the popup belongs to.", + "jsdocTags": [], + "inputAlias": "combobox", + "isRequiredInput": true }, { - "name": "combobox", - "type": "Combobox", + "name": "controlTarget", + "type": "Signal", "memberType": "property", "memberTags": [ "readonly" ], - "description": "The combobox that the input belongs to.", + "description": "The element that serves as the control target for the popup.", "jsdocTags": [] }, { - "name": "value", - "type": "ModelSignal", + "name": "popupId", + "type": "Signal", "memberType": "property", "memberTags": [ - "input", - "output" + "readonly" ], - "description": "The value of the input.", - "jsdocTags": [], - "inputAlias": "value", - "isRequiredInput": false, - "outputAlias": "valueChange" - } - ], - "generics": [], - "description": "An input that is part of a combobox. It is responsible for displaying the\ncurrent value and handling user input for filtering and selection.\n\nThis directive should be applied to an `` element within an `ngCombobox`\ncontainer. It automatically handles keyboard interactions, such as opening the\npopup and navigating through the options.\n\n```html\n\n```", - "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, - { - "name": "see", - "comment": "[Combobox](guide/aria/combobox)" - }, - { - "name": "see", - "comment": "[Select](guide/aria/select)" - }, - { - "name": "see", - "comment": "[Multiselect](guide/aria/multiselect)" + "description": "The ID of the popup.", + "jsdocTags": [] }, { - "name": "see", - "comment": "[Autocomplete](guide/aria/autocomplete)" - } - ], - "rawComment": "/**\n * An input that is part of a combobox. It is responsible for displaying the\n * current value and handling user input for filtering and selection.\n *\n * This directive should be applied to an `` element within an `ngCombobox`\n * container. It automatically handles keyboard interactions, such as opening the\n * popup and navigating through the options.\n *\n * ```html\n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */", - "implements": [], - "isStandalone": true, - "selector": "input[ngComboboxInput]", - "exportAs": [ - "ngComboboxInput" - ], - "aliases": [ - "ngComboboxInput" - ], - "source": { - "filePath": "src/aria/combobox/combobox-input.ts", - "startLine": 44, - "endLine": 90 - } - }, - { - "name": "ComboboxPopup", - "isAbstract": false, - "entryType": "directive", - "members": [ - { - "name": "combobox", - "type": "Combobox | null", + "name": "activeDescendant", + "type": "Signal", "memberType": "property", "memberTags": [ "readonly" ], - "description": "The combobox that the popup belongs to.", + "description": "The ID of the active descendant in the popup.", "jsdocTags": [] - } - ], - "generics": [ - { - "name": "V" - } - ], - "description": "Identifies an element as a popup for an `ngCombobox`.\n\nThis directive acts as a bridge, allowing the `ngCombobox` to discover and interact\nwith the underlying control (e.g., `ngListbox`, `ngTree`, or `ngComboboxDialog`) that\nmanages the options. It's primarily used as a host directive and is responsible for\nexposing the popup's control pattern to the parent combobox.", - "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" }, { - "name": "see", - "comment": "[Combobox](guide/aria/combobox)" - }, - { - "name": "see", - "comment": "[Select](guide/aria/select)" + "name": "popupType", + "type": "InputSignal<\"listbox\" | \"tree\" | \"grid\" | \"dialog\">", + "memberType": "property", + "memberTags": [ + "readonly", + "input" + ], + "description": "The type of the popup (e.g., listbox, tree, grid, dialog).", + "jsdocTags": [], + "inputAlias": "popupType", + "isRequiredInput": false }, { - "name": "see", - "comment": "[Multiselect](guide/aria/multiselect)" + "name": "ngOnInit", + "signatures": [ + { + "name": "ngOnInit", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnInit", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] }, { - "name": "see", - "comment": "[Autocomplete](guide/aria/autocomplete)" + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] } ], - "rawComment": "/**\n * Identifies an element as a popup for an `ngCombobox`.\n *\n * This directive acts as a bridge, allowing the `ngCombobox` to discover and interact\n * with the underlying control (e.g., `ngListbox`, `ngTree`, or `ngComboboxDialog`) that\n * manages the options. It's primarily used as a host directive and is responsible for\n * exposing the popup's control pattern to the parent combobox.\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */", - "implements": [], + "generics": [], + "description": "A structural directive that marks the `ng-template` to be used as the popup\nfor a combobox. This content is conditionally rendered.\n\nThe content of the popup can be any element with the `ngComboboxWidget` directive.\n\n```html\n\n
    \n \n
    \n
    \n```", + "jsdocTags": [], + "rawComment": "/**\n * A structural directive that marks the `ng-template` to be used as the popup\n * for a combobox. This content is conditionally rendered.\n *\n * The content of the popup can be any element with the `ngComboboxWidget` directive.\n *\n * ```html\n * \n *
    \n * \n *
    \n *
    \n * ```\n */", + "implements": [ + "OnInit", + "OnDestroy" + ], "isStandalone": true, - "selector": "[ngComboboxPopup]", + "selector": "ng-template[ngComboboxPopup]", "exportAs": [ "ngComboboxPopup" ], @@ -275,26 +358,16 @@ "ngComboboxPopup" ], "source": { - "filePath": "/src/aria/combobox/combobox-popup.ts", + "filePath": "src/aria/combobox/combobox-popup.ts", "startLine": 29, - "endLine": 44 + "endLine": 79 } }, { "name": "Combobox", "isAbstract": false, - "entryType": "undecorated_class", + "entryType": "directive", "members": [ - { - "name": "textDirection", - "type": "Signal", - "memberType": "property", - "memberTags": [ - "protected" - ], - "description": "A signal wrapper for directionality.", - "jsdocTags": [] - }, { "name": "element", "type": "HTMLElement", @@ -302,132 +375,116 @@ "memberTags": [ "readonly" ], - "description": "A reference to the combobox element.", + "description": "A reference to the input element.", "jsdocTags": [] }, { - "name": "popup", - "type": "Signal | undefined>", + "name": "disabled", + "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], - "description": "The combobox popup.", - "jsdocTags": [] - }, - { - "name": "filterMode", - "type": "InputSignal<\"manual\" | \"auto-select\" | \"highlight\">", - "memberType": "property", - "memberTags": [], - "description": "The filter mode for the combobox.\n- `manual`: The consumer is responsible for filtering the options.\n- `auto-select`: The combobox automatically selects the first matching option.\n- `highlight`: The combobox highlights matching text in the options without changing selection.", - "jsdocTags": [] + "description": "Whether the combobox is disabled.", + "jsdocTags": [], + "inputAlias": "disabled", + "isRequiredInput": false }, { - "name": "disabled", + "name": "softDisabled", "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], - "description": "Whether the combobox is disabled.", - "jsdocTags": [] + "description": "Whether the combobox is soft disabled (remains focusable).", + "jsdocTags": [], + "inputAlias": "softDisabled", + "isRequiredInput": false }, { - "name": "readonly", + "name": "alwaysExpanded", "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], - "description": "Whether the combobox is read-only.", - "jsdocTags": [] + "description": "Whether the combobox should always remain expanded.", + "jsdocTags": [], + "inputAlias": "alwaysExpanded", + "isRequiredInput": false }, { - "name": "firstMatch", - "type": "InputSignal", + "name": "tabIndex", + "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], - "description": "The value of the first matching item in the popup.", - "jsdocTags": [] + "description": "The tabindex of the combobox.", + "jsdocTags": [], + "inputAlias": "tabindex", + "isRequiredInput": false }, { "name": "expanded", - "type": "Signal", + "type": "ModelSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input", + "output" ], "description": "Whether the combobox is expanded.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "expanded", + "isRequiredInput": false, + "outputAlias": "expandedChange" }, { - "name": "alwaysExpanded", - "type": "InputSignalWithTransform", + "name": "value", + "type": "ModelSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input", + "output" ], - "description": "Whether the combobox popup should always be expanded, regardless of user interaction.", - "jsdocTags": [] + "description": "The value of the combobox input.", + "jsdocTags": [], + "inputAlias": "value", + "isRequiredInput": false, + "outputAlias": "valueChange" }, { - "name": "inputElement", - "type": "Signal", + "name": "inlineSuggestion", + "type": "InputSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], - "description": "Input element connected to the combobox, if any.", - "jsdocTags": [] - }, - { - "name": "open", - "signatures": [ - { - "name": "open", - "entryType": "function", - "description": "Opens the combobox to the selected item.", - "generics": [], - "isNewType": false, - "jsdocTags": [], - "params": [], - "rawComment": "/** Opens the combobox to the selected item. */", - "returnType": "void" - } - ], - "implementation": { - "params": [], - "isNewType": false, - "returnType": "void", - "generics": [], - "name": "open", - "description": "Opens the combobox to the selected item.", - "entryType": "function", - "jsdocTags": [], - "rawComment": "/** Opens the combobox to the selected item. */" - }, - "entryType": "function", - "description": "Opens the combobox to the selected item.", + "description": "An inline suggestion to be displayed in the input.", "jsdocTags": [], - "rawComment": "/** Opens the combobox to the selected item. */", - "memberType": "method", - "memberTags": [] + "inputAlias": "inlineSuggestion", + "isRequiredInput": false }, { - "name": "close", + "name": "ngOnInit", "signatures": [ { - "name": "close", + "name": "ngOnInit", "entryType": "function", - "description": "Closes the combobox.", + "description": "", "generics": [], "isNewType": false, "jsdocTags": [], "params": [], - "rawComment": "/** Closes the combobox. */", + "rawComment": "", "returnType": "void" } ], @@ -436,54 +493,62 @@ "isNewType": false, "returnType": "void", "generics": [], - "name": "close", - "description": "Closes the combobox.", + "name": "ngOnInit", + "description": "", "entryType": "function", "jsdocTags": [], - "rawComment": "/** Closes the combobox. */" + "rawComment": "" }, "entryType": "function", - "description": "Closes the combobox.", + "description": "", "jsdocTags": [], - "rawComment": "/** Closes the combobox. */", + "rawComment": "", "memberType": "method", "memberTags": [] - } - ], - "generics": [ - { - "name": "V" - } - ], - "description": "The container element that wraps a combobox input and popup, and orchestrates its behavior.\n\nThe `ngCombobox` directive is the main entry point for creating a combobox and customizing its\nbehavior. It coordinates the interactions between the `ngComboboxInput` and the popup, which\nis defined by a `ng-template` with the `ngComboboxPopupContainer` directive. If using the\n`CdkOverlay`, the `cdkConnectedOverlay` directive takes the place of `ngComboboxPopupContainer`.\n\n```html\n
    \n \n\n \n
    \n @for (option of filteredOptions(); track option) {\n
    \n {{option}}\n
    \n }\n
    \n
    \n
    \n```", - "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, - { - "name": "see", - "comment": "[Combobox](guide/aria/combobox)" }, { - "name": "see", - "comment": "[Select](guide/aria/select)" - }, - { - "name": "see", - "comment": "[Multiselect](guide/aria/multiselect)" + "name": "contentVisible", + "type": "WritableSignal", + "memberType": "property", + "memberTags": [ + "readonly", + "override" + ], + "description": "", + "jsdocTags": [] }, { - "name": "see", - "comment": "[Autocomplete](guide/aria/autocomplete)" + "name": "preserveContent", + "type": "ModelSignal", + "memberType": "property", + "memberTags": [ + "readonly", + "override" + ], + "description": "", + "jsdocTags": [] } ], - "rawComment": "/**\n * The container element that wraps a combobox input and popup, and orchestrates its behavior.\n *\n * The `ngCombobox` directive is the main entry point for creating a combobox and customizing its\n * behavior. It coordinates the interactions between the `ngComboboxInput` and the popup, which\n * is defined by a `ng-template` with the `ngComboboxPopupContainer` directive. If using the\n * `CdkOverlay`, the `cdkConnectedOverlay` directive takes the place of `ngComboboxPopupContainer`.\n *\n * ```html\n *
    \n * \n *\n * \n *
    \n * @for (option of filteredOptions(); track option) {\n *
    \n * {{option}}\n *
    \n * }\n *
    \n *
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Combobox](guide/aria/combobox)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n * @see [Autocomplete](guide/aria/autocomplete)\n */", - "implements": [], + "generics": [], + "description": "The container element that wraps a combobox input and popup, and orchestrates its behavior.\n\nThe `ngCombobox` directive is the main entry point for creating a combobox and customizing its\nbehavior. It coordinates the interactions between the input and the popup.\n\n```html\n
    \n \n\n \n
    \n \n
    \n
    \n
    \n```", + "jsdocTags": [], + "rawComment": "/**\n * The container element that wraps a combobox input and popup, and orchestrates its behavior.\n *\n * The `ngCombobox` directive is the main entry point for creating a combobox and customizing its\n * behavior. It coordinates the interactions between the input and the popup.\n *\n * ```html\n *
    \n * \n *\n * \n *
    \n * \n *
    \n *
    \n *
    \n * ```\n */", + "extends": "DeferredContentAware", + "implements": [ + "OnInit" + ], + "isStandalone": true, + "selector": "[ngCombobox]", + "exportAs": [ + "ngCombobox" + ], + "aliases": [ + "ngCombobox" + ], "source": { "filePath": "/src/aria/combobox/combobox.ts", - "startLine": 60, - "endLine": 162 + "startLine": 43, + "endLine": 143 } } ], @@ -492,10 +557,6 @@ "Directive", "@angular/core" ], - [ - "afterRenderEffect", - "@angular/core" - ], [ "ElementRef", "@angular/core" @@ -505,15 +566,15 @@ "@angular/core" ], [ - "model", + "input", "@angular/core" ], [ - "untracked", + "OnDestroy", "@angular/core" ], [ - "WritableSignal", + "OnInit", "@angular/core" ], [ @@ -521,35 +582,47 @@ "@angular/core" ], [ - "booleanAttribute", + "computed", "@angular/core" ], [ - "computed", + "DeferredContent", + "@angular/aria/private" + ], + [ + "ComboboxPopupPattern", + "@angular/aria/private" + ], + [ + "afterRenderEffect", "@angular/core" ], [ - "contentChild", + "booleanAttribute", "@angular/core" ], [ - "input", + "model", "@angular/core" ], [ - "Directionality", - "@angular/cdk/bidi" + "Renderer2", + "@angular/core" ], [ - "ComboboxPopupContainer", - "@angular/aria/combobox" + "DeferredContentAware", + "@angular/aria/private" ], [ - "ComboboxDialog", - "@angular/aria/combobox" + "ComboboxPattern", + "@angular/aria/private" + ], + [ + "tabIndexTransform", + "@angular/aria/private" ], [ - "ComboboxInput", + "ComboboxWidget", "@angular/aria/combobox" ], [ @@ -561,67 +634,75 @@ "@angular/aria/combobox" ], [ - "ComboboxPopupContainer", + "ComboboxWidget", "@angular/aria/combobox" ], [ - "ComboboxDialog", + "ComboboxWidget.element", "@angular/aria/combobox" ], [ - "ComboboxDialog.element", + "ComboboxWidget.popupId", "@angular/aria/combobox" ], [ - "ComboboxDialog.combobox", + "ComboboxWidget.activeDescendant", "@angular/aria/combobox" ], [ - "ComboboxDialog.close", + "ComboboxWidget.ngOnInit", "@angular/aria/combobox" ], [ - "ComboboxInput", + "ComboboxWidget.ngOnDestroy", "@angular/aria/combobox" ], [ - "ComboboxInput.element", + "ComboboxWidget.onFocusin", "@angular/aria/combobox" ], [ - "ComboboxInput.combobox", + "ComboboxWidget.onFocusout", "@angular/aria/combobox" ], [ - "ComboboxInput.value", + "ComboboxPopup", "@angular/aria/combobox" ], [ - "ComboboxPopup", + "ComboboxPopup.combobox", "@angular/aria/combobox" ], [ - "ComboboxPopup.combobox", + "ComboboxPopup.controlTarget", "@angular/aria/combobox" ], [ - "Combobox", + "ComboboxPopup.popupId", "@angular/aria/combobox" ], [ - "Combobox.textDirection", + "ComboboxPopup.activeDescendant", "@angular/aria/combobox" ], [ - "Combobox.element", + "ComboboxPopup.popupType", + "@angular/aria/combobox" + ], + [ + "ComboboxPopup.ngOnInit", "@angular/aria/combobox" ], [ - "Combobox.popup", + "ComboboxPopup.ngOnDestroy", "@angular/aria/combobox" ], [ - "Combobox.filterMode", + "Combobox", + "@angular/aria/combobox" + ], + [ + "Combobox.element", "@angular/aria/combobox" ], [ @@ -629,11 +710,15 @@ "@angular/aria/combobox" ], [ - "Combobox.readonly", + "Combobox.softDisabled", "@angular/aria/combobox" ], [ - "Combobox.firstMatch", + "Combobox.alwaysExpanded", + "@angular/aria/combobox" + ], + [ + "Combobox.tabIndex", "@angular/aria/combobox" ], [ @@ -641,19 +726,23 @@ "@angular/aria/combobox" ], [ - "Combobox.alwaysExpanded", + "Combobox.value", + "@angular/aria/combobox" + ], + [ + "Combobox.inlineSuggestion", "@angular/aria/combobox" ], [ - "Combobox.inputElement", + "Combobox.ngOnInit", "@angular/aria/combobox" ], [ - "Combobox.open", + "Combobox.contentVisible", "@angular/aria/combobox" ], [ - "Combobox.close", + "Combobox.preserveContent", "@angular/aria/combobox" ] ] diff --git a/adev/src/content/aria/aria-grid.json b/adev/src/content/aria/aria-grid.json index ecd363f90612..3d7d499512db 100755 --- a/adev/src/content/aria/aria-grid.json +++ b/adev/src/content/aria/aria-grid.json @@ -31,22 +31,87 @@ "jsdocTags": [], "inputAlias": "rowIndex", "isRequiredInput": false + }, + { + "name": "ngOnInit", + "signatures": [ + { + "name": "ngOnInit", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnInit", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] } ], "generics": [], "description": "Represents a row within a grid. It is a container for `ngGridCell` directives.\n\n```html\n\n \n\n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Grid](guide/aria/grid)" } ], - "rawComment": "/**\n * Represents a row within a grid. It is a container for `ngGridCell` directives.\n *\n * ```html\n * \n * \n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Grid](guide/aria/grid)\n */", - "implements": [], + "rawComment": "/**\n * Represents a row within a grid. It is a container for `ngGridCell` directives.\n *\n * ```html\n * \n * \n * \n * ```\n *\n * @see [Grid](guide/aria/grid)\n */", + "implements": [ + "OnInit", + "OnDestroy" + ], "isStandalone": true, "selector": "[ngGridRow]", "exportAs": [ @@ -58,7 +123,7 @@ "source": { "filePath": "/src/aria/grid/grid-row.ts", "startLine": 35, - "endLine": 74 + "endLine": 89 } }, { @@ -127,7 +192,7 @@ }, { "name": "focusTarget", - "type": "InputSignal | undefined>", + "type": "InputSignal", "memberType": "property", "memberTags": [ "readonly", @@ -253,16 +318,12 @@ "generics": [], "description": "Represents an interactive element inside a `GridCell`. It allows for pausing grid navigation to\ninteract with the widget.\n\nWhen the user interacts with the widget (e.g., by typing in an input or opening a menu), grid\nnavigation is temporarily suspended to allow the widget to handle keyboard\nevents.\n\n```html\n\n \n\n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Grid](guide/aria/grid)" } ], - "rawComment": "/**\n * Represents an interactive element inside a `GridCell`. It allows for pausing grid navigation to\n * interact with the widget.\n *\n * When the user interacts with the widget (e.g., by typing in an input or opening a menu), grid\n * navigation is temporarily suspended to allow the widget to handle keyboard\n * events.\n *\n * ```html\n * \n * \n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Grid](guide/aria/grid)\n */", + "rawComment": "/**\n * Represents an interactive element inside a `GridCell`. It allows for pausing grid navigation to\n * interact with the widget.\n *\n * When the user interacts with the widget (e.g., by typing in an input or opening a menu), grid\n * navigation is temporarily suspended to allow the widget to handle keyboard\n * events.\n *\n * ```html\n * \n * \n * \n * ```\n *\n * @see [Grid](guide/aria/grid)\n */", "implements": [], "isStandalone": true, "selector": "[ngGridCellWidget]", @@ -274,8 +335,8 @@ ], "source": { "filePath": "src/aria/grid/grid-cell-widget.ts", - "startLine": 42, - "endLine": 134 + "startLine": 40, + "endLine": 125 } }, { @@ -408,33 +469,123 @@ "isRequiredInput": false }, { - "name": "enableRangeSelection", - "type": "InputSignalWithTransform", + "name": "tabIndex", + "type": "InputSignal", "memberType": "property", "memberTags": [ "readonly", "input" ], - "description": "Whether enable range selections (with modifier keys or dragging).", + "description": "The tabindex of the grid.", "jsdocTags": [], - "inputAlias": "enableRangeSelection", + "inputAlias": "tabindex", "isRequiredInput": false + }, + { + "name": "activeDescendant", + "type": "Signal", + "memberType": "property", + "memberTags": [ + "readonly" + ], + "description": "The ID of the active descendant in the grid.", + "jsdocTags": [] + }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, + { + "name": "scrollActiveCellIntoView", + "signatures": [ + { + "name": "scrollActiveCellIntoView", + "entryType": "function", + "description": "Scrolls the active cell into view.", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [ + { + "name": "options", + "description": "", + "type": "ScrollIntoViewOptions", + "isOptional": true, + "isRestParam": false + } + ], + "rawComment": "/** Scrolls the active cell into view. */", + "returnType": "void" + } + ], + "implementation": { + "params": [ + { + "name": "options", + "description": "", + "type": "ScrollIntoViewOptions", + "isOptional": true, + "isRestParam": false + } + ], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "scrollActiveCellIntoView", + "description": "Scrolls the active cell into view.", + "entryType": "function", + "jsdocTags": [], + "rawComment": "/** Scrolls the active cell into view. */" + }, + "entryType": "function", + "description": "Scrolls the active cell into view.", + "jsdocTags": [], + "rawComment": "/** Scrolls the active cell into view. */", + "memberType": "method", + "memberTags": [] } ], "generics": [], "description": "The container for a grid. It provides keyboard navigation and focus management for the grid's\nrows and cells. It manages the overall behavior of the grid, including focus\nwrapping, selection, and disabled states.\n\n```html\n\n @for (row of gridData; track row) {\n \n @for (cell of row; track cell) {\n \n }\n \n }\n
    \n {{cell.value}}\n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Grid](guide/aria/grid)" } ], - "rawComment": "/**\n * The container for a grid. It provides keyboard navigation and focus management for the grid's\n * rows and cells. It manages the overall behavior of the grid, including focus\n * wrapping, selection, and disabled states.\n *\n * ```html\n * \n * @for (row of gridData; track row) {\n * \n * @for (cell of row; track cell) {\n * \n * }\n * \n * }\n *
    \n * {{cell.value}}\n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Grid](guide/aria/grid)\n */", - "implements": [], + "rawComment": "/**\n * The container for a grid. It provides keyboard navigation and focus management for the grid's\n * rows and cells. It manages the overall behavior of the grid, including focus\n * wrapping, selection, and disabled states.\n *\n * ```html\n * \n * @for (row of gridData; track row) {\n * \n * @for (cell of row; track cell) {\n * \n * }\n * \n * }\n *
    \n * {{cell.value}}\n *
    \n * ```\n *\n * @see [Grid](guide/aria/grid)\n */", + "implements": [ + "OnDestroy" + ], "isStandalone": true, "selector": "[ngGrid]", "exportAs": [ @@ -445,8 +596,8 @@ ], "source": { "filePath": "/src/aria/grid/grid.ts", - "startLine": 48, - "endLine": 177 + "startLine": 54, + "endLine": 199 } }, { @@ -604,59 +755,98 @@ "isRequiredInput": false }, { - "name": "orientation", - "type": "InputSignal<\"vertical\" | \"horizontal\">", + "name": "tabindex", + "type": "InputSignal", "memberType": "property", "memberTags": [ "readonly", "input" ], - "description": "Orientation of the widgets in the cell.", + "description": "The tabindex override.", "jsdocTags": [], - "inputAlias": "orientation", + "inputAlias": "tabindex", "isRequiredInput": false }, { - "name": "wrap", - "type": "InputSignalWithTransform", - "memberType": "property", - "memberTags": [ - "readonly", - "input" + "name": "ngOnInit", + "signatures": [ + { + "name": "ngOnInit", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } ], - "description": "Whether widgets navigation wraps.", + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnInit", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", "jsdocTags": [], - "inputAlias": "wrap", - "isRequiredInput": false + "rawComment": "", + "memberType": "method", + "memberTags": [] }, { - "name": "tabindex", - "type": "InputSignal", - "memberType": "property", - "memberTags": [ - "readonly", - "input" + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } ], - "description": "The tabindex override.", + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", "jsdocTags": [], - "inputAlias": "tabindex", - "isRequiredInput": false + "rawComment": "", + "memberType": "method", + "memberTags": [] } ], "generics": [], "description": "Represents a cell within a grid row. It is the primary focusable element\nwithin the grid. It can be disabled and can have its selection state managed\nthrough the `selected` input.\n\n```html\n\n Cell Content\n\n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Grid](guide/aria/grid)" } ], - "rawComment": "/**\n * Represents a cell within a grid row. It is the primary focusable element\n * within the grid. It can be disabled and can have its selection state managed\n * through the `selected` input.\n *\n * ```html\n * \n * Cell Content\n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Grid](guide/aria/grid)\n */", - "implements": [], + "rawComment": "/**\n * Represents a cell within a grid row. It is the primary focusable element\n * within the grid. It can be disabled and can have its selection state managed\n * through the `selected` input.\n *\n * ```html\n * \n * Cell Content\n * \n * ```\n *\n * @see [Grid](guide/aria/grid)\n */", + "implements": [ + "OnInit", + "OnDestroy" + ], "isStandalone": true, "selector": "[ngGridCell]", "exportAs": [ @@ -668,17 +858,17 @@ "source": { "filePath": "/src/aria/grid/grid-cell.ts", "startLine": 43, - "endLine": 175 + "endLine": 179 } } ], "symbols": [ [ - "computed", + "afterNextRender", "@angular/core" ], [ - "contentChildren", + "computed", "@angular/core" ], [ @@ -697,6 +887,14 @@ "input", "@angular/core" ], + [ + "OnDestroy", + "@angular/core" + ], + [ + "OnInit", + "@angular/core" + ], [ "Signal", "@angular/core" @@ -717,14 +915,14 @@ "output", "@angular/core" ], - [ - "NgZone", - "@angular/core" - ], [ "Directionality", "@angular/cdk/bidi" ], + [ + "contentChild", + "@angular/core" + ], [ "model", "@angular/core" @@ -761,6 +959,14 @@ "GridRow.rowIndex", "@angular/aria/grid" ], + [ + "GridRow.ngOnInit", + "@angular/aria/grid" + ], + [ + "GridRow.ngOnDestroy", + "@angular/aria/grid" + ], [ "GridCellWidget", "@angular/aria/grid" @@ -858,7 +1064,19 @@ "@angular/aria/grid" ], [ - "Grid.enableRangeSelection", + "Grid.tabIndex", + "@angular/aria/grid" + ], + [ + "Grid.activeDescendant", + "@angular/aria/grid" + ], + [ + "Grid.ngOnDestroy", + "@angular/aria/grid" + ], + [ + "Grid.scrollActiveCellIntoView", "@angular/aria/grid" ], [ @@ -914,15 +1132,15 @@ "@angular/aria/grid" ], [ - "GridCell.orientation", + "GridCell.tabindex", "@angular/aria/grid" ], [ - "GridCell.wrap", + "GridCell.ngOnInit", "@angular/aria/grid" ], [ - "GridCell.tabindex", + "GridCell.ngOnDestroy", "@angular/aria/grid" ] ] diff --git a/adev/src/content/aria/aria-listbox.json b/adev/src/content/aria/aria-listbox.json index 3d0f16b7d8ed..90b7a924f82b 100755 --- a/adev/src/content/aria/aria-listbox.json +++ b/adev/src/content/aria/aria-listbox.json @@ -23,7 +23,9 @@ "name": "active", "type": "Signal", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly" + ], "description": "Whether the option is currently active (focused).", "jsdocTags": [] }, @@ -40,21 +42,12 @@ "inputAlias": "id", "isRequiredInput": false }, - { - "name": "searchTerm", - "type": "Signal", - "memberType": "property", - "memberTags": [ - "protected" - ], - "description": "The text used by the typeahead search.", - "jsdocTags": [] - }, { "name": "value", "type": "InputSignal", "memberType": "property", "memberTags": [ + "readonly", "input" ], "description": "The value of the option.", @@ -67,6 +60,7 @@ "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ + "readonly", "input" ], "description": "Whether an item is disabled.", @@ -79,6 +73,7 @@ "type": "InputSignal", "memberType": "property", "memberTags": [ + "readonly", "input" ], "description": "The text used by the typeahead search.", @@ -95,6 +90,72 @@ ], "description": "Whether the option is selected.", "jsdocTags": [] + }, + { + "name": "ngOnInit", + "signatures": [ + { + "name": "ngOnInit", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnInit", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] } ], "generics": [ @@ -104,10 +165,6 @@ ], "description": "A selectable option in an `ngListbox`.\n\nThis directive should be applied to an element (e.g., `
  • `, `
    `) within an\n`ngListbox`. The `value` input is used to identify the option, and the `label` input provides\nthe accessible name for the option.\n\n```html\n
  • \n Item Name\n
  • \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Listbox](guide/aria/listbox)" @@ -125,8 +182,11 @@ "comment": "[Multiselect](guide/aria/multiselect)" } ], - "rawComment": "/**\n * A selectable option in an `ngListbox`.\n *\n * This directive should be applied to an element (e.g., `
  • `, `
    `) within an\n * `ngListbox`. The `value` input is used to identify the option, and the `label` input provides\n * the accessible name for the option.\n *\n * ```html\n *
  • \n * Item Name\n *
  • \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Listbox](guide/aria/listbox)\n * @see [Autocomplete](guide/aria/autocomplete)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n */", - "implements": [], + "rawComment": "/**\n * A selectable option in an `ngListbox`.\n *\n * This directive should be applied to an element (e.g., `
  • `, `
    `) within an\n * `ngListbox`. The `value` input is used to identify the option, and the `label` input provides\n * the accessible name for the option.\n *\n * ```html\n *
  • \n * Item Name\n *
  • \n * ```\n *\n * @see [Listbox](guide/aria/listbox)\n * @see [Autocomplete](guide/aria/autocomplete)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n */", + "implements": [ + "OnInit", + "OnDestroy" + ], "isStandalone": true, "selector": "[ngOption]", "exportAs": [ @@ -137,24 +197,27 @@ ], "source": { "filePath": "/src/aria/listbox/option.ts", - "startLine": 34, - "endLine": 88 + "startLine": 41, + "endLine": 98 } }, { "name": "Listbox", "isAbstract": false, - "entryType": "undecorated_class", + "entryType": "directive", "members": [ { "name": "id", "type": "InputSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "A unique identifier for the listbox.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "id", + "isRequiredInput": false }, { "name": "element", @@ -171,101 +234,200 @@ "type": "Signal", "memberType": "property", "memberTags": [ - "protected" + "protected", + "readonly" ], "description": "A signal wrapper for directionality.", "jsdocTags": [] }, - { - "name": "items", - "type": "Signal[]>", - "memberType": "property", - "memberTags": [ - "protected" - ], - "description": "The Option UIPatterns of the child Options.", - "jsdocTags": [] - }, { "name": "orientation", "type": "InputSignal<\"vertical\" | \"horizontal\">", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "Whether the list is vertically or horizontally oriented.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "orientation", + "isRequiredInput": false }, { "name": "multi", "type": "InputSignalWithTransform", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "Whether multiple items in the list can be selected at once.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "multi", + "isRequiredInput": false }, { "name": "wrap", "type": "InputSignalWithTransform", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "Whether focus should wrap when navigating.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "wrap", + "isRequiredInput": false }, { "name": "softDisabled", "type": "InputSignalWithTransform", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "Whether to allow disabled items to receive focus. When `true`, disabled items are\nfocusable but not interactive. When `false`, disabled items are skipped during navigation.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "softDisabled", + "isRequiredInput": false }, { "name": "focusMode", "type": "InputSignal<\"roving\" | \"activedescendant\">", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "The focus strategy used by the list.\n- `roving`: Focus is moved to the active item using `tabindex`.\n- `activedescendant`: Focus remains on the listbox container, and `aria-activedescendant` is used to indicate the active item.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "focusMode", + "isRequiredInput": false }, { "name": "selectionMode", "type": "InputSignal<\"follow\" | \"explicit\">", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "The selection strategy used by the list.\n- `follow`: The focused item is automatically selected.\n- `explicit`: Items are selected explicitly by the user (e.g., via click or spacebar).", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "selectionMode", + "isRequiredInput": false }, { "name": "typeaheadDelay", "type": "InputSignal", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "The amount of time before the typeahead search is reset.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "typeaheadDelay", + "isRequiredInput": false }, { "name": "disabled", "type": "InputSignalWithTransform", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "Whether the listbox is disabled.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "disabled", + "isRequiredInput": false }, { "name": "readonly", "type": "InputSignalWithTransform", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input" + ], "description": "Whether the listbox is readonly.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "readonly", + "isRequiredInput": false }, { - "name": "values", + "name": "tabIndex", + "type": "InputSignal", + "memberType": "property", + "memberTags": [ + "readonly", + "input" + ], + "description": "The tabindex of the listbox.", + "jsdocTags": [], + "inputAlias": "tabindex", + "isRequiredInput": false + }, + { + "name": "value", "type": "ModelSignal", "memberType": "property", - "memberTags": [], + "memberTags": [ + "readonly", + "input", + "output" + ], "description": "The values of the currently selected items.", + "jsdocTags": [], + "inputAlias": "value", + "isRequiredInput": false, + "outputAlias": "valueChange" + }, + { + "name": "activeDescendant", + "type": "Signal", + "memberType": "property", + "memberTags": [ + "readonly" + ], + "description": "The ID of the active descendant in the listbox.", "jsdocTags": [] }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, { "name": "scrollActiveItemIntoView", "signatures": [ @@ -356,10 +518,6 @@ ], "description": "Represents a container used to display a list of items for a user to select from.\n\nThe `ngListbox` is meant to be used in conjunction with `ngOption` directives to create a\nselectable list. It supports single and multiple selection modes, as well as various focus and\norientation strategies.\n\n```html\n
      \n @for (item of items; track item.id) {\n
    • \n {{item.name}}\n
    • \n }\n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Listbox](guide/aria/listbox)" @@ -377,11 +535,21 @@ "comment": "[Multiselect](guide/aria/multiselect)" } ], - "rawComment": "/**\n * Represents a container used to display a list of items for a user to select from.\n *\n * The `ngListbox` is meant to be used in conjunction with `ngOption` directives to create a\n * selectable list. It supports single and multiple selection modes, as well as various focus and\n * orientation strategies.\n *\n * ```html\n *
      \n * @for (item of items; track item.id) {\n *
    • \n * {{item.name}}\n *
    • \n * }\n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Listbox](guide/aria/listbox)\n * @see [Autocomplete](guide/aria/autocomplete)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n */", - "implements": [], + "rawComment": "/**\n * Represents a container used to display a list of items for a user to select from.\n *\n * The `ngListbox` is meant to be used in conjunction with `ngOption` directives to create a\n * selectable list. It supports single and multiple selection modes, as well as various focus and\n * orientation strategies.\n *\n * ```html\n *
      \n * @for (item of items; track item.id) {\n *
    • \n * {{item.name}}\n *
    • \n * }\n *
    \n * ```\n *\n * @see [Listbox](guide/aria/listbox)\n * @see [Autocomplete](guide/aria/autocomplete)\n * @see [Select](guide/aria/select)\n * @see [Multiselect](guide/aria/multiselect)\n */", + "implements": [ + "OnDestroy" + ], + "isStandalone": true, + "selector": "[ngListbox]", + "exportAs": [ + "ngListbox" + ], + "aliases": [ + "ngListbox" + ], "source": { "filePath": "/src/aria/listbox/listbox.ts", - "startLine": 53, + "startLine": 52, "endLine": 213 } } @@ -411,6 +579,14 @@ "input", "@angular/core" ], + [ + "OnInit", + "@angular/core" + ], + [ + "OnDestroy", + "@angular/core" + ], [ "_IdGenerator", "@angular/cdk/a11y" @@ -420,7 +596,7 @@ "@angular/core" ], [ - "contentChildren", + "afterNextRender", "@angular/core" ], [ @@ -431,6 +607,10 @@ "signal", "@angular/core" ], + [ + "Signal", + "@angular/core" + ], [ "untracked", "@angular/core" @@ -463,10 +643,6 @@ "Option.id", "@angular/aria/listbox" ], - [ - "Option.searchTerm", - "@angular/aria/listbox" - ], [ "Option.value", "@angular/aria/listbox" @@ -483,6 +659,14 @@ "Option.selected", "@angular/aria/listbox" ], + [ + "Option.ngOnInit", + "@angular/aria/listbox" + ], + [ + "Option.ngOnDestroy", + "@angular/aria/listbox" + ], [ "Listbox", "@angular/aria/listbox" @@ -499,10 +683,6 @@ "Listbox.textDirection", "@angular/aria/listbox" ], - [ - "Listbox.items", - "@angular/aria/listbox" - ], [ "Listbox.orientation", "@angular/aria/listbox" @@ -540,7 +720,19 @@ "@angular/aria/listbox" ], [ - "Listbox.values", + "Listbox.tabIndex", + "@angular/aria/listbox" + ], + [ + "Listbox.value", + "@angular/aria/listbox" + ], + [ + "Listbox.activeDescendant", + "@angular/aria/listbox" + ], + [ + "Listbox.ngOnDestroy", "@angular/aria/listbox" ], [ diff --git a/adev/src/content/aria/aria-menu.json b/adev/src/content/aria/aria-menu.json index b54675b1a7d5..de2b503fa99b 100755 --- a/adev/src/content/aria/aria-menu.json +++ b/adev/src/content/aria/aria-menu.json @@ -12,10 +12,6 @@ "generics": [], "description": "Defers the rendering of the menu content.\n\nThis structural directive should be applied to an `ng-template` within a `ngMenu`\nor `ngMenuBar` to lazily render its content only when the menu is opened.\n\n```html\n
    \n \n
    Lazy Item 1
    \n
    Lazy Item 2
    \n
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Menu](guide/aria/menu)" @@ -25,12 +21,12 @@ "comment": "[MenuBar](guide/aria/menubar)" } ], - "rawComment": "/**\n * Defers the rendering of the menu content.\n *\n * This structural directive should be applied to an `ng-template` within a `ngMenu`\n * or `ngMenuBar` to lazily render its content only when the menu is opened.\n *\n * ```html\n *
    \n * \n *
    Lazy Item 1
    \n *
    Lazy Item 2
    \n *
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", + "rawComment": "/**\n * Defers the rendering of the menu content.\n *\n * This structural directive should be applied to an `ng-template` within a `ngMenu`\n * or `ngMenuBar` to lazily render its content only when the menu is opened.\n *\n * ```html\n *
    \n * \n *
    Lazy Item 1
    \n *
    Lazy Item 2
    \n *
    \n *
    \n * ```\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", "implements": [], "source": { "filePath": "/src/aria/menu/menu-content.ts", - "startLine": 32, - "endLine": 37 + "startLine": 30, + "endLine": 35 } }, { @@ -63,6 +59,7 @@ "type": "InputSignal | undefined>", "memberType": "property", "memberTags": [ + "readonly", "input" ], "description": "The menu associated with the trigger.", @@ -190,10 +187,6 @@ ], "description": "A trigger for a menu.\n\nThe `ngMenuTrigger` directive is used to open and close menus. It can be applied to\nany interactive element (e.g., a button) to associate it with a `ngMenu` instance.\nIt also supports linking to sub-menus when applied to a `ngMenuItem`.\n\n```html\n\n\n
    \n
    Item 1
    \n
    Item 2
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Menu](guide/aria/menu)" @@ -203,7 +196,7 @@ "comment": "[MenuBar](guide/aria/menubar)" } ], - "rawComment": "/**\n * A trigger for a menu.\n *\n * The `ngMenuTrigger` directive is used to open and close menus. It can be applied to\n * any interactive element (e.g., a button) to associate it with a `ngMenu` instance.\n * It also supports linking to sub-menus when applied to a `ngMenuItem`.\n *\n * ```html\n * \n *\n *
    \n *
    Item 1
    \n *
    Item 2
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", + "rawComment": "/**\n * A trigger for a menu.\n *\n * The `ngMenuTrigger` directive is used to open and close menus. It can be applied to\n * any interactive element (e.g., a button) to associate it with a `ngMenu` instance.\n * It also supports linking to sub-menus when applied to a `ngMenuItem`.\n *\n * ```html\n * \n *\n *
    \n *
    Item 1
    \n *
    Item 2
    \n *
    \n * ```\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", "implements": [], "isStandalone": true, "selector": "[ngMenuTrigger]", @@ -215,8 +208,8 @@ ], "source": { "filePath": "/src/aria/menu/menu-trigger.ts", - "startLine": 43, - "endLine": 105 + "startLine": 41, + "endLine": 103 } }, { @@ -255,7 +248,7 @@ "readonly", "input" ], - "description": "The value of the menu item, used as the default aria-label", + "description": "The value of the menu item.", "jsdocTags": [], "inputAlias": "value", "isRequiredInput": true @@ -341,6 +334,72 @@ "description": "Whether the menu item has a popup.", "jsdocTags": [] }, + { + "name": "ngOnInit", + "signatures": [ + { + "name": "ngOnInit", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnInit", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, { "name": "open", "signatures": [ @@ -413,12 +472,8 @@ "name": "V" } ], - "description": "An item in a Menu.\n\n`ngMenuItem` directives can be used in `ngMenu` and `ngMenuBar` to represent a choice\nor action a user can take. They can also act as triggers for sub-menus.\n\n```html\n
    \n
    Action Item
    \n
    Submenu Trigger
    \n
    \n```", + "description": "An item in a Menu.\n\n`ngMenuItem` directives can be used in `ngMenu` and `ngMenuBar` to represent a choice\nor action a user can take. They can also act as triggers for sub-menus.\n\n```html\n
    \n
    Action Item
    \n
    Submenu Trigger
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Menu](guide/aria/menu)" @@ -428,8 +483,11 @@ "comment": "[MenuBar](guide/aria/menubar)" } ], - "rawComment": "/**\n * An item in a Menu.\n *\n * `ngMenuItem` directives can be used in `ngMenu` and `ngMenuBar` to represent a choice\n * or action a user can take. They can also act as triggers for sub-menus.\n *\n * ```html\n *
    \n *
    Action Item
    \n *
    Submenu Trigger
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", - "implements": [], + "rawComment": "/**\n * An item in a Menu.\n *\n * `ngMenuItem` directives can be used in `ngMenu` and `ngMenuBar` to represent a choice\n * or action a user can take. They can also act as triggers for sub-menus.\n *\n * ```html\n *
    \n *
    Action Item
    \n *
    Submenu Trigger
    \n *
    \n * ```\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", + "implements": [ + "OnInit", + "OnDestroy" + ], "isStandalone": true, "selector": "[ngMenuItem]", "exportAs": [ @@ -440,8 +498,8 @@ ], "source": { "filePath": "/src/aria/menu/menu-item.ts", - "startLine": 34, - "endLine": 109 + "startLine": 43, + "endLine": 136 } }, { @@ -496,7 +554,7 @@ "jsdocTags": [] }, { - "name": "values", + "name": "value", "type": "ModelSignal", "memberType": "property", "memberTags": [ @@ -506,9 +564,9 @@ ], "description": "The values of the currently selected menu items.", "jsdocTags": [], - "inputAlias": "values", + "inputAlias": "value", "isRequiredInput": false, - "outputAlias": "valuesChange" + "outputAlias": "valueChange" }, { "name": "wrap", @@ -548,6 +606,39 @@ "jsdocTags": [], "outputAlias": "itemSelected" }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, { "name": "close", "signatures": [ @@ -589,10 +680,6 @@ ], "description": "A menu bar of menu items.\n\nLike the `ngMenu`, a `ngMenuBar` is used to offer a list of menu item choices to users.\nHowever, a menubar is used to display a persistent, top-level, always-visible set of\nmenu item choices, typically found at the top of an application window.\n\n```html\n
    \n \n \n
    \n\n
    \n
    New
    \n
    Open
    \n
    \n\n
    \n
    Cut
    \n
    Copy
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Menu](guide/aria/menu)" @@ -602,8 +689,10 @@ "comment": "[MenuBar](guide/aria/menubar)" } ], - "rawComment": "/**\n * A menu bar of menu items.\n *\n * Like the `ngMenu`, a `ngMenuBar` is used to offer a list of menu item choices to users.\n * However, a menubar is used to display a persistent, top-level, always-visible set of\n * menu item choices, typically found at the top of an application window.\n *\n * ```html\n *
    \n * \n * \n *
    \n *\n *
    \n *
    New
    \n *
    Open
    \n *
    \n *\n *
    \n *
    Cut
    \n *
    Copy
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", - "implements": [], + "rawComment": "/**\n * A menu bar of menu items.\n *\n * Like the `ngMenu`, a `ngMenuBar` is used to offer a list of menu item choices to users.\n * However, a menubar is used to display a persistent, top-level, always-visible set of\n * menu item choices, typically found at the top of an application window.\n *\n * ```html\n *
    \n * \n * \n *
    \n *\n *
    \n *
    New
    \n *
    Open
    \n *
    \n *\n *
    \n *
    Cut
    \n *
    Copy
    \n *
    \n * ```\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", + "implements": [ + "OnDestroy" + ], "isStandalone": true, "selector": "[ngMenuBar]", "exportAs": [ @@ -614,8 +703,8 @@ ], "source": { "filePath": "/src/aria/menu/menu-bar.ts", - "startLine": 56, - "endLine": 141 + "startLine": 55, + "endLine": 140 } }, { @@ -733,6 +822,39 @@ "description": "The delay in milliseconds before expanding sub-menus on hover.", "jsdocTags": [] }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, { "name": "close", "signatures": [ @@ -774,10 +896,6 @@ ], "description": "A list of menu items.\n\nA `ngMenu` is used to offer a list of menu item choices to users. Menus can be nested\nwithin other menus to create sub-menus. It works in conjunction with `ngMenuTrigger`\nand `ngMenuItem` directives.\n\n```html\n\n\n
    \n
    Star
    \n
    Edit
    \n
    More
    \n
    \n\n
    \n
    Sub Item 1
    \n
    Sub Item 2
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Menu](guide/aria/menu)" @@ -787,12 +905,14 @@ "comment": "[MenuBar](guide/aria/menubar)" } ], - "rawComment": "/**\n * A list of menu items.\n *\n * A `ngMenu` is used to offer a list of menu item choices to users. Menus can be nested\n * within other menus to create sub-menus. It works in conjunction with `ngMenuTrigger`\n * and `ngMenuItem` directives.\n *\n * ```html\n * \n *\n *
    \n *
    Star
    \n *
    Edit
    \n *
    More
    \n *
    \n *\n *
    \n *
    Sub Item 1
    \n *
    Sub Item 2
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", - "implements": [], + "rawComment": "/**\n * A list of menu items.\n *\n * A `ngMenu` is used to offer a list of menu item choices to users. Menus can be nested\n * within other menus to create sub-menus. It works in conjunction with `ngMenuTrigger`\n * and `ngMenuItem` directives.\n *\n * ```html\n * \n *\n *
    \n *
    Star
    \n *
    Edit
    \n *
    More
    \n *
    \n *\n *
    \n *
    Sub Item 1
    \n *
    Sub Item 2
    \n *
    \n * ```\n *\n * @see [Menu](guide/aria/menu)\n * @see [MenuBar](guide/aria/menubar)\n */", + "implements": [ + "OnDestroy" + ], "source": { "filePath": "/src/aria/menu/menu.ts", - "startLine": 58, - "endLine": 194 + "startLine": 57, + "endLine": 213 } } ], @@ -834,15 +954,23 @@ "@angular/core" ], [ - "_IdGenerator", - "@angular/cdk/a11y" + "OnDestroy", + "@angular/core" + ], + [ + "OnInit", + "@angular/core" ], [ "afterRenderEffect", "@angular/core" ], [ - "contentChildren", + "_IdGenerator", + "@angular/cdk/a11y" + ], + [ + "afterNextRender", "@angular/core" ], [ @@ -969,6 +1097,14 @@ "MenuItem.hasPopup", "@angular/aria/menu" ], + [ + "MenuItem.ngOnInit", + "@angular/aria/menu" + ], + [ + "MenuItem.ngOnDestroy", + "@angular/aria/menu" + ], [ "MenuItem.open", "@angular/aria/menu" @@ -998,7 +1134,7 @@ "@angular/aria/menu" ], [ - "MenuBar.values", + "MenuBar.value", "@angular/aria/menu" ], [ @@ -1013,6 +1149,10 @@ "MenuBar.itemSelected", "@angular/aria/menu" ], + [ + "MenuBar.ngOnDestroy", + "@angular/aria/menu" + ], [ "MenuBar.close", "@angular/aria/menu" @@ -1065,6 +1205,10 @@ "Menu.expansionDelay", "@angular/aria/menu" ], + [ + "Menu.ngOnDestroy", + "@angular/aria/menu" + ], [ "Menu.close", "@angular/aria/menu" diff --git a/adev/src/content/aria/aria-tabs.json b/adev/src/content/aria/aria-tabs.json index b450c783a421..fbede0a63b55 100755 --- a/adev/src/content/aria/aria-tabs.json +++ b/adev/src/content/aria/aria-tabs.json @@ -12,21 +12,17 @@ "generics": [], "description": "A TabContent container for the lazy-loaded content.\n\nThis structural directive should be applied to an `ng-template` within an `ngTabPanel`.\nIt enables lazy loading of the tab's content, meaning the content is only rendered\nwhen the tab is activated for the first time.\n\n```html\n
    \n \n

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

    \n
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Tabs](guide/aria/tabs)" } ], - "rawComment": "/**\n * A TabContent container for the lazy-loaded content.\n *\n * This structural directive should be applied to an `ng-template` within an `ngTabPanel`.\n * It enables lazy loading of the tab's content, meaning the content is only rendered\n * when the tab is activated for the first time.\n *\n * ```html\n *
    \n * \n *

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

    \n *
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Tabs](guide/aria/tabs)\n */", + "rawComment": "/**\n * A TabContent container for the lazy-loaded content.\n *\n * This structural directive should be applied to an `ng-template` within an `ngTabPanel`.\n * It enables lazy loading of the tab's content, meaning the content is only rendered\n * when the tab is activated for the first time.\n *\n * ```html\n *
    \n * \n *

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

    \n *
    \n *
    \n * ```\n *\n * @see [Tabs](guide/aria/tabs)\n */", "implements": [], "source": { "filePath": "/src/aria/tabs/tab-content.ts", - "startLine": 31, - "endLine": 36 + "startLine": 29, + "endLine": 34 } }, { @@ -43,22 +39,53 @@ ], "description": "A reference to the host element.", "jsdocTags": [] + }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] } ], "generics": [], "description": "A Tabs container.\n\nThe `ngTabs` directive represents a set of layered sections of content. It acts as the\noverarching container for a tabbed interface, coordinating the behavior of `ngTabList`,\n`ngTab`, and `ngTabPanel` directives.\n\n```html\n
    \n
      \n
    • Tab 1
    • \n
    • Tab 2
    • \n
    • Tab 3
    • \n
    \n\n
    \n Content for Tab 1\n
    \n
    \n Content for Tab 2\n
    \n
    \n Content for Tab 3\n
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Tabs](guide/aria/tabs)" } ], - "rawComment": "/**\n * A Tabs container.\n *\n * The `ngTabs` directive represents a set of layered sections of content. It acts as the\n * overarching container for a tabbed interface, coordinating the behavior of `ngTabList`,\n * `ngTab`, and `ngTabPanel` directives.\n *\n * ```html\n *
    \n *
      \n *
    • Tab 1
    • \n *
    • Tab 2
    • \n *
    • Tab 3
    • \n *
    \n *\n *
    \n * Content for Tab 1\n *
    \n *
    \n * Content for Tab 2\n *
    \n *
    \n * Content for Tab 3\n *
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Tabs](guide/aria/tabs)\n */", - "implements": [], + "rawComment": "/**\n * A Tabs container.\n *\n * The `ngTabs` directive represents a set of layered sections of content. It acts as the\n * overarching container for a tabbed interface, coordinating the behavior of `ngTabList`,\n * `ngTab`, and `ngTabPanel` directives.\n *\n * ```html\n *
    \n *
      \n *
    • Tab 1
    • \n *
    • Tab 2
    • \n *
    • Tab 3
    • \n *
    \n *\n *
    \n * Content for Tab 1\n *
    \n *
    \n * Content for Tab 2\n *
    \n *
    \n * Content for Tab 3\n *
    \n *
    \n * ```\n *\n * @see [Tabs](guide/aria/tabs)\n */", + "implements": [ + "OnDestroy" + ], "isStandalone": true, "selector": "[ngTabs]", "exportAs": [ @@ -69,14 +96,14 @@ ], "source": { "filePath": "/src/aria/tabs/tabs.ts", - "startLine": 46, - "endLine": 93 + "startLine": 52, + "endLine": 116 } }, { - "name": "TabPanel", + "name": "Tab", "isAbstract": false, - "entryType": "undecorated_class", + "entryType": "directive", "members": [ { "name": "element", @@ -93,31 +120,93 @@ "type": "InputSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], - "description": "A global unique identifier for the tab.", - "jsdocTags": [] + "description": "A unique identifier for the widget.", + "jsdocTags": [], + "inputAlias": "id", + "isRequiredInput": false + }, + { + "name": "disabled", + "type": "InputSignalWithTransform", + "memberType": "property", + "memberTags": [ + "readonly", + "input" + ], + "description": "Whether a tab is disabled.", + "jsdocTags": [], + "inputAlias": "disabled", + "isRequiredInput": false }, { "name": "value", "type": "InputSignal", "memberType": "property", + "memberTags": [ + "readonly", + "input" + ], + "description": "The remote tabpanel unique identifier.", + "jsdocTags": [], + "inputAlias": "value", + "isRequiredInput": true + }, + { + "name": "active", + "type": "Signal", + "memberType": "property", "memberTags": [ "readonly" ], - "description": "A local unique identifier for the tabpanel.", + "description": "Whether the tab is active.", "jsdocTags": [] }, { - "name": "visible", - "type": "Signal", + "name": "selected", + "type": "Signal", "memberType": "property", "memberTags": [ "readonly" ], - "description": "Whether the tab panel is visible.", + "description": "Whether the tab is selected.", "jsdocTags": [] }, + { + "name": "open", + "signatures": [ + { + "name": "open", + "entryType": "function", + "description": "Opens this tab panel.", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "/** Opens this tab panel. */", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "open", + "description": "Opens this tab panel.", + "entryType": "function", + "jsdocTags": [], + "rawComment": "/** Opens this tab panel. */" + }, + "entryType": "function", + "description": "Opens this tab panel.", + "jsdocTags": [], + "rawComment": "/** Opens this tab panel. */", + "memberType": "method", + "memberTags": [] + }, { "name": "ngOnInit", "signatures": [ @@ -186,32 +275,37 @@ } ], "generics": [], - "description": "A TabPanel container for the resources of layered content associated with a tab.\n\nThe `ngTabPanel` directive holds the content for a specific tab. It is linked to an\n`ngTab` by a matching `value`. If a tab panel is hidden, the `inert` attribute will be\napplied to remove it from the accessibility tree. Proper styling is required for visual hiding.\n\n```html\n
    \n \n \n \n
    \n```", + "description": "A selectable tab in a TabList.\n\nThe `ngTab` directive represents an individual tab control within an `ngTabList`. It\nrequires a `value` that uniquely identifies it and links it to a corresponding `ngTabPanel`.\n\n```html\n
  • \n My Tab Label\n
  • \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Tabs](guide/aria/tabs)" } ], - "rawComment": "/**\n * A TabPanel container for the resources of layered content associated with a tab.\n *\n * The `ngTabPanel` directive holds the content for a specific tab. It is linked to an\n * `ngTab` by a matching `value`. If a tab panel is hidden, the `inert` attribute will be\n * applied to remove it from the accessibility tree. Proper styling is required for visual hiding.\n *\n * ```html\n *
    \n * \n * \n * \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Tabs](guide/aria/tabs)\n */", + "rawComment": "/**\n * A selectable tab in a TabList.\n *\n * The `ngTab` directive represents an individual tab control within an `ngTabList`. It\n * requires a `value` that uniquely identifies it and links it to a corresponding `ngTabPanel`.\n *\n * ```html\n *
  • \n * My Tab Label\n *
  • \n * ```\n *\n * @see [Tabs](guide/aria/tabs)\n */", "implements": [ + "HasElement", "OnInit", "OnDestroy" ], + "isStandalone": true, + "selector": "[ngTab]", + "exportAs": [ + "ngTab" + ], + "aliases": [ + "ngTab" + ], "source": { - "filePath": "/src/aria/tabs/tab-panel.ts", - "startLine": 42, - "endLine": 103 + "filePath": "/src/aria/tabs/tab.ts", + "startLine": 38, + "endLine": 119 } }, { - "name": "Tab", + "name": "TabPanel", "isAbstract": false, - "entryType": "directive", + "entryType": "undecorated_class", "members": [ { "name": "element", @@ -228,93 +322,31 @@ "type": "InputSignal", "memberType": "property", "memberTags": [ - "readonly", - "input" - ], - "description": "A unique identifier for the widget.", - "jsdocTags": [], - "inputAlias": "id", - "isRequiredInput": false - }, - { - "name": "disabled", - "type": "InputSignalWithTransform", - "memberType": "property", - "memberTags": [ - "readonly", - "input" + "readonly" ], - "description": "Whether a tab is disabled.", - "jsdocTags": [], - "inputAlias": "disabled", - "isRequiredInput": false + "description": "A global unique identifier for the tab.", + "jsdocTags": [] }, { "name": "value", "type": "InputSignal", "memberType": "property", - "memberTags": [ - "readonly", - "input" - ], - "description": "The remote tabpanel unique identifier.", - "jsdocTags": [], - "inputAlias": "value", - "isRequiredInput": true - }, - { - "name": "active", - "type": "Signal", - "memberType": "property", "memberTags": [ "readonly" ], - "description": "Whether the tab is active.", + "description": "A local unique identifier for the tabpanel.", "jsdocTags": [] }, { - "name": "selected", - "type": "Signal", + "name": "visible", + "type": "Signal", "memberType": "property", "memberTags": [ "readonly" ], - "description": "Whether the tab is selected.", + "description": "Whether the tab panel is visible.", "jsdocTags": [] }, - { - "name": "open", - "signatures": [ - { - "name": "open", - "entryType": "function", - "description": "Opens this tab panel.", - "generics": [], - "isNewType": false, - "jsdocTags": [], - "params": [], - "rawComment": "/** Opens this tab panel. */", - "returnType": "void" - } - ], - "implementation": { - "params": [], - "isNewType": false, - "returnType": "void", - "generics": [], - "name": "open", - "description": "Opens this tab panel.", - "entryType": "function", - "jsdocTags": [], - "rawComment": "/** Opens this tab panel. */" - }, - "entryType": "function", - "description": "Opens this tab panel.", - "jsdocTags": [], - "rawComment": "/** Opens this tab panel. */", - "memberType": "method", - "memberTags": [] - }, { "name": "ngOnInit", "signatures": [ @@ -383,35 +415,22 @@ } ], "generics": [], - "description": "A selectable tab in a TabList.\n\nThe `ngTab` directive represents an individual tab control within an `ngTabList`. It\nrequires a `value` that uniquely identifies it and links it to a corresponding `ngTabPanel`.\n\n```html\n
  • \n My Tab Label\n
  • \n```", + "description": "A TabPanel container for the resources of layered content associated with a tab.\n\nThe `ngTabPanel` directive holds the content for a specific tab. It is linked to an\n`ngTab` by a matching `value`. If a tab panel is hidden, the `inert` attribute will be\napplied to remove it from the accessibility tree. Proper styling is required for visual hiding.\n\n```html\n
    \n \n \n \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Tabs](guide/aria/tabs)" } ], - "rawComment": "/**\n * A selectable tab in a TabList.\n *\n * The `ngTab` directive represents an individual tab control within an `ngTabList`. It\n * requires a `value` that uniquely identifies it and links it to a corresponding `ngTabPanel`.\n *\n * ```html\n *
  • \n * My Tab Label\n *
  • \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Tabs](guide/aria/tabs)\n */", + "rawComment": "/**\n * A TabPanel container for the resources of layered content associated with a tab.\n *\n * The `ngTabPanel` directive holds the content for a specific tab. It is linked to an\n * `ngTab` by a matching `value`. If a tab panel is hidden, the `inert` attribute will be\n * applied to remove it from the accessibility tree. Proper styling is required for visual hiding.\n *\n * ```html\n *
    \n * \n * \n * \n *
    \n * ```\n *\n * @see [Tabs](guide/aria/tabs)\n */", "implements": [ - "HasElement", "OnInit", "OnDestroy" ], - "isStandalone": true, - "selector": "[ngTab]", - "exportAs": [ - "ngTab" - ], - "aliases": [ - "ngTab" - ], "source": { - "filePath": "/src/aria/tabs/tab.ts", - "startLine": 41, - "endLine": 111 + "filePath": "/src/aria/tabs/tab-panel.ts", + "startLine": 42, + "endLine": 130 } }, { @@ -429,16 +448,6 @@ "description": "A reference to the host element.", "jsdocTags": [] }, - { - "name": "textDirection", - "type": "WritableSignal", - "memberType": "property", - "memberTags": [ - "readonly" - ], - "description": "Text direction.", - "jsdocTags": [] - }, { "name": "orientation", "type": "InputSignal<\"vertical\" | \"horizontal\">", @@ -452,6 +461,16 @@ "inputAlias": "orientation", "isRequiredInput": false }, + { + "name": "textDirection", + "type": "WritableSignal", + "memberType": "property", + "memberTags": [ + "readonly" + ], + "description": "Text direction.", + "jsdocTags": [] + }, { "name": "wrap", "type": "InputSignalWithTransform", @@ -513,7 +532,7 @@ "input", "output" ], - "description": "The current selected tab.", + "description": "The current selected tab as a model input.", "jsdocTags": [], "inputAlias": "selectedTab", "isRequiredInput": false, @@ -646,21 +665,66 @@ "rawComment": "/** Opens the tab panel with the specified value. */", "memberType": "method", "memberTags": [] + }, + { + "name": "findTab", + "signatures": [ + { + "name": "findTab", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [ + { + "name": "value", + "description": "", + "type": "string | undefined", + "isOptional": true, + "isRestParam": false + } + ], + "rawComment": "", + "returnType": "any" + } + ], + "implementation": { + "params": [ + { + "name": "value", + "description": "", + "type": "string | undefined", + "isOptional": true, + "isRestParam": false + } + ], + "isNewType": false, + "returnType": "any", + "generics": [], + "name": "findTab", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] } ], "generics": [], "description": "A TabList container.\n\nThe `ngTabList` directive controls a list of `ngTab` elements. It manages keyboard\nnavigation, selection, and the overall orientation of the tabs. It should be placed\nwithin an `ngTabs` container.\n\n```html\n
      \n
    • First Tab
    • \n
    • Second Tab
    • \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Tabs](guide/aria/tabs)" } ], - "rawComment": "/**\n * A TabList container.\n *\n * The `ngTabList` directive controls a list of `ngTab` elements. It manages keyboard\n * navigation, selection, and the overall orientation of the tabs. It should be placed\n * within an `ngTabs` container.\n *\n * ```html\n *
      \n *
    • First Tab
    • \n *
    • Second Tab
    • \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Tabs](guide/aria/tabs)\n */", + "rawComment": "/**\n * A TabList container.\n *\n * The `ngTabList` directive controls a list of `ngTab` elements. It manages keyboard\n * navigation, selection, and the overall orientation of the tabs. It should be placed\n * within an `ngTabs` container.\n *\n * ```html\n *
      \n *
    • First Tab
    • \n *
    • Second Tab
    • \n *
    \n * ```\n *\n * @see [Tabs](guide/aria/tabs)\n */", "implements": [ "OnInit", "OnDestroy" @@ -675,8 +739,8 @@ ], "source": { "filePath": "/src/aria/tabs/tab-list.ts", - "startLine": 45, - "endLine": 174 + "startLine": 46, + "endLine": 186 } } ], @@ -701,28 +765,36 @@ "signal", "@angular/core" ], + [ + "afterNextRender", + "@angular/core" + ], + [ + "OnDestroy", + "@angular/core" + ], [ "_IdGenerator", "@angular/cdk/a11y" ], [ - "input", + "OnInit", "@angular/core" ], [ - "afterRenderEffect", + "booleanAttribute", "@angular/core" ], [ - "OnInit", + "input", "@angular/core" ], [ - "OnDestroy", + "afterRenderEffect", "@angular/core" ], [ - "booleanAttribute", + "contentChild", "@angular/core" ], [ @@ -733,6 +805,14 @@ "model", "@angular/core" ], + [ + "linkedSignal", + "@angular/core" + ], + [ + "WritableSignal", + "@angular/core" + ], [ "TabContent", "@angular/aria/tabs" @@ -742,11 +822,11 @@ "@angular/aria/tabs" ], [ - "TabPanel", + "Tab", "@angular/aria/tabs" ], [ - "Tab", + "TabPanel", "@angular/aria/tabs" ], [ @@ -766,71 +846,75 @@ "@angular/aria/tabs" ], [ - "TabPanel", + "Tabs.ngOnDestroy", "@angular/aria/tabs" ], [ - "TabPanel.element", + "Tab", "@angular/aria/tabs" ], [ - "TabPanel.id", + "Tab.element", "@angular/aria/tabs" ], [ - "TabPanel.value", + "Tab.id", "@angular/aria/tabs" ], [ - "TabPanel.visible", + "Tab.disabled", "@angular/aria/tabs" ], [ - "TabPanel.ngOnInit", + "Tab.value", "@angular/aria/tabs" ], [ - "TabPanel.ngOnDestroy", + "Tab.active", "@angular/aria/tabs" ], [ - "Tab", + "Tab.selected", "@angular/aria/tabs" ], [ - "Tab.element", + "Tab.open", "@angular/aria/tabs" ], [ - "Tab.id", + "Tab.ngOnInit", "@angular/aria/tabs" ], [ - "Tab.disabled", + "Tab.ngOnDestroy", "@angular/aria/tabs" ], [ - "Tab.value", + "TabPanel", "@angular/aria/tabs" ], [ - "Tab.active", + "TabPanel.element", "@angular/aria/tabs" ], [ - "Tab.selected", + "TabPanel.id", "@angular/aria/tabs" ], [ - "Tab.open", + "TabPanel.value", "@angular/aria/tabs" ], [ - "Tab.ngOnInit", + "TabPanel.visible", "@angular/aria/tabs" ], [ - "Tab.ngOnDestroy", + "TabPanel.ngOnInit", + "@angular/aria/tabs" + ], + [ + "TabPanel.ngOnDestroy", "@angular/aria/tabs" ], [ @@ -842,11 +926,11 @@ "@angular/aria/tabs" ], [ - "TabList.textDirection", + "TabList.orientation", "@angular/aria/tabs" ], [ - "TabList.orientation", + "TabList.textDirection", "@angular/aria/tabs" ], [ @@ -884,6 +968,10 @@ [ "TabList.open", "@angular/aria/tabs" + ], + [ + "TabList.findTab", + "@angular/aria/tabs" ] ] } \ No newline at end of file diff --git a/adev/src/content/aria/aria-toolbar.json b/adev/src/content/aria/aria-toolbar.json index 9d8925cf11af..f055e712875f 100755 --- a/adev/src/content/aria/aria-toolbar.json +++ b/adev/src/content/aria/aria-toolbar.json @@ -53,16 +53,12 @@ ], "description": "A directive that groups toolbar widgets, used for more complex widgets like radio groups\nthat have their own internal navigation.", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Toolbar](guide/aria/toolbar)" } ], - "rawComment": "/**\n * A directive that groups toolbar widgets, used for more complex widgets like radio groups\n * that have their own internal navigation.\n *\n * @developerPreview 21.0\n *\n * @see [Toolbar](guide/aria/toolbar)\n */", + "rawComment": "/**\n * A directive that groups toolbar widgets, used for more complex widgets like radio groups\n * that have their own internal navigation.\n *\n * @see [Toolbar](guide/aria/toolbar)\n */", "implements": [], "isStandalone": true, "selector": "[ngToolbarWidgetGroup]", @@ -74,8 +70,8 @@ ], "source": { "filePath": "/src/aria/toolbar/toolbar-widget-group.ts", - "startLine": 31, - "endLine": 67 + "startLine": 30, + "endLine": 81 } }, { @@ -121,6 +117,7 @@ "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ + "readonly", "input" ], "description": "Whether to allow disabled items to receive focus. When `true`, disabled items are\nfocusable but not interactive. When `false`, disabled items are skipped during navigation.", @@ -155,7 +152,7 @@ "isRequiredInput": false }, { - "name": "values", + "name": "value", "type": "ModelSignal", "memberType": "property", "memberTags": [ @@ -165,9 +162,42 @@ ], "description": "The values of the selected widgets within the toolbar.", "jsdocTags": [], - "inputAlias": "values", + "inputAlias": "value", "isRequiredInput": false, - "outputAlias": "valuesChange" + "outputAlias": "valueChange" + }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] } ], "generics": [ @@ -177,17 +207,15 @@ ], "description": "A toolbar widget container for a group of interactive widgets, such as\nbuttons or radio groups. It provides a single point of reference for keyboard navigation\nand focus management. It supports various orientations and disabled states.\n\n```html\n
    \n \n \n\n
    \n \n \n \n
    \n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Toolbar](guide/aria/toolbar)" } ], - "rawComment": "/**\n * A toolbar widget container for a group of interactive widgets, such as\n * buttons or radio groups. It provides a single point of reference for keyboard navigation\n * and focus management. It supports various orientations and disabled states.\n *\n * ```html\n *
    \n * \n * \n *\n *
    \n * \n * \n * \n *
    \n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Toolbar](guide/aria/toolbar)\n */", - "implements": [], + "rawComment": "/**\n * A toolbar widget container for a group of interactive widgets, such as\n * buttons or radio groups. It provides a single point of reference for keyboard navigation\n * and focus management. It supports various orientations and disabled states.\n *\n * ```html\n *
    \n * \n * \n *\n *
    \n * \n * \n * \n *
    \n *
    \n * ```\n *\n * @see [Toolbar](guide/aria/toolbar)\n */", + "implements": [ + "OnDestroy" + ], "isStandalone": true, "selector": "[ngToolbar]", "exportAs": [ @@ -198,8 +226,8 @@ ], "source": { "filePath": "/src/aria/toolbar/toolbar.ts", - "startLine": 47, - "endLine": 142 + "startLine": 46, + "endLine": 132 } }, { @@ -360,16 +388,12 @@ ], "description": "A widget within a toolbar.\n\nThe `ngToolbarWidget` directive should be applied to any native HTML element that acts\nas an interactive widget within an `ngToolbar` or `ngToolbarWidgetGroup`. It enables\nkeyboard navigation and selection within the toolbar.\n\n```html\n\n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Toolbar](guide/aria/toolbar)" } ], - "rawComment": "/**\n * A widget within a toolbar.\n *\n * The `ngToolbarWidget` directive should be applied to any native HTML element that acts\n * as an interactive widget within an `ngToolbar` or `ngToolbarWidgetGroup`. It enables\n * keyboard navigation and selection within the toolbar.\n *\n * ```html\n * \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Toolbar](guide/aria/toolbar)\n */", + "rawComment": "/**\n * A widget within a toolbar.\n *\n * The `ngToolbarWidget` directive should be applied to any native HTML element that acts\n * as an interactive widget within an `ngToolbar` or `ngToolbarWidgetGroup`. It enables\n * keyboard navigation and selection within the toolbar.\n *\n * ```html\n * \n * ```\n *\n * @see [Toolbar](guide/aria/toolbar)\n */", "implements": [ "OnInit", "OnDestroy" @@ -384,7 +408,7 @@ ], "source": { "filePath": "/src/aria/toolbar/toolbar-widget.ts", - "startLine": 47, + "startLine": 45, "endLine": 114 } } @@ -423,7 +447,7 @@ "@angular/core" ], [ - "signal", + "afterNextRender", "@angular/core" ], [ @@ -431,15 +455,19 @@ "@angular/core" ], [ - "Directionality", - "@angular/cdk/bidi" + "OnDestroy", + "@angular/core" ], [ - "OnInit", + "signal", "@angular/core" ], [ - "OnDestroy", + "Directionality", + "@angular/cdk/bidi" + ], + [ + "OnInit", "@angular/core" ], [ @@ -503,7 +531,11 @@ "@angular/aria/toolbar" ], [ - "Toolbar.values", + "Toolbar.value", + "@angular/aria/toolbar" + ], + [ + "Toolbar.ngOnDestroy", "@angular/aria/toolbar" ], [ diff --git a/adev/src/content/aria/aria-tree.json b/adev/src/content/aria/aria-tree.json index b701f5d14e0f..0bfd2326b80c 100755 --- a/adev/src/content/aria/aria-tree.json +++ b/adev/src/content/aria/aria-tree.json @@ -103,24 +103,20 @@ ], "description": "Group that contains children tree items.\n\nThe `ngTreeItemGroup` structural directive should be applied to an `ng-template` that\nwraps the child `ngTreeItem` elements. It is used to define a group of children for an\nexpandable `ngTreeItem`. The `ownedBy` input links the group to its parent `ngTreeItem`.\n\n```html\n
  • \n Parent Item\n
      \n \n
    • Child Item
    • \n
      \n
    \n
  • \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Tree](guide/aria/tree)" } ], - "rawComment": "/**\n * Group that contains children tree items.\n *\n * The `ngTreeItemGroup` structural directive should be applied to an `ng-template` that\n * wraps the child `ngTreeItem` elements. It is used to define a group of children for an\n * expandable `ngTreeItem`. The `ownedBy` input links the group to its parent `ngTreeItem`.\n *\n * ```html\n *
  • \n * Parent Item\n *
      \n * \n *
    • Child Item
    • \n *
      \n *
    \n *
  • \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Tree](guide/aria/tree)\n */", + "rawComment": "/**\n * Group that contains children tree items.\n *\n * The `ngTreeItemGroup` structural directive should be applied to an `ng-template` that\n * wraps the child `ngTreeItem` elements. It is used to define a group of children for an\n * expandable `ngTreeItem`. The `ownedBy` input links the group to its parent `ngTreeItem`.\n *\n * ```html\n *
  • \n * Parent Item\n *
      \n * \n *
    • Child Item
    • \n *
      \n *
    \n *
  • \n * ```\n *\n * @see [Tree](guide/aria/tree)\n */", "implements": [ "OnInit", "OnDestroy" ], "source": { "filePath": "/src/aria/tree/tree-item-group.ts", - "startLine": 45, - "endLine": 89 + "startLine": 42, + "endLine": 86 } }, { @@ -364,13 +360,8 @@ } ], "description": "A selectable and expandable item in an `ngTree`.\n\nThe `ngTreeItem` directive represents an individual node within an `ngTree`. It can be\nselected, expanded (if it has children), and disabled. The `parent` input establishes\nthe hierarchical relationship within the tree.\n\n```html\n
  • \n Item Label\n
  • \n```", - "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - } - ], - "rawComment": "/**\n * A selectable and expandable item in an `ngTree`.\n *\n * The `ngTreeItem` directive represents an individual node within an `ngTree`. It can be\n * selected, expanded (if it has children), and disabled. The `parent` input establishes\n * the hierarchical relationship within the tree.\n *\n * ```html\n *
  • \n * Item Label\n *
  • \n * ```\n *\n * @developerPreview 21.0\n */", + "jsdocTags": [], + "rawComment": "/**\n * A selectable and expandable item in an `ngTree`.\n *\n * The `ngTreeItem` directive represents an individual node within an `ngTree`. It can be\n * selected, expanded (if it has children), and disabled. The `parent` input establishes\n * the hierarchical relationship within the tree.\n *\n * ```html\n *
  • \n * Item Label\n *
  • \n * ```\n */", "extends": "DeferredContentAware", "implements": [ "OnInit", @@ -387,14 +378,14 @@ ], "source": { "filePath": "/src/aria/tree/tree-item.ts", - "startLine": 45, - "endLine": 173 + "startLine": 41, + "endLine": 167 } }, { "name": "Tree", "isAbstract": false, - "entryType": "undecorated_class", + "entryType": "directive", "members": [ { "name": "element", @@ -411,100 +402,145 @@ "type": "InputSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "A unique identifier for the tree.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "id", + "isRequiredInput": false }, { "name": "orientation", "type": "InputSignal<\"vertical\" | \"horizontal\">", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "Orientation of the tree.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "orientation", + "isRequiredInput": false }, { "name": "multi", "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "Whether multi-selection is allowed.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "multi", + "isRequiredInput": false }, { "name": "disabled", "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "Whether the tree is disabled.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "disabled", + "isRequiredInput": false }, { "name": "selectionMode", "type": "InputSignal<\"explicit\" | \"follow\">", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "The selection strategy used by the tree.\n- `explicit`: Items are selected explicitly by the user (e.g., via click or spacebar).\n- `follow`: The focused item is automatically selected.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "selectionMode", + "isRequiredInput": false }, { "name": "focusMode", "type": "InputSignal<\"roving\" | \"activedescendant\">", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "The focus strategy used by the tree.\n- `roving`: Focus is moved to the active item using `tabindex`.\n- `activedescendant`: Focus remains on the tree container, and `aria-activedescendant` is used to indicate the active item.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "focusMode", + "isRequiredInput": false }, { "name": "wrap", "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "Whether navigation wraps.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "wrap", + "isRequiredInput": false }, { "name": "softDisabled", "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "Whether to allow disabled items to receive focus. When `true`, disabled items are\nfocusable but not interactive. When `false`, disabled items are skipped during navigation.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "softDisabled", + "isRequiredInput": false }, { "name": "typeaheadDelay", "type": "InputSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "The delay in seconds before the typeahead search is reset.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "typeaheadDelay", + "isRequiredInput": false + }, + { + "name": "tabIndex", + "type": "InputSignal", + "memberType": "property", + "memberTags": [ + "readonly", + "input" + ], + "description": "The tabindex of the tree.", + "jsdocTags": [], + "inputAlias": "tabindex", + "isRequiredInput": false }, { - "name": "values", + "name": "value", "type": "ModelSignal", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input", + "output" ], "description": "The values of the currently selected items.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "value", + "isRequiredInput": false, + "outputAlias": "valueChange" }, { "name": "textDirection", @@ -521,21 +557,70 @@ "type": "InputSignalWithTransform", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "Whether the tree is in navigation mode.", - "jsdocTags": [] + "jsdocTags": [], + "inputAlias": "nav", + "isRequiredInput": false }, { "name": "currentType", "type": "InputSignal<\"page\" | \"step\" | \"location\" | \"date\" | \"time\" | \"true\" | \"false\">", "memberType": "property", "memberTags": [ - "readonly" + "readonly", + "input" ], "description": "The `aria-current` type. It can be used in navigation trees to indicate the currently active item.\nSee https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-current for more details.", + "jsdocTags": [], + "inputAlias": "currentType", + "isRequiredInput": false + }, + { + "name": "activeDescendant", + "type": "Signal", + "memberType": "property", + "memberTags": [ + "readonly" + ], + "description": "The ID of the active descendant in the tree.", "jsdocTags": [] }, + { + "name": "ngOnDestroy", + "signatures": [ + { + "name": "ngOnDestroy", + "entryType": "function", + "description": "", + "generics": [], + "isNewType": false, + "jsdocTags": [], + "params": [], + "rawComment": "", + "returnType": "void" + } + ], + "implementation": { + "params": [], + "isNewType": false, + "returnType": "void", + "generics": [], + "name": "ngOnDestroy", + "description": "", + "entryType": "function", + "jsdocTags": [], + "rawComment": "" + }, + "entryType": "function", + "description": "", + "jsdocTags": [], + "rawComment": "", + "memberType": "method", + "memberTags": [] + }, { "name": "scrollActiveItemIntoView", "signatures": [ @@ -593,21 +678,27 @@ ], "description": "A container that transforms nested lists into an accessible, ARIA-compliant tree structure.\nIt manages the overall state of the tree, including selection, expansion, and keyboard\nnavigation.\n\n```html\n
      \n \n
    \n\n\n @for (node of nodes; track node.name) {\n
  • \n {{ node.name }}\n @if (node.children) {\n
      \n \n \n \n
    \n }\n
  • \n }\n
    \n```", "jsdocTags": [ - { - "name": "developerPreview", - "comment": "21.0" - }, { "name": "see", "comment": "[Tree](guide/aria/tree)" } ], - "rawComment": "/**\n * A container that transforms nested lists into an accessible, ARIA-compliant tree structure.\n * It manages the overall state of the tree, including selection, expansion, and keyboard\n * navigation.\n *\n * ```html\n *
      \n * \n *
    \n *\n * \n * @for (node of nodes; track node.name) {\n *
  • \n * {{ node.name }}\n * @if (node.children) {\n *
      \n * \n * \n * \n *
    \n * }\n *
  • \n * }\n *
    \n * ```\n *\n * @developerPreview 21.0\n *\n * @see [Tree](guide/aria/tree)\n */", - "implements": [], + "rawComment": "/**\n * A container that transforms nested lists into an accessible, ARIA-compliant tree structure.\n * It manages the overall state of the tree, including selection, expansion, and keyboard\n * navigation.\n *\n * ```html\n *
      \n * \n *
    \n *\n * \n * @for (node of nodes; track node.name) {\n *
  • \n * {{ node.name }}\n * @if (node.children) {\n *
      \n * \n * \n * \n *
    \n * }\n *
  • \n * }\n *
    \n * ```\n *\n * @see [Tree](guide/aria/tree)\n */", + "implements": [ + "OnDestroy" + ], + "isStandalone": true, + "selector": "[ngTree]", + "exportAs": [ + "ngTree" + ], + "aliases": [ + "ngTree" + ], "source": { "filePath": "/src/aria/tree/tree.ts", - "startLine": 64, - "endLine": 230 + "startLine": 69, + "endLine": 211 } } ], @@ -660,14 +751,14 @@ "Signal", "@angular/core" ], - [ - "afterNextRender", - "@angular/core" - ], [ "_IdGenerator", "@angular/cdk/a11y" ], + [ + "afterNextRender", + "@angular/core" + ], [ "untracked", "@angular/core" @@ -821,7 +912,11 @@ "@angular/aria/tree" ], [ - "Tree.values", + "Tree.tabIndex", + "@angular/aria/tree" + ], + [ + "Tree.value", "@angular/aria/tree" ], [ @@ -836,6 +931,14 @@ "Tree.currentType", "@angular/aria/tree" ], + [ + "Tree.activeDescendant", + "@angular/aria/tree" + ], + [ + "Tree.ngOnDestroy", + "@angular/aria/tree" + ], [ "Tree.scrollActiveItemIntoView", "@angular/aria/tree" diff --git a/adev/src/content/best-practices/performance/overview.md b/adev/src/content/best-practices/performance/overview.md index 3ff3ac8044d9..0280057f3ff2 100644 --- a/adev/src/content/best-practices/performance/overview.md +++ b/adev/src/content/best-practices/performance/overview.md @@ -6,12 +6,12 @@ Angular includes many optimizations out of the box, but as applications grow, yo Loading performance determines how quickly your application becomes visible and interactive. Slow loading directly impacts [Core Web Vitals](https://web.dev/vitals/) like Largest Contentful Paint (LCP) and Time to First Byte (TTFB). -| Technique | What it does | When to use it | -| :------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------- | -| [Lazy-loaded routes](guide/routing/define-routes#lazily-loaded-components-and-routes) | Defers loading route components until navigation, reducing the initial bundle size | Applications with multiple routes where not all are needed on initial load | -| [Deferred loading with `@defer`](best-practices/performance/defer) | Splits components into separate bundles that load on demand | Components not visible on initial render, heavy third-party libraries, below-the-fold content | -| [Image optimization](best-practices/performance/image-optimization) | Prioritizes LCP images, lazy loads others, generates responsive `srcset` attributes | Any application that displays images | -| [Server-side rendering](best-practices/performance/ssr) | Renders pages on the server for faster first paint and better SEO, with [hydration](guide/hydration) to restore interactivity and [incremental hydration](guide/incremental-hydration) to defer hydrating sections until needed | Content-heavy applications, pages that need search engine indexing | +| Technique | What it does | When to use it | +| :------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------- | +| [Lazy-loaded routes](best-practices/performance/lazy-loaded-routes#lazily-loaded-components-and-routes) | Defers loading route components until navigation, reducing the initial bundle size | Applications with multiple routes where not all are needed on initial load | +| [Deferred loading with `@defer`](best-practices/performance/defer) | Splits components into separate bundles that load on demand | Components not visible on initial render, heavy third-party libraries, below-the-fold content | +| [Image optimization](best-practices/performance/image-optimization) | Prioritizes LCP images, lazy loads others, generates responsive `srcset` attributes | Any application that displays images | +| [Server-side rendering](best-practices/performance/ssr) | Renders pages on the server for faster first paint and better SEO, with [hydration](guide/hydration) to restore interactivity and [incremental hydration](guide/incremental-hydration) to defer hydrating sections until needed | Content-heavy applications, pages that need search engine indexing | ## Runtime performance diff --git a/adev/src/content/best-practices/runtime-performance/skipping-subtrees.md b/adev/src/content/best-practices/runtime-performance/skipping-subtrees.md index 02304d483b8b..405836da6e6b 100644 --- a/adev/src/content/best-practices/runtime-performance/skipping-subtrees.md +++ b/adev/src/content/best-practices/runtime-performance/skipping-subtrees.md @@ -4,33 +4,20 @@ JavaScript, by default, uses mutable data structures that you can reference from Change detection is sufficiently fast for most applications. However, when an application has an especially large component tree, running change detection across the whole application can cause performance issues. You can address this by configuring change detection to only run on a subset of the component tree. -If you are confident that a part of the application is not affected by a state change, you can use [OnPush](/api/core/ChangeDetectionStrategy) to skip change detection in an entire component subtree. - ## Using `OnPush` -OnPush change detection instructs Angular to run change detection for a component subtree **only** when: +OnPush is the default change detection strategy in Angular (since v22). It instructs Angular to run change detection for a component subtree **only** when: - The root component of the subtree receives new inputs as the result of a template binding. Angular compares the current and past value of the input with `==`. - Angular handles an event _(for example using event binding, output binding, or `@HostListener` )_ in the subtree's root component or any of its children whether they are using OnPush change detection or not. -You can set the change detection strategy of a component to `OnPush` in the `@Component` decorator: - -```ts -import {ChangeDetectionStrategy, Component} from '@angular/core'; - -@Component({ - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MyComponent {} -``` - ## Common change detection scenarios This section examines several common change detection scenarios to illustrate Angular's behavior. -### An event is handled by a component with default change detection +### An event is handled by a component with `Eager` change detection -If Angular handles an event within a component without `OnPush` strategy, the framework executes change detection on the entire component tree. Angular will skip descendant component subtrees with roots using `OnPush`, which have not received new inputs. +If Angular handles an event within a component with the `Eager` strategy, the framework executes change detection on the entire component tree. Angular will skip descendant component subtrees with roots using `OnPush`, which have not received new inputs. As an example, if we set the change detection strategy of `MainComponent` to `OnPush` and the user interacts with a component outside the subtree with root `MainComponent`, Angular will check all the pink components from the diagram below (`AppComponent`, `HeaderComponent`, `SearchComponent`, `ButtonComponent`) unless `MainComponent` receives new inputs: diff --git a/adev/src/content/best-practices/update.md b/adev/src/content/best-practices/update.md index 0e2cabdfa171..1a7cd0d08367 100644 --- a/adev/src/content/best-practices/update.md +++ b/adev/src/content/best-practices/update.md @@ -6,9 +6,9 @@ Keeping your Angular application up-to-date enables you to take advantage of lea This document contains information and resources to help you keep your Angular applications and libraries up-to-date. -For information about our versioning policy and practices —including support and deprecation practices, as well as the release schedule— see [Angular versioning and releases](reference/releases 'Angular versioning and releases'). +For information about our versioning policy and practices — including support and deprecation practices, as well as the release schedule — see [Angular versioning and releases](reference/releases 'Angular versioning and releases'). -HELPFUL: If you are currently using AngularJS, see [Upgrading from AngularJS](https://angular.io/guide/upgrade 'Upgrading from Angular JS'). +HELPFUL: If you are currently using AngularJS, see [Upgrading from AngularJS](https://angular.io/guide/upgrade 'Upgrading from AngularJS'). _AngularJS_ is the name for all v1.x versions of Angular. ## Getting notified of new releases @@ -30,7 +30,7 @@ To check your application's version of Angular use the `ng version` command from The most recent stable released version of Angular appears [on npm](https://www.npmjs.com/package/@angular/core 'Angular on npm') under "Version." For example, `16.2.4`. You can also find the most current version of Angular by using the CLI command [`ng update`](cli/update). -By default, [`ng update`](cli/update)(without additional arguments) lists the updates that are available to you. +By default, [`ng update`](cli/update) (without additional arguments) lists the updates that are available to you. ## Updating your environment and apps diff --git a/adev/src/content/cdk/_build-info.json b/adev/src/content/cdk/_build-info.json index 50993cb2cd45..40247a74d7db 100644 --- a/adev/src/content/cdk/_build-info.json +++ b/adev/src/content/cdk/_build-info.json @@ -1,4 +1,4 @@ { "branchName": "refs/heads/main", - "sha": "fc8b94d631a5d105a87abc2a008a6e8f76d7f3f5" + "sha": "4f0dcc2674e1e5c7559e8ef993df05233a2ec6f4" } \ No newline at end of file diff --git a/adev/src/content/cdk/cdk_drag_drop.json b/adev/src/content/cdk/cdk_drag_drop.json index 460494653be1..03b38560fd10 100755 --- a/adev/src/content/cdk/cdk_drag_drop.json +++ b/adev/src/content/cdk/cdk_drag_drop.json @@ -431,7 +431,7 @@ "source": { "filePath": "/src/cdk/drag-drop/drag-drop.ts", "startLine": 19, - "endLine": 49 + "endLine": 46 } }, { @@ -482,19 +482,6 @@ "endLine": 19 } }, - { - "name": "CDK_DROP_LIST_GROUP", - "type": "any", - "entryType": "constant", - "rawComment": "/**\n * Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as\n * alternative token to the actual `CdkDropListGroup` class which could cause unnecessary\n * retention of the class and its directive metadata.\n */", - "description": "Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as\nalternative token to the actual `CdkDropListGroup` class which could cause unnecessary\nretention of the class and its directive metadata.", - "jsdocTags": [], - "source": { - "filePath": "/src/cdk/drag-drop/directives/drop-list-group.ts", - "startLine": 16, - "endLine": 18 - } - }, { "name": "CdkDragRelease", "entryType": "interface", @@ -533,6 +520,19 @@ "endLine": 26 } }, + { + "name": "CDK_DROP_LIST_GROUP", + "type": "any", + "entryType": "constant", + "rawComment": "/**\n * Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as\n * alternative token to the actual `CdkDropListGroup` class which could cause unnecessary\n * retention of the class and its directive metadata.\n */", + "description": "Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as\nalternative token to the actual `CdkDropListGroup` class which could cause unnecessary\nretention of the class and its directive metadata.", + "jsdocTags": [], + "source": { + "filePath": "/src/cdk/drag-drop/directives/drop-list-group.ts", + "startLine": 17, + "endLine": 19 + } + }, { "name": "CDK_DRAG_PLACEHOLDER", "type": "any", @@ -560,18 +560,30 @@ } }, { - "name": "CdkDropListGroup", + "name": "CdkDragPlaceholder", "isAbstract": false, - "entryType": "undecorated_class", + "entryType": "directive", "members": [ { - "name": "disabled", - "type": "boolean", + "name": "templateRef", + "type": "any", "memberType": "property", "memberTags": [], - "description": "Whether starting a dragging sequence from inside this group is disabled.", + "description": "", "jsdocTags": [] }, + { + "name": "data", + "type": "T", + "memberType": "property", + "memberTags": [ + "input" + ], + "description": "Context data to be added to the placeholder template instance.", + "jsdocTags": [], + "inputAlias": "data", + "isRequiredInput": false + }, { "name": "ngOnDestroy", "signatures": [ @@ -608,46 +620,41 @@ ], "generics": [ { - "name": "T" + "name": "T", + "default": "any" } ], - "description": "Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`\nelements that are placed inside a `cdkDropListGroup` will be connected to each other\nautomatically. Can be used as an alternative to the `cdkDropListConnectedTo` input\nfrom `cdkDropList`.", + "description": "Element that will be used as a template for the placeholder of a CdkDrag when\nit is being dragged. The placeholder is displayed in place of the element being dragged.", "jsdocTags": [], - "rawComment": "/**\n * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`\n * elements that are placed inside a `cdkDropListGroup` will be connected to each other\n * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input\n * from `cdkDropList`.\n */", + "rawComment": "/**\n * Element that will be used as a template for the placeholder of a CdkDrag when\n * it is being dragged. The placeholder is displayed in place of the element being dragged.\n */", "implements": [ "OnDestroy" ], + "isStandalone": true, + "selector": "ng-template[cdkDragPlaceholder]", + "exportAs": [], + "aliases": [ + "cdkDragPlaceholder" + ], "source": { - "filePath": "/src/cdk/drag-drop/directives/drop-list-group.ts", - "startLine": 26, + "filePath": "/src/cdk/drag-drop/directives/drag-placeholder.ts", + "startLine": 23, "endLine": 42 } }, { - "name": "CdkDragPlaceholder", + "name": "CdkDropListGroup", "isAbstract": false, - "entryType": "directive", + "entryType": "undecorated_class", "members": [ { - "name": "templateRef", - "type": "any", + "name": "disabled", + "type": "boolean", "memberType": "property", "memberTags": [], - "description": "", + "description": "Whether starting a dragging sequence from inside this group is disabled.", "jsdocTags": [] }, - { - "name": "data", - "type": "T", - "memberType": "property", - "memberTags": [ - "input" - ], - "description": "Context data to be added to the placeholder template instance.", - "jsdocTags": [], - "inputAlias": "data", - "isRequiredInput": false - }, { "name": "ngOnDestroy", "signatures": [ @@ -684,26 +691,19 @@ ], "generics": [ { - "name": "T", - "default": "any" + "name": "T" } ], - "description": "Element that will be used as a template for the placeholder of a CdkDrag when\nit is being dragged. The placeholder is displayed in place of the element being dragged.", + "description": "Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`\nelements that are placed inside a `cdkDropListGroup` will be connected to each other\nautomatically. Can be used as an alternative to the `cdkDropListConnectedTo` input\nfrom `cdkDropList`.", "jsdocTags": [], - "rawComment": "/**\n * Element that will be used as a template for the placeholder of a CdkDrag when\n * it is being dragged. The placeholder is displayed in place of the element being dragged.\n */", + "rawComment": "/**\n * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`\n * elements that are placed inside a `cdkDropListGroup` will be connected to each other\n * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input\n * from `cdkDropList`.\n */", "implements": [ "OnDestroy" ], - "isStandalone": true, - "selector": "ng-template[cdkDragPlaceholder]", - "exportAs": [], - "aliases": [ - "cdkDragPlaceholder" - ], "source": { - "filePath": "/src/cdk/drag-drop/directives/drag-placeholder.ts", - "startLine": 23, - "endLine": 44 + "filePath": "/src/cdk/drag-drop/directives/drop-list-group.ts", + "startLine": 27, + "endLine": 43 } }, { @@ -784,7 +784,7 @@ "source": { "filePath": "/src/cdk/drag-drop/directives/drag-preview.ts", "startLine": 31, - "endLine": 55 + "endLine": 53 } }, { @@ -1315,7 +1315,7 @@ "source": { "filePath": "/src/cdk/drag-drop/directives/drag-handle.ts", "startLine": 33, - "endLine": 89 + "endLine": 87 } }, { @@ -1663,7 +1663,7 @@ "source": { "filePath": "/src/cdk/drag-drop/directives/drop-list.ts", "startLine": 36, - "endLine": 436 + "endLine": 434 } }, { @@ -2405,8 +2405,8 @@ ], "source": { "filePath": "/src/cdk/drag-drop/drag-drop-registry.ts", - "startLine": 59, - "endLine": 336 + "startLine": 57, + "endLine": 331 } }, { @@ -3290,7 +3290,7 @@ { "name": "changes", "description": "", - "type": "SimpleChanges", + "type": "SimpleChanges", "isOptional": false, "isRestParam": false } @@ -3304,7 +3304,7 @@ { "name": "changes", "description": "", - "type": "SimpleChanges", + "type": "SimpleChanges", "isOptional": false, "isRestParam": false } @@ -3376,7 +3376,7 @@ "source": { "filePath": "/src/cdk/drag-drop/directives/drag.ts", "startLine": 60, - "endLine": 617 + "endLine": 615 } }, { @@ -4049,10 +4049,6 @@ { "name": "param", "comment": "Event that triggered the dropping sequence." - }, - { - "name": "breaking-change", - "comment": "15.0.0 `previousIndex` and `event` parameters to become required." } ], "params": [ @@ -4109,11 +4105,11 @@ "name": "event", "description": "Event that triggered the dropping sequence.", "type": "MouseEvent | TouchEvent", - "isOptional": true, + "isOptional": false, "isRestParam": false } ], - "rawComment": "/**\n * Drops an item into this container.\n * @param item Item being dropped into the container.\n * @param currentIndex Index at which the item should be inserted.\n * @param previousIndex Index of the item when dragging started.\n * @param previousContainer Container from which the item got dragged in.\n * @param isPointerOverContainer Whether the user's pointer was over the\n * container when the item was dropped.\n * @param distance Distance the user has dragged since the start of the dragging sequence.\n * @param event Event that triggered the dropping sequence.\n *\n * @breaking-change 15.0.0 `previousIndex` and `event` parameters to become required.\n */", + "rawComment": "/**\n * Drops an item into this container.\n * @param item Item being dropped into the container.\n * @param currentIndex Index at which the item should be inserted.\n * @param previousIndex Index of the item when dragging started.\n * @param previousContainer Container from which the item got dragged in.\n * @param isPointerOverContainer Whether the user's pointer was over the\n * container when the item was dropped.\n * @param distance Distance the user has dragged since the start of the dragging sequence.\n * @param event Event that triggered the dropping sequence.\n */", "returnType": "void" } ], @@ -4172,7 +4168,7 @@ "name": "event", "description": "Event that triggered the dropping sequence.", "type": "MouseEvent | TouchEvent", - "isOptional": true, + "isOptional": false, "isRestParam": false } ], @@ -4210,13 +4206,9 @@ { "name": "param", "comment": "Event that triggered the dropping sequence." - }, - { - "name": "breaking-change", - "comment": "15.0.0 `previousIndex` and `event` parameters to become required." } ], - "rawComment": "/**\n * Drops an item into this container.\n * @param item Item being dropped into the container.\n * @param currentIndex Index at which the item should be inserted.\n * @param previousIndex Index of the item when dragging started.\n * @param previousContainer Container from which the item got dragged in.\n * @param isPointerOverContainer Whether the user's pointer was over the\n * container when the item was dropped.\n * @param distance Distance the user has dragged since the start of the dragging sequence.\n * @param event Event that triggered the dropping sequence.\n *\n * @breaking-change 15.0.0 `previousIndex` and `event` parameters to become required.\n */" + "rawComment": "/**\n * Drops an item into this container.\n * @param item Item being dropped into the container.\n * @param currentIndex Index at which the item should be inserted.\n * @param previousIndex Index of the item when dragging started.\n * @param previousContainer Container from which the item got dragged in.\n * @param isPointerOverContainer Whether the user's pointer was over the\n * container when the item was dropped.\n * @param distance Distance the user has dragged since the start of the dragging sequence.\n * @param event Event that triggered the dropping sequence.\n */" }, "entryType": "function", "description": "Drops an item into this container.", @@ -4248,13 +4240,9 @@ { "name": "param", "comment": "Event that triggered the dropping sequence." - }, - { - "name": "breaking-change", - "comment": "15.0.0 `previousIndex` and `event` parameters to become required." } ], - "rawComment": "/**\n * Drops an item into this container.\n * @param item Item being dropped into the container.\n * @param currentIndex Index at which the item should be inserted.\n * @param previousIndex Index of the item when dragging started.\n * @param previousContainer Container from which the item got dragged in.\n * @param isPointerOverContainer Whether the user's pointer was over the\n * container when the item was dropped.\n * @param distance Distance the user has dragged since the start of the dragging sequence.\n * @param event Event that triggered the dropping sequence.\n *\n * @breaking-change 15.0.0 `previousIndex` and `event` parameters to become required.\n */", + "rawComment": "/**\n * Drops an item into this container.\n * @param item Item being dropped into the container.\n * @param currentIndex Index at which the item should be inserted.\n * @param previousIndex Index of the item when dragging started.\n * @param previousContainer Container from which the item got dragged in.\n * @param isPointerOverContainer Whether the user's pointer was over the\n * container when the item was dropped.\n * @param distance Distance the user has dragged since the start of the dragging sequence.\n * @param event Event that triggered the dropping sequence.\n */", "memberType": "method", "memberTags": [] }, @@ -4835,7 +4823,7 @@ "source": { "filePath": "/src/cdk/drag-drop/drop-list-ref.ts", "startLine": 73, - "endLine": 805 + "endLine": 803 } }, { @@ -6274,7 +6262,7 @@ "@angular/core" ], [ - "Injectable", + "Service", "@angular/core" ], [ @@ -6294,11 +6282,11 @@ "@angular/core" ], [ - "OnDestroy", + "Input", "@angular/core" ], [ - "Input", + "OnDestroy", "@angular/core" ], [ @@ -6329,10 +6317,6 @@ "ChangeDetectorRef", "@angular/core" ], - [ - "ChangeDetectionStrategy", - "@angular/core" - ], [ "Component", "@angular/core" @@ -6422,11 +6406,11 @@ "@angular/cdk/drag-drop" ], [ - "CDK_DROP_LIST_GROUP", + "CdkDragRelease", "@angular/cdk/drag-drop" ], [ - "CdkDragRelease", + "CDK_DROP_LIST_GROUP", "@angular/cdk/drag-drop" ], [ @@ -6438,11 +6422,11 @@ "@angular/cdk/drag-drop" ], [ - "CdkDropListGroup", + "CdkDragPlaceholder", "@angular/cdk/drag-drop" ], [ - "CdkDragPlaceholder", + "CdkDropListGroup", "@angular/cdk/drag-drop" ], [ @@ -6593,10 +6577,6 @@ "DropListOrientation", "@angular/cdk/drag-drop" ], - [ - "CDK_DROP_LIST_GROUP", - "@angular/cdk/drag-drop" - ], [ "CdkDragRelease", "@angular/cdk/drag-drop" @@ -6609,6 +6589,10 @@ "CdkDragRelease.event", "@angular/cdk/drag-drop" ], + [ + "CDK_DROP_LIST_GROUP", + "@angular/cdk/drag-drop" + ], [ "CDK_DRAG_PLACEHOLDER", "@angular/cdk/drag-drop" @@ -6618,31 +6602,31 @@ "@angular/cdk/drag-drop" ], [ - "CdkDropListGroup", + "CdkDragPlaceholder", "@angular/cdk/drag-drop" ], [ - "CdkDropListGroup.disabled", + "CdkDragPlaceholder.templateRef", "@angular/cdk/drag-drop" ], [ - "CdkDropListGroup.ngOnDestroy", + "CdkDragPlaceholder.data", "@angular/cdk/drag-drop" ], [ - "CdkDragPlaceholder", + "CdkDragPlaceholder.ngOnDestroy", "@angular/cdk/drag-drop" ], [ - "CdkDragPlaceholder.templateRef", + "CdkDropListGroup", "@angular/cdk/drag-drop" ], [ - "CdkDragPlaceholder.data", + "CdkDropListGroup.disabled", "@angular/cdk/drag-drop" ], [ - "CdkDragPlaceholder.ngOnDestroy", + "CdkDropListGroup.ngOnDestroy", "@angular/cdk/drag-drop" ], [ diff --git a/adev/src/content/cdk/cdk_testing_protractor.json b/adev/src/content/cdk/cdk_testing_protractor.json index 0682308049ac..9ef6126b8926 100755 --- a/adev/src/content/cdk/cdk_testing_protractor.json +++ b/adev/src/content/cdk/cdk_testing_protractor.json @@ -689,9 +689,9 @@ "rawComment": "" }, "entryType": "function", - "description": "", + "description": "Click the element at the default location for the current environment. If you need to guarantee\nthe element is clicked at a specific location, consider using `click('center')` or\n`click(x, y)` instead.", "jsdocTags": [], - "rawComment": "", + "rawComment": "/**\n * Click the element at the default location for the current environment. If you need to guarantee\n * the element is clicked at a specific location, consider using `click('center')` or\n * `click(x, y)` instead.\n */", "memberType": "method", "memberTags": [] }, @@ -765,9 +765,22 @@ "rawComment": "" }, "entryType": "function", - "description": "", - "jsdocTags": [], - "rawComment": "", + "description": "Right clicks on the element at the specified coordinates relative to the top-left of it.", + "jsdocTags": [ + { + "name": "param", + "comment": "Coordinate within the element, along the X-axis at which to click." + }, + { + "name": "param", + "comment": "Coordinate within the element, along the Y-axis at which to click." + }, + { + "name": "param", + "comment": "Modifier keys held while clicking" + } + ], + "rawComment": "/**\n * Right clicks on the element at the specified coordinates relative to the top-left of it.\n * @param relativeX Coordinate within the element, along the X-axis at which to click.\n * @param relativeY Coordinate within the element, along the Y-axis at which to click.\n * @param modifiers Modifier keys held while clicking\n */", "memberType": "method", "memberTags": [] }, @@ -988,9 +1001,9 @@ "rawComment": "" }, "entryType": "function", - "description": "", + "description": "Sends the given string to the input as a series of key presses. Also fires input events\nand attempts to add the string to the Element's value.", "jsdocTags": [], - "rawComment": "", + "rawComment": "/**\n * Sends the given string to the input as a series of key presses. Also fires input events\n * and attempts to add the string to the Element's value.\n */", "memberType": "method", "memberTags": [] }, diff --git a/adev/src/content/cdk/cdk_testing_selenium_webdriver.json b/adev/src/content/cdk/cdk_testing_selenium_webdriver.json index d7d83e46ce6e..d6e10bcd809b 100755 --- a/adev/src/content/cdk/cdk_testing_selenium_webdriver.json +++ b/adev/src/content/cdk/cdk_testing_selenium_webdriver.json @@ -226,9 +226,9 @@ "rawComment": "" }, "entryType": "function", - "description": "", + "description": "Click the element at the default location for the current environment. If you need to guarantee\nthe element is clicked at a specific location, consider using `click('center')` or\n`click(x, y)` instead.", "jsdocTags": [], - "rawComment": "", + "rawComment": "/**\n * Click the element at the default location for the current environment. If you need to guarantee\n * the element is clicked at a specific location, consider using `click('center')` or\n * `click(x, y)` instead.\n */", "memberType": "method", "memberTags": [] }, @@ -302,9 +302,22 @@ "rawComment": "" }, "entryType": "function", - "description": "", - "jsdocTags": [], - "rawComment": "", + "description": "Right clicks on the element at the specified coordinates relative to the top-left of it.", + "jsdocTags": [ + { + "name": "param", + "comment": "Coordinate within the element, along the X-axis at which to click." + }, + { + "name": "param", + "comment": "Coordinate within the element, along the Y-axis at which to click." + }, + { + "name": "param", + "comment": "Modifier keys held while clicking" + } + ], + "rawComment": "/**\n * Right clicks on the element at the specified coordinates relative to the top-left of it.\n * @param relativeX Coordinate within the element, along the X-axis at which to click.\n * @param relativeY Coordinate within the element, along the Y-axis at which to click.\n * @param modifiers Modifier keys held while clicking\n */", "memberType": "method", "memberTags": [] }, @@ -525,9 +538,9 @@ "rawComment": "" }, "entryType": "function", - "description": "", + "description": "Sends the given string to the input as a series of key presses. Also fires input events\nand attempts to add the string to the Element's value.", "jsdocTags": [], - "rawComment": "", + "rawComment": "/**\n * Sends the given string to the input as a series of key presses. Also fires input events\n * and attempts to add the string to the Element's value.\n */", "memberType": "method", "memberTags": [] }, @@ -1142,8 +1155,8 @@ "implements": [], "source": { "filePath": "/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts", - "startLine": 35, - "endLine": 38 + "startLine": 34, + "endLine": 37 } }, { @@ -1194,8 +1207,8 @@ "rawComment": "/** Waits for angular to be ready after the page load. */", "source": { "filePath": "/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts", - "startLine": 65, - "endLine": 68 + "startLine": 66, + "endLine": 69 } }, { @@ -1635,8 +1648,8 @@ "implements": [], "source": { "filePath": "/src/cdk/testing/selenium-webdriver/selenium-web-driver-harness-environment.ts", - "startLine": 71, - "endLine": 150 + "startLine": 72, + "endLine": 151 } } ], diff --git a/adev/src/content/cdk/cdk_testing_testbed.json b/adev/src/content/cdk/cdk_testing_testbed.json index 96e544225bd7..7e61d2519567 100755 --- a/adev/src/content/cdk/cdk_testing_testbed.json +++ b/adev/src/content/cdk/cdk_testing_testbed.json @@ -838,9 +838,9 @@ "rawComment": "" }, "entryType": "function", - "description": "", + "description": "Click the element at the default location for the current environment. If you need to guarantee\nthe element is clicked at a specific location, consider using `click('center')` or\n`click(x, y)` instead.", "jsdocTags": [], - "rawComment": "", + "rawComment": "/**\n * Click the element at the default location for the current environment. If you need to guarantee\n * the element is clicked at a specific location, consider using `click('center')` or\n * `click(x, y)` instead.\n */", "memberType": "method", "memberTags": [] }, @@ -914,9 +914,22 @@ "rawComment": "" }, "entryType": "function", - "description": "", - "jsdocTags": [], - "rawComment": "", + "description": "Right clicks on the element at the specified coordinates relative to the top-left of it.", + "jsdocTags": [ + { + "name": "param", + "comment": "Coordinate within the element, along the X-axis at which to click." + }, + { + "name": "param", + "comment": "Coordinate within the element, along the Y-axis at which to click." + }, + { + "name": "param", + "comment": "Modifier keys held while clicking" + } + ], + "rawComment": "/**\n * Right clicks on the element at the specified coordinates relative to the top-left of it.\n * @param relativeX Coordinate within the element, along the X-axis at which to click.\n * @param relativeY Coordinate within the element, along the Y-axis at which to click.\n * @param modifiers Modifier keys held while clicking\n */", "memberType": "method", "memberTags": [] }, @@ -1137,9 +1150,9 @@ "rawComment": "" }, "entryType": "function", - "description": "", + "description": "Sends the given string to the input as a series of key presses. Also fires input events\nand attempts to add the string to the Element's value. Note that this cannot\nreproduce native browser behavior for keyboard shortcuts such as Tab, Ctrl + A, etc.", "jsdocTags": [], - "rawComment": "", + "rawComment": "/**\n * Sends the given string to the input as a series of key presses. Also fires input events\n * and attempts to add the string to the Element's value. Note that this cannot\n * reproduce native browser behavior for keyboard shortcuts such as Tab, Ctrl + A, etc.\n */", "memberType": "method", "memberTags": [] }, diff --git a/adev/src/content/cli/_build-info.json b/adev/src/content/cli/_build-info.json index 22ceb8e02d28..f1955218b1ba 100644 --- a/adev/src/content/cli/_build-info.json +++ b/adev/src/content/cli/_build-info.json @@ -1,4 +1,4 @@ { "branchName": "refs/heads/main", - "sha": "8e9555870edb3275b35d299613ef3cf9d3bfb73a" + "sha": "62f80d27ea10c10a00194db0bb286ad960281a0d" } \ No newline at end of file diff --git a/adev/src/content/cli/generate.json b/adev/src/content/cli/generate.json index ac1e71c78a7d..40c9c2fd75bc 100644 --- a/adev/src/content/cli/generate.json +++ b/adev/src/content/cli/generate.json @@ -109,7 +109,7 @@ "aliases": [ "s" ], - "description": "Include the styles for the root component directly within the `app.component.ts` file. Only CSS styles can be included inline. By default, a separate stylesheet file (e.g., `app.component.css`) is created." + "description": "Include the styles for the root component directly within the `app.ts` file. Only CSS styles can be included inline. By default, a separate stylesheet file (e.g., `app.css`) is created." }, { "name": "inline-template", @@ -117,7 +117,7 @@ "aliases": [ "t" ], - "description": "Include the HTML template for the root component directly within the `app.component.ts` file. By default, a separate template file (e.g., `app.component.html`) is created." + "description": "Include the HTML template for the root component directly within the `app.ts` file. By default, a separate template file (e.g., `app.html`) is created." }, { "name": "minimal", @@ -285,9 +285,9 @@ "aliases": [ "c" ], - "default": "Default", + "default": "OnPush", "enum": [ - "Default", + "Eager", "OnPush" ], "description": "Configures the change detection strategy for the component." @@ -1009,6 +1009,12 @@ "default": true, "description": "Creates files at the top level of the project or the given path. If set to false, a new folder with the service's name will be created to contain the files." }, + { + "name": "injectable", + "type": "boolean", + "default": false, + "description": "When true, generates an `@Injectable` instead of `@Service`." + }, { "name": "name", "type": "string", diff --git a/adev/src/content/cli/new.json b/adev/src/content/cli/new.json index a4345b245b5a..73d26561c1e6 100644 --- a/adev/src/content/cli/new.json +++ b/adev/src/content/cli/new.json @@ -91,7 +91,7 @@ "aliases": [ "s" ], - "description": "Include the styles for the initial application's root component directly within the `app.component.ts` file. By default, a separate stylesheet file (e.g., `app.component.css`) is created." + "description": "Include the styles for the initial application's root component directly within the `app.ts` file. By default, a separate stylesheet file (e.g., `app.css`) is created." }, { "name": "inline-template", @@ -99,7 +99,7 @@ "aliases": [ "t" ], - "description": "Include the HTML template for the initial application's root component directly within the `app.component.ts` file. By default, a separate template file (e.g., `app.component.html`) is created." + "description": "Include the HTML template for the initial application's root component directly within the `app.ts` file. By default, a separate template file (e.g., `app.html`) is created." }, { "name": "interactive", diff --git a/adev/src/content/cli/test.json b/adev/src/content/cli/test.json index 80bfa6196d4e..140564488bba 100644 --- a/adev/src/content/cli/test.json +++ b/adev/src/content/cli/test.json @@ -35,8 +35,7 @@ { "name": "coverage", "type": "boolean", - "default": false, - "description": "Enables coverage reporting for tests." + "description": "Enables coverage reporting for tests. If not specified, the coverage configuration from a runner configuration file will be used if present. Otherwise, coverage is disabled by default." }, { "name": "coverage-exclude", @@ -98,6 +97,11 @@ ], "description": "Specifies glob patterns of files to include for testing, relative to the project root. This option also has special handling for directory paths (includes all test files within) and file paths (includes the corresponding test file if one exists)." }, + { + "name": "isolate", + "type": "boolean", + "description": "Enables isolation for test execution. When true, Vitest runs tests in separate threads or processes. This option is only available for the Vitest runner. Defaults to false to align with the Karma/Jasmine experience." + }, { "name": "list-tests", "type": "boolean", @@ -125,6 +129,11 @@ "type": "string", "description": "Specifies the path to a TypeScript file that provides an array of Angular providers for the test environment. The file must contain a default export of the provider array." }, + { + "name": "quiet", + "type": "boolean", + "description": "Suppresses the verbose build summary and stats table on each rebuild. Defaults to `true` locally and `false` in CI environments." + }, { "name": "reporters", "type": "array", @@ -143,7 +152,7 @@ { "name": "runner-config", "type": "string", - "description": "Specifies the configuration file for the selected test runner. If a string is provided, it will be used as the path to the configuration file. If `true`, the builder will search for a default configuration file (e.g., `vitest.config.ts` or `karma.conf.js`). If `false`, no external configuration file will be used.\\nFor Vitest, this enables advanced options and the use of custom plugins. Please note that while the file is loaded, the Angular team does not provide direct support for its specific contents or any third-party plugins used within it." + "description": "Specifies the configuration file for the selected test runner. If a string is provided, it will be used as the path to the configuration file. If `true`, the builder will search for a default configuration file (e.g., `vitest-base.config.ts` or `karma.conf.js`). If `false`, no external configuration file will be used.\\nFor Vitest, this enables advanced options and the use of custom plugins. Please note that while the file is loaded, the Angular team does not provide direct support for its specific contents or any third-party plugins used within it." }, { "name": "setup-files", diff --git a/adev/src/content/cli/update.json b/adev/src/content/cli/update.json index d2ec6d311d4d..dab07561bd13 100644 --- a/adev/src/content/cli/update.json +++ b/adev/src/content/cli/update.json @@ -3,7 +3,7 @@ "command": "ng update [packages..]", "shortDescription": "Updates your workspace and its dependencies. See https://update.angular.dev/.", "longDescriptionRelativePath": "@angular/cli/src/commands/update/long-description.md", - "longDescription": "Perform a basic update to the current stable release of the core framework and CLI by running the following command.\n\n```\nng update @angular/cli @angular/core\n```\n\nTo update to the next beta or pre-release version, use the `--next` option.\n\nTo update from one major version to another, use the format\n\n```\nng update @angular/cli@^ @angular/core@^\n```\n\nWe recommend that you always update to the latest patch version, as it contains fixes we released since the initial major release.\nFor example, use the following command to take the latest 10.x.x version and use that to update.\n\n```\nng update @angular/cli@^10 @angular/core@^10\n```\n\nFor detailed information and guidance on updating your application, see the interactive [Angular Update Guide](https://update.angular.dev/).\n", + "longDescription": "Perform a basic update to the current stable release of the core framework and CLI by running the following command.\n\n```\nng update @angular/cli @angular/core\n```\n\nTo update to the next beta or pre-release version, use the `--next` option.\n\nTo update from one major version to another, use the format\n\n```\nng update @angular/cli@^ @angular/core@^\n```\n\nWe recommend that you always update to the latest patch version, as it contains fixes we released since the initial major release.\nFor example, use the following command to take the latest 21.x.x version and use that to update.\n\n```\nng update @angular/cli@^21 @angular/core@^21\n```\n\nFor detailed information and guidance on updating your application, see the interactive [Angular Update Guide](/update-guide).\n", "aliases": [], "deprecated": false, "options": [ diff --git a/adev/src/content/ecosystem/custom-build-pipeline.md b/adev/src/content/ecosystem/custom-build-pipeline.md index 6670076a62ee..d2bab6eba505 100644 --- a/adev/src/content/ecosystem/custom-build-pipeline.md +++ b/adev/src/content/ecosystem/custom-build-pipeline.md @@ -10,7 +10,7 @@ There are some niche use cases when you may want to maintain a custom build pipe - You have an existing app using a different toolchain and you’d like to add Angular to it - You’re strongly coupled to [module federation](https://module-federation.io/) and unable to adopt bundler-agnostic [native federation](https://www.npmjs.com/package/@angular-architects/native-federation) -- You’d like to create an short-lived experiment using your favorite build tool +- You’d like to create a short-lived experiment using your favorite build tool ## What are the options? diff --git a/adev/src/content/ecosystem/rxjs-interop/output-interop.md b/adev/src/content/ecosystem/rxjs-interop/output-interop.md index 5990164ab756..ae5abe8718f0 100644 --- a/adev/src/content/ecosystem/rxjs-interop/output-interop.md +++ b/adev/src/content/ecosystem/rxjs-interop/output-interop.md @@ -37,7 +37,7 @@ The `outputToObservable` function lets you create an RxJS observable from a comp import {outputToObservable} from '@angular/core/rxjs-interop'; @Component(/*...*/) - class CustomSlider { +class CustomSlider { valueChange = output(); } diff --git a/adev/src/content/ecosystem/rxjs-interop/signals-interop.md b/adev/src/content/ecosystem/rxjs-interop/signals-interop.md index 98facdb29754..3e8fd13c9ce0 100644 --- a/adev/src/content/ecosystem/rxjs-interop/signals-interop.md +++ b/adev/src/content/ecosystem/rxjs-interop/signals-interop.md @@ -47,7 +47,7 @@ If you don't provide an `initialValue`, the resulting signal will return `undefi Some Observables are guaranteed to emit synchronously, such as `BehaviorSubject`. In those cases, you can specify the `requireSync: true` option. -When `requiredSync` is `true`, `toSignal` enforces that the Observable emits synchronously on subscription. This guarantees that the signal always has a value, and no `undefined` type or initial value is required. +When `requireSync` is `true`, `toSignal` enforces that the Observable emits synchronously on subscription. This guarantees that the signal always has a value, and no `undefined` type or initial value is required. ### `manualCleanup` @@ -128,8 +128,6 @@ Here, only the last value (3) will be logged. ## Using `rxResource` for async data -IMPORTANT: `rxResource` is [experimental](reference/releases#experimental). It's ready for you to try, but it might change before it is stable. - Angular's [`resource` function](/guide/signals/resource) gives you a way to incorporate async data into your application's signal-based code. Building on top of this pattern, `rxResource` lets you define a resource where the source of your data is defined in terms of an RxJS `Observable`. Instead of accepting a `loader` function, `rxResource` accepts a `stream` function that accepts an RxJS `Observable`. ```typescript diff --git a/adev/src/content/ecosystem/service-workers/config.md b/adev/src/content/ecosystem/service-workers/config.md index 9a6500f88754..d0ea43baa8da 100644 --- a/adev/src/content/ecosystem/service-workers/config.md +++ b/adev/src/content/ecosystem/service-workers/config.md @@ -85,7 +85,7 @@ For example, an asset group that matches `/foo.js` should appear before one that Each asset group specifies both a group of resources and a policy that governs them. This policy determines when the resources are fetched and what happens when changes are detected. -Asset groups follow the Typescript interface shown here: +Asset groups follow the TypeScript interface shown here: ```ts interface AssetGroup { @@ -179,7 +179,7 @@ The first data group that matches the requested resource handles the request. It is recommended that you put the more specific data groups higher in the list. For example, a data group that matches `/api/foo.json` should appear before one that matches `/api/*.json`. -Data groups follow this Typescript interface: +Data groups follow this TypeScript interface: ```ts export interface DataGroup { diff --git a/adev/src/content/ecosystem/service-workers/devops.md b/adev/src/content/ecosystem/service-workers/devops.md index a2fc1876eac2..498cceabd69f 100644 --- a/adev/src/content/ecosystem/service-workers/devops.md +++ b/adev/src/content/ecosystem/service-workers/devops.md @@ -283,7 +283,7 @@ When the service worker's request for `ngsw.json` returns a `404`, then the serv -A small script, `safety-worker.js`, is also included in the `@angular/service-worker` NPM package. +A small script, `safety-worker.js`, is also included in the `@angular/service-worker` npm package. When loaded, it un-registers itself from the browser and removes the service worker caches. This script can be used as a last resort to get rid of unwanted service workers already installed on client pages. diff --git a/adev/src/content/events/v21.md b/adev/src/content/events/v21.md index e1f5ace3b672..46838e632d2e 100644 --- a/adev/src/content/events/v21.md +++ b/adev/src/content/events/v21.md @@ -22,16 +22,4 @@ Angular v21 is being delivered to you as a brand new release adventure. With mod - Your first look at Signal Forms, our new streamlined, signal-based approach to forms in Angular - Exciting new details about the Angular Aria package -
    - -
    + diff --git a/adev/src/content/examples/animations/src/app/enter-and-leave/leave-parent.html b/adev/src/content/examples/animations/src/app/enter-and-leave/leave-parent.html index 06c295b6381a..39d5897b2e34 100644 --- a/adev/src/content/examples/animations/src/app/enter-and-leave/leave-parent.html +++ b/adev/src/content/examples/animations/src/app/enter-and-leave/leave-parent.html @@ -1,13 +1,12 @@ -

    animate.leave Parent Sub-tree No Animation Example

    +

    animate.leave Parent Sub-tree Animation Example

    @if (isShown()) { -
    +

    Goodbye

    diff --git a/adev/src/content/examples/aria/accordion/src/disabled-focusable/basic/app/app.html b/adev/src/content/examples/aria/accordion/src/disabled-focusable/basic/app/app.html index e8fc4e316141..e44f5d0e5af1 100644 --- a/adev/src/content/examples/aria/accordion/src/disabled-focusable/basic/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/disabled-focusable/basic/app/app.html @@ -1,6 +1,6 @@

    - + Which attribute tells assistive tech whether the panel is open or closed?

    -
    +

    Use aria-expanded on the button element. Set it to "true" when the content @@ -20,7 +20,7 @@

    - + How do you link the button to the content it controls?

    -
    +

    Use the aria-controls attribute on the button, and set its value to match the @@ -40,7 +40,7 @@

    - + What role should the heading element containing the accordion button have?

    -
    +

    The element containing the button should typically have role="heading" with an diff --git a/adev/src/content/examples/aria/accordion/src/disabled-focusable/material/app/app.html b/adev/src/content/examples/aria/accordion/src/disabled-focusable/material/app/app.html index e433b2933853..d04a1fcabc8a 100644 --- a/adev/src/content/examples/aria/accordion/src/disabled-focusable/material/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/disabled-focusable/material/app/app.html @@ -1,6 +1,6 @@

    - + Which attribute tells assistive tech whether the panel is open or closed?

    -
    +

    Use aria-expanded on the button element. Set it to "true" when the content @@ -22,7 +22,7 @@

    - + How do you link the button to the content it controls?

    -
    +

    Use the aria-controls attribute on the button, and set its value to match the @@ -44,7 +44,7 @@

    - + What role should the heading element containing the accordion button have?

    -
    +

    The element containing the button should typically have role="heading" with an diff --git a/adev/src/content/examples/aria/accordion/src/disabled-focusable/retro/app/app.css b/adev/src/content/examples/aria/accordion/src/disabled-focusable/retro/app/app.css index ab6ba7e6937a..e58ac6398285 100644 --- a/adev/src/content/examples/aria/accordion/src/disabled-focusable/retro/app/app.css +++ b/adev/src/content/examples/aria/accordion/src/disabled-focusable/retro/app/app.css @@ -6,28 +6,28 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--page-background)); --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast), + 0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast), + 0px -4px 0px 0px var(--quaternary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--quaternary-contrast), 0px 4px 0px 0px var(--quaternary-contrast), + -4px 0px 0px 0px var(--quaternary-contrast), 0px -4px 0px 0px var(--quaternary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast), + 0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast), + 0px -4px 0px 0px var(--quaternary-contrast), 8px 8px 0px 0px var(--quaternary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--quaternary-contrast), + 0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast), + 0px -4px 0px 0px var(--quaternary-contrast), 0px 0px 0px 0px var(--quaternary-contrast); } [ngAccordionGroup] { diff --git a/adev/src/content/examples/aria/accordion/src/disabled-focusable/retro/app/app.html b/adev/src/content/examples/aria/accordion/src/disabled-focusable/retro/app/app.html index 17704c597722..ce9b5674203a 100644 --- a/adev/src/content/examples/aria/accordion/src/disabled-focusable/retro/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/disabled-focusable/retro/app/app.html @@ -1,41 +1,41 @@

    - + Unlock Treasure Box

    -
    +
    👻

    - + Unlock Treasure Box

    -
    +
    💎💎💎

    - + Unlock Treasure Box

    -
    +
    🐸 ribbit...ribbit...
    diff --git a/adev/src/content/examples/aria/accordion/src/multi-expansion/basic/app/app.html b/adev/src/content/examples/aria/accordion/src/multi-expansion/basic/app/app.html index c9cfc0d1e625..7c49bde5b7b6 100644 --- a/adev/src/content/examples/aria/accordion/src/multi-expansion/basic/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/multi-expansion/basic/app/app.html @@ -1,6 +1,6 @@

    - + Which attribute tells assistive tech whether the panel is open or closed?

    -
    +

    Use aria-expanded on the button element. Set it to "true" when the content @@ -20,7 +20,7 @@

    - + How do you link the button to the content it controls?

    -
    +

    Use the aria-controls attribute on the button, and set its value to match the @@ -40,7 +40,7 @@

    - + What role should the heading element containing the accordion button have?

    -
    +

    The element containing the button should typically have role="heading" with an diff --git a/adev/src/content/examples/aria/accordion/src/multi-expansion/material/app/app.html b/adev/src/content/examples/aria/accordion/src/multi-expansion/material/app/app.html index 427516aa7d85..3c8a33069ef5 100644 --- a/adev/src/content/examples/aria/accordion/src/multi-expansion/material/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/multi-expansion/material/app/app.html @@ -1,6 +1,6 @@

    - + Which attribute tells assistive tech whether the panel is open or closed?

    -
    +

    Use aria-expanded on the button element. Set it to "true" when the content @@ -22,7 +22,7 @@

    - + How do you link the button to the content it controls?

    -
    +

    Use the aria-controls attribute on the button, and set its value to match the @@ -44,7 +44,7 @@

    - + What role should the heading element containing the accordion button have?

    -
    +

    The element containing the button should typically have role="heading" with an diff --git a/adev/src/content/examples/aria/accordion/src/multi-expansion/retro/app/app.css b/adev/src/content/examples/aria/accordion/src/multi-expansion/retro/app/app.css index ab6ba7e6937a..913fdc3a7341 100644 --- a/adev/src/content/examples/aria/accordion/src/multi-expansion/retro/app/app.css +++ b/adev/src/content/examples/aria/accordion/src/multi-expansion/retro/app/app.css @@ -6,28 +6,28 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--page-background)); --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngAccordionGroup] { diff --git a/adev/src/content/examples/aria/accordion/src/multi-expansion/retro/app/app.html b/adev/src/content/examples/aria/accordion/src/multi-expansion/retro/app/app.html index 8bf9feec945a..bed63470483b 100644 --- a/adev/src/content/examples/aria/accordion/src/multi-expansion/retro/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/multi-expansion/retro/app/app.html @@ -1,41 +1,41 @@

    - + Unlock Treasure Box

    -
    +
    👻

    - + Unlock Treasure Box

    -
    +
    💎💎💎

    - + Unlock Treasure Box

    -
    +
    🐸 ribbit...ribbit...
    diff --git a/adev/src/content/examples/aria/accordion/src/single-expansion/basic/app/app.html b/adev/src/content/examples/aria/accordion/src/single-expansion/basic/app/app.html index 011e67e1b56d..5115cdae88d5 100644 --- a/adev/src/content/examples/aria/accordion/src/single-expansion/basic/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/single-expansion/basic/app/app.html @@ -1,6 +1,6 @@

    - + Which attribute tells assistive tech whether the panel is open or closed?

    -
    +

    Use aria-expanded on the button element. Set it to "true" when the content @@ -20,7 +20,7 @@

    - + How do you link the button to the content it controls?

    -
    +

    Use the aria-controls attribute on the button, and set its value to match the @@ -40,7 +40,7 @@

    - + What role should the heading element containing the accordion button have?

    -
    +

    The element containing the button should typically have role="heading" with an diff --git a/adev/src/content/examples/aria/accordion/src/single-expansion/material/app/app.html b/adev/src/content/examples/aria/accordion/src/single-expansion/material/app/app.html index acc3c6f34c9f..e848c2b113a5 100644 --- a/adev/src/content/examples/aria/accordion/src/single-expansion/material/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/single-expansion/material/app/app.html @@ -1,6 +1,6 @@

    - + Which attribute tells assistive tech whether the panel is open or closed?

    -
    +

    Use aria-expanded on the button element. Set it to "true" when the content @@ -22,7 +22,7 @@

    - + How do you link the button to the content it controls?

    -
    +

    Use the aria-controls attribute on the button, and set its value to match the @@ -44,7 +44,7 @@

    - + What role should the heading element containing the accordion button have?

    -
    +

    The element containing the button should typically have role="heading" with an diff --git a/adev/src/content/examples/aria/accordion/src/single-expansion/retro/app/app.css b/adev/src/content/examples/aria/accordion/src/single-expansion/retro/app/app.css index ab6ba7e6937a..913fdc3a7341 100644 --- a/adev/src/content/examples/aria/accordion/src/single-expansion/retro/app/app.css +++ b/adev/src/content/examples/aria/accordion/src/single-expansion/retro/app/app.css @@ -6,28 +6,28 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--page-background)); --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngAccordionGroup] { diff --git a/adev/src/content/examples/aria/accordion/src/single-expansion/retro/app/app.html b/adev/src/content/examples/aria/accordion/src/single-expansion/retro/app/app.html index d1eb1b91b0d8..6da53927589e 100644 --- a/adev/src/content/examples/aria/accordion/src/single-expansion/retro/app/app.html +++ b/adev/src/content/examples/aria/accordion/src/single-expansion/retro/app/app.html @@ -1,41 +1,41 @@

    - + Unlock Treasure Box

    -
    +
    👻

    - + Unlock Treasure Box

    -
    +
    💎💎💎

    - + Unlock Treasure Box

    -
    +
    🐸 ribbit...ribbit...
    diff --git a/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css b/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css deleted file mode 100644 index 45e65487e775..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/assets/autocomplete.css +++ /dev/null @@ -1,89 +0,0 @@ -html { - font-family: var(--inter-font); -} - -.combobox-container { - max-width: 400px; - margin: 20px; -} - -label { - display: block; - margin-bottom: 8px; - font-weight: 500; - color: #e0e0e0; -} - -.input-container { - position: relative; -} - -.combobox-input { - width: 100%; - padding: 10px 12px; - border: 1px solid #404040; - border-radius: 4px; - font-size: 16px; - box-sizing: border-box; - background-color: #1a1a1a; - color: #e0e0e0; -} - -.combobox-input::placeholder { - color: #888; -} - -.combobox-input:focus { - outline: none; - border-color: #4a9eff; - background-color: #1f1f1f; -} - -.popover { - margin: 0; - padding: 0; - border: 1px solid #404040; - border-radius: 4px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5); - background: #1a1a1a; - max-height: 300px; - overflow-y: auto; -} - -.listbox { - padding: 4px 0; -} - -.option { - padding: 10px 12px; - cursor: pointer; - user-select: none; - color: #e0e0e0; -} - -.option:hover { - background-color: #2a2a2a; -} - -.option[data-active] { - background-color: #2d4a6e; - color: #ffffff; -} - -.option[aria-selected='true'] { - background-color: #4a9eff; - color: #000000; -} - -.info { - margin: 20px; - padding: 16px; - background-color: #1f1f1f; - border-radius: 4px; - border-left: 4px solid #4a9eff; - color: #e0e0e0; -} - -.info p { - margin: 8px 0; -} diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.css b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.css deleted file mode 100644 index c7aee9916952..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.css +++ /dev/null @@ -1,101 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: var(--quaternary-contrast); -} - -[ngComboboxInput] { - width: 13rem; - font-size: 1rem; - border-radius: 0.25rem; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: var(--primary-contrast); - outline-color: var(--hot-pink); - border: 1px solid var(--quinary-contrast); - background-color: var(--page-background); -} - -[ngComboboxInput]::placeholder { - color: var(--quaternary-contrast); -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 8px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.html b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.html deleted file mode 100644 index 583ced285ed4..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/basic/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.css b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.css deleted file mode 100644 index c41ab594e7f3..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.css +++ /dev/null @@ -1,108 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); -} - -.material-autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: var(--quaternary-contrast); -} - -[ngComboboxInput] { - width: 13rem; - font-size: 1rem; - border-radius: 3rem; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: var(--primary-contrast); - outline-color: var(--primary); - border: 1px solid var(--quinary-contrast); - background-color: var(--page-background); -} - -[ngComboboxInput]::placeholder { - color: var(--quaternary-contrast); -} - -[ngCombobox]:focus-within [ngComboboxInput] { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 8px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 3rem; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} \ No newline at end of file diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.html b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.html deleted file mode 100644 index 0f2b696936a7..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/material/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.css b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.css deleted file mode 100644 index 60e04c6fecf4..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.css +++ /dev/null @@ -1,130 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.6rem; - font-family: 'Press Start 2P'; - - --retro-button-color: #fff; - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.retro-autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: #000; - z-index: 1; -} - -[ngComboboxInput] { - width: 15rem; - font-size: 0.6rem; - border-radius: 0; - font-family: 'Press Start 2P'; - word-spacing: -5px; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: #000; - border: none; - box-shadow: var(--retro-flat-shadow); - background-color: var(--retro-button-color); -} - -[ngComboboxInput]::placeholder { - color: #000; - opacity: 0.7; -} - -[ngComboboxInput]:focus { - outline: none; - transform: translate(1px, 1px); - box-shadow: var(--retro-pressed-shadow); -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 20px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 0; - background-color: var(--septenary-contrast); - box-shadow: var(--retro-flat-shadow); -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.html b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.html deleted file mode 100644 index f3dac2851118..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/basic/retro/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.css b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.css deleted file mode 100644 index c7aee9916952..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.css +++ /dev/null @@ -1,101 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: var(--quaternary-contrast); -} - -[ngComboboxInput] { - width: 13rem; - font-size: 1rem; - border-radius: 0.25rem; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: var(--primary-contrast); - outline-color: var(--hot-pink); - border: 1px solid var(--quinary-contrast); - background-color: var(--page-background); -} - -[ngComboboxInput]::placeholder { - color: var(--quaternary-contrast); -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 8px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.html b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.html deleted file mode 100644 index 814e3ece9f4f..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.css b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.css deleted file mode 100644 index c41ab594e7f3..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.css +++ /dev/null @@ -1,108 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); -} - -.material-autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: var(--quaternary-contrast); -} - -[ngComboboxInput] { - width: 13rem; - font-size: 1rem; - border-radius: 3rem; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: var(--primary-contrast); - outline-color: var(--primary); - border: 1px solid var(--quinary-contrast); - background-color: var(--page-background); -} - -[ngComboboxInput]::placeholder { - color: var(--quaternary-contrast); -} - -[ngCombobox]:focus-within [ngComboboxInput] { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 8px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 3rem; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} \ No newline at end of file diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.html b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.html deleted file mode 100644 index 776d3676d95c..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/material/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.css b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.css deleted file mode 100644 index 60e04c6fecf4..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.css +++ /dev/null @@ -1,130 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.6rem; - font-family: 'Press Start 2P'; - - --retro-button-color: #fff; - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.retro-autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: #000; - z-index: 1; -} - -[ngComboboxInput] { - width: 15rem; - font-size: 0.6rem; - border-radius: 0; - font-family: 'Press Start 2P'; - word-spacing: -5px; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: #000; - border: none; - box-shadow: var(--retro-flat-shadow); - background-color: var(--retro-button-color); -} - -[ngComboboxInput]::placeholder { - color: #000; - opacity: 0.7; -} - -[ngComboboxInput]:focus { - outline: none; - transform: translate(1px, 1px); - box-shadow: var(--retro-pressed-shadow); -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 20px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 0; - background-color: var(--septenary-contrast); - box-shadow: var(--retro-flat-shadow); -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.html b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.html deleted file mode 100644 index c3ad881c85bb..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/highlight/retro/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.css b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.css deleted file mode 100644 index c7aee9916952..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.css +++ /dev/null @@ -1,101 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: var(--quaternary-contrast); -} - -[ngComboboxInput] { - width: 13rem; - font-size: 1rem; - border-radius: 0.25rem; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: var(--primary-contrast); - outline-color: var(--hot-pink); - border: 1px solid var(--quinary-contrast); - background-color: var(--page-background); -} - -[ngComboboxInput]::placeholder { - color: var(--quaternary-contrast); -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 8px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.html b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.html deleted file mode 100644 index dd94d75f34cf..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/manual/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.css b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.css deleted file mode 100644 index c41ab594e7f3..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.css +++ /dev/null @@ -1,108 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); -} - -.material-autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: var(--quaternary-contrast); -} - -[ngComboboxInput] { - width: 13rem; - font-size: 1rem; - border-radius: 3rem; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: var(--primary-contrast); - outline-color: var(--primary); - border: 1px solid var(--quinary-contrast); - background-color: var(--page-background); -} - -[ngComboboxInput]::placeholder { - color: var(--quaternary-contrast); -} - -[ngCombobox]:focus-within [ngComboboxInput] { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 8px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 3rem; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} \ No newline at end of file diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.html b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.html deleted file mode 100644 index c0b9048f5e1a..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/material/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.css b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.css deleted file mode 100644 index 60e04c6fecf4..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.css +++ /dev/null @@ -1,130 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.6rem; - font-family: 'Press Start 2P'; - - --retro-button-color: #fff; - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.retro-autocomplete { - display: flex; - position: relative; - align-items: center; -} - -.material-symbols-outlined { - font-size: 1.25rem; - pointer-events: none; -} - -.search-icon { - left: 0.75rem; - position: absolute; - color: #000; - z-index: 1; -} - -[ngComboboxInput] { - width: 15rem; - font-size: 0.6rem; - border-radius: 0; - font-family: 'Press Start 2P'; - word-spacing: -5px; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: #000; - border: none; - box-shadow: var(--retro-flat-shadow); - background-color: var(--retro-button-color); -} - -[ngComboboxInput]::placeholder { - color: #000; - opacity: 0.7; -} - -[ngComboboxInput]:focus { - outline: none; - transform: translate(1px, 1px); - box-shadow: var(--retro-pressed-shadow); -} - -[ngCombobox]:has([aria-expanded='false']) .popup { - display: none; -} - -.popup { - width: 100%; - margin-top: 20px; - padding: 0.5rem; - max-height: 11rem; - border-radius: 0; - background-color: var(--septenary-contrast); - box-shadow: var(--retro-flat-shadow); -} - -.no-results { - padding: 1rem; -} - -[ngListbox] { - gap: 2px; - height: 100%; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.html b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.html deleted file mode 100644 index 3b387d4c955a..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.html +++ /dev/null @@ -1,37 +0,0 @@ -
    -
    - - -
    - - - - - - -
    diff --git a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.ts b/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.ts deleted file mode 100644 index eace85787639..000000000000 --- a/adev/src/content/examples/aria/autocomplete/src/manual/retro/app/app.ts +++ /dev/null @@ -1,261 +0,0 @@ -import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {OverlayModule} from '@angular/cdk/overlay'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: 'app.html', - styleUrl: 'app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - FormsModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 query string used to filter the list of countries. */ - query = signal(''); - - /** The list of countries filtered by the query. */ - countries = computed(() => - ALL_COUNTRIES.filter((country) => country.toLowerCase().startsWith(this.query().toLowerCase())), - ); - - constructor() { - // 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); - } - }); - } -} - -const ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/combobox/src/auto-select/app/app.css b/adev/src/content/examples/aria/combobox/src/auto-select/app/app.css deleted file mode 100644 index bc6e9c409adb..000000000000 --- a/adev/src/content/examples/aria/combobox/src/auto-select/app/app.css +++ /dev/null @@ -1,9 +0,0 @@ -h1 { - background: #1e1e1e; - color: #e0e0e0; - padding: 2rem; - margin: 0; - font-family: system-ui, -apple-system, sans-serif; - font-size: 2rem; - text-align: center; -} diff --git a/adev/src/content/examples/aria/combobox/src/auto-select/app/app.html b/adev/src/content/examples/aria/combobox/src/auto-select/app/app.html deleted file mode 100644 index d39d7de603bc..000000000000 --- a/adev/src/content/examples/aria/combobox/src/auto-select/app/app.html +++ /dev/null @@ -1 +0,0 @@ -

    PLACEHOLDER

    diff --git a/adev/src/content/examples/aria/combobox/src/auto-select/app/app.ts b/adev/src/content/examples/aria/combobox/src/auto-select/app/app.ts deleted file mode 100644 index 183576f2c249..000000000000 --- a/adev/src/content/examples/aria/combobox/src/auto-select/app/app.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {Component} from '@angular/core'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', -}) -export class App {} diff --git a/adev/src/content/examples/aria/combobox/src/basic/app/app.css b/adev/src/content/examples/aria/combobox/src/basic/app/app.css deleted file mode 100644 index bc6e9c409adb..000000000000 --- a/adev/src/content/examples/aria/combobox/src/basic/app/app.css +++ /dev/null @@ -1,9 +0,0 @@ -h1 { - background: #1e1e1e; - color: #e0e0e0; - padding: 2rem; - margin: 0; - font-family: system-ui, -apple-system, sans-serif; - font-size: 2rem; - text-align: center; -} diff --git a/adev/src/content/examples/aria/combobox/src/basic/app/app.html b/adev/src/content/examples/aria/combobox/src/basic/app/app.html deleted file mode 100644 index d39d7de603bc..000000000000 --- a/adev/src/content/examples/aria/combobox/src/basic/app/app.html +++ /dev/null @@ -1 +0,0 @@ -

    PLACEHOLDER

    diff --git a/adev/src/content/examples/aria/combobox/src/basic/app/app.ts b/adev/src/content/examples/aria/combobox/src/basic/app/app.ts deleted file mode 100644 index 183576f2c249..000000000000 --- a/adev/src/content/examples/aria/combobox/src/basic/app/app.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {Component} from '@angular/core'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', -}) -export class App {} diff --git a/adev/src/content/examples/aria/combobox/src/dialog/app/app.css b/adev/src/content/examples/aria/combobox/src/dialog/app/app.css deleted file mode 100644 index d2fb9cd08940..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/app/app.css +++ /dev/null @@ -1,175 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font, system-ui, sans-serif); - --border-color: color-mix(in srgb, var(--full-contrast, #000) 20%, var(--page-background, #fff)); -} - -[ngCombobox] { - position: relative; - width: 100%; - display: flex; - flex-direction: column; - border: 1px solid var(--border-color); - border-radius: 0.25rem; -} - -[ngCombobox]:has([readonly='true']) { - width: 15rem; -} - -.combobox-input-container { - display: flex; - position: relative; - align-items: center; - border-radius: 0.25rem; -} - -[ngComboboxInput] { - border-radius: 0.25rem; -} - -[ngComboboxInput][readonly='true'] { - cursor: pointer; - padding: 0.7rem 1rem; -} - -[ngCombobox]:focus-within [ngComboboxInput] { - outline: none; - box-shadow: none; -} - -.icon { - width: 24px; - height: 24px; - font-size: 20px; - display: grid; - place-items: center; - pointer-events: none; -} - -.search-icon { - padding: 0 0.5rem; - position: absolute; - opacity: 0.8; -} - -.arrow-icon { - padding: 0 0.5rem; - position: absolute; - right: 0; - opacity: 0.8; - transition: transform 0.2s ease; -} - -[ngComboboxInput][aria-expanded='true'] + .arrow-icon { - transform: rotate(180deg); -} - -[ngComboboxInput] { - width: 100%; - border: none; - outline: none; - font-size: 1rem; - padding: 0.7rem 1rem 0.7rem 2.5rem; - background-color: var(--septenary-contrast, #f5f5f5); - color: var(--primary-contrast, #1a1a1a); -} - -.popover { - margin: 0; - padding: 0; - border: 1px solid var(--border-color); - border-radius: 0.25rem; - background-color: var(--septenary-contrast, #f5f5f5); -} - -[ngListbox] { - gap: 2px; - max-height: 200px; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast, #1a1a1a) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--vivid-pink, #f542a4); -} - -[ngOption][aria-selected='true'] { - color: var(--vivid-pink, #f542a4); - background-color: color-mix(in srgb, var(--vivid-pink, #f542a4) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} - -.dialog { - position: absolute; - left: auto; - right: auto; - top: auto; - bottom: auto; - padding: 0; - border: 1px solid var(--border-color); - border-radius: 0.25rem; - background-color: var(--septenary-contrast, #f5f5f5); - color: inherit; -} - -.dialog .combobox-input-container { - border-radius: 0; -} - -.dialog [ngCombobox], -.dialog .combobox-input-container { - border: none; -} - -.dialog [ngComboboxInput] { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -[ngCombobox]:focus-within [ngComboboxInput]:not(.combobox-input) { - outline: 1.5px solid var(--vivid-pink); - box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent); -} - -.dialog .combobox-input-container { - border-bottom: 1px solid var(--border-color); -} - -.dialog::backdrop { - opacity: 0; -} - -.no-results { - padding: 1rem; -} diff --git a/adev/src/content/examples/aria/combobox/src/dialog/app/app.html b/adev/src/content/examples/aria/combobox/src/dialog/app/app.html deleted file mode 100644 index c1382a7dbaef..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/app/app.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    - - -
    - - - -
    -
    - - -
    - - - @if (options().length === 0) { -
    No results found
    - } - -
    - @for (option of options(); track option) { -
    - {{ option }} - -
    - } -
    -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/combobox/src/dialog/app/app.ts b/adev/src/content/examples/aria/combobox/src/dialog/app/app.ts deleted file mode 100644 index ca035fdf4cf0..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/app/app.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { - Combobox, - ComboboxDialog, - ComboboxInput, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, untracked, viewChild} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - ComboboxDialog, - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - FormsModule, - ], -}) -export class App { - dialog = viewChild(ComboboxDialog); - listbox = viewChild>(Listbox); - combobox = viewChild>(Combobox); - - value = signal(''); - searchString = signal(''); - - options = computed(() => - ALL_COUNTRIES.filter((country) => - country.toLowerCase().startsWith(this.searchString().toLowerCase()), - ), - ); - - selectedCountries = signal([]); - - constructor() { - afterRenderEffect(() => { - if (this.dialog() && this.combobox()?.expanded()) { - untracked(() => this.listbox()?.gotoFirst()); - this.positionDialog(); - } - }); - - afterRenderEffect(() => { - if (this.selectedCountries().length > 0) { - untracked(() => this.dialog()?.close()); - this.value.set(this.selectedCountries()[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 ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.css b/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.css deleted file mode 100644 index 8d5cb7d28645..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.css +++ /dev/null @@ -1,165 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font, system-ui, sans-serif); - --border-color: color-mix(in srgb, var(--full-contrast, #000) 20%, var(--page-background, #fff)); - --primary: var(--vivid-pink, #f542a4); -} - -[ngCombobox] { - position: relative; - width: 100%; - display: flex; - flex-direction: column; - border: 1px solid var(--border-color); - border-radius: 1rem; -} - -[ngCombobox]:has([readonly='true']) { - width: 15rem; -} - -.combobox-input-container { - display: flex; - position: relative; - align-items: center; - border-radius: 1rem; -} - -[ngComboboxInput] { - border-radius: 1rem; -} - -[ngComboboxInput][readonly='true'] { - cursor: pointer; - padding: 0.7rem 1rem; -} - -[ngCombobox]:focus-within [ngComboboxInput] { - outline: none; - box-shadow: none; -} - -.icon { - width: 24px; - height: 24px; - font-size: 20px; - display: grid; - place-items: center; - pointer-events: none; -} - -.search-icon { - padding: 0 0.5rem; - position: absolute; - opacity: 0.8; -} - -.arrow-icon { - padding: 0 0.5rem; - position: absolute; - right: 0; - opacity: 0.8; - transition: transform 0.2s ease; -} - -[ngComboboxInput][aria-expanded='true'] + .arrow-icon { - transform: rotate(180deg); -} - -[ngComboboxInput] { - width: 100%; - border: none; - outline: none; - font-size: 1rem; - padding: 0.7rem 1rem 0.7rem 2.5rem; - background-color: var(--septenary-contrast, #f5f5f5); - color: var(--primary-contrast, #1a1a1a); -} - -[ngListbox] { - gap: 2px; - max-height: 10rem; - display: flex; - overflow: auto; - flex-direction: column; -} - -[ngOption] { - display: flex; - cursor: pointer; - align-items: center; - margin: 1px; - padding: 1rem; - min-height: 1rem; - border-radius: 1rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast, #1a1a1a) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -[ngOption]:not([aria-selected='true']) .check-icon { - display: none; -} - -.option-label { - flex: 1; -} - -.check-icon { - font-size: 0.9rem; -} - -.dialog { - padding: none; - position: absolute; - left: auto; - right: auto; - top: auto; - bottom: auto; - border: 1px solid var(--border-color); - border-radius: 1rem; - background-color: var(--septenary-contrast, #f5f5f5); - color: inherit; -} - -.dialog .combobox-input-container { - border-radius: 0; -} - -.dialog [ngCombobox], -.dialog .combobox-input-container { - border: none; -} - -.dialog [ngComboboxInput] { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; -} - -[ngCombobox]:focus-within [ngComboboxInput]:not(.combobox-input) { - outline: 1.5px solid var(--vivid-pink); - box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent); -} - -.dialog::backdrop { - opacity: 0; -} - -.no-results { - padding: 1rem; -} diff --git a/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.html b/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.html deleted file mode 100644 index 9d253a4ef0da..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    - - -
    - - - -
    -
    - - -
    - - - @if (options().length === 0) { -
    No results found
    - } - -
    - @for (option of options(); track option) { -
    - {{ option }} - -
    - } -
    -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.ts b/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.ts deleted file mode 100644 index ca035fdf4cf0..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/material/app/app.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { - Combobox, - ComboboxDialog, - ComboboxInput, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, untracked, viewChild} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - ComboboxDialog, - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - FormsModule, - ], -}) -export class App { - dialog = viewChild(ComboboxDialog); - listbox = viewChild>(Listbox); - combobox = viewChild>(Combobox); - - value = signal(''); - searchString = signal(''); - - options = computed(() => - ALL_COUNTRIES.filter((country) => - country.toLowerCase().startsWith(this.searchString().toLowerCase()), - ), - ); - - selectedCountries = signal([]); - - constructor() { - afterRenderEffect(() => { - if (this.dialog() && this.combobox()?.expanded()) { - untracked(() => this.listbox()?.gotoFirst()); - this.positionDialog(); - } - }); - - afterRenderEffect(() => { - if (this.selectedCountries().length > 0) { - untracked(() => this.dialog()?.close()); - this.value.set(this.selectedCountries()[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 ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.css b/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.css deleted file mode 100644 index 80f30021f69e..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.css +++ /dev/null @@ -1,188 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.6rem; - font-family: 'Press Start 2P'; - - --retro-button-color: #fff; - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -[ngComboboxInput] { - width: 15rem; - font-size: 0.6rem; - border-radius: 0; - font-family: 'Press Start 2P'; - word-spacing: -5px; - padding: 0.75rem 0.5rem 0.75rem 2.5rem; - color: #000; - border: none; -} - -[ngComboboxInput]::placeholder { - color: #000; - opacity: 0.7; -} - -[ngCombobox]:has([readonly='true']) { - width: 15rem; - box-shadow: var(--retro-flat-shadow); - background-color: var(--retro-button-color); -} - -.combobox-input-container { - display: flex; - position: relative; - align-items: center; -} - -[ngComboboxInput][readonly='true'] { - cursor: pointer; - padding: 0.7rem 1rem; -} - -[ngCombobox]:focus-within [ngComboboxInput] { - outline: none; - box-shadow: none; -} - -.icon { - width: 24px; - height: 24px; - font-size: 20px; - display: grid; - place-items: center; - pointer-events: none; -} - -.search-icon { - padding: 0 0.5rem; - position: absolute; - opacity: 0.8; - color: #000; -} - -.arrow-icon { - padding: 0 0.5rem; - position: absolute; - right: 0; - opacity: 0.8; - transition: transform 0.2s ease; - color: #000; -} - -[ngComboboxInput][aria-expanded='true'] + .arrow-icon { - transform: rotate(180deg); -} - -[ngListbox] { - display: flex; - flex-direction: column; - overflow: auto; - max-height: 10rem; - padding: 0.5rem; - gap: 4px; - font-size: 0.9rem; -} - -[ngOption] { - cursor: pointer; - padding: 0.3rem 1rem; - display: flex; - overflow: hidden; - flex-shrink: 0; - align-items: center; - justify-content: space-between; - gap: 1rem; - font-size: 0.6rem; -} - -.checkbox-blank-icon, -[ngOption][aria-selected='true'] .checkbox-filled-icon { - display: flex; - align-items: center; -} - -.checkbox-filled-icon, -[ngOption][aria-selected='true'] .checkbox-blank-icon { - display: none; -} - -.checkbox-blank-icon { - opacity: 0.6; -} - -.selected-icon { - visibility: hidden; -} - -[ngOption][aria-selected='true'] .selected-icon { - visibility: visible; -} - -[ngOption][aria-selected='true'] { - color: var(--vivid-pink, #f542a4); - background-color: color-mix(in srgb, var(--vivid-pink, #f542a4) 10%, transparent); -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--mat-sys-on-surface, #1a1a1a) 10%, transparent); -} - -[ngCombobox]:focus-within [data-active='true'] { - outline: 2px dashed var(--vivid-pink, #f542a4); - outline-offset: -2px; -} - -.dialog { - margin-top: 8px; - position: absolute; - left: auto; - right: auto; - top: auto; - bottom: auto; - padding: 0; - border: none; - box-shadow: var(--retro-flat-shadow); - background-color: var(--septenary-contrast, #f5f5f5); - color: inherit; -} - -.dialog .combobox-input-container { - border-radius: 0; -} - -.dialog [ngCombobox], -.dialog .combobox-input-container { - border: none; -} - -.dialog [ngComboboxInput] { - border-bottom: 4px solid var(--gray-700, #374151); -} - -[ngCombobox]:focus-within [ngComboboxInput]:not(.combobox-input) { - outline: 1.5px solid var(--vivid-pink); - box-shadow: 0 0 0 4px color-mix(in srgb, var(--vivid-pink) 25%, transparent); -} - -.dialog::backdrop { - opacity: 0; -} diff --git a/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.html b/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.html deleted file mode 100644 index 1f3dc3fd100c..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.html +++ /dev/null @@ -1,42 +0,0 @@ -
    -
    - - -
    - - - -
    -
    - - -
    - - -
    - @for (option of options(); track option) { -
    - {{ option }} - -
    - } -
    -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.ts b/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.ts deleted file mode 100644 index ca035fdf4cf0..000000000000 --- a/adev/src/content/examples/aria/combobox/src/dialog/retro/app/app.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { - Combobox, - ComboboxDialog, - ComboboxInput, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import {afterRenderEffect, Component, computed, signal, untracked, viewChild} from '@angular/core'; -import {FormsModule} from '@angular/forms'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - ComboboxDialog, - Combobox, - ComboboxInput, - ComboboxPopupContainer, - Listbox, - Option, - FormsModule, - ], -}) -export class App { - dialog = viewChild(ComboboxDialog); - listbox = viewChild>(Listbox); - combobox = viewChild>(Combobox); - - value = signal(''); - searchString = signal(''); - - options = computed(() => - ALL_COUNTRIES.filter((country) => - country.toLowerCase().startsWith(this.searchString().toLowerCase()), - ), - ); - - selectedCountries = signal([]); - - constructor() { - afterRenderEffect(() => { - if (this.dialog() && this.combobox()?.expanded()) { - untracked(() => this.listbox()?.gotoFirst()); - this.positionDialog(); - } - }); - - afterRenderEffect(() => { - if (this.selectedCountries().length > 0) { - untracked(() => this.dialog()?.close()); - this.value.set(this.selectedCountries()[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 ALL_COUNTRIES = [ - 'Afghanistan', - 'Albania', - 'Algeria', - 'Andorra', - 'Angola', - 'Antigua and Barbuda', - 'Argentina', - 'Armenia', - 'Australia', - 'Austria', - 'Azerbaijan', - 'Bahamas', - 'Bahrain', - 'Bangladesh', - 'Barbados', - 'Belarus', - 'Belgium', - 'Belize', - 'Benin', - 'Bhutan', - 'Bolivia', - 'Bosnia and Herzegovina', - 'Botswana', - 'Brazil', - 'Brunei', - 'Bulgaria', - 'Burkina Faso', - 'Burundi', - 'Cabo Verde', - 'Cambodia', - 'Cameroon', - 'Canada', - 'Central African Republic', - 'Chad', - 'Chile', - 'China', - 'Colombia', - 'Comoros', - 'Congo (Congo-Brazzaville)', - 'Costa Rica', - "Côte d'Ivoire", - 'Croatia', - 'Cuba', - 'Cyprus', - 'Czechia (Czech Republic)', - 'Democratic Republic of the Congo', - 'Denmark', - 'Djibouti', - 'Dominica', - 'Dominican Republic', - 'Ecuador', - 'Egypt', - 'El Salvador', - 'Equatorial Guinea', - 'Eritrea', - 'Estonia', - 'Eswatini (fmr. ""Swaziland"")', - 'Ethiopia', - 'Fiji', - 'Finland', - 'France', - 'Gabon', - 'Gambia', - 'Georgia', - 'Germany', - 'Ghana', - 'Greece', - 'Grenada', - 'Guatemala', - 'Guinea', - 'Guinea-Bissau', - 'Guyana', - 'Haiti', - 'Holy See', - 'Honduras', - 'Hungary', - 'Iceland', - 'India', - 'Indonesia', - 'Iran', - 'Iraq', - 'Ireland', - 'Israel', - 'Italy', - 'Jamaica', - 'Japan', - 'Jordan', - 'Kazakhstan', - 'Kenya', - 'Kiribati', - 'Kuwait', - 'Kyrgyzstan', - 'Laos', - 'Latvia', - 'Lebanon', - 'Lesotho', - 'Liberia', - 'Libya', - 'Liechtenstein', - 'Lithuania', - 'Luxembourg', - 'Madagascar', - 'Malawi', - 'Malaysia', - 'Maldives', - 'Mali', - 'Malta', - 'Marshall Islands', - 'Mauritania', - 'Mauritius', - 'Mexico', - 'Micronesia', - 'Moldova', - 'Monaco', - 'Mongolia', - 'Montenegro', - 'Morocco', - 'Mozambique', - 'Myanmar (formerly Burma)', - 'Namibia', - 'Nauru', - 'Nepal', - 'Netherlands', - 'New Zealand', - 'Nicaragua', - 'Niger', - 'Nigeria', - 'North Korea', - 'North Macedonia', - 'Norway', - 'Oman', - 'Pakistan', - 'Palau', - 'Palestine State', - 'Panama', - 'Papua New Guinea', - 'Paraguay', - 'Peru', - 'Philippines', - 'Poland', - 'Portugal', - 'Qatar', - 'Romania', - 'Russia', - 'Rwanda', - 'Saint Kitts and Nevis', - 'Saint Lucia', - 'Saint Vincent and the Grenadines', - 'Samoa', - 'San Marino', - 'Sao Tome and Principe', - 'Saudi Arabia', - 'Senegal', - 'Serbia', - 'Seychelles', - 'Sierra Leone', - 'Singapore', - 'Slovakia', - 'Slovenia', - 'Solomon Islands', - 'Somalia', - 'South Africa', - 'South Korea', - 'South Sudan', - 'Spain', - 'Sri Lanka', - 'Sudan', - 'Suriname', - 'Sweden', - 'Switzerland', - 'Syria', - 'Tajikistan', - 'Tanzania', - 'Thailand', - 'Timor-Leste', - 'Togo', - 'Tonga', - 'Trinidad and Tobago', - 'Tunisia', - 'Turkey', - 'Turkmenistan', - 'Tuvalu', - 'Uganda', - 'Ukraine', - 'United Arab Emirates', - 'United Kingdom', - 'United States of America', - 'Uruguay', - 'Uzbekistan', - 'Vanuatu', - 'Venezuela', - 'Vietnam', - 'Yemen', - 'Zambia', - 'Zimbabwe', -]; diff --git a/adev/src/content/examples/aria/grid/src/calendar/retro/app/app.css b/adev/src/content/examples/aria/grid/src/calendar/retro/app/app.css index b4d0db976a6a..b66966afe55b 100644 --- a/adev/src/content/examples/aria/grid/src/calendar/retro/app/app.css +++ b/adev/src/content/examples/aria/grid/src/calendar/retro/app/app.css @@ -6,28 +6,28 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--always-pink) 90%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--always-pink) 90%, var(--page-background)); --retro-button-text-color: color-mix(in srgb, var(--always-pink) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } .calendar { diff --git a/adev/src/content/examples/aria/grid/src/pill-list/retro/app/app.css b/adev/src/content/examples/aria/grid/src/pill-list/retro/app/app.css index 3dd6d47fa907..587ac8b1265b 100644 --- a/adev/src/content/examples/aria/grid/src/pill-list/retro/app/app.css +++ b/adev/src/content/examples/aria/grid/src/pill-list/retro/app/app.css @@ -6,28 +6,28 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--always-pink) 90%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--always-pink) 90%, var(--page-background)); --retro-button-text-color: color-mix(in srgb, var(--always-pink) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngGrid] { diff --git a/adev/src/content/examples/aria/grid/src/table/retro/app/app.css b/adev/src/content/examples/aria/grid/src/table/retro/app/app.css index 789e6f4b8d85..af4cca355534 100644 --- a/adev/src/content/examples/aria/grid/src/table/retro/app/app.css +++ b/adev/src/content/examples/aria/grid/src/table/retro/app/app.css @@ -6,28 +6,28 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--page-background)); --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } .hidden { diff --git a/adev/src/content/examples/aria/listbox/src/basic/app/app.html b/adev/src/content/examples/aria/listbox/src/basic/app/app.html index 3ba1f58a884c..fe16527f6974 100644 --- a/adev/src/content/examples/aria/listbox/src/basic/app/app.html +++ b/adev/src/content/examples/aria/listbox/src/basic/app/app.html @@ -1,5 +1,5 @@
    -
    +
    @for (option of options; track option) {
    {{ option }} diff --git a/adev/src/content/examples/aria/listbox/src/basic/app/app.ts b/adev/src/content/examples/aria/listbox/src/basic/app/app.ts index c1e5450af5ee..589be4525e4b 100644 --- a/adev/src/content/examples/aria/listbox/src/basic/app/app.ts +++ b/adev/src/content/examples/aria/listbox/src/basic/app/app.ts @@ -1,12 +1,11 @@ import {Listbox, Option} from '@angular/aria/listbox'; -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { /** The options available in the listbox. */ diff --git a/adev/src/content/examples/aria/listbox/src/basic/material/app/app.ts b/adev/src/content/examples/aria/listbox/src/basic/material/app/app.ts index c1e5450af5ee..589be4525e4b 100644 --- a/adev/src/content/examples/aria/listbox/src/basic/material/app/app.ts +++ b/adev/src/content/examples/aria/listbox/src/basic/material/app/app.ts @@ -1,12 +1,11 @@ import {Listbox, Option} from '@angular/aria/listbox'; -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { /** The options available in the listbox. */ diff --git a/adev/src/content/examples/aria/listbox/src/basic/retro/app/app.css b/adev/src/content/examples/aria/listbox/src/basic/retro/app/app.css index 7c702a47ba2b..264da551ca11 100644 --- a/adev/src/content/examples/aria/listbox/src/basic/retro/app/app.css +++ b/adev/src/content/examples/aria/listbox/src/basic/retro/app/app.css @@ -7,12 +7,12 @@ font-size: 0.8rem; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background)); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); } .retro-listbox { diff --git a/adev/src/content/examples/aria/listbox/src/basic/retro/app/app.ts b/adev/src/content/examples/aria/listbox/src/basic/retro/app/app.ts index c1e5450af5ee..589be4525e4b 100644 --- a/adev/src/content/examples/aria/listbox/src/basic/retro/app/app.ts +++ b/adev/src/content/examples/aria/listbox/src/basic/retro/app/app.ts @@ -1,12 +1,11 @@ import {Listbox, Option} from '@angular/aria/listbox'; -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { /** The options available in the listbox. */ diff --git a/adev/src/content/examples/aria/listbox/src/horizontal/app/app.ts b/adev/src/content/examples/aria/listbox/src/horizontal/app/app.ts index 9b235784f253..0b3a13ebe6cb 100644 --- a/adev/src/content/examples/aria/listbox/src/horizontal/app/app.ts +++ b/adev/src/content/examples/aria/listbox/src/horizontal/app/app.ts @@ -1,12 +1,11 @@ import {Listbox, Option} from '@angular/aria/listbox'; -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { /** The options available in the listbox. */ diff --git a/adev/src/content/examples/aria/listbox/src/horizontal/material/app/app.ts b/adev/src/content/examples/aria/listbox/src/horizontal/material/app/app.ts index 9b235784f253..0b3a13ebe6cb 100644 --- a/adev/src/content/examples/aria/listbox/src/horizontal/material/app/app.ts +++ b/adev/src/content/examples/aria/listbox/src/horizontal/material/app/app.ts @@ -1,12 +1,11 @@ import {Listbox, Option} from '@angular/aria/listbox'; -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { /** The options available in the listbox. */ diff --git a/adev/src/content/examples/aria/listbox/src/horizontal/retro/app/app.css b/adev/src/content/examples/aria/listbox/src/horizontal/retro/app/app.css index 365a76febc54..299161c7a2ee 100644 --- a/adev/src/content/examples/aria/listbox/src/horizontal/retro/app/app.css +++ b/adev/src/content/examples/aria/listbox/src/horizontal/retro/app/app.css @@ -7,15 +7,15 @@ font-size: 0.8rem; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--page-background)); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow-small: - 2px 0px 0px 0px var(--gray-700), 0px 2px 0px 0px var(--gray-700), - -2px 0px 0px 0px var(--gray-700), 0px -2px 0px 0px var(--gray-700); + 2px 0px 0px 0px var(--tertiary-contrast), 0px 2px 0px 0px var(--tertiary-contrast), + -2px 0px 0px 0px var(--tertiary-contrast), 0px -2px 0px 0px var(--tertiary-contrast); } .retro-listbox { @@ -59,4 +59,4 @@ [ngOption][aria-selected='true'] .check-icon { width: 1.2rem; -} \ No newline at end of file +} diff --git a/adev/src/content/examples/aria/listbox/src/horizontal/retro/app/app.ts b/adev/src/content/examples/aria/listbox/src/horizontal/retro/app/app.ts index 9b235784f253..0b3a13ebe6cb 100644 --- a/adev/src/content/examples/aria/listbox/src/horizontal/retro/app/app.ts +++ b/adev/src/content/examples/aria/listbox/src/horizontal/retro/app/app.ts @@ -1,12 +1,11 @@ import {Listbox, Option} from '@angular/aria/listbox'; -import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.html', styleUrl: './app.css', imports: [Listbox, Option], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { /** The options available in the listbox. */ diff --git a/adev/src/content/examples/aria/menu/src/menu-context/app/app.css b/adev/src/content/examples/aria/menu/src/menu-context/app/app.css index d2d3d555cd66..bcf9944ca8b3 100644 --- a/adev/src/content/examples/aria/menu/src/menu-context/app/app.css +++ b/adev/src/content/examples/aria/menu/src/menu-context/app/app.css @@ -1,9 +1,12 @@ h1 { - background: var(--gray-600); + background: var(--tertiary-contrast); color: #e0e0e0; padding: 2rem; margin: 0; - font-family: system-ui, -apple-system, sans-serif; + font-family: + system-ui, + -apple-system, + sans-serif; font-size: 2rem; text-align: center; } diff --git a/adev/src/content/examples/aria/menu/src/menu-standalone/material/app/app.css b/adev/src/content/examples/aria/menu/src/menu-standalone/material/app/app.css index f3212034aaec..a657fc867fdd 100644 --- a/adev/src/content/examples/aria/menu/src/menu-standalone/material/app/app.css +++ b/adev/src/content/examples/aria/menu/src/menu-standalone/material/app/app.css @@ -78,7 +78,7 @@ } [ngMenu] .separator { - border-top: 1px solid var(--gray-500); + border-top: 1px solid var(--quaternary-contrast); margin: 0.25rem 0; opacity: 0.25; } diff --git a/adev/src/content/examples/aria/menu/src/menu-standalone/retro/app/app.css b/adev/src/content/examples/aria/menu/src/menu-standalone/retro/app/app.css index 191aa34685ba..dc0d55807cf7 100644 --- a/adev/src/content/examples/aria/menu/src/menu-standalone/retro/app/app.css +++ b/adev/src/content/examples/aria/menu/src/menu-standalone/retro/app/app.css @@ -14,22 +14,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngMenu] { diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/material/app/app.css b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/material/app/app.css index 8fbc6df14158..8a768b24b203 100644 --- a/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/material/app/app.css +++ b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/material/app/app.css @@ -92,7 +92,7 @@ } [ngMenu] .separator { - border-top: 1px solid var(--gray-500); + border-top: 1px solid var(--quaternary-contrast); margin: 0.25rem 0; opacity: 0.25; } diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/retro/app/app.css b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/retro/app/app.css index c79fc052c132..096dc4dc71f6 100644 --- a/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/retro/app/app.css +++ b/adev/src/content/examples/aria/menu/src/menu-trigger-disabled/retro/app/app.css @@ -14,22 +14,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngMenuTrigger] { diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.css b/adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.css index f10d0911c373..1d006d56930f 100644 --- a/adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.css +++ b/adev/src/content/examples/aria/menu/src/menu-trigger/material/app/app.css @@ -92,7 +92,7 @@ } [ngMenu] .separator { - border-top: 1px solid var(--gray-500); + border-top: 1px solid var(--quaternary-contrast); margin: 0.25rem 0; opacity: 0.25; } diff --git a/adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.css b/adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.css index 5496b4cdae6b..a56bd144f8cb 100644 --- a/adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.css +++ b/adev/src/content/examples/aria/menu/src/menu-trigger/retro/app/app.css @@ -14,22 +14,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngMenuTrigger] { diff --git a/adev/src/content/examples/aria/menubar/src/basic/material/app/app.css b/adev/src/content/examples/aria/menubar/src/basic/material/app/app.css index 8017d9ede8f9..91bad6591554 100644 --- a/adev/src/content/examples/aria/menubar/src/basic/material/app/app.css +++ b/adev/src/content/examples/aria/menubar/src/basic/material/app/app.css @@ -67,7 +67,7 @@ } [ngMenu] .separator { - border-top: 1px solid var(--gray-500); + border-top: 1px solid var(--quaternary-contrast); margin: 0.25rem 0; opacity: 0.25; } diff --git a/adev/src/content/examples/aria/menubar/src/basic/retro/app/app.css b/adev/src/content/examples/aria/menubar/src/basic/retro/app/app.css index 7a4815056a7c..8e4ea496b4f9 100644 --- a/adev/src/content/examples/aria/menubar/src/basic/retro/app/app.css +++ b/adev/src/content/examples/aria/menubar/src/basic/retro/app/app.css @@ -14,22 +14,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngMenuBar] { diff --git a/adev/src/content/examples/aria/menubar/src/disabled/material/app/app.css b/adev/src/content/examples/aria/menubar/src/disabled/material/app/app.css index 8017d9ede8f9..91bad6591554 100644 --- a/adev/src/content/examples/aria/menubar/src/disabled/material/app/app.css +++ b/adev/src/content/examples/aria/menubar/src/disabled/material/app/app.css @@ -67,7 +67,7 @@ } [ngMenu] .separator { - border-top: 1px solid var(--gray-500); + border-top: 1px solid var(--quaternary-contrast); margin: 0.25rem 0; opacity: 0.25; } diff --git a/adev/src/content/examples/aria/menubar/src/disabled/retro/app/app.css b/adev/src/content/examples/aria/menubar/src/disabled/retro/app/app.css index 7a4815056a7c..8e4ea496b4f9 100644 --- a/adev/src/content/examples/aria/menubar/src/disabled/retro/app/app.css +++ b/adev/src/content/examples/aria/menubar/src/disabled/retro/app/app.css @@ -14,22 +14,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngMenuBar] { diff --git a/adev/src/content/examples/aria/menubar/src/rtl/material/app/app.css b/adev/src/content/examples/aria/menubar/src/rtl/material/app/app.css index 8017d9ede8f9..91bad6591554 100644 --- a/adev/src/content/examples/aria/menubar/src/rtl/material/app/app.css +++ b/adev/src/content/examples/aria/menubar/src/rtl/material/app/app.css @@ -67,7 +67,7 @@ } [ngMenu] .separator { - border-top: 1px solid var(--gray-500); + border-top: 1px solid var(--quaternary-contrast); margin: 0.25rem 0; opacity: 0.25; } diff --git a/adev/src/content/examples/aria/menubar/src/rtl/retro/app/app.css b/adev/src/content/examples/aria/menubar/src/rtl/retro/app/app.css index 7a4815056a7c..8e4ea496b4f9 100644 --- a/adev/src/content/examples/aria/menubar/src/rtl/retro/app/app.css +++ b/adev/src/content/examples/aria/menubar/src/rtl/retro/app/app.css @@ -14,22 +14,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngMenuBar] { diff --git a/adev/src/content/examples/aria/multiselect/src/basic/app/app.css b/adev/src/content/examples/aria/multiselect/src/basic/app/app.css deleted file mode 100644 index 27fc9b1888bb..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/app/app.css +++ /dev/null @@ -1,133 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast)); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); - border-radius: 0.5rem; - border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 2.5rem; - height: 2.5rem; - border: none; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -.combobox-label { - gap: 1rem; - left: 1.5rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/basic/app/app.html b/adev/src/content/examples/aria/multiselect/src/basic/app/app.html deleted file mode 100644 index 189e09d3d9ef..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/basic/app/app.ts b/adev/src/content/examples/aria/multiselect/src/basic/app/app.ts deleted file mode 100644 index f72cf502793b..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/app/app.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select a label'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} + ${values.length - 1} more`; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.css b/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.css deleted file mode 100644 index d467e8068b10..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.css +++ /dev/null @@ -1,141 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); - --on-primary: var(--page-background); -} - -.docs-light-mode { - --on-primary: #fff; -} - -.select { - display: flex; - position: relative; - align-items: center; - border-radius: 3rem; - color: var(--on-primary); - background-color: var(--primary); - border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--primary) 90%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -[ngComboboxInput] { - opacity: 0; - border: none; - cursor: pointer; - height: 3rem; - padding: 0 2.5rem; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -.combobox-label { - gap: 1rem; - left: 1.5rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 13rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - padding: 2px; - 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; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.html b/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.html deleted file mode 100644 index 20d7fe0cb1c3..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.ts b/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.ts deleted file mode 100644 index f72cf502793b..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/material/app/app.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select a label'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} + ${values.length - 1} more`; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.css b/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.css deleted file mode 100644 index 5eb8edbd1fd9..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.css +++ /dev/null @@ -1,172 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.8rem; - - font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--gray-1000)); - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-clickable-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: var(--page-background); - background-color: var(--hot-pink); - box-shadow: var(--retro-clickable-shadow); -} - -.select:hover, -.select:focus-within { - transform: translate(1px, 1px); -} - -.select:active { - transform: translate(4px, 4px); - box-shadow: var(--retro-pressed-shadow); - background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--gray-50)); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 5rem; - height: 2.5rem; - border: none; -} - -.select:has([ngComboboxInput][aria-expanded='false']):focus-within { - outline-offset: 8px; - outline: 4px dashed var(--hot-pink); -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 20px; - box-shadow: var(--retro-flat-shadow); - background-color: var(--septenary-contrast); - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - font-size: 0.6rem; - min-height: 2.25rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.html b/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.html deleted file mode 100644 index b24917977519..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.ts b/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.ts deleted file mode 100644 index f72cf502793b..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/basic/retro/app/app.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select a label'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} + ${values.length - 1} more`; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/multiselect/src/icons/app/app.css b/adev/src/content/examples/aria/multiselect/src/icons/app/app.css deleted file mode 100644 index 1b785164d6de..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/app/app.css +++ /dev/null @@ -1,143 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast)); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); - border-radius: 0.5rem; - border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 3.5rem; - height: 2.5rem; - border: none; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/icons/app/app.html b/adev/src/content/examples/aria/multiselect/src/icons/app/app.html deleted file mode 100644 index d0b79deee2fd..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/app/app.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    - - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/icons/app/app.ts b/adev/src/content/examples/aria/multiselect/src/icons/app/app.ts deleted file mode 100644 index 624849910bac..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/app/app.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 icon that is displayed in the combobox. */ - displayIcon = computed(() => { - const values = this.listbox()?.values() || []; - const label = this.labels.find((label) => label.value === values[0]); - return label ? label.icon : ''; - }); - - /** The string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select a label'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} + ${values.length - 1} more`; - }); - - /** 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() { - // 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/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.css b/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.css deleted file mode 100644 index 447c6e829b71..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.css +++ /dev/null @@ -1,151 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); - --on-primary: var(--page-background); -} - -.docs-light-mode { - --on-primary: #fff; -} - -.select { - display: flex; - position: relative; - align-items: center; - border-radius: 3rem; - color: var(--on-primary); - background-color: var(--primary); - border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--primary) 90%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - border: none; - cursor: pointer; - height: 3rem; - padding: 0 3.5rem; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 13rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - padding: 2px; - 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; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.html b/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.html deleted file mode 100644 index 01c6162c98c1..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    - - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.ts b/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.ts deleted file mode 100644 index 624849910bac..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/material/app/app.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 icon that is displayed in the combobox. */ - displayIcon = computed(() => { - const values = this.listbox()?.values() || []; - const label = this.labels.find((label) => label.value === values[0]); - return label ? label.icon : ''; - }); - - /** The string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select a label'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} + ${values.length - 1} more`; - }); - - /** 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() { - // 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/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.css b/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.css deleted file mode 100644 index 5ee354ffed5c..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.css +++ /dev/null @@ -1,172 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.8rem; - - font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--gray-1000)); - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-clickable-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: var(--page-background); - background-color: var(--hot-pink); - box-shadow: var(--retro-clickable-shadow); -} - -.select:hover, -.select:focus-within { - transform: translate(1px, 1px); -} - -.select:active { - transform: translate(4px, 4px); - box-shadow: var(--retro-pressed-shadow); - background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--gray-50)); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 6rem; - height: 2.5rem; - border: none; -} - -.select:has([ngComboboxInput][aria-expanded='false']):focus-within { - outline-offset: 8px; - outline: 4px dashed var(--hot-pink); -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 20px; - box-shadow: var(--retro-flat-shadow); - background-color: var(--septenary-contrast); - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - font-size: 0.6rem; - min-height: 2.25rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.html b/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.html deleted file mode 100644 index b1e4b18c32a6..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    - - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.ts b/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.ts deleted file mode 100644 index 624849910bac..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/icons/retro/app/app.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 icon that is displayed in the combobox. */ - displayIcon = computed(() => { - const values = this.listbox()?.values() || []; - const label = this.labels.find((label) => label.value === values[0]); - return label ? label.icon : ''; - }); - - /** The string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select a label'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} + ${values.length - 1} more`; - }); - - /** 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() { - // 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/adev/src/content/examples/aria/multiselect/src/limited/app/app.css b/adev/src/content/examples/aria/multiselect/src/limited/app/app.css deleted file mode 100644 index 862d37e5b2e1..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/app/app.css +++ /dev/null @@ -1,142 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast)); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); - border-radius: 0.5rem; - border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 2.5rem; - height: 2.5rem; - border: none; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -.combobox-label { - gap: 1rem; - left: 1.5rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -[ngOption][aria-disabled='true'] { - opacity: 0.6; - cursor: default; -} - -[ngOption][aria-disabled='true']:hover { - background-color: transparent; -} - -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/limited/app/app.html b/adev/src/content/examples/aria/multiselect/src/limited/app/app.html deleted file mode 100644 index 1e446f9aeb35..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/limited/app/app.ts b/adev/src/content/examples/aria/multiselect/src/limited/app/app.ts deleted file mode 100644 index 344bea041549..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/app/app.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select 2 labels'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} & ${values[1]}`; - }); - - /** The labels that are available for selection. */ - labels = [ - {value: 'Important', disabled: computed(() => this.isOptionDisabled('Important'))}, - {value: 'Starred', disabled: computed(() => this.isOptionDisabled('Starred'))}, - {value: 'Work', disabled: computed(() => this.isOptionDisabled('Work'))}, - {value: 'Personal', disabled: computed(() => this.isOptionDisabled('Personal'))}, - {value: 'To Do', disabled: computed(() => this.isOptionDisabled('To Do'))}, - {value: 'Later', disabled: computed(() => this.isOptionDisabled('Later'))}, - {value: 'Read', disabled: computed(() => this.isOptionDisabled('Read'))}, - {value: 'Travel', disabled: computed(() => this.isOptionDisabled('Travel'))}, - ]; - - constructor() { - // 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); - } - }); - } - - isOptionDisabled(value: string) { - const values = this.listbox()?.values(); - - if (!values || values.length < 2) { - return false; - } - - return !values.includes(value); - } -} diff --git a/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.css b/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.css deleted file mode 100644 index 8f0d24db6702..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.css +++ /dev/null @@ -1,150 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); - --on-primary: var(--page-background); -} - -.docs-light-mode { - --on-primary: #fff; -} - -.select { - display: flex; - position: relative; - align-items: center; - border-radius: 3rem; - color: var(--on-primary); - background-color: var(--primary); - border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--primary) 90%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -[ngComboboxInput] { - opacity: 0; - border: none; - cursor: pointer; - height: 3rem; - padding: 0 2.5rem; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -.combobox-label { - gap: 1rem; - left: 1.5rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 13rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - padding: 2px; - 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; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -[ngOption][aria-disabled='true'] { - opacity: 0.6; - cursor: default; -} - -[ngOption][aria-disabled='true']:hover { - background-color: transparent; -} - -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.html b/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.html deleted file mode 100644 index 98280b61b85a..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.ts b/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.ts deleted file mode 100644 index 344bea041549..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/material/app/app.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select 2 labels'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} & ${values[1]}`; - }); - - /** The labels that are available for selection. */ - labels = [ - {value: 'Important', disabled: computed(() => this.isOptionDisabled('Important'))}, - {value: 'Starred', disabled: computed(() => this.isOptionDisabled('Starred'))}, - {value: 'Work', disabled: computed(() => this.isOptionDisabled('Work'))}, - {value: 'Personal', disabled: computed(() => this.isOptionDisabled('Personal'))}, - {value: 'To Do', disabled: computed(() => this.isOptionDisabled('To Do'))}, - {value: 'Later', disabled: computed(() => this.isOptionDisabled('Later'))}, - {value: 'Read', disabled: computed(() => this.isOptionDisabled('Read'))}, - {value: 'Travel', disabled: computed(() => this.isOptionDisabled('Travel'))}, - ]; - - constructor() { - // 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); - } - }); - } - - isOptionDisabled(value: string) { - const values = this.listbox()?.values(); - - if (!values || values.length < 2) { - return false; - } - - return !values.includes(value); - } -} diff --git a/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.css b/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.css deleted file mode 100644 index 508e29301894..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.css +++ /dev/null @@ -1,181 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.8rem; - - font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--gray-1000)); - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-clickable-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: var(--page-background); - background-color: var(--hot-pink); - box-shadow: var(--retro-clickable-shadow); -} - -.select:hover, -.select:focus-within { - transform: translate(1px, 1px); -} - -.select:active { - transform: translate(4px, 4px); - box-shadow: var(--retro-pressed-shadow); - background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--gray-50)); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 5rem; - height: 2.5rem; - border: none; -} - -.select:has([ngComboboxInput][aria-expanded='false']):focus-within { - outline-offset: 8px; - outline: 4px dashed var(--hot-pink); -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 20px; - box-shadow: var(--retro-flat-shadow); - background-color: var(--septenary-contrast); - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - font-size: 0.6rem; - min-height: 2.25rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -[ngOption][aria-disabled='true'] { - opacity: 0.6; - cursor: default; -} - -[ngOption][aria-disabled='true']:hover { - background-color: transparent; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.html b/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.html deleted file mode 100644 index 5466a0c1956e..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.ts b/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.ts deleted file mode 100644 index 344bea041549..000000000000 --- a/adev/src/content/examples/aria/multiselect/src/limited/retro/app/app.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - if (values.length === 0) { - return 'Select 2 labels'; - } - if (values.length === 1) { - return values[0]; - } - return `${values[0]} & ${values[1]}`; - }); - - /** The labels that are available for selection. */ - labels = [ - {value: 'Important', disabled: computed(() => this.isOptionDisabled('Important'))}, - {value: 'Starred', disabled: computed(() => this.isOptionDisabled('Starred'))}, - {value: 'Work', disabled: computed(() => this.isOptionDisabled('Work'))}, - {value: 'Personal', disabled: computed(() => this.isOptionDisabled('Personal'))}, - {value: 'To Do', disabled: computed(() => this.isOptionDisabled('To Do'))}, - {value: 'Later', disabled: computed(() => this.isOptionDisabled('Later'))}, - {value: 'Read', disabled: computed(() => this.isOptionDisabled('Read'))}, - {value: 'Travel', disabled: computed(() => this.isOptionDisabled('Travel'))}, - ]; - - constructor() { - // 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); - } - }); - } - - isOptionDisabled(value: string) { - const values = this.listbox()?.values(); - - if (!values || values.length < 2) { - return false; - } - - return !values.includes(value); - } -} diff --git a/adev/src/content/examples/aria/select/src/basic/app/app.css b/adev/src/content/examples/aria/select/src/basic/app/app.css deleted file mode 100644 index a6b6fba421fa..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/app/app.css +++ /dev/null @@ -1,133 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast)); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); - border-radius: 0.5rem; - border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 1.5rem; - height: 2.5rem; - border: none; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -.combobox-label { - gap: 1rem; - left: 1.5rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/basic/app/app.html b/adev/src/content/examples/aria/select/src/basic/app/app.html deleted file mode 100644 index c210f87afb30..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/basic/app/app.ts b/adev/src/content/examples/aria/select/src/basic/app/app.ts deleted file mode 100644 index 257689c52e20..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/app/app.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/select/src/basic/material/app/app.css b/adev/src/content/examples/aria/select/src/basic/material/app/app.css deleted file mode 100644 index 6439b778c88a..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/material/app/app.css +++ /dev/null @@ -1,141 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); - --on-primary: var(--page-background); -} - -.docs-light-mode { - --on-primary: #fff; -} - -.select { - display: flex; - position: relative; - align-items: center; - border-radius: 3rem; - color: var(--on-primary); - background-color: var(--primary); - border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--primary) 90%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -[ngComboboxInput] { - opacity: 0; - border: none; - cursor: pointer; - height: 3rem; - padding: 0 1.5rem; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -.combobox-label { - gap: 1rem; - left: 1.5rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 13rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - padding: 2px; - 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; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/basic/material/app/app.html b/adev/src/content/examples/aria/select/src/basic/material/app/app.html deleted file mode 100644 index 654cbaf55358..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/material/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/basic/material/app/app.ts b/adev/src/content/examples/aria/select/src/basic/material/app/app.ts deleted file mode 100644 index 257689c52e20..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/material/app/app.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/select/src/basic/retro/app/app.css b/adev/src/content/examples/aria/select/src/basic/retro/app/app.css deleted file mode 100644 index 651415f9d3c4..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/retro/app/app.css +++ /dev/null @@ -1,172 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.8rem; - - font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--gray-1000)); - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-clickable-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: var(--page-background); - background-color: var(--hot-pink); - box-shadow: var(--retro-clickable-shadow); -} - -.select:hover, -.select:focus-within { - transform: translate(1px, 1px); -} - -.select:active { - transform: translate(4px, 4px); - box-shadow: var(--retro-pressed-shadow); - background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--gray-50)); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 3.5rem; - height: 2.5rem; - border: none; -} - -.select:has([ngComboboxInput][aria-expanded='false']):focus-within { - outline-offset: 8px; - outline: 4px dashed var(--hot-pink); -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 20px; - box-shadow: var(--retro-flat-shadow); - background-color: var(--septenary-contrast); - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - font-size: 0.6rem; - min-height: 2.25rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/basic/retro/app/app.html b/adev/src/content/examples/aria/select/src/basic/retro/app/app.html deleted file mode 100644 index b6e6aaba4c72..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/retro/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/basic/retro/app/app.ts b/adev/src/content/examples/aria/select/src/basic/retro/app/app.ts deleted file mode 100644 index 257689c52e20..000000000000 --- a/adev/src/content/examples/aria/select/src/basic/retro/app/app.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/select/src/disabled/app/app.css b/adev/src/content/examples/aria/select/src/disabled/app/app.css deleted file mode 100644 index 7864bf49303f..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/app/app.css +++ /dev/null @@ -1,132 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast)); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); - border-radius: 0.5rem; - border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 1.5rem; - height: 2.5rem; - border: none; -} - -[ngComboboxInput][aria-disabled='true'] { - cursor: default; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -.combobox-label { - gap: 1rem; - left: 1.5rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/disabled/app/app.html b/adev/src/content/examples/aria/select/src/disabled/app/app.html deleted file mode 100644 index fcb529285adc..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/disabled/app/app.ts b/adev/src/content/examples/aria/select/src/disabled/app/app.ts deleted file mode 100644 index 257689c52e20..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/app/app.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/select/src/disabled/material/app/app.css b/adev/src/content/examples/aria/select/src/disabled/material/app/app.css deleted file mode 100644 index 4a3b4e2d23b8..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/material/app/app.css +++ /dev/null @@ -1,140 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); - --on-primary: var(--page-background); -} - -.docs-light-mode { - --on-primary: #fff; -} - -.select { - display: flex; - position: relative; - align-items: center; - border-radius: 3rem; - color: var(--on-primary); - background-color: var(--primary); - border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; -} - -[ngComboboxInput] { - opacity: 0; - border: none; - cursor: pointer; - height: 3rem; - padding: 0 1.5rem; -} - -[ngComboboxInput][aria-disabled='true'] { - cursor: default; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -.combobox-label { - gap: 1rem; - left: 1.5rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 13rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - padding: 2px; - 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; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/disabled/material/app/app.html b/adev/src/content/examples/aria/select/src/disabled/material/app/app.html deleted file mode 100644 index 9f9a3007e5af..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/material/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/disabled/material/app/app.ts b/adev/src/content/examples/aria/select/src/disabled/material/app/app.ts deleted file mode 100644 index 257689c52e20..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/material/app/app.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/select/src/disabled/retro/app/app.css b/adev/src/content/examples/aria/select/src/disabled/retro/app/app.css deleted file mode 100644 index 8b67bbe26a88..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/retro/app/app.css +++ /dev/null @@ -1,164 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.8rem; - - font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--gray-1000)); - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-clickable-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: var(--page-background); - background-color: var(--hot-pink); - box-shadow: var(--retro-clickable-shadow); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 3.5rem; - height: 2.5rem; - border: none; -} - -[ngComboboxInput][aria-disabled='true'] { - cursor: default; -} - -.select:has([ngComboboxInput][aria-expanded='false']):focus-within { - outline-offset: 8px; - outline: 4px dashed var(--hot-pink); -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 20px; - box-shadow: var(--retro-flat-shadow); - background-color: var(--septenary-contrast); - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - font-size: 0.6rem; - min-height: 2.25rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/disabled/retro/app/app.html b/adev/src/content/examples/aria/select/src/disabled/retro/app/app.html deleted file mode 100644 index ab6d36df9cae..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/retro/app/app.html +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label) { -
    - {{ label }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/disabled/retro/app/app.ts b/adev/src/content/examples/aria/select/src/disabled/retro/app/app.ts deleted file mode 100644 index 257689c52e20..000000000000 --- a/adev/src/content/examples/aria/select/src/disabled/retro/app/app.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** The labels that are available for selection. */ - labels = ['Important', 'Starred', 'Work', 'Personal', 'To Do', 'Later', 'Read', 'Travel']; - - constructor() { - // 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/adev/src/content/examples/aria/select/src/icons/app/app.css b/adev/src/content/examples/aria/select/src/icons/app/app.css deleted file mode 100644 index 660e31d4553b..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/app/app.css +++ /dev/null @@ -1,143 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: color-mix(in srgb, var(--hot-pink) 90%, var(--primary-contrast)); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); - border-radius: 0.5rem; - border: 1px solid color-mix(in srgb, var(--hot-pink) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--hot-pink) 15%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 3rem; - height: 2.5rem; - border: none; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 0.5rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - margin: 1px; - padding: 0 1rem; - min-height: 2.25rem; - border-radius: 0.5rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid color-mix(in srgb, var(--hot-pink) 50%, transparent); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/icons/app/app.html b/adev/src/content/examples/aria/select/src/icons/app/app.html deleted file mode 100644 index dbdfc53444fa..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/app/app.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    - - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/icons/app/app.ts b/adev/src/content/examples/aria/select/src/icons/app/app.ts deleted file mode 100644 index 935e9dd3108f..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/app/app.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 icon that is displayed in the combobox. */ - displayIcon = computed(() => { - const values = this.listbox()?.values() || []; - const label = this.labels.find((label) => label.value === values[0]); - return label ? label.icon : ''; - }); - - /** The string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** 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() { - // 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/adev/src/content/examples/aria/select/src/icons/material/app/app.css b/adev/src/content/examples/aria/select/src/icons/material/app/app.css deleted file mode 100644 index 55ee304a9c8e..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/material/app/app.css +++ /dev/null @@ -1,151 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); - -:host { - display: flex; - justify-content: center; - font-family: var(--inter-font); - --primary: var(--hot-pink); - --on-primary: var(--page-background); -} - -.docs-light-mode { - --on-primary: #fff; -} - -.select { - display: flex; - position: relative; - align-items: center; - border-radius: 3rem; - color: var(--on-primary); - background-color: var(--primary); - border: 1px solid color-mix(in srgb, var(--primary) 80%, transparent); -} - -.select:hover { - background-color: color-mix(in srgb, var(--primary) 90%, transparent); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - border: none; - cursor: pointer; - height: 3rem; - padding: 0 3rem; -} - -[ngCombobox]:focus-within .select { - outline: 2px solid var(--primary); - outline-offset: 2px; -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 2rem; - background-color: var(--septenary-contrast); - font-size: 0.9rem; - - max-height: 13rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - padding: 2px; - 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; - border-radius: 3rem; -} - -[ngOption]:hover, -[ngOption][data-active='true'] { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px solid var(--primary); -} - -[ngOption][aria-selected='true'] { - color: var(--primary); - background-color: color-mix(in srgb, var(--primary) 10%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/icons/material/app/app.html b/adev/src/content/examples/aria/select/src/icons/material/app/app.html deleted file mode 100644 index a65655204670..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/material/app/app.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    - - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/icons/material/app/app.ts b/adev/src/content/examples/aria/select/src/icons/material/app/app.ts deleted file mode 100644 index 935e9dd3108f..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/material/app/app.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 icon that is displayed in the combobox. */ - displayIcon = computed(() => { - const values = this.listbox()?.values() || []; - const label = this.labels.find((label) => label.value === values[0]); - return label ? label.icon : ''; - }); - - /** The string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** 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() { - // 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/adev/src/content/examples/aria/select/src/icons/retro/app/app.css b/adev/src/content/examples/aria/select/src/icons/retro/app/app.css deleted file mode 100644 index 77f6393e9ec9..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/retro/app/app.css +++ /dev/null @@ -1,172 +0,0 @@ -@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -:host { - display: flex; - justify-content: center; - font-size: 0.8rem; - - font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--hot-pink) 80%, var(--gray-1000)); - --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); - --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); - --retro-elevated-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); - --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); - --retro-clickable-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); - --retro-pressed-shadow: - inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); -} - -.select { - display: flex; - position: relative; - align-items: center; - color: var(--page-background); - background-color: var(--hot-pink); - box-shadow: var(--retro-clickable-shadow); -} - -.select:hover, -.select:focus-within { - transform: translate(1px, 1px); -} - -.select:active { - transform: translate(4px, 4px); - box-shadow: var(--retro-pressed-shadow); - background-color: color-mix(in srgb, var(--retro-button-color) 60%, var(--gray-50)); -} - -.select:has([ngComboboxInput][aria-disabled='true']) { - opacity: 0.6; - cursor: default; -} - -.selected-label-icon { - font-size: 1.25rem; -} - -[ngComboboxInput] { - opacity: 0; - cursor: pointer; - padding: 0 4rem; - height: 2.5rem; - border: none; -} - -.select:has([ngComboboxInput][aria-expanded='false']):focus-within { - outline-offset: 8px; - outline: 4px dashed var(--hot-pink); -} - -.combobox-label { - gap: 1rem; - left: 1rem; - display: flex; - position: absolute; - align-items: center; - pointer-events: none; -} - -.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: 20px; - box-shadow: var(--retro-flat-shadow); - background-color: var(--septenary-contrast); - - max-height: 11rem; - opacity: 1; - visibility: visible; - transition: - max-height 150ms ease-out, - visibility 0s, - opacity 25ms ease-out; -} - -[ngListbox] { - gap: 2px; - 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; - font-size: 0.6rem; - min-height: 2.25rem; -} - -[ngOption]:hover { - background-color: color-mix(in srgb, var(--primary-contrast) 5%, transparent); -} - -[ngOption][data-active='true'] { - outline-offset: -2px; - outline: 2px dashed var(--hot-pink); -} - -[ngOption][aria-selected='true'] { - color: var(--hot-pink); - background-color: color-mix(in srgb, var(--hot-pink) 5%, transparent); -} - -.example-option-icon { - font-size: 1.25rem; - padding-right: 1rem; -} - -[ngOption]:not([aria-selected='true']) .example-option-check { - display: none; -} - -.example-option-icon, -.example-option-check { - font-size: 0.9rem; -} - -.example-option-text { - flex: 1; -} diff --git a/adev/src/content/examples/aria/select/src/icons/retro/app/app.html b/adev/src/content/examples/aria/select/src/icons/retro/app/app.html deleted file mode 100644 index bf0533149646..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/retro/app/app.html +++ /dev/null @@ -1,46 +0,0 @@ -
    -
    - - - {{ displayValue() }} - - - -
    - - - -
    -
    - @for (label of labels; track label.value) { -
    - - {{ label.value }} - -
    - } -
    -
    -
    -
    -
    diff --git a/adev/src/content/examples/aria/select/src/icons/retro/app/app.ts b/adev/src/content/examples/aria/select/src/icons/retro/app/app.ts deleted file mode 100644 index 935e9dd3108f..000000000000 --- a/adev/src/content/examples/aria/select/src/icons/retro/app/app.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, -} from '@angular/aria/combobox'; -import {Listbox, Option} from '@angular/aria/listbox'; -import { - afterRenderEffect, - ChangeDetectionStrategy, - Component, - computed, - signal, - viewChild, - viewChildren, -} from '@angular/core'; -import {OverlayModule} from '@angular/cdk/overlay'; - -@Component({ - selector: 'app-root', - templateUrl: './app.html', - styleUrl: './app.css', - imports: [ - Combobox, - ComboboxInput, - ComboboxPopup, - ComboboxPopupContainer, - Listbox, - Option, - OverlayModule, - ], - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class App { - /** 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 icon that is displayed in the combobox. */ - displayIcon = computed(() => { - const values = this.listbox()?.values() || []; - const label = this.labels.find((label) => label.value === values[0]); - return label ? label.icon : ''; - }); - - /** The string that is displayed in the combobox. */ - displayValue = computed(() => { - const values = this.listbox()?.values() || []; - return values.length ? values[0] : 'Select a label'; - }); - - /** 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() { - // 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/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.css b/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.css index e8cbc8136954..c28817a4d76c 100644 --- a/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.css +++ b/adev/src/content/examples/aria/tabs/src/disabled/retro/app/app.css @@ -12,22 +12,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngTabs] { diff --git a/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.css b/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.css index 03d13fdffa56..e1ff29aeca08 100644 --- a/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.css +++ b/adev/src/content/examples/aria/tabs/src/explicit-selection/retro/app/app.css @@ -12,22 +12,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast), + 0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast), + 0px -4px 0px 0px var(--quaternary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--quaternary-contrast), 0px 4px 0px 0px var(--quaternary-contrast), + -4px 0px 0px 0px var(--quaternary-contrast), 0px -4px 0px 0px var(--quaternary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--quaternary-contrast), + 0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast), + 0px -4px 0px 0px var(--quaternary-contrast), 8px 8px 0px 0px var(--quaternary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--quaternary-contrast), + 0px 4px 0px 0px var(--quaternary-contrast), -4px 0px 0px 0px var(--quaternary-contrast), + 0px -4px 0px 0px var(--quaternary-contrast), 0px 0px 0px 0px var(--quaternary-contrast); } [ngTabs] { diff --git a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.css b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.css index 03d13fdffa56..a43e937647d3 100644 --- a/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.css +++ b/adev/src/content/examples/aria/tabs/src/selection-follows-focus/retro/app/app.css @@ -12,22 +12,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngTabs] { diff --git a/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.css b/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.css index 7906f6f6e9fa..881459c4b4d6 100644 --- a/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.css +++ b/adev/src/content/examples/aria/tabs/src/vertical/retro/app/app.css @@ -12,22 +12,22 @@ --retro-shadow-dark: color-mix(in srgb, #000 20%, transparent); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngTabs] { diff --git a/adev/src/content/examples/aria/toolbar/src/basic/retro/app/app.css b/adev/src/content/examples/aria/toolbar/src/basic/retro/app/app.css index d5663ddc23e1..a7e3b3569bbb 100644 --- a/adev/src/content/examples/aria/toolbar/src/basic/retro/app/app.css +++ b/adev/src/content/examples/aria/toolbar/src/basic/retro/app/app.css @@ -5,27 +5,27 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background)); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngToolbar] { diff --git a/adev/src/content/examples/aria/toolbar/src/disabled/retro/app/app.css b/adev/src/content/examples/aria/toolbar/src/disabled/retro/app/app.css index 073db91df856..42e2e280e1a7 100644 --- a/adev/src/content/examples/aria/toolbar/src/disabled/retro/app/app.css +++ b/adev/src/content/examples/aria/toolbar/src/disabled/retro/app/app.css @@ -5,27 +5,27 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background)); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngToolbar] { @@ -79,7 +79,7 @@ outline: 4px dashed var(--retro-button-color); } -[ngToolbarWidget][aria-disabled="true"] { +[ngToolbarWidget][aria-disabled='true'] { cursor: default; opacity: 0.45; } diff --git a/adev/src/content/examples/aria/toolbar/src/rtl/retro/app/app.css b/adev/src/content/examples/aria/toolbar/src/rtl/retro/app/app.css index d5663ddc23e1..a7e3b3569bbb 100644 --- a/adev/src/content/examples/aria/toolbar/src/rtl/retro/app/app.css +++ b/adev/src/content/examples/aria/toolbar/src/rtl/retro/app/app.css @@ -5,27 +5,27 @@ justify-content: center; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background)); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngToolbar] { diff --git a/adev/src/content/examples/aria/toolbar/src/vertical/retro/app/app.css b/adev/src/content/examples/aria/toolbar/src/vertical/retro/app/app.css index d0fc730319aa..2fb268439f65 100644 --- a/adev/src/content/examples/aria/toolbar/src/vertical/retro/app/app.css +++ b/adev/src/content/examples/aria/toolbar/src/vertical/retro/app/app.css @@ -5,27 +5,27 @@ justify-content: flex-start; font-family: 'Press Start 2P'; - --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--gray-1000)); + --retro-button-color: color-mix(in srgb, var(--vivid-pink) 80%, var(--page-background)); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast); --retro-flat-shadow: - 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), - -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); + 4px 0px 0px 0px var(--tertiary-contrast), 0px 4px 0px 0px var(--tertiary-contrast), + -4px 0px 0px 0px var(--tertiary-contrast), 0px -4px 0px 0px var(--tertiary-contrast); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), - inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 8px 8px 0px 0px var(--tertiary-contrast); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), - inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), - 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), - 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700); + inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--tertiary-contrast), + 0px 4px 0px 0px var(--tertiary-contrast), -4px 0px 0px 0px var(--tertiary-contrast), + 0px -4px 0px 0px var(--tertiary-contrast), 0px 0px 0px 0px var(--tertiary-contrast); } [ngToolbar] { diff --git a/adev/src/content/examples/aria/tree/src/disabled-focusable/basic/app/app.html b/adev/src/content/examples/aria/tree/src/disabled-focusable/basic/app/app.html index f551bcf62c3e..d30b93294f6a 100644 --- a/adev/src/content/examples/aria/tree/src/disabled-focusable/basic/app/app.html +++ b/adev/src/content/examples/aria/tree/src/disabled-focusable/basic/app/app.html @@ -1,4 +1,4 @@ -
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { theme = signal<'light' | 'dark'>('light'); diff --git a/adev/src/content/tutorials/signals/steps/10-reacting-to-signal-changes-with-effect/src/app/app.ts b/adev/src/content/tutorials/signals/steps/10-reacting-to-signal-changes-with-effect/src/app/app.ts index d00b073a99fb..60ad635ec7d0 100644 --- a/adev/src/content/tutorials/signals/steps/10-reacting-to-signal-changes-with-effect/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/10-reacting-to-signal-changes-with-effect/src/app/app.ts @@ -1,5 +1,5 @@ // TODO: Import effect from @angular/core -import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, signal} from '@angular/core'; @Component({ selector: 'app-root', @@ -50,7 +50,6 @@ import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/cor
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { theme = signal<'light' | 'dark'>('light'); diff --git a/adev/src/content/tutorials/signals/steps/2-deriving-state-with-computed-signals/answer/src/app/app.ts b/adev/src/content/tutorials/signals/steps/2-deriving-state-with-computed-signals/answer/src/app/app.ts index 0c90d97a5445..8cf00325edd6 100644 --- a/adev/src/content/tutorials/signals/steps/2-deriving-state-with-computed-signals/answer/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/2-deriving-state-with-computed-signals/answer/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, signal} from '@angular/core'; @Component({ selector: 'app-root', @@ -39,7 +39,6 @@ import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/cor
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { userStatus = signal<'online' | 'away' | 'offline'>('offline'); diff --git a/adev/src/content/tutorials/signals/steps/2-deriving-state-with-computed-signals/src/app/app.ts b/adev/src/content/tutorials/signals/steps/2-deriving-state-with-computed-signals/src/app/app.ts index cde33628eb9b..e1c0013b5baa 100644 --- a/adev/src/content/tutorials/signals/steps/2-deriving-state-with-computed-signals/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/2-deriving-state-with-computed-signals/src/app/app.ts @@ -1,5 +1,5 @@ // TODO: Import computed from @angular/core -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; @Component({ selector: 'app-root', @@ -38,7 +38,6 @@ import {Component, signal, ChangeDetectionStrategy} from '@angular/core';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { userStatus = signal<'online' | 'away' | 'offline'>('offline'); diff --git a/adev/src/content/tutorials/signals/steps/3-deriving-state-with-linked-signals/answer/src/app/app.ts b/adev/src/content/tutorials/signals/steps/3-deriving-state-with-linked-signals/answer/src/app/app.ts index 389f8f9cc746..be665ec39279 100644 --- a/adev/src/content/tutorials/signals/steps/3-deriving-state-with-linked-signals/answer/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/3-deriving-state-with-linked-signals/answer/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, signal, computed, linkedSignal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, linkedSignal, signal} from '@angular/core'; @Component({ selector: 'app-root', @@ -46,7 +46,6 @@ import {Component, signal, computed, linkedSignal, ChangeDetectionStrategy} from
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { userStatus = signal<'online' | 'offline' | 'away'>('offline'); diff --git a/adev/src/content/tutorials/signals/steps/3-deriving-state-with-linked-signals/src/app/app.ts b/adev/src/content/tutorials/signals/steps/3-deriving-state-with-linked-signals/src/app/app.ts index 23031f49c9ce..a90ea4d066a6 100644 --- a/adev/src/content/tutorials/signals/steps/3-deriving-state-with-linked-signals/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/3-deriving-state-with-linked-signals/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, signal} from '@angular/core'; // TODO: Import linkedSignal from @angular/core @@ -42,7 +42,6 @@ import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/cor
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { userStatus = signal<'online' | 'away' | 'offline'>('offline'); diff --git a/adev/src/content/tutorials/signals/steps/4-managing-async-data-with-signals/answer/src/app/app.ts b/adev/src/content/tutorials/signals/steps/4-managing-async-data-with-signals/answer/src/app/app.ts index 223a21701a89..e837f960e253 100644 --- a/adev/src/content/tutorials/signals/steps/4-managing-async-data-with-signals/answer/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/4-managing-async-data-with-signals/answer/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, signal, computed, resource, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, resource, signal} from '@angular/core'; import {getUserData} from './user-api'; @Component({ @@ -29,7 +29,6 @@ import {getUserData} from './user-api';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { userId = signal(1); diff --git a/adev/src/content/tutorials/signals/steps/4-managing-async-data-with-signals/src/app/app.ts b/adev/src/content/tutorials/signals/steps/4-managing-async-data-with-signals/src/app/app.ts index 8782fb44a54e..3fb412a56ae9 100644 --- a/adev/src/content/tutorials/signals/steps/4-managing-async-data-with-signals/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/4-managing-async-data-with-signals/src/app/app.ts @@ -1,6 +1,5 @@ // TODO: Add the resource import from @angular/core -import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/core'; -import {getUserData} from './user-api'; +import {Component} from '@angular/core'; @Component({ selector: 'app-root', @@ -28,7 +27,6 @@ import {getUserData} from './user-api';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { // TODO: Create a signal for userId diff --git a/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/answer/src/app/app.ts b/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/answer/src/app/app.ts index 4b518f57b04c..632227bacb97 100644 --- a/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/answer/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/answer/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, signal} from '@angular/core'; import {ProductCard} from './product-card'; @Component({ @@ -26,7 +26,6 @@ import {ProductCard} from './product-card';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { // Signal inputs data diff --git a/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/answer/src/app/product-card.ts b/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/answer/src/app/product-card.ts index c678c169c483..389af4f92d93 100644 --- a/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/answer/src/app/product-card.ts +++ b/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/answer/src/app/product-card.ts @@ -1,4 +1,4 @@ -import {Component, input, ChangeDetectionStrategy} from '@angular/core'; +import {Component, input} from '@angular/core'; @Component({ selector: 'product-card', @@ -16,7 +16,6 @@ import {Component, input, ChangeDetectionStrategy} from '@angular/core';

    `, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProductCard { // Signal inputs - receive data from parent diff --git a/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/src/app/app.ts b/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/src/app/app.ts index 4073c92a0867..7a8354c59860 100644 --- a/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/src/app/app.ts @@ -1,5 +1,5 @@ // TODO: Import signal from @angular/core -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {ProductCard} from './product-card'; @Component({ @@ -24,7 +24,6 @@ import {ProductCard} from './product-card';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { // TODO: Create parent signals for product data diff --git a/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/src/app/product-card.ts b/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/src/app/product-card.ts index 3d2e4e2eba2c..a24d8da152b1 100644 --- a/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/src/app/product-card.ts +++ b/adev/src/content/tutorials/signals/steps/5-component-communication-with-signals/src/app/product-card.ts @@ -1,5 +1,5 @@ // TODO: Import input from @angular/core -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'product-card', @@ -11,7 +11,6 @@ import {Component, ChangeDetectionStrategy} from '@angular/core';

    Status: Available

    `, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProductCard { // TODO: Create signal inputs for name, price, and available diff --git a/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/answer/src/app/app.ts b/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/answer/src/app/app.ts index 5771c1ac5665..93486f4b79e2 100644 --- a/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/answer/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/answer/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, model, ChangeDetectionStrategy} from '@angular/core'; +import {Component, model} from '@angular/core'; import {CustomCheckbox} from './custom-checkbox'; @Component({ @@ -39,7 +39,6 @@ import {CustomCheckbox} from './custom-checkbox';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { // Parent signal models diff --git a/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/answer/src/app/custom-checkbox.ts b/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/answer/src/app/custom-checkbox.ts index 75b3129f61fc..9645d83a6447 100644 --- a/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/answer/src/app/custom-checkbox.ts +++ b/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/answer/src/app/custom-checkbox.ts @@ -1,4 +1,4 @@ -import {Component, model, input, ChangeDetectionStrategy} from '@angular/core'; +import {Component, input, model} from '@angular/core'; @Component({ selector: 'custom-checkbox', @@ -9,7 +9,6 @@ import {Component, model, input, ChangeDetectionStrategy} from '@angular/core'; {{ label() }} `, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CustomCheckbox { // Model signal for two-way binding diff --git a/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/src/app/app.ts b/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/src/app/app.ts index 3568966f45d8..b7ad65da3603 100644 --- a/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/src/app/app.ts @@ -1,5 +1,5 @@ // TODO: Import model from @angular/core -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {CustomCheckbox} from './custom-checkbox'; @Component({ @@ -42,7 +42,6 @@ import {CustomCheckbox} from './custom-checkbox';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { // TODO: Add parent signal models diff --git a/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/src/app/custom-checkbox.ts b/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/src/app/custom-checkbox.ts index 4fdbff00c7d6..c2b3836909ce 100644 --- a/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/src/app/custom-checkbox.ts +++ b/adev/src/content/tutorials/signals/steps/6-two-way-binding-with-model-signals/src/app/custom-checkbox.ts @@ -1,5 +1,5 @@ // TODO: Import model and input from @angular/core -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; @Component({ selector: 'custom-checkbox', @@ -10,7 +10,6 @@ import {Component, ChangeDetectionStrategy} from '@angular/core'; `, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CustomCheckbox { // TODO: Add model signal for two-way binding diff --git a/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/answer/src/app/app.ts b/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/answer/src/app/app.ts index ffee00ea9c50..1d261479e8a3 100644 --- a/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/answer/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/answer/src/app/app.ts @@ -1,6 +1,6 @@ -import {Component, inject, ChangeDetectionStrategy} from '@angular/core'; -import {CartStore} from './cart-store'; +import {Component, inject} from '@angular/core'; import {CartDisplay} from './cart-display'; +import {CartStore} from './cart-store'; @Component({ selector: 'app-root', @@ -20,7 +20,6 @@ import {CartDisplay} from './cart-display';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { cartStore = inject(CartStore); diff --git a/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/answer/src/app/cart-display.ts b/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/answer/src/app/cart-display.ts index 460a74abefd2..3da1bd3406e3 100644 --- a/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/answer/src/app/cart-display.ts +++ b/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/answer/src/app/cart-display.ts @@ -1,4 +1,4 @@ -import {Component, inject, ChangeDetectionStrategy} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {CartStore} from './cart-store'; import {CartItem} from './cart-types'; @@ -46,7 +46,6 @@ import {CartItem} from './cart-types'; }
    `, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CartDisplay { cartStore = inject(CartStore); diff --git a/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/src/app/app.ts b/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/src/app/app.ts index 9bf7070cc96e..5752c1ad1b84 100644 --- a/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/src/app/app.ts @@ -1,5 +1,5 @@ // TODO: Import inject from @angular/core -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; // TODO: Import CartStore from './cart-store' // TODO: Import CartDisplay from './cart-display' @@ -19,7 +19,6 @@ import {Component, ChangeDetectionStrategy} from '@angular/core';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { // TODO: Inject CartStore using inject(CartStore) diff --git a/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/src/app/cart-display.ts b/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/src/app/cart-display.ts index 6d0d4d7cbe47..86dbc5817997 100644 --- a/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/src/app/cart-display.ts +++ b/adev/src/content/tutorials/signals/steps/7-using-signals-with-services/src/app/cart-display.ts @@ -1,4 +1,4 @@ -import {Component, inject, ChangeDetectionStrategy} from '@angular/core'; +import {Component, inject} from '@angular/core'; import {CartStore} from './cart-store'; @Component({ @@ -45,7 +45,6 @@ import {CartStore} from './cart-store'; }
    `, - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CartDisplay { cartStore = inject(CartStore); diff --git a/adev/src/content/tutorials/signals/steps/8-using-signals-with-directives/answer/src/app/app.ts b/adev/src/content/tutorials/signals/steps/8-using-signals-with-directives/answer/src/app/app.ts index 4094437479ba..3d61b4e78d53 100644 --- a/adev/src/content/tutorials/signals/steps/8-using-signals-with-directives/answer/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/8-using-signals-with-directives/answer/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {HighlightDirective} from './highlight-directive'; @Component({ @@ -16,6 +16,5 @@ import {HighlightDirective} from './highlight-directive';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App {} diff --git a/adev/src/content/tutorials/signals/steps/8-using-signals-with-directives/src/app/app.ts b/adev/src/content/tutorials/signals/steps/8-using-signals-with-directives/src/app/app.ts index c760949d46f2..9fc9d03753d9 100644 --- a/adev/src/content/tutorials/signals/steps/8-using-signals-with-directives/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/8-using-signals-with-directives/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, ChangeDetectionStrategy} from '@angular/core'; +import {Component} from '@angular/core'; import {HighlightDirective} from './highlight-directive'; @Component({ @@ -16,6 +16,5 @@ import {HighlightDirective} from './highlight-directive';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App {} diff --git a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/app.ts b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/app.ts index 645b05495abb..d115f0e03292 100644 --- a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/app.ts @@ -1,4 +1,4 @@ -import {Component, signal, computed, viewChild, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, signal, viewChild} from '@angular/core'; import {CartSummary} from './cart-summary'; import {ProductCard} from './product-card'; @@ -38,7 +38,6 @@ import {ProductCard} from './product-card';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { cartQuantity = signal(2); diff --git a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/cart-summary.ts b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/cart-summary.ts index 794770b0e8d4..ee64d53fe6eb 100644 --- a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/cart-summary.ts +++ b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/cart-summary.ts @@ -1,4 +1,4 @@ -import {Component, DestroyRef, inject, input, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, DestroyRef, inject, input, signal} from '@angular/core'; @Component({ selector: 'cart-summary', @@ -13,7 +13,6 @@ import {Component, DestroyRef, inject, input, signal, ChangeDetectionStrategy} f
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CartSummary { itemCount = input.required(); diff --git a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/product-card.ts b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/product-card.ts index bb3438ec92fa..1f3acaf819eb 100644 --- a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/product-card.ts +++ b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/answer/src/app/product-card.ts @@ -1,4 +1,4 @@ -import {Component, input, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, input, signal} from '@angular/core'; @Component({ selector: 'product-card', @@ -26,7 +26,6 @@ import {Component, input, signal, ChangeDetectionStrategy} from '@angular/core';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProductCard { name = input.required(); diff --git a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/app.ts b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/app.ts index 4c81eb03bc3a..d7d022e4d00e 100644 --- a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/app.ts +++ b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/app.ts @@ -1,5 +1,5 @@ // TODO: Import viewChild from @angular/core -import {Component, signal, computed, ChangeDetectionStrategy} from '@angular/core'; +import {Component, computed, signal} from '@angular/core'; import {CartSummary} from './cart-summary'; import {ProductCard} from './product-card'; @@ -39,7 +39,6 @@ import {ProductCard} from './product-card';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class App { cartQuantity = signal(2); diff --git a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/cart-summary.ts b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/cart-summary.ts index 794770b0e8d4..ee64d53fe6eb 100644 --- a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/cart-summary.ts +++ b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/cart-summary.ts @@ -1,4 +1,4 @@ -import {Component, DestroyRef, inject, input, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, DestroyRef, inject, input, signal} from '@angular/core'; @Component({ selector: 'cart-summary', @@ -13,7 +13,6 @@ import {Component, DestroyRef, inject, input, signal, ChangeDetectionStrategy} f
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class CartSummary { itemCount = input.required(); diff --git a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/product-card.ts b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/product-card.ts index bb3438ec92fa..1f3acaf819eb 100644 --- a/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/product-card.ts +++ b/adev/src/content/tutorials/signals/steps/9-query-child-elements-with-signal-queries/src/app/product-card.ts @@ -1,4 +1,4 @@ -import {Component, input, signal, ChangeDetectionStrategy} from '@angular/core'; +import {Component, input, signal} from '@angular/core'; @Component({ selector: 'product-card', @@ -26,7 +26,6 @@ import {Component, input, signal, ChangeDetectionStrategy} from '@angular/core';
    `, styleUrl: './app.css', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProductCard { name = input.required(); diff --git a/adev/src/context/airules.md b/adev/src/context/airules.md index fc15b336a775..0044786c9522 100644 --- a/adev/src/context/airules.md +++ b/adev/src/context/airules.md @@ -13,7 +13,6 @@ import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; @Component({ selector: '{{tag-name}}-root', templateUrl: '{{tag-name}}.html', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class {{ClassName}} { protected readonly isServerRunning = signal(true); @@ -87,7 +86,6 @@ Here is a link to the most recent Angular style guide https://angular.dev/style- - Use `input()` signal instead of decorators, learn more here https://angular.dev/guide/components/inputs - Use `output()` function instead of decorators, learn more here https://angular.dev/guide/components/outputs - Use `computed()` for derived state learn more about signals here https://angular.dev/guide/signals. -- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator - Prefer inline templates for small components - Prefer Reactive forms instead of Template-driven ones - Do NOT use `ngClass`, use `class` bindings instead, for context: https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings diff --git a/adev/src/context/angular-20.mdc b/adev/src/context/angular-20.mdc index 10a378c7d367..552050c64625 100644 --- a/adev/src/context/angular-20.mdc +++ b/adev/src/context/angular-20.mdc @@ -64,7 +64,6 @@ This project adheres to modern Angular best practices, emphasizing maintainabili userSelected = output(); ``` * **`computed()` for Derived State:** Use the `computed()` function from `@angular/core` for derived state based on signals. -* **`ChangeDetectionStrategy.OnPush`:** Always set `changeDetection: ChangeDetectionStrategy.OnPush` in the `@Component` decorator for performance benefits by reducing unnecessary change detection cycles. * **Inline Templates:** Prefer inline templates (template: `...`) for small components to keep related code together. For larger templates, use external HTML files. * **Reactive Forms:** Prefer Reactive forms over Template-driven forms for complex forms, validation, and dynamic controls due to their explicit, immutable, and synchronous nature. * **No `ngClass` / `NgClass`:** Do not use the `ngClass` directive. Instead, use native `class` bindings for conditional styling. diff --git a/adev/src/context/guidelines.md b/adev/src/context/guidelines.md index ca84199e3d9f..8bc6f835c0ad 100644 --- a/adev/src/context/guidelines.md +++ b/adev/src/context/guidelines.md @@ -13,7 +13,6 @@ import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; @Component({ selector: '{{tag-name}}-root', templateUrl: '{{tag-name}}.html', - changeDetection: ChangeDetectionStrategy.OnPush, }) export class {{ClassName}} { protected readonly isServerRunning = signal(true); @@ -93,7 +92,6 @@ Here is a link to the most recent Angular style guide https://angular.dev/style- - Use `input()` signal instead of decorators, learn more here https://angular.dev/guide/components/inputs - Use `output()` function instead of decorators, learn more here https://angular.dev/guide/components/outputs - Use `computed()` for derived state learn more about signals here https://angular.dev/guide/signals. -- Set `changeDetection: ChangeDetectionStrategy.OnPush` in `@Component` decorator - Prefer inline templates for small components - Prefer Reactive forms instead of Template-driven ones - Do NOT use `ngClass`, use `class` bindings instead, for context: https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings diff --git a/adev/src/context/llms-list.md b/adev/src/context/llms-list.md index a379f90ffa3b..8942066a1474 100644 --- a/adev/src/context/llms-list.md +++ b/adev/src/context/llms-list.md @@ -42,6 +42,7 @@ adev/src/content/guide/signals/resource.md adev/src/content/guide/di/dependency-injection.md adev/src/content/guide/di/creating-injectable-service.md adev/src/content/guide/di/dependency-injection-providers.md +adev/src/content/guide/di/lazy-loading-services.md adev/src/content/guide/di/dependency-injection-context.md adev/src/content/guide/di/hierarchical-dependency-injection.md adev/src/content/guide/di/lightweight-injection-tokens.md diff --git a/adev/src/llms.txt b/adev/src/llms.txt index 7c302e1e32a3..844631671612 100644 --- a/adev/src/llms.txt +++ b/adev/src/llms.txt @@ -48,6 +48,7 @@ Angular — Deliver web apps with confidence 🚀 - [Understanding Dependency injection](https://angular.dev/guide/di/dependency-injection) - [Creating an injectable service](https://angular.dev/guide/di/creating-injectable-service) - [Configuring dependency providers](https://angular.dev/guide/di/dependency-injection-providers) +- [Lazy loading services](https://angular.dev/guide/di/lazy-loading-services) - [Injection context](https://angular.dev/guide/di/dependency-injection-context) - [Hierarchical injectors](https://angular.dev/guide/di/hierarchical-dependency-injection) - [Optimizing Injection tokens](https://angular.dev/guide/di/lightweight-injection-tokens) @@ -76,6 +77,7 @@ Angular — Deliver web apps with confidence 🚀 ## Routing - [Routing overview](https://angular.dev/guide/routing) - [Define routes](https://angular.dev/guide/routing/define-routes) +- [Route loading strategies](https://angular.dev/guide/routing/loading-strategies) - [Show routes with outlets](https://angular.dev/guide/routing/show-routes-with-outlets) - [Navigate to routes](https://angular.dev/guide/routing/navigate-to-routes) - [Read route state](https://angular.dev/guide/routing/read-route-state) diff --git a/browser-providers.conf.d.ts b/browser-providers.conf.d.ts deleted file mode 100644 index eae2795e9731..000000000000 --- a/browser-providers.conf.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -type CustomLauncher = { - base: string; - browserName: string; - platformName: string; - platformVersion: string; - deviceName: string; - appiumVersion: string; - extendedDebugging: boolean; -} - -type CustomLaunchers = { - [string]: CustomLauncher; -}; - -type SauceAliases = { - [string]: string[]; -}; - -export const customLaunchers: CustomLaunchers; -export const sauceAliases: SauceAliases; diff --git a/browser-providers.conf.js b/browser-providers.conf.js deleted file mode 100644 index 049e8dbf0fa3..000000000000 --- a/browser-providers.conf.js +++ /dev/null @@ -1,75 +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 - */ - -// Unique place to configure the browsers which are used in the different CI jobs in Sauce Labs (SL) -// If the target is set to null, then the browser is not run anywhere during CI. -// If a category becomes empty (e.g. BS and required), then the corresponding job must be commented -// out in the CI configuration. -const config = { - 'Android13': {unitTest: {target: 'SL', required: true}}, - 'Android14': {unitTest: {target: 'SL', required: true}}, -}; - -/** Whether browsers should be remotely acquired in debug mode. */ -const debugMode = false; - -// Karma-sauce-launcher isn't really maintained and doesn't support officially appium2 -// Looking at the source code https://github.com/karma-runner/karma-sauce-launcher/blob/69dcb822a45d29e57297b0eda7af4123ae55aafd/src/process-config.ts#L60 -// We can force the config to be recognized as W3C by setting a browserVersion property -const browserVersion = 'latest'; - -// Specific platform configuration can be found at: -// https://saucelabs.com/platform/platform-configurator -const customLaunchers = { - 'SL_ANDROID13': { - base: 'SauceLabs', - platformName: 'Android', - browserName: 'Chrome', - browserVersion, - 'appium:deviceName': 'Google Pixel 5a GoogleAPI Emulator', - 'appium:platformVersion': '13.0', - 'appium:automationName': 'uiautomator2', - 'sauce:options': { - appiumVersion: '2.0.0', - extendedDebugging: debugMode, - }, - }, - - 'SL_ANDROID14': { - base: 'SauceLabs', - platformName: 'Android', - browserName: 'Chrome', - browserVersion, - 'appium:deviceName': 'Google Pixel 6 Pro GoogleAPI Emulator', - 'appium:platformVersion': '14.0', - 'appium:automationName': 'uiautomator2', - 'sauce:options': { - appiumVersion: '2.0.0', - extendedDebugging: debugMode, - }, - }, -}; - -const sauceAliases = { - 'CI_REQUIRED': buildConfiguration('unitTest', 'SL', true), - 'CI_OPTIONAL': buildConfiguration('unitTest', 'SL', false), -}; - -module.exports = { - customLaunchers: customLaunchers, - sauceAliases: sauceAliases, -}; - -function buildConfiguration(type, target, required) { - return Object.keys(config) - .filter((item) => { - const conf = config[item][type]; - return conf.required === required && conf.target === target; - }) - .map((item) => target + '_' + item.toUpperCase()); -} diff --git a/contributing-docs/branches-and-versioning.md b/contributing-docs/branches-and-versioning.md index 1ce5a556b943..9a5adec81e7f 100644 --- a/contributing-docs/branches-and-versioning.md +++ b/contributing-docs/branches-and-versioning.md @@ -102,14 +102,14 @@ major version. Two additional target labels do not map to specific versions while still defining which branch to merge into. -| Label | Description | -| ------------------ | ----------------------------------------------------- | -| target: automation | A automated change made by the angular-robot account. | -| target: feature | A change to be made in a feature branch. | +| Label | Description | +| ------------------ | ------------------------------------------------------ | +| target: automation | An automated change made by the angular-robot account. | +| target: feature | A change to be made in a feature branch. | Changes made to a feature branch, outside of our typical branching and merging process utilize the `target: feature` branch. Additionally, the `target: automation` label, which is only able to be -utilized by the `angular-robot` account targets only the branch defined within the Github UI. +utilized by the `angular-robot` account targets only the branch defined within the GitHub UI. ### Pull request examples diff --git a/contributing-docs/building-and-testing-angular.md b/contributing-docs/building-and-testing-angular.md index 2d3264e61c8e..d466ce37511b 100644 --- a/contributing-docs/building-and-testing-angular.md +++ b/contributing-docs/building-and-testing-angular.md @@ -6,8 +6,9 @@ It also explains the basic mechanics of using `git`, `node`, and `pnpm`. - [Building and Testing Angular](#building-and-testing-angular) - [Prerequisite Software](#prerequisite-software) - [Development in a Container](#development-in-a-container) + - [Experimenting in dev-app](#experimenting-in-dev-app) - [Getting the Sources](#getting-the-sources) - - [Installing NPM Modules](#installing-npm-modules) + - [Installing npm Modules](#installing-npm-modules) - [Building](#building) - [Running Tests Locally](#running-tests-locally) - [Testing changes against a local library/project](#testing-changes-against-a-local-libraryproject) @@ -57,6 +58,10 @@ following on your development machine: You can also use the provided [Dev Container](https://containers.dev/) configuration to set up a development environment. This approach uses Docker to create a container with all the necessary tools (Node.js, pnpm, etc.) pre-installed. +## Experimenting in dev-app + +For experimentation while developing Angular, consider running the [dev-app](../dev-app/README.md) in this repository. + **Prerequisites:** - [Docker Desktop](https://www.docker.com/products/docker-desktop) @@ -90,7 +95,7 @@ cd angular git remote add upstream https://github.com/angular/angular.git ``` -## Installing NPM Modules +## Installing npm Modules Next, install the JavaScript modules needed to build and test Angular: diff --git a/contributing-docs/caretaking.md b/contributing-docs/caretaking.md index 434fb3ae564b..d4ee14a6fe72 100644 --- a/contributing-docs/caretaking.md +++ b/contributing-docs/caretaking.md @@ -26,7 +26,7 @@ pnpm ng-dev pr merge Some directories in the Angular codebase have additional protections or rules. For example, code under `//packages/core/primitives` must be merged and synced into Google separately from other changes. Attempting to combine changes in `primitives` with other changes results in an error. This -practices makes it significantly easier to rollback or revert changes in the event of a breakage or +practice makes it significantly easier to rollback or revert changes in the event of a breakage or outage. ## PRs that require global presubmits diff --git a/contributing-docs/commit-message-guidelines.md b/contributing-docs/commit-message-guidelines.md index 709e830ecd19..1a858d80b9da 100644 --- a/contributing-docs/commit-message-guidelines.md +++ b/contributing-docs/commit-message-guidelines.md @@ -45,7 +45,7 @@ Must be one of the following: | Type | Description | | ------------ | --------------------------------------------------------------------------------------------------- | | **build** | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) | -| **ci** | Changes to our CI configuration files and scripts (examples: Github Actions, SauceLabs) | +| **ci** | Changes to our CI configuration files and scripts (examples: GitHub Actions) | | **docs** | Documentation only changes | | **feat** | A new feature | | **fix** | A bug fix | diff --git a/contributing-docs/documentation-authoring.md b/contributing-docs/documentation-authoring.md index 6d8eaa1df01c..9dad61ce85ac 100644 --- a/contributing-docs/documentation-authoring.md +++ b/contributing-docs/documentation-authoring.md @@ -94,7 +94,7 @@ because they are _very_ common. - Use callouts for a brief aside that offers more context on a topic that's not strictly necessary to understanding the main content - Never put multiple callouts next to each other or in proximity (such as separated - by one a line or two of text) + by a line or two of text) - Never put a callout inside another element, such as a card or table cell - **Alerts** - Use sparingly to call attention to a very brief but relevant point diff --git a/contributing-docs/saved-issue-replies.md b/contributing-docs/saved-issue-replies.md index c35fbe53dc1c..99450a3da7ac 100644 --- a/contributing-docs/saved-issue-replies.md +++ b/contributing-docs/saved-issue-replies.md @@ -78,7 +78,7 @@ If the problem still exists in your application, please [open a new issue](https ## Angular: Support Request (v1) ``` -Hello, we reviewed this issue and determined that it doesn't fall into the bug report or feature request category. This issue tracker is not suitable for support requests, please repost your issue on [StackOverflow](https://stackoverflow.com/) using tag `angular`. +Hello, we reviewed this issue and determined that it doesn't fall into the bug report or feature request category. This issue tracker is not suitable for support requests, please repost your issue on [Stack Overflow](https://stackoverflow.com/) using tag `angular`. If you are wondering why we don't resolve support issues via the issue tracker, please [check out this explanation](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#question). ``` @@ -96,3 +96,13 @@ Once you've finished that update, you will need to force push using `git push [o ``` Please rebase and squash your commits. To do this, make sure to `git fetch upstream` to get the latest changes from the angular repository. Then in your branch run `git rebase upstream/main -i` to do an interactive rebase. This should allow you to fixup or drop any unnecessary commits. After you finish the rebase, force push using `git push [origin name] [branch name] --force`. ``` + +## Angular: Spam + +``` +Woah, looks like you've opened a lot of issues/PRs recently. While we appreciate contributions from the community, triaging and reviewing a large influx of content in a short time period takes time away from other ongoing projects. As a result, we're closing these issues/PRs to maintain the team's focus. + +Note that this is not necessarily a rejection of the goals or direction of any of these contributions in particular, so much as a reflection of the team's current capacity and priorities. + +You are welcome to open a smaller subset of issues/PRs in accordance with [our policy](/contributing-docs/spam.md) focused on the most important and impactful contributions and we will do our best to prioritize a response as soon as possible. +``` diff --git a/contributing-docs/spam.md b/contributing-docs/spam.md new file mode 100644 index 000000000000..46a822fc5f0c --- /dev/null +++ b/contributing-docs/spam.md @@ -0,0 +1,25 @@ +# Spam Policy + +Users who create excessive amounts of issues or PRs in a short time frame, +particularly low-quality or low-value contributions, may see those contributions +automatically closed without consideration. + +Community contributors should limit themselves to no more than 3 open PRs at a +single time. If you have more than 3 open PRs, we ask that you wait until some of those +PRs are merged or closed before opening more. + +Draft pull requests are considered acceptable beyond the limit if 3, but within a reasonable limit (~10 or more within a short time frame). Beyond that reasonable limit, the team may still consider it spam and close them without consideration. + +## Why? + +Reviewing and shepherding PRs as well as managing large issue counts takes time +and energy from the core team. Triaging a PR well enough to understand its goals +and implementation takes a significant amount of time, regardless of whether the +PR is ultimately accepted. This policy ensures the team is able to balance its +limited resources across all contributors and projects. + +## Exceptions + +If you plan to undertake more significant work that you anticipate will generate +many individual PRs, please reach out to the team in a GitHub issue first to +validate that Angular will be able to support and accept your contributions. diff --git a/contributing-docs/triage-and-labelling.md b/contributing-docs/triage-and-labelling.md index aa717e4121b7..c0b97ebe3bc3 100644 --- a/contributing-docs/triage-and-labelling.md +++ b/contributing-docs/triage-and-labelling.md @@ -9,7 +9,7 @@ The owner of the component is then responsible for the detailed / component-leve ### Areas The caretaker should be able to determine the _area_ for incoming issues. -Most areas generally corresponds to a specific directory or set of directories in this repo. Some +Most areas generally correspond to a specific directory or set of directories in this repo. Some areas are more cross-cutting (e.g. for performance or security). Apply all labels that make sense for an issue. Each `area: ` label on GitHub should have a description of what it's for. diff --git a/contributing-docs/using-fixup-commits.md b/contributing-docs/using-fixup-commits.md index baac7dd95a5c..a98b74cb4617 100644 --- a/contributing-docs/using-fixup-commits.md +++ b/contributing-docs/using-fixup-commits.md @@ -51,7 +51,7 @@ However, amending an existing commit with the changes makes it difficult for the exactly what has changed since the last time they reviewed the Pull Request. Here is where fixup commits come in handy. -By addressing review feedback in fixup commits, you make it very straight forward for the reviewer +By addressing review feedback in fixup commits, you make it very straightforward for the reviewer to see what are the new changes that need to be reviewed and verify that their earlier feedback has been addressed. This can save a lot of effort, especially on larger Pull Requests (where having to re-review _all_ diff --git a/dev-app/BUILD.bazel b/dev-app/BUILD.bazel index 5df24eacfb9b..654edbbcc225 100644 --- a/dev-app/BUILD.bazel +++ b/dev-app/BUILD.bazel @@ -43,6 +43,10 @@ ng_application( ng_config = ":ng_config", node_modules = ":node_modules", project_name = "dev-app", + serve_args = [ + "--port", + "4201", + ], tags = [ # Tagged as manual as both build and build.production use the same output directory. "manual", diff --git a/dev-app/angular.json b/dev-app/angular.json index 7447a8105f3e..7f2cac9d90e3 100644 --- a/dev-app/angular.json +++ b/dev-app/angular.json @@ -22,6 +22,7 @@ "externalDependencies": ["xhr2"], "browser": "src/main.ts", "tsConfig": "tsconfig.app.json", + "polyfills": ["@angular/localize/init"], "assets": [ { "glob": "**/*", diff --git a/dev-app/package.json b/dev-app/package.json index 07d5691e9f7d..4c2067f4eb82 100644 --- a/dev-app/package.json +++ b/dev-app/package.json @@ -13,17 +13,18 @@ "@angular/forms": "workspace:*", "@angular/platform-browser": "workspace:*", "@angular/platform-server": "workspace:*", + "@angular/localize": "workspace:*", "@angular/router": "workspace:*", - "@angular/ssr": "21.2.0-next.2", + "@angular/ssr": "22.0.0-rc.0", "rxjs": "~7.8.0", "tslib": "^2.3.0" }, "devDependencies": { - "@angular/build": "21.2.0-next.2", - "@angular/cli": "21.2.0-next.2", + "@angular/build": "22.0.0-rc.0", + "@angular/cli": "22.0.0-rc.0", "@angular/compiler-cli": "workspace:*", - "jsdom": "^28.0.0", - "typescript": "~5.9.2", + "jsdom": "^29.0.0", + "typescript": "~6.0.2", "vitest": "^4.0.0" } } diff --git a/dev-app/public/BUILD.bazel b/dev-app/public/BUILD.bazel index 20ef9cc9550c..4ad9fb977d1b 100644 --- a/dev-app/public/BUILD.bazel +++ b/dev-app/public/BUILD.bazel @@ -1,4 +1,4 @@ -load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") +load("@bazel_lib//lib:copy_to_bin.bzl", "copy_to_bin") exports_files( glob(["*"]), diff --git a/dev-app/src/app/app.spec.ts b/dev-app/src/app/app.spec.ts index 6690af8000b3..7a6dc876d1d7 100644 --- a/dev-app/src/app/app.spec.ts +++ b/dev-app/src/app/app.spec.ts @@ -5,7 +5,7 @@ describe('App', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [App], - }).compileComponents(); + }); }); it('should create the app', () => { diff --git a/devtools/BUILD.bazel b/devtools/BUILD.bazel index 207b4561c183..2b70eebb5636 100644 --- a/devtools/BUILD.bazel +++ b/devtools/BUILD.bazel @@ -1,7 +1,25 @@ +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") +load("@npm//:defs.bzl", "npm_link_all_packages") load("//devtools/tools:defaults.bzl", "ts_config") package(default_visibility = ["//visibility:public"]) +npm_link_all_packages( + name = "node_modules", +) + +bool_flag( + name = "debug", + build_setting_default = False, +) + +config_setting( + name = "debug_build", + flag_values = { + ":debug": "True", + }, +) + exports_files([ "tsconfig.json", "cypress.json", diff --git a/devtools/README.md b/devtools/README.md index 29b6ca172e30..b89ec8aae997 100644 --- a/devtools/README.md +++ b/devtools/README.md @@ -50,6 +50,44 @@ This would start a development server that you can access on service worker" button to open a debugger. + +### Enabling sourcemaps + +To enable sourcemaps you need to add the `sourcemap = "inline"` flag to the `esbuild` macro located in `tools/defaults.bzl`. + ### Running End-to-End Tests Before running end-to-end tests, you need to start the development server using: diff --git a/devtools/cypress/integration/comment-nodes.e2e.js b/devtools/cypress/integration/comment-nodes.e2e.js index 2096f5f5b2e9..c04cb106d141 100644 --- a/devtools/cypress/integration/comment-nodes.e2e.js +++ b/devtools/cypress/integration/comment-nodes.e2e.js @@ -8,7 +8,7 @@ function showComments() { cy.get('.main-toolbar > .settings > button:last-child').click(); - cy.get('.cdk-overlay-container mat-slide-toggle + span:contains("Show comment nodes")').click(); + cy.get('ng-settings #show-comment-nodes').click(); } describe('Comment nodes', () => { diff --git a/devtools/cypress/integration/node-search.e2e.js b/devtools/cypress/integration/node-search.e2e.js index 153ee5ce429c..5ee6cc089179 100644 --- a/devtools/cypress/integration/node-search.e2e.js +++ b/devtools/cypress/integration/node-search.e2e.js @@ -103,9 +103,9 @@ describe('Search items in component tree', () => { it('should be able to search and select @defers in different Angular applications', () => { inputSearchText('@defer'); checkSearchedNodesLength('.matched-text', 2); - cy.get('.defer-details').should('contain.text', '@placeholder(minimum 5000 ms)'); + cy.get('ng-defer-view').should('contain.text', '@placeholder(minimum 5000 ms)'); inputSearchText('{enter}'); - cy.get('.defer-details').should('contain.text', '@placeholder(minimum 2000 ms)'); + cy.get('ng-defer-view').should('contain.text', '@placeholder(minimum 2000 ms)'); }); it('should not duplicate application roots if multiple applications are present', () => { diff --git a/devtools/cypress/integration/track-state.e2e.js b/devtools/cypress/integration/track-state.e2e.js index bde47ddc943a..8c79e31430d2 100644 --- a/devtools/cypress/integration/track-state.e2e.js +++ b/devtools/cypress/integration/track-state.e2e.js @@ -8,9 +8,7 @@ function showTransferState() { cy.get('.main-toolbar > .settings > button:last-child').click(); - cy.get( - '.cdk-overlay-container mat-slide-toggle + span:contains("Enable Transfer State Tab")', - ).click(); + cy.get('ng-settings #enable-transfer-state').click(); } function goToTransferStateTab() { diff --git a/devtools/cypress/integration/view-component-metadata.e2e.js b/devtools/cypress/integration/view-component-metadata.e2e.js index a1548c95b9df..f27af702d6c2 100644 --- a/devtools/cypress/integration/view-component-metadata.e2e.js +++ b/devtools/cypress/integration/view-component-metadata.e2e.js @@ -26,7 +26,7 @@ describe('Viewing component metadata', () => { }); it('should display change detection strategy', () => { - cy.contains('ng-component-metadata', 'Change Detection Strategy: Default'); + cy.contains('ng-component-metadata', 'Change Detection Strategy: OnPush'); }); }); @@ -40,7 +40,7 @@ describe('Viewing component metadata', () => { }); it('should display change detection strategy', () => { - cy.contains('ng-component-metadata', 'Change Detection Strategy: Default'); + cy.contains('ng-component-metadata', 'Change Detection Strategy: OnPush'); }); it('should display correct set of inputs', () => { diff --git a/devtools/package.json b/devtools/package.json new file mode 100644 index 000000000000..4677d3d8fff0 --- /dev/null +++ b/devtools/package.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "d3": "^7.0.0", + "dagre-d3-es": "^7.0.14", + "ngx-flamegraph": "0.1.1", + "todomvc-app-css": "^2.3.0", + "todomvc-common": "^1.0.5", + "webtreemap": "^2.0.1" + }, + "devDependencies": { + "@types/d3": "^7.4.3", + "@types/firefox-webext-browser": "^143.0.0" + } +} diff --git a/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts b/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts index 7b7fca6adc98..ef7a8f4a3fcb 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts @@ -40,9 +40,8 @@ import { getInjectorResolutionPath, getLatestComponentState, idToInjector, - injectorsSeen, isElementInjector, - isOnPushDirective, + getDirectiveCdStrategy, logValue, nodeInjectorToResolutionPath, queryDirectiveForest, @@ -146,22 +145,6 @@ const getLatestComponentExplorerViewCallback = ngDebugDependencyInjectionApiIsSupported(), ); - // cleanup injector id mappings - for (const injectorId of idToInjector.keys()) { - if (!injectorsSeen.has(injectorId)) { - const injector = idToInjector.get(injectorId)!; - if (isElementInjector(injector)) { - const element = getElementInjectorElement(injector); - if (element) { - nodeInjectorToResolutionPath.delete(element); - } - } - - idToInjector.delete(injectorId); - } - } - injectorsSeen.clear(); - if (!query) { messageBus.emit('latestComponentExplorerView', [{forest}]); return; @@ -241,7 +224,7 @@ const getNestedPropertiesCallback = return emitEmpty(); } const current = - position.directive === undefined ? node.component : node.directives[position.directive]; + position.directive === undefined ? node.component : node.directives?.[position.directive]; if (!current) { return emitEmpty(); } @@ -387,8 +370,10 @@ export interface SerializableComponentInstanceType extends ComponentType { id: number; } -export interface SerializableComponentTreeNode - extends DevToolsNode { +export interface SerializableComponentTreeNode extends DevToolsNode< + SerializableDirectiveInstanceType, + SerializableComponentInstanceType +> { children: SerializableComponentTreeNode[]; nativeElement?: never; // Since the nativeElement is not serializable, we will use this boolean as backup @@ -430,14 +415,14 @@ const prepareForestForSerialization = ( id: initializeOrGetDirectiveForestHooks().getDirectiveId(node.component.instance)!, } : null, - directives: node.directives.map((d) => ({ + directives: node.directives?.map((d) => ({ name: d.name, id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance)!, })), children: prepareForestForSerialization(node.children, includeResolutionPath), hydration: node.hydration, - defer: node.defer, - onPush: node.component ? isOnPushDirective(node.component) : false, + controlFlowBlock: node.controlFlowBlock, + changeDetection: node.component ? getDirectiveCdStrategy(node.component) : undefined, // native elements are not serializable hasNativeElement: !!node.nativeElement, @@ -477,21 +462,17 @@ function getNodeDIResolutionPath(node: ComponentTreeNode): SerializedInjector[] nodeInjectorToResolutionPath.set(element, serializeResolutionPath(resolutionPaths)); } - const serializedPath = nodeInjectorToResolutionPath.get(element)!; - for (const injector of serializedPath) { - injectorsSeen.add(injector.id); - } - - return serializedPath; + return nodeInjectorToResolutionPath.get(element)!; } const getInjectorProvidersCallback = (messageBus: MessageBus) => (injector: SerializedInjector) => { - if (!idToInjector.has(injector.id)) { + const resolvedInjector = idToInjector.get(injector.id)?.deref(); + if (!resolvedInjector) { return; } - const providerRecords = getInjectorProviders(idToInjector.get(injector.id)!); + const providerRecords = getInjectorProviders(resolvedInjector); const allProviderRecords: SerializedProviderRecord[] = []; const tokenToRecords: Map = new Map(); @@ -542,12 +523,11 @@ const logProvider = ( serializedInjector: SerializedInjector, serializedProvider: SerializedProviderRecord, ): void => { - if (!idToInjector.has(serializedInjector.id)) { + const injector = idToInjector.get(serializedInjector.id)?.deref(); + if (!injector) { return; } - const injector = idToInjector.get(serializedInjector.id)!; - const providerRecords = getInjectorProviders(injector); console.group( @@ -619,11 +599,11 @@ const getInjectorInstance = ( serializedInjector: SerializedInjector, serializedProvider: SerializedProviderRecord, ) => { - if (!idToInjector.has(serializedInjector.id)) { + const injector = idToInjector.get(serializedInjector.id)?.deref(); + if (!injector) { return; } - const injector = idToInjector.get(serializedInjector.id)!; const providerRecords = getInjectorProviders(injector); if (typeof serializedProvider.index === 'number') { diff --git a/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.spec.ts b/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.spec.ts index 00601d20e700..57a5f713d3bc 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.spec.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.spec.ts @@ -6,7 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Injector, ɵGlobalDevModeUtils} from '@angular/core'; +import { + Injector, + ɵGlobalDevModeUtils, + ɵProviderRecord as ProviderRecord, + InjectionToken, +} from '@angular/core'; import { getInjectorFromElementNode, getRootElements, @@ -59,7 +64,13 @@ describe('component-tree', () => { describe('getRootElements', () => { beforeEach(() => { const ng: Partial = { - getComponent: jasmine.createSpy('getComponent').and.returnValue({}), + getComponent: jasmine.createSpy('getComponent').and.callFake((element: HTMLElement) => { + // Will treat only `ng-*` elements as Angular components. + if (element.tagName.toLowerCase().startsWith('ng-')) { + return element; + } + return null; + }), }; (window as any).ng = ng; }); @@ -105,6 +116,21 @@ describe('component-tree', () => { expect(roots.length).toEqual(1); expect(roots).toContain(document.body); }); + + it('should return all root elements with all non-application root components', () => { + const rootElement = createRoot(); + const childElement = createRoot(); + const nonAppRootCmp = document.createElement('ng-cmp'); + + rootElement.appendChild(childElement); + document.body.appendChild(rootElement); + document.body.appendChild(nonAppRootCmp); + + const roots = getRootElements(); + + expect(roots.length).toEqual(2); + expect(roots).toEqual([rootElement, nonAppRootCmp]); + }); }); describe('serializeProviderRecord', () => { @@ -144,5 +170,17 @@ describe('component-tree', () => { expect(result.type).toBe('type'); expect(result.token).toBe('MyCustomService'); }); + + it('should handle injection tokens', () => { + const result = serializeProviderRecord( + { + token: new InjectionToken('FOO'), + isViewProvider: false, + } as ProviderRecord, + 0, + ); + + expect(result.token).toBe('InjectionToken (FOO)'); + }); }); }); diff --git a/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts b/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts index 9a24f08e3b31..fb6012f417f4 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts @@ -20,6 +20,7 @@ import type { ɵProviderRecord as ProviderRecord, } from '@angular/core'; import { + ChangeDetection, ComponentExplorerViewQuery, DirectiveMetadata, DirectivePosition, @@ -52,14 +53,26 @@ import {unwrapSignal} from '../utils'; export const injectorToId = new WeakMap(); export const nodeInjectorToResolutionPath = new WeakMap(); -export const idToInjector = new Map(); -export const injectorsSeen = new Set(); +export const idToInjector = new Map>(); +const injectorFinalizer = new FinalizationRegistry((id) => { + idToInjector.delete(id); +}); let injectorId = 0; export function getInjectorId() { return `${injectorId++}`; } +function getOrCreateInjectorId(key: Injector | HTMLElement, injector: Injector): string { + if (!injectorToId.has(key)) { + const newId = getInjectorId(); + injectorToId.set(key, newId); + idToInjector.set(newId, new WeakRef(injector)); + injectorFinalizer.register(injector, newId); + } + return injectorToId.get(key)!; +} + const INTERNAL_TOKENS = [ 'ElementRef', 'Renderer2', @@ -160,7 +173,7 @@ export const getLatestComponentState = ( } }; - node.directives.forEach((dir) => populateResultSet(dir)); + node.directives?.forEach((dir) => populateResultSet(dir)); if (node.component) { populateResultSet(node.component); } @@ -171,18 +184,8 @@ export const getLatestComponentState = ( }; function serializeElementInjectorWithId(injector: Injector): SerializedInjector | null { - let id: string; const element = getElementInjectorElement(injector); - - if (!injectorToId.has(element)) { - id = getInjectorId(); - injectorToId.set(element, id); - idToInjector.set(id, injector); - } - - id = injectorToId.get(element)!; - idToInjector.set(id, injector); - injectorsSeen.add(id); + const id = getOrCreateInjectorId(element, injector); const serializedInjector = serializeInjector(injector); if (serializedInjector === null) { @@ -201,17 +204,7 @@ function serializeInjectorWithId(injector: Injector): SerializedInjector | null } function serializeEnvironmentInjectorWithId(injector: Injector): SerializedInjector | null { - let id: string; - - if (!injectorToId.has(injector)) { - id = getInjectorId(); - injectorToId.set(injector, id); - idToInjector.set(id, injector); - } - - id = injectorToId.get(injector)!; - idToInjector.set(id, injector); - injectorsSeen.add(id); + const id = getOrCreateInjectorId(injector, injector); const serializedInjector = serializeInjector(injector); if (serializedInjector === null) { @@ -225,7 +218,7 @@ const enum DirectiveMetadataKey { INPUTS = 'inputs', OUTPUTS = 'outputs', ENCAPSULATION = 'encapsulation', - ON_PUSH = 'onPush', + CHANGE_DETECTION = 'changeDetection', } // Gets directive metadata. For newer versions of Angular (v12+) it uses @@ -246,7 +239,7 @@ const getDirectiveMetadata = (dir: any): DirectiveMetadata => { inputs: meta.inputs, outputs: meta.outputs, encapsulation: meta.encapsulation, - onPush: meta.changeDetection === ChangeDetectionStrategy.OnPush, + changeDetection: meta.changeDetection, }; } case Framework.ACX: { @@ -257,7 +250,7 @@ const getDirectiveMetadata = (dir: any): DirectiveMetadata => { inputs: meta.inputs, outputs: meta.outputs, encapsulation: meta.encapsulation, - onPush: meta.changeDetection === AcxChangeDetectionStrategy.OnPush, + changeDetection: meta.changeDetection, }; } case Framework.Wiz: { @@ -288,19 +281,33 @@ const getDirectiveMetadata = (dir: any): DirectiveMetadata => { inputs: safelyGrabMetadata(DirectiveMetadataKey.INPUTS), outputs: safelyGrabMetadata(DirectiveMetadataKey.OUTPUTS), encapsulation: safelyGrabMetadata(DirectiveMetadataKey.ENCAPSULATION), - onPush: safelyGrabMetadata(DirectiveMetadataKey.ON_PUSH), + changeDetection: safelyGrabMetadata(DirectiveMetadataKey.CHANGE_DETECTION), }; }; -export function isOnPushDirective(dir: any): boolean { +export function getDirectiveCdStrategy(dir: any): ChangeDetection | undefined { const metadata = getDirectiveMetadata(dir.instance); + switch (metadata.framework) { case Framework.Angular: - return Boolean(metadata.onPush); + switch (metadata.changeDetection) { + case ChangeDetectionStrategy.OnPush: + return 'ng-on-push'; + case ChangeDetectionStrategy.Eager: + return 'ng-eager'; + } + case Framework.ACX: - return Boolean(metadata.onPush); + switch (metadata.changeDetection) { + case AcxChangeDetectionStrategy.Default: + return 'acx-default'; + case AcxChangeDetectionStrategy.OnPush: + return 'acx-on-push'; + } + case Framework.Wiz: - return false; + return undefined; + default: throw new Error(`Unknown framework: "${(metadata as {framework: string}).framework}".`); } @@ -392,10 +399,6 @@ const getDependenciesForDirective = ( resolutionPath: dependencyResolutionPath, }; - if (dependency.token && isInjectionToken(dependency.token)) { - service.token = dependency.token!.toString(); - } - serializedInjectedServices.push(service); } @@ -407,7 +410,14 @@ const getDependenciesForDirective = ( const valueToLabel = (value: any): string => { if (isInjectionToken(value)) { - return value.toString(); + const token = value.toString(); + + // This logic relies on the current InjectionToken.toString implementation. + if (token.startsWith('InjectionToken')) { + const tokenName = token.replace('InjectionToken', '').trim(); + return `InjectionToken (${tokenName})`; + } + return token; } if (typeof value === 'object') { @@ -689,7 +699,7 @@ export const queryDirectiveForest = ( } forest = node.children; } - if (node?.defer) { + if (node?.controlFlowBlock) { return null; } return node; @@ -721,7 +731,7 @@ export const updateState = (updatedStateData: UpdatedStateData): void => { ); return; } - if (updatedStateData.directiveId.directive !== undefined) { + if (node.directives && updatedStateData.directiveId.directive !== undefined) { const directive = node.directives[updatedStateData.directiveId.directive].instance; mutateNestedProp(directive, updatedStateData.keyPath, updatedStateData.newValue); if (ngDebugApiIsSupported(ng, 'getOwningComponent')) { @@ -751,7 +761,7 @@ export function logValue(valueInfo: { return; } - if (valueInfo.directiveId.directive !== undefined) { + if (node.directives && valueInfo.directiveId.directive !== undefined) { const directiveInstance = node.directives[valueInfo.directiveId.directive].instance; if (valueInfo.keyPath === null) { logToConsole(directiveInstance); diff --git a/devtools/projects/ng-devtools-backend/src/lib/component-tree/core-enums.ts b/devtools/projects/ng-devtools-backend/src/lib/component-tree/core-enums.ts index 49789239c947..a751b8a1acaa 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/component-tree/core-enums.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/component-tree/core-enums.ts @@ -12,7 +12,7 @@ // for Trusted Types, which we reinstantiate. export enum ChangeDetectionStrategy { OnPush = 0, - Default = 1, + Eager = 1, } export enum AcxChangeDetectionStrategy { diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/BUILD.bazel index f2b7789e9c4a..c998453c6dcb 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/BUILD.bazel @@ -10,12 +10,14 @@ ts_project( ), deps = [ "//:node_modules/@angular/core", - "//:node_modules/semver-dsl", + "//:node_modules/@types/semver", + "//:node_modules/semver", "//devtools/projects/ng-devtools-backend/src/lib:highlighter", "//devtools/projects/ng-devtools-backend/src/lib:interfaces", "//devtools/projects/ng-devtools-backend/src/lib:utils", "//devtools/projects/ng-devtools-backend/src/lib:version", "//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api", + "//devtools/projects/ng-devtools-backend/src/lib/state-serializer", "//devtools/projects/protocol", ], ) diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/control-flow.ts b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/control-flow.ts new file mode 100644 index 000000000000..d80e873a5f44 --- /dev/null +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/control-flow.ts @@ -0,0 +1,134 @@ +/** + * @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 { + ɵDeferBlockData as DeferBlockDataInternal, + ɵControlFlowBlock as ControlFlowBlockInternal, + ɵControlFlowBlockType as ControlFlowBlockTypeInternal, +} from '@angular/core'; +import { + ControlFlowBlock, + ControlFlowBlockType, + ForLoopBlock, + DeferBlock, + RenderedDeferBlock, +} from '../../../../protocol'; +import {ComponentTreeNode} from '../interfaces'; +import {serializeValue} from '../state-serializer/state-serializer'; + +const ELEMENT_NAME_MAP: {[key in ControlFlowBlockType]: string} = { + [ControlFlowBlockType.Defer]: '@defer', + [ControlFlowBlockType.For]: '@for', +}; + +export function isControlFlowBlock(node: Node, iterator: ControlFlowBlocksIterator) { + const currentBlock = iterator.currentBlock; + // Handles the case where the @defer is still unresolved but doesn't + // have a placeholder, for instance, by which children we mark + // the position of the block normally. In this case, we use the host. + return node === currentBlock?.hostNode || node === currentBlock?.rootNodes[0]; +} + +export function mapToDevtoolsControlFlowModel( + block: ControlFlowBlockInternal, + iteratorCurrentIdx: number, + rootId: number, +): ControlFlowBlock { + switch (block.type) { + case ControlFlowBlockTypeInternal.For: + const serializedItems = block.items.map((item) => serializeValue(item, 5)); + + return { + id: `forId-${rootId}-${iteratorCurrentIdx}`, + type: ControlFlowBlockType.For, + hasEmptyBlock: block.hasEmptyBlock, + items: serializedItems, + trackExpression: block.trackExpression, + } satisfies ForLoopBlock as ForLoopBlock; + + case ControlFlowBlockTypeInternal.Defer: + return { + id: `deferId-${rootId}-${iteratorCurrentIdx}`, + type: ControlFlowBlockType.Defer, + state: block.state, + renderedBlock: getRenderedBlock(block), + triggers: groupTriggers(block.triggers), + blocks: { + hasErrorBlock: block.hasErrorBlock, + placeholderBlock: block.placeholderBlock, + loadingBlock: block.loadingBlock, + }, + } satisfies DeferBlock as DeferBlock; + } +} + +/** + * Creates a synthetic ComponentTreeNode for control flow blocks (@defer, @for). + */ +export function createControlFlowTreeNode( + controlFlowBlock: ControlFlowBlockInternal, + children: ComponentTreeNode[], + iteratorCurrentIdx: number, + rootId: number, +): ComponentTreeNode { + return { + children, + component: null, + directives: [], + element: ELEMENT_NAME_MAP[controlFlowBlock.type], + nativeElement: undefined, + hydration: null, + controlFlowBlock: mapToDevtoolsControlFlowModel(controlFlowBlock, iteratorCurrentIdx, rootId), + }; +} + +export class ControlFlowBlocksIterator< + T extends ControlFlowBlockInternal = ControlFlowBlockInternal, +> { + public currentIndex = 0; + private blocks: T[] = []; + + constructor(blocks: T[]) { + this.blocks = blocks; + } + + advance() { + this.currentIndex++; + } + + get currentBlock(): T | undefined { + return this.blocks[this.currentIndex]; + } +} + +function groupTriggers(triggers: string[]) { + const defer: string[] = []; + const hydrate: string[] = []; + const prefetch: string[] = []; + + for (let trigger of triggers) { + if (trigger.startsWith('hydrate')) { + hydrate.push(trigger); + } else if (trigger.startsWith('prefetch')) { + prefetch.push(trigger); + } else { + defer.push(trigger); + } + } + return {defer, hydrate, prefetch}; +} + +function getRenderedBlock(deferBlock: DeferBlockDataInternal): RenderedDeferBlock | null { + if (['placeholder', 'loading', 'error'].includes(deferBlock.state)) { + return deferBlock.state as 'placeholder' | 'loading' | 'error'; + } + if (deferBlock.state === 'complete') { + return 'defer'; + } + return null; +} diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/ltree.ts b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/ltree.ts index a06659e948a9..19ef2b9199a2 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/ltree.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/ltree.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {SemVerDSL} from 'semver-dsl'; +import semver from 'semver'; import {getDirectiveName} from '../highlighter'; import {ComponentInstanceType, ComponentTreeNode, DirectiveInstanceType} from '../interfaces'; @@ -19,10 +19,14 @@ const latest = () => { HEADER_OFFSET = 20; }; -SemVerDSL(VERSION).gte('10.0.0-next.4', latest); +if (semver.gte(VERSION, '10.0.0-next.4')) { + latest(); +} // In g3 everyone has version 0.0.0, using the currently synced commits in the g3 codebase. -SemVerDSL(VERSION).eq('0.0.0', latest); +if (semver.eq(VERSION, '0.0.0')) { + latest(); +} const TYPE = 1; const ELEMENT = 0; @@ -91,7 +95,7 @@ export class LTreeStrategy { directives: [], component: null, hydration: null, // We know there is no hydration if we use the LTreeStrategy - defer: null, // neither there will be any defer + controlFlowBlock: null, // neither there will be any control flow block }; } for (let i = tNode.directiveStart; i < tNode.directiveEnd; i++) { @@ -117,7 +121,7 @@ export class LTreeStrategy { directives, component, hydration: null, // We know there is no hydration if we use the LTreeStrategy - defer: null, // neither there will be any defer + controlFlowBlock: null, // neither there will be any control flow block }; } @@ -138,7 +142,7 @@ export class LTreeStrategy { const node = this._getNode(lView, tView.data, i); // TODO(mgechev): verify if this won't make us skip projected content. - if (node.component || node.directives.length) { + if (node.component || node.directives?.length) { nodes.push(node); this._extract(lViewItem, node.children); } diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.spec.ts b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.spec.ts index 8c3b26528b73..e2d530394c3a 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.spec.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.spec.ts @@ -10,6 +10,8 @@ import { ɵDirectiveDebugMetadata as DirectiveDebugMetadata, ɵFramework as Framework, ɵFrameworkAgnosticGlobalUtils as FrameworkAgnosticGlobalUtils, + ɵControlFlowBlock as ControlFlowBlock, + ɵControlFlowBlockType as ControlFlowBlockType, } from '@angular/core'; import {RTreeStrategy} from './render-tree'; @@ -18,12 +20,14 @@ describe('render tree extraction', () => { let directiveMap: Map; let componentMap: Map; let directiveMetadataMap: Map; + let controlFlowBlocksMap: Map[]>; beforeEach(() => { treeStrategy = new RTreeStrategy(); directiveMap = new Map(); componentMap = new Map(); directiveMetadataMap = new Map(); + controlFlowBlocksMap = new Map(); (window as any).ng = { getDirectiveMetadata(dir: any): DirectiveDebugMetadata | null { @@ -35,6 +39,9 @@ describe('render tree extraction', () => { getDirectives(node: Node): any { return directiveMap.get(node) || []; }, + ɵgetControlFlowBlocks(node: Node): ControlFlowBlock[] { + return (controlFlowBlocksMap.get(node) as ControlFlowBlock[]) || []; + }, } satisfies Partial; }); @@ -72,7 +79,8 @@ describe('render tree extraction', () => { expect(rtree[0].children.length).toBe(2); expect(rtree[0].children[0].component?.instance).toBe(childComponent); expect(rtree[0].children[1].component).toBe(null); - expect(rtree[0].children[1].directives[0].instance).toBe(childDirective); + expect(rtree[0].children[1].directives).toBeDefined(); + expect(rtree[0].children[1].directives![0].instance).toBe(childDirective); }); it('should skip nodes without directives', () => { @@ -107,4 +115,81 @@ describe('render tree extraction', () => { const rtree = treeStrategy.build(appNode); expect(rtree[0].component!.name).toBe('AppComponent'); }); + + it('should extract a control flow block', () => { + // Represent: + // + // + // @defer { + // + // @defer { + // + // } + // } + // + // + + // Create the DOM + const appNode = document.createElement('app'); + const deferHostNode = document.createElement('comment'); + const deferChildNode = document.createElement('defer-child'); + const nestedDeferHostNode = document.createElement('comment'); + const nestedDeferChildNode = document.createElement('nested-defer-child'); + const childNode = document.createElement('child'); + + appNode.appendChild(deferHostNode); + appNode.appendChild(deferChildNode); + appNode.appendChild(childNode); + appNode.appendChild(nestedDeferHostNode); + appNode.appendChild(nestedDeferChildNode); + + // Create and set the component instances + componentMap.set(appNode, {}); + componentMap.set(deferChildNode, {}); + componentMap.set(nestedDeferChildNode, {}); + componentMap.set(childNode, {}); + + // Create a the outer and the inner @defer blocks. + controlFlowBlocksMap.set(appNode, [ + { + type: ControlFlowBlockType.Defer, + hostNode: deferHostNode, + rootNodes: [deferChildNode, nestedDeferHostNode, nestedDeferChildNode], + triggers: [], + }, + { + type: ControlFlowBlockType.Defer, + hostNode: nestedDeferHostNode, + rootNodes: [nestedDeferChildNode], + triggers: [], + }, + ]); + + const rtree = treeStrategy.build(appNode); + + expect(rtree.length).toBe(1); + + const appRTreeNode = rtree[0]; + expect(appRTreeNode.children.map((c) => c.element)).toEqual(['@defer', 'child']); + + const outerDefer = appRTreeNode.children[0]; + expect(outerDefer.children.length).toBe(2); + + const [deferChild, innerDefer] = outerDefer.children; + expect(deferChild).toEqual( + jasmine.objectContaining({ + element: 'defer-child', + nativeElement: deferChildNode, + }), + ); + expect(innerDefer.element).toEqual('@defer'); + + expect(innerDefer.children.length).toBe(1); + expect(innerDefer.children[0]).toEqual( + jasmine.objectContaining({ + element: 'nested-defer-child', + nativeElement: nestedDeferChildNode, + }), + ); + }); }); diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts index fe8a092bcc20..e4d117e94f9f 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts @@ -8,32 +8,46 @@ import { ɵFrameworkAgnosticGlobalUtils as FrameworkAgnosticGlobalUtils, - ɵDeferBlockData as DeferBlockData, ɵHydratedNode as HydrationNode, } from '@angular/core'; -import {RenderedDeferBlock, HydrationStatus} from '../../../../protocol'; +import {HydrationStatus} from '../../../../protocol'; import {ComponentTreeNode} from '../interfaces'; import {ngDebugClient} from '../ng-debug-api/ng-debug-api'; import {isCustomElement} from '../utils'; +import { + ControlFlowBlocksIterator, + createControlFlowTreeNode, + isControlFlowBlock, +} from './control-flow'; + +interface TreeExtractionContext { + blocksIterator: ControlFlowBlocksIterator; + rootId: number; + getComponent?: FrameworkAgnosticGlobalUtils['getComponent']; + getDirectives?: FrameworkAgnosticGlobalUtils['getDirectives']; + getDirectiveMetadata?: FrameworkAgnosticGlobalUtils['getDirectiveMetadata']; +} -const extractViewTree = ( +function extractViewTree( domNode: Node | Element, result: ComponentTreeNode[], - deferBlocks: DeferBlocksIterator, - rootId: number, - getComponent?: FrameworkAgnosticGlobalUtils['getComponent'], - getDirectives?: FrameworkAgnosticGlobalUtils['getDirectives'], - getDirectiveMetadata?: FrameworkAgnosticGlobalUtils['getDirectiveMetadata'], -): ComponentTreeNode[] => { + ctx: TreeExtractionContext, + nodesToSkip = new Set(), +): void { // Ignore DOM Node if it came from a different frame. Use instanceof Node to check this. if (!(domNode instanceof Node)) { - return result; + return; + } + + if (isControlFlowBlock(domNode, ctx.blocksIterator)) { + groupControlFlowBlocksChildren(ctx, result, nodesToSkip); + return; } - const directives = getDirectives?.(domNode) ?? []; + const directives = ctx.getDirectives?.(domNode) ?? []; if (!directives.length && !(domNode instanceof Element)) { - return result; + return; } const componentTreeNode: ComponentTreeNode = { children: [], @@ -47,132 +61,77 @@ const extractViewTree = ( element: domNode.nodeName.toLowerCase(), nativeElement: domNode, hydration: hydrationStatus(domNode), - defer: null, + controlFlowBlock: null, }; if (!(domNode instanceof Element)) { // In case we show the Comment nodes result.push(componentTreeNode); - return result; + return; } const isDehydratedElement = componentTreeNode.hydration?.status === 'dehydrated'; - const component = getComponent?.(domNode); + const component = ctx.getComponent?.(domNode); if (component) { componentTreeNode.component = { instance: component, isElement: isCustomElement(domNode), - name: getDirectiveMetadata?.(component)?.name ?? domNode.nodeName.toLowerCase(), + name: ctx.getDirectiveMetadata?.(component)?.name ?? domNode.nodeName.toLowerCase(), }; } - const isDisplayableNode = component || componentTreeNode.directives.length || isDehydratedElement; + const isDisplayableNode = + component || componentTreeNode.directives?.length || isDehydratedElement; if (isDisplayableNode) { result.push(componentTreeNode); } - // Nodes that are part of a defer block will be added as children of the defer block - // and should be skipped from the regular code path - const deferredNodesToSkip = new Set(); - const appendTo = isDisplayableNode ? componentTreeNode.children : result; + const childrenResult = isDisplayableNode ? componentTreeNode.children : result; - domNode.childNodes.forEach((node) => { - groupDeferChildrenIfNeeded( - node, - deferredNodesToSkip, - appendTo, - deferBlocks, - rootId, - getComponent, - getDirectives, - getDirectiveMetadata, - ); - - if (!deferredNodesToSkip.has(node)) { - extractViewTree( - node, - appendTo, - deferBlocks, - rootId, - getComponent, - getDirectives, - getDirectiveMetadata, - ); + for (const node of domNode.childNodes) { + if (!nodesToSkip.has(node)) { + extractViewTree(node, childrenResult, ctx, nodesToSkip); } - }); - - return result; -}; + } +} /** - * Group Nodes under a defer block if they are part of it. - * - * @param node - * @param deferredNodesToSkip Will mutate the set with the nodes that are grouped into the created deferblock. - * @param deferBlocks - * @param appendTo - * @param getComponent - * @param getDirectives - * @param getDirectiveMetadata + * Groups nodes under a @defer block if the given node is the first child of one. + * @returns true if a defer block was created, false otherwise. */ -function groupDeferChildrenIfNeeded( - node: Node, - deferredNodesToSkip: Set, - appendTo: ComponentTreeNode[], - deferBlocks: DeferBlocksIterator, - rootId: number, - getComponent?: FrameworkAgnosticGlobalUtils['getComponent'], - getDirectives?: FrameworkAgnosticGlobalUtils['getDirectives'], - getDirectiveMetadata?: FrameworkAgnosticGlobalUtils['getDirectiveMetadata'], +function groupControlFlowBlocksChildren( + ctx: TreeExtractionContext, + result: ComponentTreeNode[], + nodesToSkip: Set, ) { - const currentDeferBlock = deferBlocks.currentBlock; - const isFirstDeferredChild = node === currentDeferBlock?.rootNodes[0]; - // Handles the case where the @defer is still unresolved but doesn't - // have a placeholder, for instance, by which children we mark - // the position of the block normally. In this case, we use the host. - const isHostNode = node === currentDeferBlock?.hostNode; + const currentBlock = ctx.blocksIterator.currentBlock; + if (!currentBlock) { + throw new Error('There is no current block in the control flow block iterator.'); + } - if (isFirstDeferredChild || isHostNode) { - deferBlocks.advance(); + ctx.blocksIterator.advance(); + // It's important to store the here index before the recursive call. + const iteratorCurrentIdx = ctx.blocksIterator.currentIndex; - // When encountering the first child of a defer block (or the host node), - // we create a synthetic TreeNode representing the defer block. - const childrenTree: ComponentTreeNode[] = []; - for (const child of currentDeferBlock.rootNodes) { - extractViewTree( - child, - childrenTree, - deferBlocks, - rootId, - getComponent, - getDirectives, - getDirectiveMetadata, - ); + const childrenTree: ComponentTreeNode[] = []; + // Extract children + for (const child of currentBlock.rootNodes) { + if (!nodesToSkip.has(child)) { + extractViewTree(child, childrenTree, ctx, nodesToSkip); } + } - const deferBlockTreeNode = { - children: childrenTree, - component: null, - directives: [], - element: '@defer', - nativeElement: undefined, - hydration: null, - defer: { - id: `deferId-${rootId}-${deferBlocks.currentIndex}`, - state: currentDeferBlock.state, - renderedBlock: getRenderedBlock(currentDeferBlock), - triggers: groupTriggers(currentDeferBlock.triggers), - blocks: { - hasErrorBlock: currentDeferBlock.hasErrorBlock, - placeholderBlock: currentDeferBlock.placeholderBlock, - loadingBlock: currentDeferBlock.loadingBlock, - }, - }, - } satisfies ComponentTreeNode; + const blockTreeNode = createControlFlowTreeNode( + currentBlock, + childrenTree, + iteratorCurrentIdx, + ctx.rootId, + ); - currentDeferBlock?.rootNodes.forEach((child) => deferredNodesToSkip.add(child)); - appendTo.push(deferBlockTreeNode); + for (const child of currentBlock.rootNodes) { + nodesToSkip.add(child); } + result.push(blockTreeNode); } function hydrationStatus(element: Node): HydrationStatus { @@ -201,33 +160,6 @@ function hydrationStatus(element: Node): HydrationStatus { } } -function groupTriggers(triggers: string[]) { - const defer: string[] = []; - const hydrate: string[] = []; - const prefetch: string[] = []; - - for (let trigger of triggers) { - if (trigger.startsWith('hydrate')) { - hydrate.push(trigger); - } else if (trigger.startsWith('prefetch')) { - prefetch.push(trigger); - } else { - defer.push(trigger); - } - } - return {defer, hydrate, prefetch}; -} - -function getRenderedBlock(deferBlock: DeferBlockData): RenderedDeferBlock | null { - if (['placeholder', 'loading', 'error'].includes(deferBlock.state)) { - return deferBlock.state as 'placeholder' | 'loading' | 'error'; - } - if (deferBlock.state === 'complete') { - return 'defer'; - } - return null; -} - export class RTreeStrategy { supports(): boolean { return (['getDirectiveMetadata', 'getComponent'] as const).every( @@ -237,32 +169,18 @@ export class RTreeStrategy { build(element: Element, rootId: number = 0): ComponentTreeNode[] { const ng = ngDebugClient(); - const deferBlocks = ng.ɵgetDeferBlocks?.(element) ?? []; - - return extractViewTree( - element, - [], - new DeferBlocksIterator(deferBlocks), + const controlFlowBlocks = ng.ɵgetControlFlowBlocks?.(element) ?? []; + const ctx: TreeExtractionContext = { + blocksIterator: new ControlFlowBlocksIterator(controlFlowBlocks), rootId, - ng.getComponent, - ng.getDirectives, - ng.getDirectiveMetadata, - ); - } -} - -class DeferBlocksIterator { - public currentIndex = 0; - private blocks: DeferBlockData[] = []; - constructor(blocks: DeferBlockData[]) { - this.blocks = blocks; - } + getComponent: ng.getComponent, + getDirectives: ng.getDirectives, + getDirectiveMetadata: ng.getDirectiveMetadata, + }; - advance() { - this.currentIndex++; - } + const tree: ComponentTreeNode[] = []; + extractViewTree(element, tree, ctx); - get currentBlock(): DeferBlockData | undefined { - return this.blocks[this.currentIndex]; + return tree; } } diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts b/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts index ec920c756a8f..b222aef8f1d3 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts @@ -7,6 +7,7 @@ */ import { + ControlFlowBlockType, DirectiveProfile, ElementPosition, ElementProfile, @@ -20,6 +21,7 @@ import {isCustomElement, runOutsideAngular} from '../utils'; import {initializeOrGetDirectiveForestHooks} from '.'; import {DirectiveForestHooks} from './hooks'; +import {IdentityTracker} from './identity-tracker'; import {Hooks} from './profiler'; let inProgress = false; @@ -28,12 +30,18 @@ let eventMap: Map; let frameDuration = 0; let hooks: Partial = {}; +const DIRECTIVE_CONTROL_FLOW: {[key in ControlFlowBlockType]: ElementProfile['type']} = { + [ControlFlowBlockType.For]: 'for', + [ControlFlowBlockType.Defer]: 'defer', +}; + export const start = (onFrame: (frame: ProfilerFrame) => void): void => { if (inProgress) { throw new Error('Recording already in progress'); } eventMap = new Map(); inProgress = true; + IdentityTracker.getInstance().setProfilingActive(true); hooks = getHooks(onFrame); initializeOrGetDirectiveForestHooks().profiler.subscribe(hooks); }; @@ -44,6 +52,7 @@ export const stop = (): ProfilerFrame => { initializeOrGetDirectiveForestHooks().profiler.unsubscribe(hooks); hooks = {}; inProgress = false; + IdentityTracker.getInstance().setProfilingActive(false); return result; }; @@ -290,24 +299,25 @@ const prepareInitialFrame = (source: string, duration: number) => { let position: ElementPosition | undefined; if (node.component) { position = directiveForestHooks.getDirectivePosition(node.component.instance); - } else if (node.directives[0]) { + } else if (node.directives?.[0]) { position = directiveForestHooks.getDirectivePosition(node.directives[0].instance); - } else if (node.defer) { - position = directiveForestHooks.getDirectivePosition(node.defer); + } else if (node.controlFlowBlock) { + position = directiveForestHooks.getDirectivePosition(node.controlFlowBlock); } if (position === undefined) { return; } - const directives = node.directives.map((d) => { - return { - isComponent: false, - isElement: false, - name: getDirectiveName(d.instance), - outputs: {}, - lifecycle: {}, - }; - }); + const directives = + node.directives?.map((d) => { + return { + isComponent: false, + isElement: false, + name: getDirectiveName(d.instance), + outputs: {}, + lifecycle: {}, + }; + }) ?? []; if (node.component) { directives.push({ isElement: node.component.isElement, @@ -320,7 +330,7 @@ const prepareInitialFrame = (source: string, duration: number) => { const result: ElementProfile = { children: [], directives, - type: node.defer ? 'defer' : 'element', + type: !node.controlFlowBlock ? 'element' : DIRECTIVE_CONTROL_FLOW[node.controlFlowBlock.type], }; children[position[position.length - 1]] = result; node.children.forEach((n) => traverse(n, result.children)); diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.spec.ts b/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.spec.ts new file mode 100644 index 000000000000..467b4484ac5e --- /dev/null +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.spec.ts @@ -0,0 +1,161 @@ +/** + * @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 {IdentityTracker} from './identity-tracker'; + +describe('IdentityTracker', () => { + let tracker: IdentityTracker; + + beforeEach(() => { + (IdentityTracker as any)._instance = undefined; + tracker = IdentityTracker.getInstance(); + }); + + afterEach(() => { + (IdentityTracker as any)._instance = undefined; + document.querySelectorAll('[ng-version]').forEach((el) => el.remove()); + }); + + function seedDirective(opts: {dir: object; id: number; isComponent?: boolean}): void { + const internal = tracker as any; + internal._currentDirectiveId.set(opts.dir, opts.id); + internal._currentDirectivePosition.set(opts.dir, [opts.id]); + internal.isComponent.set(opts.dir, opts.isComponent ?? false); + } + + describe('setProfilingActive', () => { + it('removes pending directives from all maps when profiling stops', () => { + const dir = {}; + seedDirective({dir, id: 0, isComponent: true}); + (tracker as any)._pendingRemovals.add(dir); + + tracker.setProfilingActive(false); + + expect(tracker.hasDirective(dir)).toBeFalse(); + expect(tracker.getDirectiveId(dir)).toBeUndefined(); + expect(tracker.getDirectivePosition(dir)).toBeUndefined(); + }); + + it('does not flush pending removals when profiling starts', () => { + const dir = {}; + seedDirective({dir, id: 0, isComponent: true}); + (tracker as any)._pendingRemovals.add(dir); + + tracker.setProfilingActive(true); + + expect(tracker.hasDirective(dir)).toBeTrue(); + expect(tracker.getDirectiveId(dir)).toBe(0); + expect(tracker.getDirectivePosition(dir)).toEqual([0]); + }); + + it('clears the pending set after flushing', () => { + const dir = {}; + (tracker as any)._pendingRemovals.add(dir); + + tracker.setProfilingActive(false); + + expect((tracker as any)._pendingRemovals.size).toBe(0); + }); + + it('handles an empty pending set gracefully', () => { + expect(() => tracker.setProfilingActive(false)).not.toThrow(); + }); + + it('flushes multiple pending directives at once', () => { + const dirs = [{}, {}, {}]; + dirs.forEach((dir, i) => { + seedDirective({dir, id: i}); + (tracker as any)._pendingRemovals.add(dir); + }); + + tracker.setProfilingActive(false); + + dirs.forEach((dir) => { + expect(tracker.hasDirective(dir)).toBeFalse(); + }); + expect((tracker as any)._pendingRemovals.size).toBe(0); + }); + }); + + describe('index() cleanup behavior', () => { + it('immediately removes a stale directive from all maps when not profiling', () => { + const dir = {}; + seedDirective({dir, id: 0}); + + tracker.index(); + + expect(tracker.hasDirective(dir)).toBeFalse(); + expect(tracker.getDirectiveId(dir)).toBeUndefined(); + expect(tracker.getDirectivePosition(dir)).toBeUndefined(); + }); + + it('keeps a stale directive in maps during profiling, staging it for deferred cleanup', () => { + const dir = {}; + seedDirective({dir, id: 0}); + + tracker.setProfilingActive(true); + tracker.index(); + + expect(tracker.hasDirective(dir)).toBeTrue(); + expect(tracker.getDirectiveId(dir)).toBe(0); + expect(tracker.getDirectivePosition(dir)).toEqual([0]); + expect((tracker as any)._pendingRemovals.has(dir)).toBeTrue(); + }); + + it('removes deferred directives from maps once profiling stops', () => { + const dir = {}; + seedDirective({dir, id: 0}); + + tracker.setProfilingActive(true); + tracker.index(); + + expect(tracker.hasDirective(dir)).toBeTrue(); + + tracker.setProfilingActive(false); + + expect(tracker.hasDirective(dir)).toBeFalse(); + }); + + it('includes removed directives in the returned removedNodes regardless of profiling state', () => { + const dir = {}; + seedDirective({dir, id: 0, isComponent: true}); + + const {removedNodes} = tracker.index(); + + expect(removedNodes.length).toBe(1); + expect(removedNodes[0].directive).toBe(dir); + expect(removedNodes[0].isComponent).toBeTrue(); + }); + + it('does not add an already-pending directive to removedNodes twice on the next index call', () => { + const dir = {}; + seedDirective({dir, id: 0}); + + tracker.setProfilingActive(true); + tracker.index(); + + const {removedNodes} = tracker.index(); + + expect(removedNodes.some((n) => n.directive === dir)).toBeTrue(); + }); + + it('grows maps monotonically during profiling and fully clears on stop', () => { + const dirs = [{}, {}, {}]; + dirs.forEach((dir, i) => seedDirective({dir, id: i})); + + tracker.setProfilingActive(true); + tracker.index(); + + dirs.forEach((dir) => expect(tracker.hasDirective(dir)).toBeTrue()); + + tracker.setProfilingActive(false); + + dirs.forEach((dir) => expect(tracker.hasDirective(dir)).toBeFalse()); + }); + }); +}); diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.ts b/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.ts index eb86917edbda..9936769b652c 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/identity-tracker.ts @@ -34,6 +34,14 @@ export class IdentityTracker { private _currentDirectiveId = new Map(); isComponent = new Map(); + /** + * Directives that were removed while profiling was active. + * Cleanup is deferred until profiling stops so that the profiler + * can still look up IDs / positions of destroyed components. + */ + private _pendingRemovals = new Set(); + private _isProfiling = false; + // private constructor for Singleton Pattern private constructor() {} @@ -56,6 +64,18 @@ export class IdentityTracker { return this._currentDirectiveId.has(dir); } + /** + * Toggle profiling state. While profiling is active, removed directive + * entries are kept so the profiler can still resolve IDs and positions. + * When profiling stops, deferred removals are flushed. + */ + setProfilingActive(active: boolean): void { + this._isProfiling = active; + if (!active) { + this._flushPendingRemovals(); + } + } + index(): { newNodes: NodeArray; removedNodes: NodeArray; @@ -71,10 +91,11 @@ export class IdentityTracker { this._currentDirectiveId.forEach((_: number, dir: any) => { if (!allNodes.has(dir)) { removedNodes.push({directive: dir, isComponent: !!this.isComponent.get(dir)}); - // We can't clean these up because during profiling - // they might be requested for removed components - // this._currentDirectiveId.delete(dir); - // this._currentDirectivePosition.delete(dir); + if (this._isProfiling) { + this._pendingRemovals.add(dir); + } else { + this._cleanupDirective(dir); + } } }); return {newNodes, removedNodes, indexedForest, directiveForest}; @@ -96,8 +117,8 @@ export class IdentityTracker { this.isComponent.set(dir.instance, false); this._indexNode(dir.instance, node.position, newNodes); }); - if (node.defer) { - this._indexNode(node.defer, node.position, newNodes); + if (node.controlFlowBlock) { + this._indexNode(node.controlFlowBlock, node.position, newNodes); } node.children.forEach((child) => this._index(child, parent, newNodes, allNodes)); } @@ -110,9 +131,25 @@ export class IdentityTracker { } } + private _cleanupDirective(dir: any): void { + this._currentDirectiveId.delete(dir); + this._currentDirectivePosition.delete(dir); + this.isComponent.delete(dir); + } + + private _flushPendingRemovals(): void { + for (const dir of this._pendingRemovals) { + this._cleanupDirective(dir); + } + this._pendingRemovals.clear(); + } + destroy(): void { this._currentDirectivePosition = new Map(); this._currentDirectiveId = new Map(); + this.isComponent = new Map(); + this._pendingRemovals.clear(); + this._isProfiling = false; } } @@ -131,11 +168,11 @@ const indexTree = ({position, ...d})), + directives: node.directives?.map((d) => ({position, ...d})), children: node.children.map((n, i) => indexTree(n, i, position)), nativeElement: node.nativeElement, hydration: node.hydration, - defer: node.defer, + controlFlowBlock: node.controlFlowBlock, } as IndexedNode; }; diff --git a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/serialized-descriptor-factory.ts b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/serialized-descriptor-factory.ts index e7e3548971e1..40bf8a47020d 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/serialized-descriptor-factory.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/serialized-descriptor-factory.ts @@ -54,14 +54,16 @@ const typeToDescriptorPreview: Formatter = { [PropType.Array]: (prop: Array) => `Array(${prop.length})`, [PropType.Set]: (prop: Set) => `Set(${prop.size})`, [PropType.Map]: (prop: Map) => `Map(${prop.size})`, - [PropType.BigInt]: (prop: bigint) => truncate(prop.toString()), + [PropType.BigInt]: (prop: bigint) => `${truncate(prop.toString())}n`, [PropType.Boolean]: (prop: boolean) => truncate(prop.toString()), [PropType.String]: (prop: string) => `"${prop}"`, - [PropType.Function]: (prop: Function) => `${prop.name}(...)`, + [PropType.Function]: (prop: Function) => `${prop.name ? 'ƒ ' : ''}(...)`, [PropType.HTMLNode]: (prop: Node) => prop.constructor.name, [PropType.Null]: (_: null) => 'null', [PropType.Number]: (prop: any) => prop.toString(), - [PropType.Object]: (prop: Object) => (getKeys(prop).length > 0 ? '{...}' : '{}'), + [PropType.Object]: (prop: Object) => + (prop.constructor.name !== 'Object' ? `${prop.constructor.name} ` : '') + + (getKeys(prop).length > 0 ? '{...}' : '{}'), [PropType.Symbol]: (symbol: symbol) => `Symbol(${symbol.description})`, [PropType.Undefined]: (_: undefined) => 'undefined', [PropType.Date]: (prop: unknown) => { diff --git a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/state-serializer.ts b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/state-serializer.ts index 565e61060b44..e8da6552e509 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/state-serializer.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/state-serializer.ts @@ -129,8 +129,8 @@ export function serializeDirectiveState(instance: object): Record + + + + + + + + + + + + + diff --git a/devtools/projects/ng-devtools/src/lib/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/BUILD.bazel index 3877a9727afb..46192f658ecf 100644 --- a/devtools/projects/ng-devtools/src/lib/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/BUILD.bazel @@ -15,7 +15,7 @@ ng_project( srcs = glob( include = ["*.ts"], exclude = [ - "*_spec.ts", + "*.spec.ts", ], ), angular_assets = [ @@ -29,9 +29,11 @@ ng_project( "//:node_modules/@angular/material", "//:node_modules/rxjs", "//devtools/projects/ng-devtools/src/lib/application-environment", + "//devtools/projects/ng-devtools/src/lib/application-providers:app_data", "//devtools/projects/ng-devtools/src/lib/application-providers:supported_apis", "//devtools/projects/ng-devtools/src/lib/application-services:browser_styles", "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", + "//devtools/projects/ng-devtools/src/lib/application-services:settings", "//devtools/projects/ng-devtools/src/lib/application-services:theme", "//devtools/projects/ng-devtools/src/lib/devtools-tabs", "//devtools/projects/protocol", @@ -40,11 +42,12 @@ ng_project( ts_test_library( name = "devtools_test", - srcs = ["devtools_spec.ts"], + srcs = ["devtools.component.spec.ts"], deps = [ ":lib", "//:node_modules/@angular/core", "//:node_modules/tslib", + "//devtools/projects/ng-devtools/src/lib/application-providers:app_data", "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", "//devtools/projects/ng-devtools/src/lib/application-services/test-utils:settings_mock", "//devtools/projects/ng-devtools/src/lib/devtools-tabs", diff --git a/devtools/projects/ng-devtools/src/lib/application-providers/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/application-providers/BUILD.bazel index 14f87eab3645..b62aef01c4b2 100644 --- a/devtools/projects/ng-devtools/src/lib/application-providers/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/application-providers/BUILD.bazel @@ -30,10 +30,19 @@ ng_project( ], ) +ng_project( + name = "app_data", + srcs = ["app_data.ts"], + deps = [ + "//:node_modules/@angular/core", + ], +) + ts_test_library( name = "test_application_providers_lib", srcs = glob(["*_spec.ts"]), deps = [ + ":app_data", ":supported_apis", "//:node_modules/@angular/core", ], diff --git a/devtools/projects/ng-devtools/src/lib/application-providers/app_data.ts b/devtools/projects/ng-devtools/src/lib/application-providers/app_data.ts new file mode 100644 index 000000000000..ad366f0b3186 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-providers/app_data.ts @@ -0,0 +1,72 @@ +/** + * @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 {InjectionToken, signal} from '@angular/core'; + +export interface AppData { + fullVersion: string | undefined; + majorVersion: number; + minorVersion: number; + patchVersion: number; + devMode: boolean; + hydration: boolean; + ivy: boolean; +} + +interface ConfigurableAppData { + version: string | undefined; + devMode: boolean; + hydration: boolean; + ivy: boolean; +} + +/** A signal derivative containing information about the client app. */ +export interface AppDataSignal { + /** Returns the app data read-only signal. */ + (): AppData; + + /** Initialize (set once) the app data. */ + init: (appData: ConfigurableAppData) => void; +} + +export const APP_DATA = new InjectionToken('APP_DATA', { + providedIn: 'root', + factory: () => { + let isSet = false; + const data = signal(undefined); + const dataReadonlySignal = data.asReadonly(); + + const readonlyData = () => { + const data = dataReadonlySignal(); + if (!data) { + throw new Error('DevTools APP_DATA is not initialized.'); + } + return data; + }; + + readonlyData.init = (appData: ConfigurableAppData) => { + if (isSet) { + throw new Error('App data signal is already set.'); + } + const versions = appData.version ? appData.version.split('.').map((v) => Number(v)) : []; + + data.set({ + devMode: appData.devMode, + hydration: appData.hydration, + ivy: appData.ivy, + fullVersion: appData.version, + majorVersion: versions[0] ?? 0, + minorVersion: versions[1] ?? 0, + patchVersion: versions[2] ?? 0, + }); + isSet = true; + }; + + return readonlyData; + }, +}); diff --git a/devtools/projects/ng-devtools/src/lib/application-providers/app_data_spec.ts b/devtools/projects/ng-devtools/src/lib/application-providers/app_data_spec.ts new file mode 100644 index 000000000000..44a3866e7350 --- /dev/null +++ b/devtools/projects/ng-devtools/src/lib/application-providers/app_data_spec.ts @@ -0,0 +1,97 @@ +/** + * @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 {TestBed} from '@angular/core/testing'; +import {APP_DATA, AppDataSignal} from './app_data'; + +describe('APP_DATA', () => { + let appData: AppDataSignal; + + beforeEach(() => { + appData = TestBed.inject(APP_DATA); + }); + + it('should throw an error if not initialized', () => { + expect(() => appData()).toThrowError('DevTools APP_DATA is not initialized.'); + }); + + it('should set the app data', () => { + appData.init({ + devMode: true, + hydration: false, + ivy: true, + version: '1.2.3', + }); + + expect(appData()).toEqual({ + fullVersion: '1.2.3', + majorVersion: 1, + minorVersion: 2, + patchVersion: 3, + devMode: true, + hydration: false, + ivy: true, + }); + }); + + it('should disallow initializing the app data more than once', () => { + appData.init({ + devMode: true, + hydration: false, + ivy: true, + version: '1.2.3', + }); + + expect(() => { + appData.init({ + devMode: true, + hydration: false, + ivy: true, + version: '1.2.3', + }); + }).toThrowError('App data signal is already set.'); + }); + + it('should gracefully handle an undefined version', () => { + appData.init({ + devMode: true, + hydration: false, + ivy: true, + version: undefined, + }); + + expect(appData()).toEqual({ + fullVersion: undefined, + majorVersion: 0, + minorVersion: 0, + patchVersion: 0, + devMode: true, + hydration: false, + ivy: true, + }); + }); + + it('should gracefully handle an incomplete version', () => { + appData.init({ + devMode: true, + hydration: false, + ivy: true, + version: '1.2', + }); + + expect(appData()).toEqual({ + fullVersion: '1.2', + majorVersion: 1, + minorVersion: 2, + patchVersion: 0, + devMode: true, + hydration: false, + ivy: true, + }); + }); +}); diff --git a/devtools/projects/ng-devtools/src/lib/application-services/settings.ts b/devtools/projects/ng-devtools/src/lib/application-services/settings.ts index 82518e7636dc..a7d5c9ecda55 100644 --- a/devtools/projects/ng-devtools/src/lib/application-services/settings.ts +++ b/devtools/projects/ng-devtools/src/lib/application-services/settings.ts @@ -17,13 +17,13 @@ export class Settings { readonly showCommentNodes = this.settingsStore.create({ key: 'show_comment_nodes', - category: 'general', + category: 'general', // Good candidate for migration to `components` initialValue: false, }); readonly timingAPIEnabled = this.settingsStore.create({ key: 'timing_api_enabled', - category: 'general', + category: 'general', // Good candidate for migration to `profiler` initialValue: false, }); @@ -44,4 +44,10 @@ export class Settings { category: 'general', initialValue: 'Components', }); + + readonly showHydrationOverlays = this.settingsStore.create({ + key: 'show_hydration_overlays', + category: 'components', + initialValue: false, + }); } diff --git a/devtools/projects/ng-devtools/src/lib/application-services/theme_service.ts b/devtools/projects/ng-devtools/src/lib/application-services/theme_service.ts index 67a01e7567cc..ede303fd6ca7 100644 --- a/devtools/projects/ng-devtools/src/lib/application-services/theme_service.ts +++ b/devtools/projects/ng-devtools/src/lib/application-services/theme_service.ts @@ -11,9 +11,9 @@ import { computed, effect, inject, - Injectable, RendererFactory2, signal, + Service, } from '@angular/core'; import {DOCUMENT} from '@angular/common'; import {WINDOW} from '../application-providers/window_provider'; @@ -24,7 +24,7 @@ import {ThemeUi} from './theme_types'; const DARK_THEME_CLASS = 'dark-theme'; const LIGHT_THEME_CLASS = 'light-theme'; -@Injectable({providedIn: 'root'}) +@Service() export class ThemeService { private readonly win = inject(WINDOW); private readonly doc = inject(DOCUMENT); diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel index 5cc709aacc53..956a0e0dbddd 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/BUILD.bazel @@ -26,14 +26,15 @@ ng_project( "//:node_modules/@types/chrome", "//:node_modules/rxjs", "//devtools/projects/ng-devtools/src/lib/application-environment", + "//devtools/projects/ng-devtools/src/lib/application-providers:app_data", "//devtools/projects/ng-devtools/src/lib/application-providers:supported_apis", "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", "//devtools/projects/ng-devtools/src/lib/application-services:settings", - "//devtools/projects/ng-devtools/src/lib/application-services:theme", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/injector-tree:injector_tree", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/profiler", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/router-tree", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/settings", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/tab-update", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/transfer-state", "//devtools/projects/ng-devtools/src/lib/shared/button", @@ -52,6 +53,7 @@ ts_test_library( "//:node_modules/rxjs", "//:node_modules/tslib", "//devtools/projects/ng-devtools/src/lib/application-environment", + "//devtools/projects/ng-devtools/src/lib/application-providers:app_data", "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", "//devtools/projects/ng-devtools/src/lib/application-services:theme", "//devtools/projects/ng-devtools/src/lib/application-services:theme_types_lib", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html index 838ebd9624ce..7e67a38208f3 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.html @@ -64,9 +64,9 @@ ng-button btnType="icon" class="toolbar-btn" - [matMenuTriggerFor]="menu" matTooltip="Open settings" aria-label="Open settings" + (click)="settingsOpened.set(true)" > settings @@ -80,7 +80,6 @@ @@ -111,50 +110,19 @@ @defer (when transferStateVisible; prefetch on idle) { } + + @if (settingsOpened()) { + + }
    } - -
    - @if (!profilingNotificationsSupported) { - - } - - - @if (supportedApis().transferState) { - - } -
    -
    -

    Angular version: - @if (majorAngularVersion() > 12 || majorAngularVersion() === 0) { - {{ angularVersion() }} + @if (appData().majorVersion > 12 || appData().majorVersion === 0) { + {{ appData().fullVersion }} } @else { - {{ angularVersion() }} (unsupported) + {{ appData().fullVersion }} (unsupported) }

    diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts index 16ce30ea0c5f..7f7fcd61a706 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/devtools-tabs.component.ts @@ -6,18 +6,9 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - ChangeDetectionStrategy, - Component, - computed, - inject, - input, - output, - signal, -} from '@angular/core'; +import {Component, computed, inject, output, signal} from '@angular/core'; import {MatIcon} from '@angular/material/icon'; import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu'; -import {MatSlideToggle} from '@angular/material/slide-toggle'; import {MatTabLink, MatTabNav, MatTabNavPanel} from '@angular/material/tabs'; import {MatTooltip} from '@angular/material/tooltip'; import { @@ -31,7 +22,6 @@ import { import {ApplicationEnvironment, Frame, TOP_LEVEL_FRAME_ID} from '../application-environment/index'; import {FrameManager} from '../application-services/frame_manager'; -import {ThemeService} from '../application-services/theme_service'; import {DirectiveExplorerComponent} from './directive-explorer/directive-explorer.component'; import {InjectorTreeComponent} from './injector-tree/injector-tree.component'; @@ -42,6 +32,8 @@ import {TabUpdate} from './tab-update/index'; import {Settings} from '../application-services/settings'; import {SUPPORTED_APIS} from '../application-providers/supported_apis'; import {ButtonComponent} from '../shared/button/button.component'; +import {APP_DATA} from '../application-providers/app_data'; +import {SettingsComponent} from './settings/settings.component'; type Tab = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree' | 'Transfer State'; @@ -63,30 +55,27 @@ type Tab = 'Components' | 'Profiler' | 'Router Tree' | 'Injector Tree' | 'Transf RouterTreeComponent, InjectorTreeComponent, TransferStateComponent, - MatSlideToggle, + SettingsComponent, ButtonComponent, ], providers: [TabUpdate], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class DevToolsTabsComponent { public readonly frameManager = inject(FrameManager); - protected readonly themeService = inject(ThemeService); private readonly tabUpdate = inject(TabUpdate); + private readonly settings = inject(Settings); protected readonly messageBus = inject>(MessageBus); - protected readonly settings = inject(Settings); protected readonly applicationEnvironment = inject(ApplicationEnvironment); protected readonly supportedApis = inject(SUPPORTED_APIS); + protected readonly appData = inject(APP_DATA); - protected readonly isHydrationEnabled = input(false); readonly frameSelected = output(); readonly inspectorRunning = signal(false); - protected readonly showCommentNodes = this.settings.showCommentNodes; - protected readonly timingAPIEnabled = this.settings.timingAPIEnabled; protected readonly signalGraphEnabled = () => this.supportedApis().signals; protected readonly transferStateEnabled = this.settings.transferStateEnabled; + protected readonly showCommentNodes = this.settings.showCommentNodes; protected readonly activeTab = this.settings.activeTab; protected readonly componentExplorerView = signal(null); @@ -118,16 +107,8 @@ export class DevToolsTabsComponent { ); protected readonly TOP_LEVEL_FRAME_ID = TOP_LEVEL_FRAME_ID; - protected readonly angularVersion = input(); - readonly majorAngularVersion = computed(() => { - const version = this.angularVersion(); - if (!version) { - return -1; - } - return parseInt(version.toString().split('.')[0], 10); - }); - protected readonly extensionVersion = signal('dev-build'); + protected readonly settingsOpened = signal(false); constructor() { this.messageBus.on('updateRouterTree', (routes: any[]) => { @@ -188,18 +169,4 @@ export class DevToolsTabsComponent { toggleInspectorState(): void { this.inspectorRunning.update((state) => !state); } - - toggleTimingAPI(): void { - this.timingAPIEnabled.update((state) => !state); - this.timingAPIEnabled() - ? this.messageBus.emit('enableTimingAPI') - : this.messageBus.emit('disableTimingAPI'); - } - - protected setTransferStateTab(enabled: boolean): void { - this.transferStateEnabled.set(enabled); - if (!enabled && this.activeTab() === 'Transfer State') { - this.activeTab.set('Components'); - } - } } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/BUILD.bazel index c9cc89b93fa0..ebd3ce164887 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/BUILD.bazel @@ -24,15 +24,17 @@ ng_project( "//:node_modules/rxjs", "//devtools/projects/ng-devtools/src/lib/application-operations", "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", + "//devtools/projects/ng-devtools/src/lib/application-services:settings", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-pane", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signals-view:signals-tab", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-manager", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-pane", "//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer:types", + "//devtools/projects/ng-devtools/src/lib/shared/signal-graph", "//devtools/projects/ng-devtools/src/lib/shared/split", "//devtools/projects/ng-devtools/src/lib/shared/split:responsive-split", "//devtools/projects/protocol", @@ -48,13 +50,16 @@ ts_test_library( "//:node_modules/@angular/platform-browser", "//:node_modules/tslib", "//devtools/projects/ng-devtools/src/lib/application-operations", + "//devtools/projects/ng-devtools/src/lib/application-providers:app_data", "//devtools/projects/ng-devtools/src/lib/application-services:frame_manager", + "//devtools/projects/ng-devtools/src/lib/application-services:settings", + "//devtools/projects/ng-devtools/src/lib/application-services:settings_store", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-pane", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/signal-graph-manager", "//devtools/projects/ng-devtools/src/lib/shared/object-tree-explorer:types", "//devtools/projects/protocol", ], diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing/BUILD.bazel similarity index 100% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing/BUILD.bazel rename to devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing/BUILD.bazel diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing/diffing.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing/diffing.spec.ts similarity index 96% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing/diffing.spec.ts rename to devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing/diffing.spec.ts index 8c3e551ac7ef..ef8acfce9634 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing/diffing.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing/diffing.spec.ts @@ -69,7 +69,7 @@ describe('diff', () => { expect(host).toEqual(newArr); }); - it('should add remove an item', () => { + it('should add remove an item (1)', () => { const newArr = [host[0], host[2]]; const output = diff(differ, host, newArr); @@ -82,7 +82,7 @@ describe('diff', () => { expect(host).toEqual(newArr); }); - it('should add remove an item', () => { + it('should add remove an item (2)', () => { // Note that an item will be marked as // updated only if its reference changes. const newArr = [ diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing/index.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing/index.ts similarity index 100% rename from devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing/index.ts rename to devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing/index.ts diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.html index 83f008ce2c73..dd02ee6426a1 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.component.html @@ -19,16 +19,6 @@ [currentSelectedElement]="currentSelectedElement()!" [showCommentNodes]="showCommentNodes()" /> - @if (isHydrationEnabled()) { -
    - - -
    - } @if (parents()) { @if (signalsOpen() && signalGraph.element()) { - @@ -51,7 +41,7 @@
    @if (currentSelectedElement(); as currentSelectedElement) { - { if (a.component && b.component && a.component.id !== b.component.id) { return false; } - const aDirectives = new Set(a.directives.map((d) => d.id)); + if (!a.directives && !b.directives) { + return true; + } + if (!a.directives || !b.directives) { + return false; + } + const aDirectives = new Set(a.directives.map((d) => d.id) ?? []); for (const dir of b.directives) { if (!aDirectives.has(dir.id)) { return false; @@ -93,17 +100,15 @@ const sameDirectives = (a: IndexedNode, b: IndexedNode) => { SplitAreaDirective, DirectiveForestComponent, BreadcrumbsComponent, - PropertyTabComponent, + PropertyPaneComponent, FormsModule, MatSnackBarModule, - SignalsTabComponent, + SignalGraphPaneComponent, ResponsiveSplitDirective, ], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class DirectiveExplorerComponent { readonly showCommentNodes = input(false); - readonly isHydrationEnabled = input(false); readonly toggleInspector = output(); readonly directiveForest = viewChild.required(DirectiveForestComponent); @@ -121,13 +126,13 @@ export class DirectiveExplorerComponent { private _clickedElement: IndexedNode | null = null; private _refreshRetryTimeout: null | ReturnType = null; - private showHydrationNodeHighlights = false; private readonly _appOperations = inject(ApplicationOperations); private readonly _messageBus = inject>(MessageBus); private readonly _propResolver = inject(ElementPropertyResolver); private readonly _frameManager = inject(FrameManager); + private readonly settings = inject(Settings); private readonly platform = inject(Platform); private readonly snackBar = inject(MatSnackBar); protected readonly signalGraph = inject(SignalGraphManager); @@ -201,6 +206,7 @@ export class DirectiveExplorerComponent { subscribeToBackendEvents(): void { this._messageBus.on('latestComponentExplorerView', (view: ComponentExplorerView) => { this.forest.set(view.forest); + this.currentSelectedElement.set(this._clickedElement); if (view.properties && this._clickedElement) { this._propResolver.setProperties(this._clickedElement, view.properties); @@ -233,9 +239,8 @@ export class DirectiveExplorerComponent { const selectedEl = this.currentSelectedElement(); if (!selectedEl) return; - const directiveIndex = selectedEl.directives.findIndex( - (directive) => directive.name === directiveName, - ); + const directiveIndex = + selectedEl.directives?.findIndex((directive) => directive.name === directiveName) ?? -1; const selectedFrame = this._frameManager.selectedFrame(); if (!this._frameManager.activeFrameHasUniqueUrl()) { @@ -360,27 +365,18 @@ export class DirectiveExplorerComponent { } } - toggleHydrationNodesHighlights(toggle: boolean) { - if (toggle) { - this.hightlightHydrationNodes(); - } else { - this.removeHydrationNodesHightlights(); - } - this.showHydrationNodeHighlights = toggle; - } - - hightlightHydrationNodes() { + createHydrationOverlays() { this._messageBus.emit('createHydrationOverlay'); } - removeHydrationNodesHightlights() { + removeHydrationOverlays() { this._messageBus.emit('removeHydrationOverlay'); } private refreshHydrationNodeHighlightsIfNeeded() { - if (this.showHydrationNodeHighlights) { - this.removeHydrationNodesHightlights(); - this.hightlightHydrationNodes(); + if (untracked(this.settings.showHydrationOverlays)) { + this.removeHydrationOverlays(); + this.createHydrationOverlays(); } } diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts index e0d5d27820e3..bdce522fdc68 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-explorer.spec.ts @@ -18,12 +18,15 @@ import {IndexedNode} from './directive-forest/index-forest'; import SpyObj = jasmine.SpyObj; import {By} from '@angular/platform-browser'; import {FrameManager} from '../../application-services/frame_manager'; -import {Component, CUSTOM_ELEMENTS_SCHEMA, output, input} from '@angular/core'; +import {Component, CUSTOM_ELEMENTS_SCHEMA, output, input, signal} from '@angular/core'; import {ElementPropertyResolver} from './property-resolver/element-property-resolver'; import {BreadcrumbsComponent} from './directive-forest/breadcrumbs/breadcrumbs.component'; -import {PropertyTabComponent} from './property-tab/property-tab.component'; -import {SignalGraphManager} from './signal-graph/signal-graph-manager'; +import {PropertyPaneComponent} from './property-pane/property-pane.component'; +import {SignalGraphManager} from './signal-graph-manager/signal-graph-manager'; import {FlatNode} from '../../shared/object-tree-explorer/object-tree-types'; +import {APP_DATA, AppData} from '../../application-providers/app_data'; +import {SettingsStore} from '../../application-services/settings_store'; +import {Settings} from '../../application-services/settings'; @Component({ selector: 'ng-directive-forest', @@ -62,55 +65,81 @@ class MockPropertyTabComponent { readonly viewSource = output(); } -describe('DirectiveExplorerComponent', () => { - let messageBusMock: SpyObj; - let fixture: ComponentFixture; - let comp: DirectiveExplorerComponent; - let applicationOperationsSpy: SpyObj; +async function configureTestingModule(appData?: Partial) { + TestBed.resetTestingModule(); + let contentScriptConnected = (frameId: number, name: string, url: string) => {}; let frameConnected = (frameId: number) => {}; - beforeEach(async () => { - applicationOperationsSpy = jasmine.createSpyObj('_appOperations', [ + const applicationOperationsSpy: SpyObj = + jasmine.createSpyObj('_appOperations', [ 'viewSource', 'selectDomElement', 'inspect', + 'setStorageItems', + 'getStorageItems', ]); - messageBusMock = jasmine.createSpyObj('messageBus', ['on', 'once', 'emit', 'destroy']); - - messageBusMock.on.and.callFake((topic: string, cb: Function) => { - if (topic === 'contentScriptConnected') { - contentScriptConnected = cb as (frameId: number, name: string, url: string) => void; - } - if (topic === 'frameConnected') { - frameConnected = cb as (frameId: number) => void; - } - }); - messageBusMock.emit.and.callFake((topic: string, args: any[]) => { - if (topic === 'enableFrameConnection') { - frameConnected(args[0]); - } - }); + const messageBusMock: SpyObj = jasmine.createSpyObj('messageBus', [ + 'on', + 'once', + 'emit', + 'destroy', + ]); + + messageBusMock.on.and.callFake((topic: string, cb: Function) => { + if (topic === 'contentScriptConnected') { + contentScriptConnected = cb as (frameId: number, name: string, url: string) => void; + } + if (topic === 'frameConnected') { + frameConnected = cb as (frameId: number) => void; + } + }); + messageBusMock.emit.and.callFake((topic: string, args: any[]) => { + if (topic === 'enableFrameConnection') { + frameConnected(args[0]); + } + }); - TestBed.configureTestingModule({ - providers: [ - {provide: ApplicationOperations, useValue: applicationOperationsSpy}, - {provide: MessageBus, useValue: messageBusMock}, - { - provide: ElementPropertyResolver, - useValue: new ElementPropertyResolver(messageBusMock), - }, - {provide: FrameManager, useFactory: () => FrameManager.initialize(123)}, - ], - }).overrideComponent(DirectiveExplorerComponent, { - add: {schemas: [CUSTOM_ELEMENTS_SCHEMA]}, - remove: {imports: [DirectiveForestComponent]}, - }); + TestBed.configureTestingModule({ + providers: [ + {provide: ApplicationOperations, useValue: applicationOperationsSpy}, + {provide: MessageBus, useValue: messageBusMock}, + { + provide: ElementPropertyResolver, + useValue: new ElementPropertyResolver(messageBusMock), + }, + {provide: FrameManager, useFactory: () => FrameManager.initialize(123)}, + { + provide: SettingsStore, + useFactory: () => new SettingsStore({}), + }, + Settings, + { + provide: APP_DATA, + useFactory: () => + signal({ + devMode: true, + ivy: true, + hydration: false, + fullVersion: '0.0.0', + majorVersion: 0, + minorVersion: 0, + patchVersion: 0, + ...appData, + }), + }, + ], + }).overrideComponent(DirectiveExplorerComponent, { + add: {schemas: [CUSTOM_ELEMENTS_SCHEMA]}, + remove: {imports: [DirectiveForestComponent]}, + }); - fixture = TestBed.overrideComponent(DirectiveExplorerComponent, { + const fixture: ComponentFixture = TestBed.overrideComponent( + DirectiveExplorerComponent, + { remove: { - imports: [DirectiveForestComponent, BreadcrumbsComponent, PropertyTabComponent], + imports: [DirectiveForestComponent, BreadcrumbsComponent, PropertyPaneComponent], providers: [SignalGraphManager], }, add: { @@ -125,12 +154,36 @@ describe('DirectiveExplorerComponent', () => { }, ], }, - }).createComponent(DirectiveExplorerComponent); - comp = fixture.componentInstance; + }, + ).createComponent(DirectiveExplorerComponent); + let comp: DirectiveExplorerComponent = fixture.componentInstance; + + comp = fixture.componentInstance; + await fixture.whenStable(); + + return { + contentScriptConnected, + frameConnected, + messageBusMock, + applicationOperationsSpy, + fixture, + comp, + }; +} + +describe('DirectiveExplorerComponent', () => { + let messageBusMock: SpyObj; + let comp: DirectiveExplorerComponent; + let applicationOperationsSpy: SpyObj; + let contentScriptConnected: (frameId: number, name: string, url: string) => void; + + beforeEach(async () => { + const module = await configureTestingModule(); - TestBed.inject(FrameManager); - comp = fixture.componentInstance; - await fixture.whenStable(); + messageBusMock = module.messageBusMock; + applicationOperationsSpy = module.applicationOperationsSpy; + contentScriptConnected = module.contentScriptConnected; + comp = module.comp; }); it('should create instance from class', () => { @@ -201,24 +254,12 @@ describe('DirectiveExplorerComponent', () => { describe('hydration', () => { it('should highlight hydration nodes', () => { - comp.hightlightHydrationNodes(); + comp.createHydrationOverlays(); expect(messageBusMock.emit).toHaveBeenCalledWith('createHydrationOverlay'); - comp.removeHydrationNodesHightlights(); + comp.removeHydrationOverlays(); expect(messageBusMock.emit).toHaveBeenCalledWith('removeHydrationOverlay'); }); - - it('should show hydration checkbox toggle', async () => { - fixture.componentRef.setInput('isHydrationEnabled', true); - await fixture.whenStable(); - const toggle = fixture.debugElement.query(By.css('#show-hydration-overlays')); - expect(toggle).toBeTruthy(); - - fixture.componentRef.setInput('isHydrationEnabled', false); - await fixture.whenStable(); - const toggle2 = fixture.debugElement.query(By.css('#show-hydration-overlays')); - expect(toggle2).toBeFalsy(); - }); }); describe('applicaton operations', () => { diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/BUILD.bazel index e95cbdd07f2b..b51dfe1e5282 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/BUILD.bazel @@ -20,7 +20,7 @@ ng_project( ":directive_forest_utils", "//:node_modules/@angular/cdk", "//:node_modules/@angular/core", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/filter", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/filter:directive-forest-filter-fn-generator", diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs/breadcrumbs.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs/breadcrumbs.component.ts index 8cc15850bc23..bb258e32fa75 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs/breadcrumbs.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/breadcrumbs/breadcrumbs.component.ts @@ -7,7 +7,6 @@ */ import { - ChangeDetectionStrategy, Component, computed, effect, @@ -26,7 +25,6 @@ import {MatIcon} from '@angular/material/icon'; templateUrl: './breadcrumbs.component.html', styleUrls: ['./breadcrumbs.component.scss'], imports: [MatIcon], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class BreadcrumbsComponent { readonly parents = input.required(); diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/BUILD.bazel index 72bbf7025707..1e9e15405395 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/BUILD.bazel @@ -11,7 +11,7 @@ ts_project( "//:node_modules/@angular/material", "//:node_modules/@types", "//:node_modules/rxjs", - "//devtools/projects/ng-devtools/src/lib/devtools-tabs/diffing", + "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/diffing", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest", "//devtools/projects/protocol", ], diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/component-data-source.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/component-data-source.spec.ts index d88619619e27..5b4e82bb5851 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/component-data-source.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/component-data-source.spec.ts @@ -21,6 +21,8 @@ const tree1: DevToolsNode = { ], component: null, hydration: null, + controlFlowBlock: null, + children: [ { children: [], @@ -33,11 +35,10 @@ const tree1: DevToolsNode = { element: 'bar', hydration: null, nativeElement: document.createElement('bar'), - defer: null, + controlFlowBlock: null, }, ], nativeElement: document.createElement('foo'), - defer: null, }; const tree2: DevToolsNode = { @@ -50,6 +51,8 @@ const tree2: DevToolsNode = { ], component: null, hydration: null, + controlFlowBlock: null, + children: [ { children: [], @@ -62,7 +65,7 @@ const tree2: DevToolsNode = { element: 'bar', hydration: null, nativeElement: document.createElement('bar'), - defer: null, + controlFlowBlock: null, }, { children: [], @@ -74,12 +77,10 @@ const tree2: DevToolsNode = { directives: [], element: 'qux', hydration: null, - nativeElement: document.createElement('qux'), - defer: null, + controlFlowBlock: null, }, ], nativeElement: document.createElement('foo'), - defer: null, }; const tree3: DevToolsNode = { @@ -92,6 +93,7 @@ const tree3: DevToolsNode = { ], component: null, hydration: null, + controlFlowBlock: null, children: [ { children: [], @@ -103,7 +105,7 @@ const tree3: DevToolsNode = { directives: [], element: '#comment', hydration: null, - defer: null, + controlFlowBlock: null, nativeElement: document.createComment('bar'), }, { @@ -116,17 +118,17 @@ const tree3: DevToolsNode = { directives: [], element: '#comment', hydration: null, - defer: null, + controlFlowBlock: null, nativeElement: document.createComment('bar'), }, ], nativeElement: document.createElement('foo'), - defer: null, }; const tree4: DevToolsNode = { element: 'app', hydration: null, + controlFlowBlock: null, directives: [ { id: 1, @@ -153,7 +155,7 @@ const tree4: DevToolsNode = { directives: [], element: 'bar', hydration: null, - defer: null, + controlFlowBlock: null, nativeElement: document.createComment('bar'), }, ], @@ -165,7 +167,7 @@ const tree4: DevToolsNode = { directives: [], element: '#comment', hydration: null, - defer: null, + controlFlowBlock: null, nativeElement: document.createComment('bar'), }, ], @@ -177,7 +179,7 @@ const tree4: DevToolsNode = { directives: [], element: '#comment', hydration: null, - defer: null, + controlFlowBlock: null, nativeElement: document.createComment('bar'), }, ], @@ -189,7 +191,7 @@ const tree4: DevToolsNode = { directives: [], element: '#comment', hydration: null, - defer: null, + controlFlowBlock: null, nativeElement: document.createComment('bar'), }, ], @@ -202,10 +204,9 @@ const tree4: DevToolsNode = { element: '#comment', hydration: null, nativeElement: document.createComment('bar'), - defer: null, + controlFlowBlock: null, }, ], - defer: null, nativeElement: document.createElement('foo'), }; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/index.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/index.ts index 1e437fd2cba8..a8f867a13eb0 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/index.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source/index.ts @@ -10,11 +10,16 @@ import {CollectionViewer, DataSource} from '@angular/cdk/collections'; import {FlatTreeControl} from '@angular/cdk/tree'; import {DefaultIterableDiffer, TrackByFunction} from '@angular/core'; import {MatTreeFlattener} from '@angular/material/tree'; -import {DeferInfo, DevToolsNode, HydrationStatus} from '../../../../../../../protocol'; +import { + DevToolsNode, + ControlFlowBlock, + HydrationStatus, + ChangeDetection, +} from '../../../../../../../protocol'; import {BehaviorSubject, merge, Observable} from 'rxjs'; import {map} from 'rxjs/operators'; -import {diff} from '../../../diffing'; +import {diff} from '../../diffing'; import {IndexedNode, indexForest} from '../index-forest'; /** Flat node with expandable and level information */ @@ -28,8 +33,8 @@ export interface FlatNode { original: IndexedNode; newItem?: boolean; hydration: HydrationStatus; - defer: DeferInfo | null; - onPush?: boolean; + controlFlowBlock: ControlFlowBlock | null; + changeDetection?: ChangeDetection; hasNativeElement: boolean; } @@ -39,8 +44,8 @@ const trackBy: TrackByFunction = (_: number, item: FlatNode) => `${item.id}#${item.expandable}`; const getId = (node: IndexedNode) => { - if (node.defer) { - return node.defer.id; + if (node.controlFlowBlock) { + return node.controlFlowBlock.id; } else if (node.hydration?.status === 'dehydrated') { return node.position.join('-'); } @@ -49,11 +54,12 @@ const getId = (node: IndexedNode) => { if (node.component) { prefix = node.component.id.toString(); } - const dirIds = node.directives - .map((d) => d.id) - .sort((a, b) => { - return a - b; - }); + const dirIds = + node.directives + ?.map((d) => d.id) + .sort((a, b) => { + return a - b; + }) ?? []; return prefix + '-' + dirIds.join('-'); }; @@ -99,12 +105,12 @@ export class ComponentDataSource extends DataSource { // and the reference is preserved after transformation. position: node.position, name: node.component ? node.component.name : node.element, - directives: node.directives.map((d) => d.name), + directives: node.directives?.map((d) => d.name) ?? [], original: node, level, hydration: node.hydration, - defer: node.defer, - onPush: node.onPush, + controlFlowBlock: node.controlFlowBlock, + changeDetection: node.changeDetection, hasNativeElement: node.hasNativeElement, }; this._nodeToFlat.set(node, flatNode); @@ -131,6 +137,14 @@ export class ComponentDataSource extends DataSource { return this._nodeToFlat.get(indexedNode); } + getFlatNodeByPosition(position: number[]): FlatNode | undefined { + return this.data.find( + (node) => + node.position.length === position.length && + node.position.every((p, i) => p === position[i]), + ); + } + update( forest: DevToolsNode[], showCommentNodes: boolean, diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/directive-forest.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/directive-forest.component.ts index 660a76b77cf5..328662196f4f 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/directive-forest.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/directive-forest.component.ts @@ -14,7 +14,6 @@ import { import {FlatTreeControl} from '@angular/cdk/tree'; import { afterRenderEffect, - ChangeDetectionStrategy, Component, computed, DestroyRef, @@ -45,7 +44,6 @@ const RESIZE_OBSERVER_DEBOUNCE = 250; // ms selector: 'ng-directive-forest', templateUrl: './directive-forest.component.html', styleUrls: ['./directive-forest.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, imports: [ FilterComponent, CdkVirtualScrollViewport, diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/filter/filter.component.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/filter/filter.component.ts index 9916c5b1e69f..6cd507d24f62 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/filter/filter.component.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/filter/filter.component.ts @@ -6,14 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - ChangeDetectionStrategy, - Component, - ElementRef, - input, - output, - viewChild, -} from '@angular/core'; +import {Component, ElementRef, input, output, viewChild} from '@angular/core'; import {MatIcon} from '@angular/material/icon'; import {MatTooltip} from '@angular/material/tooltip'; @@ -52,7 +45,6 @@ const genericSearchGenerator: FilterFnGenerator = (filter: string) => { templateUrl: './filter.component.html', styleUrls: ['./filter.component.scss'], imports: [MatIcon, MatTooltip], - changeDetection: ChangeDetectionStrategy.OnPush, }) export class FilterComponent { protected readonly input = viewChild.required('input'); diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest/index-forest.spec.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest/index-forest.spec.ts index 67a3fd0f8a72..3e3c5242e126 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest/index-forest.spec.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest/index-forest.spec.ts @@ -42,8 +42,8 @@ describe('indexForest', () => { ], component: null, children: [], - onPush: false, - defer: null, + changeDetection: 'ng-on-push', + controlFlowBlock: null, hasNativeElement: true, } as DevToolsNode & {hasNativeElement?: boolean}, { @@ -56,13 +56,13 @@ describe('indexForest', () => { id: 1, }, children: [], - onPush: false, - defer: null, + changeDetection: 'ng-on-push', + controlFlowBlock: null, hasNativeElement: true, } as DevToolsNode & {hasNativeElement?: boolean}, ], - onPush: false, - defer: null, + changeDetection: 'ng-on-push', + controlFlowBlock: null, hasNativeElement: true, }, { @@ -82,8 +82,8 @@ describe('indexForest', () => { hydration: null, component: null, children: [], - onPush: true, - defer: null, + changeDetection: 'ng-eager', + controlFlowBlock: null, hasNativeElement: true, } as DevToolsNode & {hasNativeElement?: boolean}, { @@ -101,13 +101,13 @@ describe('indexForest', () => { component: null, hydration: null, children: [], - onPush: true, - defer: null, + changeDetection: 'ng-eager', + controlFlowBlock: null, hasNativeElement: true, } as DevToolsNode & {hasNativeElement?: boolean}, ], - onPush: true, - defer: null, + changeDetection: 'ng-eager', + controlFlowBlock: null, hasNativeElement: true, }, ]), @@ -139,8 +139,8 @@ describe('indexForest', () => { component: null, hydration: null, children: [], - onPush: false, - defer: null, + changeDetection: 'ng-on-push', + controlFlowBlock: null, hasNativeElement: true, }, { @@ -154,13 +154,14 @@ describe('indexForest', () => { }, hydration: null, children: [], - defer: null, - onPush: false, + controlFlowBlock: null, + changeDetection: 'ng-on-push', + hasNativeElement: true, }, ], - defer: null, - onPush: false, + controlFlowBlock: null, + changeDetection: 'ng-on-push', hasNativeElement: true, }, { @@ -182,8 +183,8 @@ describe('indexForest', () => { component: null, hydration: null, children: [], - onPush: true, - defer: null, + changeDetection: 'ng-eager', + controlFlowBlock: null, hasNativeElement: true, }, { @@ -202,13 +203,13 @@ describe('indexForest', () => { component: null, children: [], hydration: null, - onPush: true, - defer: null, + changeDetection: 'ng-eager', + controlFlowBlock: null, hasNativeElement: true, }, ], - onPush: true, - defer: null, + changeDetection: 'ng-eager', + controlFlowBlock: null, hasNativeElement: true, }, ]); diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest/index.ts b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest/index.ts index 629cb6896d21..07afd3a9ac90 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest/index.ts +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest/index.ts @@ -28,14 +28,30 @@ const indexTree = ( position, element: node.element, component: node.component, - directives: node.directives.map((d, i) => ({name: d.name, id: d.id})), + directives: node.directives?.map((d) => ({name: d.name, id: d.id})), children: node.children.map((n, i) => indexTree(n, i, position)), hydration: node.hydration, - defer: node.defer, - onPush: node.onPush, + controlFlowBlock: node.controlFlowBlock, + changeDetection: node.changeDetection, hasNativeElement: (node as any).hasNativeElement, }; }; export const indexForest = (forest: (DevToolsNode & {hasNativeElement?: boolean})[]) => forest.map((n, i) => indexTree(n, i)); + +export const findNodeByPosition = ( + forest: IndexedNode[], + position: ElementPosition, +): IndexedNode | null => { + if (position.length === 0) { + return null; + } + + let current: IndexedNode | undefined = forest[position[0]]; + for (let i = 1; i < position.length && current; i++) { + current = current.children[position[i]]; + } + + return current ?? null; +}; diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/BUILD.bazel b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/BUILD.bazel index 5c9fb6e6705e..477976663106 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/BUILD.bazel +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/BUILD.bazel @@ -24,8 +24,10 @@ ng_project( "//:node_modules/@angular/common", "//:node_modules/@angular/core", "//:node_modules/@angular/material", + "//devtools/projects/ng-devtools/src/lib/application-providers:app_data", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest:directive_forest_utils", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source", + "//devtools/projects/ng-devtools/src/lib/shared/utils", ], ) @@ -37,7 +39,9 @@ ts_test_library( "//:node_modules/@angular/cdk", "//:node_modules/@angular/core", "//:node_modules/@angular/platform-browser", + "//devtools/projects/ng-devtools/src/lib/application-providers:app_data", "//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/component-data-source", + "//devtools/projects/protocol", ], ) diff --git a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/tree-node.component.html b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/tree-node.component.html index ea358b723923..90e301d3b7ec 100644 --- a/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/tree-node.component.html +++ b/devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/tree-node/tree-node.component.html @@ -4,7 +4,6 @@ @if (node().expandable) { @@ -144,7 +146,8 @@ export declare class TestComp { import { Component, signal } from '@angular/core'; import * as i0 from "@angular/core"; export class TestComp { - someSignal = signal('', ...(ngDevMode ? [{ debugName: "someSignal" }] : [])); + someSignal = signal('', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "someSignal" }] : /* istanbul ignore next */ [])); componentProp = 0; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComp, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: TestComp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` @@ -272,7 +275,8 @@ export declare class TestDir { import { Directive, signal } from '@angular/core'; import * as i0 from "@angular/core"; export class TestDir { - someSignal = signal(0, ...(ngDevMode ? [{ debugName: "someSignal" }] : [])); + someSignal = signal(0, /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "someSignal" }] : /* istanbul ignore next */ [])); componentProp = 1; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestDir, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: TestDir, isStandalone: true, host: { listeners: { "click": "someSignal.update(prev => prev + 1)", "mousedown": "someSignal.update(() => componentProp + 1)" } }, ngImport: i0 }); @@ -502,6 +506,47 @@ export declare class TestComp { static ɵcmp: i0.ɵɵComponentDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: arrow_function_safe_access_use_null.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class TestComp { + componentProp = {}; + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComp, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: TestComp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` + {{(value => value?.a?.b?.c?.()?.()?.()?.())(componentProp)}} +
    + {{() => componentProp?.a?.b?.c?.()?.()?.()?.()}} + `, isInline: true }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComp, decorators: [{ + type: Component, + args: [{ + template: ` + {{(value => value?.a?.b?.c?.()?.()?.()?.())(componentProp)}} +
    + {{() => componentProp?.a?.b?.c?.()?.()?.()?.()}} + ` + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: arrow_function_safe_access_use_null.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class TestComp { + componentProp: { + a?: { + b?: { + c?: () => () => () => () => string; + }; + }; + }; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: arrow_function_safe_access_nested_views.js ****************************************************************************************************/ @@ -551,6 +596,55 @@ export declare class TestComp { static ɵcmp: i0.ɵɵComponentDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: arrow_function_safe_access_nested_views_use_null.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class TestComp { + componentProp = {}; + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComp, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: TestComp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` + @if (true) { + @if (true) { + @if (true) { + {{() => componentProp?.a?.b?.c?.()?.()?.()?.()}} + } + } + } + `, isInline: true }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComp, decorators: [{ + type: Component, + args: [{ + template: ` + @if (true) { + @if (true) { + @if (true) { + {{() => componentProp?.a?.b?.c?.()?.()?.()?.()}} + } + } + } + `, + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: arrow_function_safe_access_nested_views_use_null.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class TestComp { + componentProp: { + a?: { + b?: { + c?: () => () => () => () => string; + }; + }; + }; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: arrow_function_pipe.js ****************************************************************************************************/ @@ -611,7 +705,8 @@ import { Component, signal } from '@angular/core'; import * as i0 from "@angular/core"; export class TestComp { componentProp = 0; - result = signal('', ...(ngDevMode ? [{ debugName: "result" }] : [])); + result = signal('', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "result" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComp, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: TestComp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` @let topLevelLet = 1; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/TEST_CASES.json index 063f63ff5c97..0826a3971c06 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/TEST_CASES.json @@ -141,6 +141,20 @@ } ] }, + { + "description": "should handle arrow function with optional reads with legacyOptionalChaining", + "inputFiles": ["arrow_function_safe_access_use_null.ts"], + "compilationModeFilter": ["full compile", "instruction compile"], + "expectations": [ + { + "failureMessage": "Invalid template", + "files": ["arrow_function_safe_access_use_null.js"] + } + ], + "angularCompilerOptions": { + "legacyOptionalChaining": true + } + }, { "description": "should handle arrow function with optional reads in nested views", "inputFiles": ["arrow_function_safe_access_nested_views.ts"], @@ -151,6 +165,20 @@ } ] }, + { + "description": "should handle arrow function with optional reads in nested views with legacyOptionalChaining", + "inputFiles": ["arrow_function_safe_access_nested_views_use_null.ts"], + "compilationModeFilter": ["full compile", "instruction compile"], + "expectations": [ + { + "failureMessage": "Invalid template", + "files": ["arrow_function_safe_access_nested_views_use_null.js"] + } + ], + "angularCompilerOptions": { + "legacyOptionalChaining": true + } + }, { "description": "should handle arrow function that is passed into a pipe", "inputFiles": ["arrow_function_pipe.ts"], diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access.js index 0adec41157ae..980153d5e59f 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access.js @@ -1,40 +1,6 @@ -const $arrowFn0$ = (ctx, view) => value => { - let $tmp_0_0$; - return value == null - ? null - : value.a == null - ? null - : value.a.b == null - ? null - : value.a.b.c == null - ? null - : ($tmp_0_0$ = value.a.b.c()) == null - ? null - : ($tmp_0_0$ = $tmp_0_0$()) == null - ? null - : ($tmp_0_0$ = $tmp_0_0$()) == null - ? null - : $tmp_0_0$(); -}; +const $arrowFn0$ = (ctx, view) => value =>value?.a?.b?.c?.()?.()?.()?.(); … -const $arrowFn1$ = (ctx, view) => () => { - let $tmp_0_0$; - return ctx.componentProp == null - ? null - : ctx.componentProp.a == null - ? null - : ctx.componentProp.a.b == null - ? null - : ctx.componentProp.a.b.c == null - ? null - : ($tmp_0_0$ = ctx.componentProp.a.b.c()) == null - ? null - : ($tmp_0_0$ = $tmp_0_0$()) == null - ? null - : ($tmp_0_0$ = $tmp_0_0$()) == null - ? null - : $tmp_0_0$(); -}; +const arrowFn1 = (ctx, view) => () => ctx.componentProp?.a?.b?.c?.()?.()?.()?.(); … $r3$.ɵɵdefineComponent({ … diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views.js index a4838b3c9e13..d1ae033fd881 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views.js @@ -1,23 +1,7 @@ const $arrowFn0$ = (ctx, view) => () => { - let $tmp_4_0$; - $r3$.ɵɵrestoreView($_r1$); - const $ctx_r1$ = $r3$.ɵɵnextContext(3); - return $r3$.ɵɵresetView( - $ctx_r1$.componentProp == null - ? null - : $ctx_r1$.componentProp.a == null - ? null - : $ctx_r1$.componentProp.a.b == null - ? null - : $ctx_r1$.componentProp.a.b.c == null - ? null - : ($tmp_4_0$ = $ctx_r1$.componentProp.a.b.c()) == null - ? null - : ($tmp_4_0$ = $tmp_4_0$()) == null - ? null - : ($tmp_4_0$ = $tmp_4_0$()) == null - ? null - : $tmp_4_0$()); + $r3$.ɵɵrestoreView(view); + const ctx_r0 = $r3$.ɵɵnextContext(3); + return $r3$.ɵɵresetView(ctx_r0.componentProp?.a?.b?.c?.()?.()?.()?.() ); }; function TestComp_Conditional_0_Conditional_0_Conditional_0_Template(rf, ctx) { diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views_use_null.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views_use_null.js new file mode 100644 index 000000000000..a4838b3c9e13 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views_use_null.js @@ -0,0 +1,65 @@ +const $arrowFn0$ = (ctx, view) => () => { + let $tmp_4_0$; + $r3$.ɵɵrestoreView($_r1$); + const $ctx_r1$ = $r3$.ɵɵnextContext(3); + return $r3$.ɵɵresetView( + $ctx_r1$.componentProp == null + ? null + : $ctx_r1$.componentProp.a == null + ? null + : $ctx_r1$.componentProp.a.b == null + ? null + : $ctx_r1$.componentProp.a.b.c == null + ? null + : ($tmp_4_0$ = $ctx_r1$.componentProp.a.b.c()) == null + ? null + : ($tmp_4_0$ = $tmp_4_0$()) == null + ? null + : ($tmp_4_0$ = $tmp_4_0$()) == null + ? null + : $tmp_4_0$()); +}; + +function TestComp_Conditional_0_Conditional_0_Conditional_0_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵtext(0); + } + if (rf & 2) { + $r3$.ɵɵtextInterpolate1(" ", $r3$.ɵɵarrowFunction(1, $arrowFn0$, ctx), " "); + } +} + +function TestComp_Conditional_0_Conditional_0_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵconditionalCreate(0, TestComp_Conditional_0_Conditional_0_Conditional_0_Template, 1, 2); + } + if (rf & 2) { + $r3$.ɵɵconditional(true ? 0 : -1); + } +} + +function TestComp_Conditional_0_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵconditionalCreate(0, TestComp_Conditional_0_Conditional_0_Template, 1, 1); + } + if (rf & 2) { + $r3$.ɵɵconditional(true ? 0 : -1); + } +} + +… + +$r3$.ɵɵdefineComponent({ + … + decls: 1, + vars: 1, + template: function TestComp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵconditionalCreate(0, TestComp_Conditional_0_Template, 1, 1); + } + if (rf & 2) { + $r3$.ɵɵconditional(true ? 0 : -1); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views_use_null.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views_use_null.ts new file mode 100644 index 000000000000..4a9cf6f8d903 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_nested_views_use_null.ts @@ -0,0 +1,16 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + @if (true) { + @if (true) { + @if (true) { + {{() => componentProp?.a?.b?.c?.()?.()?.()?.()}} + } + } + } + `, +}) +export class TestComp { + componentProp: {a?: {b?: {c?: () => () => () => () => string}}} = {}; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_use_null.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_use_null.js new file mode 100644 index 000000000000..0adec41157ae --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_use_null.js @@ -0,0 +1,56 @@ +const $arrowFn0$ = (ctx, view) => value => { + let $tmp_0_0$; + return value == null + ? null + : value.a == null + ? null + : value.a.b == null + ? null + : value.a.b.c == null + ? null + : ($tmp_0_0$ = value.a.b.c()) == null + ? null + : ($tmp_0_0$ = $tmp_0_0$()) == null + ? null + : ($tmp_0_0$ = $tmp_0_0$()) == null + ? null + : $tmp_0_0$(); +}; +… +const $arrowFn1$ = (ctx, view) => () => { + let $tmp_0_0$; + return ctx.componentProp == null + ? null + : ctx.componentProp.a == null + ? null + : ctx.componentProp.a.b == null + ? null + : ctx.componentProp.a.b.c == null + ? null + : ($tmp_0_0$ = ctx.componentProp.a.b.c()) == null + ? null + : ($tmp_0_0$ = $tmp_0_0$()) == null + ? null + : ($tmp_0_0$ = $tmp_0_0$()) == null + ? null + : $tmp_0_0$(); +}; +… +$r3$.ɵɵdefineComponent({ + … + decls: 3, + vars: 4, + template: function TestComp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵtext(0); + $r3$.ɵɵdomElement(1, "hr"); + $r3$.ɵɵtext(2); + } + if (rf & 2) { + $r3$.ɵɵtextInterpolate1(" ", $r3$.ɵɵarrowFunction(2, $arrowFn0$, ctx)(ctx.componentProp), " "); + $r3$.ɵɵadvance(2); + $r3$.ɵɵtextInterpolate1(" ", $r3$.ɵɵarrowFunction(3, $arrowFn1$, ctx), " "); + } + }, + … +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_use_null.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_use_null.ts new file mode 100644 index 000000000000..bead348258ac --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_arrow_functions/arrow_function_safe_access_use_null.ts @@ -0,0 +1,12 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + {{(value => value?.a?.b?.c?.()?.()?.()?.())(componentProp)}} +
    + {{() => componentProp?.a?.b?.c?.()?.()?.()?.()}} + ` +}) +export class TestComp { + componentProp: {a?: {b?: {c?: () => () => () => () => string}}} = {}; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/control_bindings/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/control_bindings/GOLDEN_PARTIAL.js index b05eb6851530..b91a161a08d1 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/control_bindings/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/control_bindings/GOLDEN_PARTIAL.js @@ -4,7 +4,8 @@ import { Component, Directive, input } from '@angular/core'; import * as i0 from "@angular/core"; export class FormField { - formField = input(...(ngDevMode ? [undefined, { debugName: "formField" }] : [])); + formField = input(/* @ts-ignore */ + ...(ngDevMode ? [undefined, { debugName: "formField" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FormField, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "0.0.0-PLACEHOLDER", type: FormField, isStandalone: true, selector: "[formField]", inputs: { formField: { classPropertyName: "formField", publicName: "formField", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); } @@ -54,7 +55,8 @@ export declare class MyComponent { import { Component, Directive, input } from '@angular/core'; import * as i0 from "@angular/core"; export class FormField { - formField = input(...(ngDevMode ? [undefined, { debugName: "formField" }] : [])); + formField = input(/* @ts-ignore */ + ...(ngDevMode ? [undefined, { debugName: "formField" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: FormField, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "0.0.0-PLACEHOLDER", type: FormField, isStandalone: true, selector: "[formField]", inputs: { formField: { classPropertyName: "formField", publicName: "formField", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/GOLDEN_PARTIAL.js index bc7848859f61..993805dc69ac 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/GOLDEN_PARTIAL.js @@ -124,6 +124,51 @@ export declare class MyModule { static ɵinj: i0.ɵɵInjectorDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: host_bindings_with_temporaries_use_null.js + ****************************************************************************************************/ +import { Directive, NgModule } from '@angular/core'; +import * as i0 from "@angular/core"; +export class HostBindingDir { + getData = () => undefined; + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: HostBindingDir, deps: [], target: i0.ɵɵFactoryTarget.Directive }); + static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: HostBindingDir, isStandalone: false, selector: "[hostBindingDir]", host: { properties: { "id": "getData()?.id" } }, ngImport: i0 }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: HostBindingDir, decorators: [{ + type: Directive, + args: [{ + selector: '[hostBindingDir]', + host: { '[id]': 'getData()?.id' }, + standalone: false, + }] + }] }); +export class MyModule { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); + static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [HostBindingDir] }); + static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{ + type: NgModule, + args: [{ declarations: [HostBindingDir] }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: host_bindings_with_temporaries_use_null.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class HostBindingDir { + getData: () => { + id: number; + } | undefined; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; +} +export declare class MyModule { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵinj: i0.ɵɵInjectorDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: host_class_bindings_with_temporaries.js ****************************************************************************************************/ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/TEST_CASES.json index 9c7ba5a3755e..d923b21fde57 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/TEST_CASES.json @@ -31,6 +31,20 @@ } ] }, + { + "description": "should support host bindings with temporary expressions with legacyOptionalChaining", + "inputFiles": ["host_bindings_with_temporaries_use_null.ts"], + "compilationModeFilter": ["full compile", "instruction compile"], + "expectations": [ + { + "failureMessage": "Invalid host binding code", + "files": ["host_bindings_with_temporaries_use_null.js"] + } + ], + "angularCompilerOptions": { + "legacyOptionalChaining": true + } + }, { "description": "should support host class bindings with temporary expressions", "inputFiles": ["host_class_bindings_with_temporaries.ts"], diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries.js index 3dd63f8f9be2..5af084073439 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries.js @@ -6,8 +6,7 @@ export class HostBindingDir { hostVars: 1, hostBindings: function HostBindingDir_HostBindings(rf, ctx) { if (rf & 2) { - let $tmp0$; - $r3$.ɵɵdomProperty("id", ($tmp0$ = ctx.getData()) == null ? null : $tmp0$.id); + i0.ɵɵdomProperty("id", ctx.getData()?.id); } }, standalone: false diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries_use_null.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries_use_null.js new file mode 100644 index 000000000000..3dd63f8f9be2 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries_use_null.js @@ -0,0 +1,15 @@ +export class HostBindingDir { + // ... + static ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({ + type: HostBindingDir, + selectors: [["", "hostBindingDir", ""]], + hostVars: 1, + hostBindings: function HostBindingDir_HostBindings(rf, ctx) { + if (rf & 2) { + let $tmp0$; + $r3$.ɵɵdomProperty("id", ($tmp0$ = ctx.getData()) == null ? null : $tmp0$.id); + } + }, + standalone: false + }); +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries_use_null.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries_use_null.ts new file mode 100644 index 000000000000..990aa2c5f0e5 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/host_bindings/host_bindings_with_temporaries_use_null.ts @@ -0,0 +1,17 @@ +import {Directive, NgModule} from '@angular/core'; + +@Directive({ + selector: '[hostBindingDir]', + host: {'[id]': 'getData()?.id'}, + standalone: false, +}) +export class HostBindingDir { + getData: () => + | { + id: number; + } + | undefined = () => undefined; +} + +@NgModule({declarations: [HostBindingDir]}) +export class MyModule {} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js index 3cb8221ff98b..e006527a27e8 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/GOLDEN_PARTIAL.js @@ -386,6 +386,75 @@ export declare class MyMod { static ɵinj: i0.ɵɵInjectorDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: temporary_variables_use_null.js + ****************************************************************************************************/ +import { Component, NgModule, Pipe } from '@angular/core'; +import * as i0 from "@angular/core"; +export class AsyncPipe { + transform(v) { } + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: AsyncPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); + static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: AsyncPipe, isStandalone: false, name: "async" }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: AsyncPipe, decorators: [{ + type: Pipe, + args: [{ + name: 'async', + standalone: false + }] + }] }); +// https://github.com/angular/angular/issues/37194 +// Verifies that temporary expressions used for expressions with potential side-effects in +// the LHS of a safe navigation access are emitted within the binding expression itself, to +// ensure that these temporaries are evaluated during the evaluation of the binding. This +// is important for when the LHS contains a pipe, as pipe evaluation depends on the current +// binding index. +export class MyComponent { + myTitle = 'hello'; + auth; + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: false, selector: "ng-component", ngImport: i0, template: '', isInline: true, dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }] }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{ + type: Component, + args: [{ + template: '', + standalone: false + }] + }] }); +export class MyMod { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); + static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, declarations: [MyComponent, AsyncPipe] }); + static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, decorators: [{ + type: NgModule, + args: [{ declarations: [MyComponent, AsyncPipe] }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: temporary_variables_use_null.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class AsyncPipe { + transform(v: any): null | any; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵpipe: i0.ɵɵPipeDeclaration; +} +export declare class MyComponent { + myTitle: string; + auth: () => { + identity(): any; + }; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} +export declare class MyMod { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵinj: i0.ɵɵInjectorDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: chain_multiple_bindings.js ****************************************************************************************************/ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/TEST_CASES.json index 6419e6d22aba..7daf95b5705a 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/TEST_CASES.json @@ -3,251 +3,193 @@ "cases": [ { "description": "should generate attribute instructions for DOM-only ARIA bindings", - "inputFiles": [ - "aria_dom_properties.ts" - ], + "inputFiles": ["aria_dom_properties.ts"], "expectations": [ { - "files": [ - "aria_dom_properties.js" - ] + "files": ["aria_dom_properties.js"] } ] }, { "description": "should generate ariaProperty instructions for ARIA bindings", - "inputFiles": [ - "aria_properties.ts" - ], + "inputFiles": ["aria_properties.ts"], "expectations": [ { - "files": [ - "aria_properties.js" - ] + "files": ["aria_properties.js"] } ] }, { "description": "should generate bind instruction", - "inputFiles": [ - "bind.ts" - ], + "inputFiles": ["bind.ts"], "expectations": [ { "failureMessage": "Incorrect property binding", - "files": [ - "bind.js" - ] + "files": ["bind.js"] } ] }, { "description": "should generate interpolation instruction for {{...}} bindings", - "inputFiles": [ - "interpolation.ts" - ], + "inputFiles": ["interpolation.ts"], "expectations": [ { "failureMessage": "Incorrect interpolated property binding", - "files": [ - "interpolation.js" - ] + "files": ["interpolation.js"] } ] }, { "description": "should generate the proper update instructions for interpolated properties", - "inputFiles": [ - "interpolated_properties.ts" - ], + "inputFiles": ["interpolated_properties.ts"], "expectations": [ { "failureMessage": "Incorrect handling of interpolated properties", - "files": [ - "interpolated_properties.js" - ] + "files": ["interpolated_properties.js"] } ] }, { "description": "should not remap special property names when outputting property instructions", - "inputFiles": [ - "special_property_remapping_property.ts" - ], + "inputFiles": ["special_property_remapping_property.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "special_property_remapping_property.js" - ] + "files": ["special_property_remapping_property.js"] } ] }, { "description": "should remap special property names when outputting domProperty instructions", - "inputFiles": [ - "special_property_remapping_dom_property.ts" - ], + "inputFiles": ["special_property_remapping_dom_property.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "special_property_remapping_dom_property.js" - ] + "files": ["special_property_remapping_dom_property.js"] } ] }, { "description": "should emit temporary evaluation within the binding expression for in-order execution", - "inputFiles": [ - "temporary_variables.ts" - ], + "inputFiles": ["temporary_variables.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "temporary_variables.js" - ] + "files": ["temporary_variables.js"] } ] }, { - "description": "should chain multiple property bindings into a single instruction", - "inputFiles": [ - "chain_multiple_bindings.ts" + "description": "should emit temporary evaluation within the binding expression for in-order execution with legacyOptionalChaining", + "inputFiles": ["temporary_variables_use_null.ts"], + "compilationModeFilter": ["full compile", "instruction compile"], + "expectations": [ + { + "failureMessage": "Incorrect template", + "files": ["temporary_variables_use_null.js"] + } ], + "angularCompilerOptions": { + "legacyOptionalChaining": true + } + }, + { + "description": "should chain multiple property bindings into a single instruction", + "inputFiles": ["chain_multiple_bindings.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "chain_multiple_bindings.js" - ] + "files": ["chain_multiple_bindings.js"] } ] }, { "description": "should chain property bindings in the presence of other bindings", - "inputFiles": [ - "chain_multiple_bindings_mixed.ts" - ], + "inputFiles": ["chain_multiple_bindings_mixed.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "chain_multiple_bindings_mixed.js" - ] + "files": ["chain_multiple_bindings_mixed.js"] } ] }, { "description": "should not add interpolated properties to the property instruction chain", - "inputFiles": [ - "chain_bindings_with_interpolations.ts" - ], + "inputFiles": ["chain_bindings_with_interpolations.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "chain_bindings_with_interpolations.js" - ] + "files": ["chain_bindings_with_interpolations.js"] } ] }, { "description": "should chain synthetic property bindings together with regular property bindings", - "inputFiles": [ - "chain_synthetic_bindings.ts" - ], + "inputFiles": ["chain_synthetic_bindings.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "chain_synthetic_bindings.js" - ] + "files": ["chain_synthetic_bindings.js"] } ] }, { "description": "should chain multiple property bindings on an ng-template", - "inputFiles": [ - "chain_ngtemplate_bindings.ts" - ], + "inputFiles": ["chain_ngtemplate_bindings.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "chain_ngtemplate_bindings.js" - ] + "files": ["chain_ngtemplate_bindings.js"] } ] }, { "description": "should chain multiple property bindings when there are multiple elements", - "inputFiles": [ - "chain_multiple_bindings_for_multiple_elements.ts" - ], + "inputFiles": ["chain_multiple_bindings_for_multiple_elements.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "chain_multiple_bindings_for_multiple_elements.js" - ] + "files": ["chain_multiple_bindings_for_multiple_elements.js"] } ] }, { "description": "should chain multiple property bindings when there are child elements", - "inputFiles": [ - "chain_multiple_bindings_with_child_elements.ts" - ], + "inputFiles": ["chain_multiple_bindings_with_child_elements.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "chain_multiple_bindings_with_child_elements.js" - ] + "files": ["chain_multiple_bindings_with_child_elements.js"] } ] }, { "description": "should generate synthetic bindings and listeners on structural directives", - "inputFiles": [ - "synthetic_bindings_and_listeners_on_structural.ts" - ], + "inputFiles": ["synthetic_bindings_and_listeners_on_structural.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "synthetic_bindings_and_listeners_on_structural.js" - ] + "files": ["synthetic_bindings_and_listeners_on_structural.js"] } ] }, { "description": "should sanitize dangerous bindings", - "inputFiles": [ - "sanitization.ts" - ], + "inputFiles": ["sanitization.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "sanitization.js" - ] + "files": ["sanitization.js"] } ] }, { "description": "should maintain the binding order between one-way and two-way properties", - "inputFiles": [ - "mixed_one_way_two_way_property_order.ts" - ], + "inputFiles": ["mixed_one_way_two_way_property_order.ts"], "expectations": [ { "failureMessage": "Incorrect template", - "files": [ - "mixed_one_way_two_way_property_order.js" - ] + "files": ["mixed_one_way_two_way_property_order.js"] } ] } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables.js index b7dc723c1cc5..0fe16a1063dd 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables.js @@ -1,7 +1,5 @@ template: function MyComponent_Template(rf, ctx) { … if (rf & 2) { - let $tmp0$; - $r3$.ɵɵproperty("title", ctx.myTitle)("id", ($tmp0$ = i0.ɵɵpipeBind1(1, 3, ctx.auth().identity())) == null ? null : $tmp0$.id)("tabindex", 1); - } + i0.ɵɵproperty("title", ctx.myTitle)("id", i0.ɵɵpipeBind1(1, 3, ctx.auth().identity())?.id)("tabindex", 1); } } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables_use_null.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables_use_null.js new file mode 100644 index 000000000000..b7dc723c1cc5 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables_use_null.js @@ -0,0 +1,7 @@ +template: function MyComponent_Template(rf, ctx) { + … + if (rf & 2) { + let $tmp0$; + $r3$.ɵɵproperty("title", ctx.myTitle)("id", ($tmp0$ = i0.ɵɵpipeBind1(1, 3, ctx.auth().identity())) == null ? null : $tmp0$.id)("tabindex", 1); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables_use_null.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables_use_null.ts new file mode 100644 index 000000000000..a1cd05058043 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_bindings/property_bindings/temporary_variables_use_null.ts @@ -0,0 +1,30 @@ +import {Component, NgModule, Pipe} from '@angular/core'; + +@Pipe({ + name: 'async', + standalone: false +}) +export class AsyncPipe { + transform(v: any): null|any {} +} + +// https://github.com/angular/angular/issues/37194 +// Verifies that temporary expressions used for expressions with potential side-effects in +// the LHS of a safe navigation access are emitted within the binding expression itself, to +// ensure that these temporaries are evaluated during the evaluation of the binding. This +// is important for when the LHS contains a pipe, as pipe evaluation depends on the current +// binding index. +@Component({ + template: '', + standalone: false +}) +export class MyComponent { + myTitle = 'hello'; + auth!: () => { + identity(): any; + }; +} + +@NgModule({declarations: [MyComponent, AsyncPipe]}) +export class MyMod { +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js index da0a9bc5b8f4..acf2665caabd 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js @@ -2665,6 +2665,44 @@ export declare class MyApp { static ɵcmp: i0.ɵɵComponentDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: for_track_by_temporary_variables_use_null.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyApp { + constructor() { + this.items = []; + } +} +MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` + @for (item of items; track item?.name?.[0]?.toUpperCase() ?? foo) {} + @for (item of items; track item.name ?? $index ?? foo) {} + `, isInline: true }); +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ + type: Component, + args: [{ + template: ` + @for (item of items; track item?.name?.[0]?.toUpperCase() ?? foo) {} + @for (item of items; track item.name ?? $index ?? foo) {} + `, + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: for_track_by_temporary_variables_use_null.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + foo: any; + items: { + name?: string; + }[]; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: else_if_with_alias.js ****************************************************************************************************/ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/TEST_CASES.json index ec24269b1a89..2c6c9c4f50ac 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/TEST_CASES.json @@ -724,6 +724,28 @@ } ] }, + { + "description": "should support expressions requiring temporary variables inside `track` with legacyOptionalChaining", + "inputFiles": ["for_track_by_temporary_variables_use_null.ts"], + "compilationModeFilter": ["full compile", "instruction compile"], + "compilerOptions": { + "target": "es2020" + }, + "expectations": [ + { + "failureMessage": "Incorrect generated output.", + "files": [ + { + "expected": "for_track_by_temporary_variables_template_use_null.js", + "generated": "for_track_by_temporary_variables_use_null.js" + } + ] + } + ], + "angularCompilerOptions": { + "legacyOptionalChaining": true + } + }, { "description": "should support `as` expression on `@else if` block", "inputFiles": ["else_if_with_alias.ts"], diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_impure_track_reuse_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_impure_track_reuse_template.js index 7107664c2d91..fc72f56cfc93 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_impure_track_reuse_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_impure_track_reuse_template.js @@ -1,4 +1,5 @@ function $_forTrack0$($index, $item) { + /* @ts-ignore */ return this.trackFn($item, this.message); } … diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_template.js index 4224c7b59ad2..3d537d554cf8 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_template.js @@ -1,14 +1,10 @@ function _forTrack0($index, $item) { - return ($item == null - ? null - : $item.name == null - ? null - : $item.name[0] == null - ? null - : $item.name[0].toUpperCase()) ?? this.foo; + /* @ts-ignore */ + return $item?.name?.[0]?.toUpperCase() ?? this.foo; } function _forTrack1($index, $item) { + /* @ts-ignore */ return $item.name ?? $index ?? this.foo; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_template_use_null.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_template_use_null.js new file mode 100644 index 000000000000..96bbc0fc2469 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_template_use_null.js @@ -0,0 +1,23 @@ +function _forTrack0($index, $item) { + /* @ts-ignore */ + return ($item == null ? null : $item.name == null ? null : $item.name[0] == null ? null : $item.name[0].toUpperCase()) ?? this.foo; +} + +function _forTrack1($index, $item) { + /* @ts-ignore */ + return $item.name ?? $index ?? this.foo; +} + +… + +function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵrepeaterCreate(0, MyApp_For_1_Template, 0, 0, null, null, _forTrack0, true); + $r3$.ɵɵrepeaterCreate(2, MyApp_For_3_Template, 0, 0, null, null, _forTrack1, true); + } + if (rf & 2) { + $r3$.ɵɵrepeater(ctx.items); + $r3$.ɵɵadvance(2); + $r3$.ɵɵrepeater(ctx.items); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_use_null.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_use_null.ts new file mode 100644 index 000000000000..77c3d53dc78c --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_by_temporary_variables_use_null.ts @@ -0,0 +1,12 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + @for (item of items; track item?.name?.[0]?.toUpperCase() ?? foo) {} + @for (item of items; track item.name ?? $index ?? foo) {} + `, +}) +export class MyApp { + foo: any; + items: {name?: string}[] = []; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_literals_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_literals_template.js index 43e0ccd8f857..3d1021ee1375 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_literals_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/for_track_literals_template.js @@ -1,4 +1,5 @@ function $_forTrack0$($index, $item) { + /* @ts-ignore */ return this.trackFn({ foo: $item, bar: $item diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_tracking_function_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_tracking_function_template.js index 81eac05a0b24..e143a155bb87 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_tracking_function_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/nested_for_tracking_function_template.js @@ -1,12 +1,15 @@ function _forTrack0($index, $item) { + /* @ts-ignore */ return this.trackByGrandparent($item, $index); } function _forTrack1($index, $item) { + /* @ts-ignore */ return this.trackByParent($item, $index); } function _forTrack2($index, $item) { + /* @ts-ignore */ return this.trackByChild($item, $index); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js index 245ccf0de806..45339e471448 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js @@ -395,9 +395,11 @@ export class MyApp { }
    - `, isInline: true, dependencies: [{ kind: "directive", type: EagerDep, selector: "eager-dep" }, { kind: "directive", type: LoadingDep, selector: "loading-dep" }], deferBlockDependencies: [() => [import("./deferred_with_external_deps_lazy").then(m => m.LazyDep)]] }); + `, isInline: true, dependencies: [{ kind: "directive", type: EagerDep, selector: "eager-dep" }, { kind: "directive", type: LoadingDep, selector: "loading-dep" }], deferBlockDependencies: [() => [/* @ts-ignore */ + import("./deferred_with_external_deps_lazy").then(m => m.LazyDep)]] }); } -i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, resolveDeferredDeps: () => [import("./deferred_with_external_deps_lazy").then(m => m.LazyDep)], resolveMetadata: LazyDep => ({ decorators: [{ +i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, resolveDeferredDeps: () => [/* @ts-ignore */ + import("./deferred_with_external_deps_lazy").then(m => m.LazyDep)], resolveMetadata: LazyDep => ({ decorators: [{ type: Component, args: [{ template: ` @@ -631,6 +633,116 @@ export declare class MyApp { static ɵcmp: i0.ɵɵComponentDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: defer_multiple_hydrate_single_activator.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyApp { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` + @defer (hydrate on idle) { + One + } + @defer (hydrate on timer(500)) { + Two + } + `, isInline: true }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ + type: Component, + args: [{ + template: ` + @defer (hydrate on idle) { + One + } + @defer (hydrate on timer(500)) { + Two + } + `, + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: defer_multiple_hydrate_single_activator.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * PARTIAL FILE: defer_nested_hydrate_inner.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class InnerCmp { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: InnerCmp, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: InnerCmp, isStandalone: true, selector: "inner-cmp", ngImport: i0, template: ` + @defer (hydrate on idle) { + hello + } + `, isInline: true }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: InnerCmp, decorators: [{ + type: Component, + args: [{ + selector: 'inner-cmp', + template: ` + @defer (hydrate on idle) { + hello + } + `, + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: defer_nested_hydrate_inner.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class InnerCmp { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + +/**************************************************************************************************** + * PARTIAL FILE: defer_nested_hydrate.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyApp { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "my-app", ngImport: i0, template: ` + @defer (on idle) { + + } + `, isInline: true, deferBlockDependencies: [() => [/* @ts-ignore */ + import("./defer_nested_hydrate_inner").then(m => m.InnerCmp)]] }); +} +i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, resolveDeferredDeps: () => [/* @ts-ignore */ + import("./defer_nested_hydrate_inner").then(m => m.InnerCmp)], resolveMetadata: InnerCmp => ({ decorators: [{ + type: Component, + args: [{ + selector: 'my-app', + imports: [InnerCmp], + template: ` + @defer (on idle) { + + } + `, + }] + }], ctorParameters: null, propDecorators: null }) }); + +/**************************************************************************************************** + * PARTIAL FILE: defer_nested_hydrate.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: deferred_when_with_pipe.js ****************************************************************************************************/ @@ -927,9 +1039,11 @@ export class TestCmp { } -`, isInline: true, deferBlockDependencies: [() => [import("./defer_deps_ext").then(m => m.CmpA), LocalDep]] }); +`, isInline: true, deferBlockDependencies: [() => [/* @ts-ignore */ + import("./defer_deps_ext").then(m => m.CmpA), LocalDep]] }); } -i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, resolveDeferredDeps: () => [import("./defer_deps_ext").then(m => m.CmpA)], resolveMetadata: CmpA => ({ decorators: [{ +i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, resolveDeferredDeps: () => [/* @ts-ignore */ + import("./defer_deps_ext").then(m => m.CmpA)], resolveMetadata: CmpA => ({ decorators: [{ type: Component, args: [{ selector: 'test-cmp', @@ -1002,9 +1116,11 @@ export class TestCmp { } -`, isInline: true, deferBlockDependencies: [() => [import("./defer_default_deps_ext").then(m => m.default), LocalDep]] }); +`, isInline: true, deferBlockDependencies: [() => [/* @ts-ignore */ + import("./defer_default_deps_ext").then(m => m.default), LocalDep]] }); } -i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, resolveDeferredDeps: () => [import("./defer_default_deps_ext").then(m => m.default)], resolveMetadata: CmpA => ({ decorators: [{ +i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, resolveDeferredDeps: () => [/* @ts-ignore */ + import("./defer_default_deps_ext").then(m => m.default)], resolveMetadata: CmpA => ({ decorators: [{ type: Component, args: [{ selector: 'test-cmp', @@ -1162,9 +1278,14 @@ export class MyApp { @defer { } - `, isInline: true, deferBlockDependencies: [() => [import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep)], () => [import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep)], () => [import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)]] }); -} -i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, resolveDeferredDeps: () => [import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep), import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)], resolveMetadata: (DuplicateLazyDep, OtherLazyDep) => ({ decorators: [{ + `, isInline: true, deferBlockDependencies: [() => [/* @ts-ignore */ + import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep)], () => [/* @ts-ignore */ + import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep)], () => [/* @ts-ignore */ + import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)]] }); +} +i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, resolveDeferredDeps: () => [/* @ts-ignore */ + import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep), /* @ts-ignore */ + import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)], resolveMetadata: (DuplicateLazyDep, OtherLazyDep) => ({ decorators: [{ type: Component, args: [{ template: ` @@ -1241,9 +1362,11 @@ export class TestCmp { @defer { } - `, isInline: true, deferBlockDependencies: [() => [import("./deferred_import_alias_index").then(m => m.MyCounterCmp)]] }); + `, isInline: true, deferBlockDependencies: [() => [/* @ts-ignore */ + import("./deferred_import_alias_index").then(m => m.MyCounterCmp)]] }); } -i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, resolveDeferredDeps: () => [import("./deferred_import_alias_index").then(m => m.MyCounterCmp)], resolveMetadata: MyCounterCmp => ({ decorators: [{ +i0.ɵɵngDeclareClassMetadataAsync({ minVersion: "18.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, resolveDeferredDeps: () => [/* @ts-ignore */ + import("./deferred_import_alias_index").then(m => m.MyCounterCmp)], resolveMetadata: MyCounterCmp => ({ decorators: [{ type: Component, args: [{ selector: 'test-cmp', @@ -1385,3 +1508,42 @@ export declare class MyApp { static ɵcmp: i0.ɵɵComponentDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: deferred_on_idle_with_timeout.js + ****************************************************************************************************/ +import { Component } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyApp { + message = 'hello'; + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` + @defer (on idle(500ms)) { + {{message}} + } @placeholder { +

    Placeholder

    + } + `, isInline: true }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ + type: Component, + args: [{ + template: ` + @defer (on idle(500ms)) { + {{message}} + } @placeholder { +

    Placeholder

    + } + `, + }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: deferred_on_idle_with_timeout.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyApp { + message: string; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} + diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json index 7c64e7f3e8e8..103c1f8ef92d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json @@ -171,6 +171,40 @@ } ] }, + { + "description": "should emit `ɵɵenableIncrementalHydrationRuntime` only once per view, even when multiple sibling `@defer` blocks use hydrate triggers", + "inputFiles": ["defer_multiple_hydrate_single_activator.ts"], + "expectations": [ + { + "files": [ + { + "expected": "defer_multiple_hydrate_single_activator_template.js", + "generated": "defer_multiple_hydrate_single_activator.js" + } + ], + "failureMessage": "Incorrect template" + } + ] + }, + { + "description": "should wire incremental hydration only on the component whose template uses hydrate triggers, when a hydrating `@defer` lives inside a deferred-loaded child component", + "inputFiles": ["defer_nested_hydrate.ts", "defer_nested_hydrate_inner.ts"], + "expectations": [ + { + "files": [ + { + "expected": "defer_nested_hydrate_template.js", + "generated": "defer_nested_hydrate.js" + }, + { + "expected": "defer_nested_hydrate_inner_template.js", + "generated": "defer_nested_hydrate_inner.js" + } + ], + "failureMessage": "Incorrect template" + } + ] + }, { "description": "should generate a deferred block with a `when` trigger that has a pipe", "inputFiles": ["deferred_when_with_pipe.ts"], @@ -374,6 +408,21 @@ "failureMessage": "Incorrect template" } ] + }, + { + "description": "should generate a defer block with an `on idle` trigger that has a timeout", + "inputFiles": ["deferred_on_idle_with_timeout.ts"], + "expectations": [ + { + "files": [ + { + "expected": "deferred_on_idle_with_timeout_template.js", + "generated": "deferred_on_idle_with_timeout.js" + } + ], + "failureMessage": "Incorrect template" + } + ] } ] } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_default_deps_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_default_deps_template.js index a3be09b2e069..cd4a0bb2254d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_default_deps_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_default_deps_template.js @@ -1,4 +1,8 @@ -const $TestCmp_Defer_1_DepsFn$ = () => [import("./defer_default_deps_ext").then(m => m.default), LocalDep]; +const $TestCmp_Defer_1_DepsFn$ = () => [ + /* @ts-ignore */ + import("./defer_default_deps_ext").then(m => m.default), + LocalDep +]; function TestCmp_Defer_0_Template(rf, ctx) { if (rf & 1) { @@ -23,7 +27,10 @@ function TestCmp_Template(rf, ctx) { … (() => { - (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadataAsync(TestCmp, () => [import("./defer_default_deps_ext").then(m => m.default)], CmpA => { + (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadataAsync(TestCmp, () => [ + /* @ts-ignore */ + import("./defer_default_deps_ext").then(m => m.default) + ], CmpA => { $r3$.ɵsetClassMetadata(TestCmp, [{ type: Component, args: [{ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_deps_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_deps_template.js index 281335b31ea4..35166c0a4c69 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_deps_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_deps_template.js @@ -1,4 +1,8 @@ -const $TestCmp_Defer_1_DepsFn$ = () => [import("./defer_deps_ext").then(m => m.CmpA), LocalDep]; +const $TestCmp_Defer_1_DepsFn$ = () => [ + /* @ts-ignore */ + import("./defer_deps_ext").then(m => m.CmpA), + LocalDep +]; function TestCmp_Defer_0_Template(rf, ctx) { if (rf & 1) { @@ -21,7 +25,10 @@ function TestCmp_Template(rf, ctx) { if (rf & 1) { … (() => { - (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadataAsync(TestCmp, () => [import("./defer_deps_ext").then(m => m.CmpA)], CmpA => { + (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadataAsync(TestCmp, () => [ + /* @ts-ignore */ + import("./defer_deps_ext").then(m => m.CmpA) + ], CmpA => { $r3$.ɵsetClassMetadata(TestCmp, [{ type: Component, args: [{ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_hydrate_order_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_hydrate_order_template.js index a68a01deba8f..50d4d91e0a7b 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_hydrate_order_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_hydrate_order_template.js @@ -1,6 +1,7 @@ function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵdomTemplate(0, MyApp_Defer_0_Template, 1, 0)(1, MyApp_DeferPlaceholder_1_Template, 2, 0); + $r3$.ɵɵenableIncrementalHydrationRuntime(); $r3$.ɵɵdefer(2, 0, null, null, 1, null, null, null, null, 1); $r3$.ɵɵdeferHydrateOnTimer(1337); $r3$.ɵɵdeferPrefetchOnViewport(0, -1); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_multiple_hydrate_single_activator.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_multiple_hydrate_single_activator.ts new file mode 100644 index 000000000000..d1ec0a73e317 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_multiple_hydrate_single_activator.ts @@ -0,0 +1,13 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + @defer (hydrate on idle) { + One + } + @defer (hydrate on timer(500)) { + Two + } + `, +}) +export class MyApp {} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_multiple_hydrate_single_activator_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_multiple_hydrate_single_activator_template.js new file mode 100644 index 000000000000..d125f6949b48 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_multiple_hydrate_single_activator_template.js @@ -0,0 +1,13 @@ +function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵdomTemplate(0, MyApp_Defer_0_Template, 1, 0); + $r3$.ɵɵenableIncrementalHydrationRuntime(); + $r3$.ɵɵdefer(1, 0, null, null, null, null, null, null, null, 1); + $r3$.ɵɵdeferHydrateOnIdle(); + $r3$.ɵɵdeferOnIdle(); + $r3$.ɵɵdomTemplate(3, MyApp_Defer_3_Template, 1, 0); + $r3$.ɵɵdefer(4, 3, null, null, null, null, null, null, null, 1); + $r3$.ɵɵdeferHydrateOnTimer(500); + $r3$.ɵɵdeferOnIdle(); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate.ts new file mode 100644 index 000000000000..c3356c1c65fb --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; + +import {InnerCmp} from './defer_nested_hydrate_inner'; + +@Component({ + selector: 'my-app', + imports: [InnerCmp], + template: ` + @defer (on idle) { + + } + `, +}) +export class MyApp {} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_inner.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_inner.ts new file mode 100644 index 000000000000..881410941f73 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_inner.ts @@ -0,0 +1,12 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'inner-cmp', + template: ` + @defer (hydrate on idle) { + hello + } + `, +}) +export class InnerCmp { +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_inner_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_inner_template.js new file mode 100644 index 000000000000..304fafaf79d1 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_inner_template.js @@ -0,0 +1,15 @@ +function InnerCmp_Defer_0_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵtext(0, " hello "); + } +} +… +template: function InnerCmp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵdomTemplate(0, InnerCmp_Defer_0_Template, 1, 0); + $r3$.ɵɵenableIncrementalHydrationRuntime(); + $r3$.ɵɵdefer(1, 0, null, null, null, null, null, null, null, 1); + $r3$.ɵɵdeferHydrateOnIdle(); + $r3$.ɵɵdeferOnIdle(); + } +}, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_template.js new file mode 100644 index 000000000000..bf71c23aef3c --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_nested_hydrate_template.js @@ -0,0 +1,13 @@ +function MyApp_Defer_0_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelement(0, "inner-cmp"); + } +} +… +template: function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵdomTemplate(0, MyApp_Defer_0_Template, 1, 0); + $r3$.ɵɵdefer(1, 0, $MyApp_Defer_1_DepsFn$); + $r3$.ɵɵdeferOnIdle(); + } +}, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_with_hydrate_triggers_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_with_hydrate_triggers_template.js index 5673799a66e7..19a9d85a2aa5 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_with_hydrate_triggers_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/defer_with_hydrate_triggers_template.js @@ -2,6 +2,7 @@ function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtext(0); $r3$.ɵɵdomTemplate(1, MyApp_Defer_1_Template, 1, 1); + $r3$.ɵɵenableIncrementalHydrationRuntime(); $r3$.ɵɵdefer(2, 1, null, null, null, null, null, null, null, 1); $r3$.ɵɵdeferHydrateOnIdle(); $r3$.ɵɵdeferHydrateOnImmediate(); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options_template.js index bb87d8a76f41..00d4827c53ba 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_hydrate_on_viewport_with_options_template.js @@ -2,6 +2,7 @@ function MyApp_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵtext(0); $r3$.ɵɵdomTemplate(1, MyApp_Defer_1_Template, 1, 1); + $r3$.ɵɵenableIncrementalHydrationRuntime(); $r3$.ɵɵdefer(2, 1, null, null, null, null, null, null, null, 1); $r3$.ɵɵdeferHydrateOnViewport({rootMargin: "123px", threshold: 59}); $r3$.ɵɵdeferOnIdle(); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_idle_with_timeout.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_idle_with_timeout.ts new file mode 100644 index 000000000000..421143086e44 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_idle_with_timeout.ts @@ -0,0 +1,14 @@ +import {Component} from '@angular/core'; + +@Component({ + template: ` + @defer (on idle(500ms)) { + {{message}} + } @placeholder { +

    Placeholder

    + } + `, +}) +export class MyApp { + message = 'hello'; +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_idle_with_timeout_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_idle_with_timeout_template.js new file mode 100644 index 000000000000..8e1f65b34676 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_on_idle_with_timeout_template.js @@ -0,0 +1,8 @@ +function MyApp_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵdomTemplate(0, MyApp_Defer_0_Template, 1, 1)(1, MyApp_DeferPlaceholder_1_Template, 2, 0); + $r3$.ɵɵdefer(2, 0, null, null, 1); + $r3$.ɵɵdeferOnIdle(500); + } + … +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_duplicate_external_dep_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_duplicate_external_dep_template.js index 10e1bd05d957..ffdfd4b04d05 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_duplicate_external_dep_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_duplicate_external_dep_template.js @@ -1,7 +1,13 @@ -const MyApp_Defer_1_DepsFn = () => [import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep)]; +const MyApp_Defer_1_DepsFn = () => [ + /* @ts-ignore */ + import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep) +]; // NOTE: in linked tests there is one more loader here, because linked compilation doesn't have the ability to de-dupe identical functions. … -const MyApp_Defer_7_DepsFn = () => [import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep)]; +const MyApp_Defer_7_DepsFn = () => [ + /* @ts-ignore */ + import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep) +]; … @@ -28,7 +34,9 @@ $r3$.ɵɵdefineComponent({ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadataAsync(MyApp, () => [ + /* @ts-ignore */ import("./deferred_with_duplicate_external_dep_lazy").then(m => m.DuplicateLazyDep), + /* @ts-ignore */ import("./deferred_with_duplicate_external_dep_other").then(m => m.OtherLazyDep) ], (DuplicateLazyDep, OtherLazyDep) => { $r3$.ɵsetClassMetadata(MyApp, [{ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_external_deps_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_external_deps_template.js index 9da88cfba9fc..7d3cfe02a9d4 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_external_deps_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/deferred_with_external_deps_template.js @@ -2,7 +2,10 @@ import {EagerDep} from './deferred_with_external_deps_eager'; import {LoadingDep} from './deferred_with_external_deps_loading'; import * as $r3$ from "@angular/core"; -const $MyApp_Defer_4_DepsFn$ = () => [import("./deferred_with_external_deps_lazy").then(m => m.LazyDep)]; +const $MyApp_Defer_4_DepsFn$ = () => [ + /* @ts-ignore */ + import("./deferred_with_external_deps_lazy").then(m => m.LazyDep) +]; function MyApp_Defer_2_Template(rf, ctx) { if (rf & 1) { @@ -38,7 +41,10 @@ export class MyApp { … (() => { - (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadataAsync(MyApp, () => [import("./deferred_with_external_deps_lazy").then(m => m.LazyDep)], LazyDep => { + (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadataAsync(MyApp, () => [ + /* @ts-ignore */ + import("./deferred_with_external_deps_lazy").then(m => m.LazyDep) + ], LazyDep => { $r3$.ɵsetClassMetadata(MyApp, [{ type: Component, args: [{ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/GOLDEN_PARTIAL.js index 21664445f476..1f80e591e47f 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/GOLDEN_PARTIAL.js @@ -232,12 +232,13 @@ import * as i0 from "@angular/core"; class SomeDep { } class MyAlternateService { - static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); + constructor(dep) { } + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService, deps: [{ token: SomeDep }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyAlternateService, decorators: [{ type: Injectable - }] }); + }], ctorParameters: () => [{ type: SomeDep }] }); export class MyService { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, providedIn: 'root', useClass: MyAlternateService, deps: [{ token: SomeDep }] }); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/component_factory.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/component_factory.js index 2edae1bd725e..c8809b5a5eca 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/component_factory.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/component_factory.js @@ -1,16 +1,8 @@ export class MyComponent { // ... static ɵfac = function MyComponent_Factory(__ngFactoryType__) { - return new (__ngFactoryType__ || MyComponent)( - $r3$.ɵɵinjectAttribute('name'), - $r3$.ɵɵinjectAttribute(dynamicAttrName()), - $r3$.ɵɵdirectiveInject(MyService), - $r3$.ɵɵdirectiveInject(MyService, 1), - $r3$.ɵɵdirectiveInject(MyService, 2), - $r3$.ɵɵdirectiveInject(MyService, 4), - $r3$.ɵɵdirectiveInject(MyService, 8), - $r3$.ɵɵdirectiveInject(MyService, 10) - ); + /* @ts-ignore */ + return new (__ngFactoryType__ || MyComponent)($r3$.ɵɵinjectAttribute('name'), $r3$.ɵɵinjectAttribute(dynamicAttrName()), $r3$.ɵɵdirectiveInject(MyService), $r3$.ɵɵdirectiveInject(MyService, 1), $r3$.ɵɵdirectiveInject(MyService, 2), $r3$.ɵɵdirectiveInject(MyService, 4), $r3$.ɵɵdirectiveInject(MyService, 8), $r3$.ɵɵdirectiveInject(MyService, 10)); } // ... } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/ctor_overload_fac.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/ctor_overload_fac.js index 8bcdd4637e1a..693f7404ad9d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/ctor_overload_fac.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/ctor_overload_fac.js @@ -1,6 +1,7 @@ export class MyService { // ... static ɵfac = function MyService_Factory(__ngFactoryType__) { + /* @ts-ignore */ return new (__ngFactoryType__ || MyService)($r3$.ɵɵinject(MyDependency), $r3$.ɵɵinject(MyOptionalDependency, 8)); } // ... diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/injectable_factory_fac.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/injectable_factory_fac.js index ea23a036d056..c1c72bd535c8 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/injectable_factory_fac.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/injectable_factory_fac.js @@ -1,6 +1,7 @@ export class MyService { // ... static ɵfac = function MyService_Factory(__ngFactoryType__) { + /* @ts-ignore */ return new (__ngFactoryType__ || MyService)($r3$.ɵɵinject(MyDependency)); } // ... diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/pipe_and_injectable_pipe_first.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/pipe_and_injectable_pipe_first.js index 6e2f3d5ea7c5..16cbab19c369 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/pipe_and_injectable_pipe_first.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/pipe_and_injectable_pipe_first.js @@ -1,7 +1,10 @@ // NOTE The prov definition must be last so MyOtherPipe.fac is defined export class MyOtherPipe { // ... - static ɵfac = function MyOtherPipe_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MyOtherPipe)(i0.ɵɵdirectiveInject(Service, 16)); }; + static ɵfac = function MyOtherPipe_Factory(__ngFactoryType__) { + /* @ts-ignore */ + return new (__ngFactoryType__ || MyOtherPipe)(i0.ɵɵdirectiveInject(Service, 16)); + }; static ɵpipe = /*@__PURE__*/ i0.ɵɵdefinePipe({ name: "myOtherPipe", type: MyOtherPipe, pure: true, standalone: false }); static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyOtherPipe, factory: MyOtherPipe.ɵfac }); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/pipe_and_injectable_pipe_last.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/pipe_and_injectable_pipe_last.js index 77cbcb897403..077cb9632972 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/pipe_and_injectable_pipe_last.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/pipe_and_injectable_pipe_last.js @@ -1,7 +1,10 @@ // NOTE The prov definition must be last so MyPipe.fac is defined export class MyPipe { // ... - static ɵfac = function MyPipe_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MyPipe)(i0.ɵɵdirectiveInject(Service, 16)); }; + static ɵfac = function MyPipe_Factory(__ngFactoryType__) { + /* @ts-ignore */ + return new (__ngFactoryType__ || MyPipe)(i0.ɵɵdirectiveInject(Service, 16)); + }; static ɵpipe = /*@__PURE__*/ i0.ɵɵdefinePipe({ name: "myPipe", type: MyPipe, pure: true, standalone: false }); static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: MyPipe, factory: MyPipe.ɵfac }); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/providedin_forwardref.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/providedin_forwardref.js index 6c947dad4473..06452a168609 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/providedin_forwardref.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/providedin_forwardref.js @@ -1,6 +1,9 @@ export class Service { … - static ɵfac = function Service_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || Service)($i0$.ɵɵinject(Dep)); }; + static ɵfac = function Service_Factory(__ngFactoryType__) { + /* @ts-ignore */ + return new (__ngFactoryType__ || Service)($i0$.ɵɵinject(Dep)); + }; static ɵprov = /*@__PURE__*/ $i0$.ɵɵdefineInjectable({ token: Service, factory: Service.ɵfac, providedIn: $i0$.forwardRef(() => Mod) }); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/useclass_with_deps.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/useclass_with_deps.js index 6a88827dc274..aacd3814effc 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/useclass_with_deps.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/useclass_with_deps.js @@ -7,6 +7,7 @@ export class MyService { if (__ngFactoryType__) { __ngConditionalFactory__ = new __ngFactoryType__(); } else { + /* @ts-ignore */ __ngConditionalFactory__ = new MyAlternateService($r3$.ɵɵinject(SomeDep)); } return __ngConditionalFactory__; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/useclass_with_deps.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/useclass_with_deps.ts index 687bdf02470c..db28edb50082 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/useclass_with_deps.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/useclass_with_deps.ts @@ -4,6 +4,7 @@ class SomeDep {} @Injectable() class MyAlternateService { + constructor(dep: SomeDep) {} } @Injectable({providedIn: 'root', useClass: MyAlternateService, deps: [SomeDep]}) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.js index 532ec111cedb..1222e59fca36 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_di/di/usefactory_with_deps.js @@ -7,6 +7,7 @@ export class MyService { if (__ngFactoryType__) { __ngConditionalFactory__ = new __ngFactoryType__(); } else { + /* @ts-ignore */ __ngConditionalFactory__ = ((dep, optional) => new MyAlternateService(dep, optional))($r3$.ɵɵinject(SomeDep), $r3$.ɵɵinject(SomeDep, 8)); } return __ngConditionalFactory__; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/conditional_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/conditional_template.js index 09c1e9106968..19a792b4a08c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/conditional_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/conditional_template.js @@ -41,7 +41,7 @@ export class MyApp { /** * @suppress {msgDescriptions} */ - const $MSG_ID_WITH_SUFFIX$ = goog.getMsg(" Content: {$startBlockIf} before{$startTagSpan}zero{$closeTagSpan}after {$closeBlockIf}{$startBlockElseIf} before{$startTagDiv}one{$closeTagDiv}after {$closeBlockElseIf}{$startBlockElse} before{$startTagButton}otherwise{$closeTagButton}after {$closeBlockElse}! {$startBlockIf_1} before{$startTagSpan}seven{$closeTagSpan}after {$closeBlockIf}", { + const $MSG_ID_WITH_SUFFIX$ = /* @ts-ignore */ goog.getMsg(" Content: {$startBlockIf} before{$startTagSpan}zero{$closeTagSpan}after {$closeBlockIf}{$startBlockElseIf} before{$startTagDiv}one{$closeTagDiv}after {$closeBlockElseIf}{$startBlockElse} before{$startTagButton}otherwise{$closeTagButton}after {$closeBlockElse}! {$startBlockIf_1} before{$startTagSpan}seven{$closeTagSpan}after {$closeBlockIf}", { "closeBlockElse": "\uFFFD/*4:3\uFFFD", "closeBlockElseIf": "\uFFFD/*3:2\uFFFD", "closeBlockIf": "[\uFFFD/*2:1\uFFFD|\uFFFD/*5:4\uFFFD]", @@ -74,6 +74,7 @@ export class MyApp { }); i18n_0 = $MSG_ID_WITH_SUFFIX$; } else { + /* @ts-ignore */ i18n_0 = $localize` Content: ${"\uFFFD*2:1\uFFFD"}:START_BLOCK_IF: before${"[\uFFFD#1:1\uFFFD|\uFFFD#1:4\uFFFD]"}:START_TAG_SPAN:zero${"[\uFFFD/#1:1\uFFFD|\uFFFD/#1:4\uFFFD]"}:CLOSE_TAG_SPAN:after ${"[\uFFFD/*2:1\uFFFD|\uFFFD/*5:4\uFFFD]"}:CLOSE_BLOCK_IF:${"\uFFFD*3:2\uFFFD"}:START_BLOCK_ELSE_IF: before${"\uFFFD#1:2\uFFFD"}:START_TAG_DIV:one${"\uFFFD/#1:2\uFFFD"}:CLOSE_TAG_DIV:after ${"\uFFFD/*3:2\uFFFD"}:CLOSE_BLOCK_ELSE_IF:${"\uFFFD*4:3\uFFFD"}:START_BLOCK_ELSE: before${"\uFFFD#1:3\uFFFD"}:START_TAG_BUTTON:otherwise${"\uFFFD/#1:3\uFFFD"}:CLOSE_TAG_BUTTON:after ${"\uFFFD/*4:3\uFFFD"}:CLOSE_BLOCK_ELSE:! ${"\uFFFD*5:4\uFFFD"}:START_BLOCK_IF_1: before${"[\uFFFD#1:1\uFFFD|\uFFFD#1:4\uFFFD]"}:START_TAG_SPAN:seven${"[\uFFFD/#1:1\uFFFD|\uFFFD/#1:4\uFFFD]"}:CLOSE_TAG_SPAN:after ${"[\uFFFD/*2:1\uFFFD|\uFFFD/*5:4\uFFFD]"}:CLOSE_BLOCK_IF:`; } i18n_0 = $r3$.ɵɵi18nPostprocess(i18n_0); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/defer_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/defer_template.js index 877c7882427c..a047e2c2be2f 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/defer_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/defer_template.js @@ -42,7 +42,7 @@ export class MyApp { /** * @suppress {msgDescriptions} */ - const $MSG_ID_WITH_SUFFIX$ = goog.getMsg(" Content: {$startBlockDefer} before{$startTagSpan}middle{$closeTagSpan}after {$closeBlockDefer}{$startBlockPlaceholder} before{$startTagDiv}placeholder{$closeTagDiv}after {$closeBlockPlaceholder}{$startBlockLoading} before{$startTagButton}loading{$closeTagButton}after {$closeBlockLoading}{$startBlockError} before{$startHeadingLevel1}error{$closeHeadingLevel1}after {$closeBlockError}", { + const $MSG_ID_WITH_SUFFIX$ = /* @ts-ignore */ goog.getMsg(" Content: {$startBlockDefer} before{$startTagSpan}middle{$closeTagSpan}after {$closeBlockDefer}{$startBlockPlaceholder} before{$startTagDiv}placeholder{$closeTagDiv}after {$closeBlockPlaceholder}{$startBlockLoading} before{$startTagButton}loading{$closeTagButton}after {$closeBlockLoading}{$startBlockError} before{$startHeadingLevel1}error{$closeHeadingLevel1}after {$closeBlockError}", { "closeBlockDefer": "\uFFFD/*2:1\uFFFD", "closeBlockError": "\uFFFD/*5:4\uFFFD", "closeBlockLoading": "\uFFFD/*3:2\uFFFD", @@ -81,6 +81,7 @@ export class MyApp { }); i18n_0 = $MSG_ID_WITH_SUFFIX$; } else { + /* @ts-ignore */ i18n_0 = $localize ` Content: ${"\uFFFD*2:1\uFFFD"}:START_BLOCK_DEFER: before${"\uFFFD#1:1\uFFFD"}:START_TAG_SPAN:middle${"\uFFFD/#1:1\uFFFD"}:CLOSE_TAG_SPAN:after ${"\uFFFD/*2:1\uFFFD"}:CLOSE_BLOCK_DEFER:${"\uFFFD*4:3\uFFFD"}:START_BLOCK_PLACEHOLDER: before${"\uFFFD#1:3\uFFFD"}:START_TAG_DIV:placeholder${"\uFFFD/#1:3\uFFFD"}:CLOSE_TAG_DIV:after ${"\uFFFD/*4:3\uFFFD"}:CLOSE_BLOCK_PLACEHOLDER:${"\uFFFD*3:2\uFFFD"}:START_BLOCK_LOADING: before${"\uFFFD#1:2\uFFFD"}:START_TAG_BUTTON:loading${"\uFFFD/#1:2\uFFFD"}:CLOSE_TAG_BUTTON:after ${"\uFFFD/*3:2\uFFFD"}:CLOSE_BLOCK_LOADING:${"\uFFFD*5:4\uFFFD"}:START_BLOCK_ERROR: before${"\uFFFD#1:4\uFFFD"}:START_HEADING_LEVEL1:error${"\uFFFD/#1:4\uFFFD"}:CLOSE_HEADING_LEVEL1:after ${"\uFFFD/*5:4\uFFFD"}:CLOSE_BLOCK_ERROR:`; } return [i18n_0]; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/for_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/for_template.js index 780430fdb91f..78935f74baea 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/for_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/for_template.js @@ -25,7 +25,7 @@ export class MyApp { /** * @suppress {msgDescriptions} */ - const $MSG_ID_WITH_SUFFIX$ = goog.getMsg(" Content: {$startBlockFor} before{$startTagSpan}middle{$closeTagSpan}after {$closeBlockFor}{$startBlockEmpty} before{$startTagDiv}empty{$closeTagDiv}after {$closeBlockEmpty}! ", { + const $MSG_ID_WITH_SUFFIX$ = /* @ts-ignore */ goog.getMsg(" Content: {$startBlockFor} before{$startTagSpan}middle{$closeTagSpan}after {$closeBlockFor}{$startBlockEmpty} before{$startTagDiv}empty{$closeTagDiv}after {$closeBlockEmpty}! ", { "closeBlockEmpty": "\uFFFD/*4:2\uFFFD", "closeBlockFor": "\uFFFD/*3:1\uFFFD", "closeTagDiv": "\uFFFD/#1:2\uFFFD", @@ -48,6 +48,7 @@ export class MyApp { }); i18n_0 = $MSG_ID_WITH_SUFFIX$; } else { + /* @ts-ignore */ i18n_0 = $localize` Content: ${"\uFFFD*3:1\uFFFD"}:START_BLOCK_FOR: before${"\uFFFD#1:1\uFFFD"}:START_TAG_SPAN:middle${"\uFFFD/#1:1\uFFFD"}:CLOSE_TAG_SPAN:after ${"\uFFFD/*3:1\uFFFD"}:CLOSE_BLOCK_FOR:${"\uFFFD*4:2\uFFFD"}:START_BLOCK_EMPTY: before${"\uFFFD#1:2\uFFFD"}:START_TAG_DIV:empty${"\uFFFD/#1:2\uFFFD"}:CLOSE_TAG_DIV:after ${"\uFFFD/*4:2\uFFFD"}:CLOSE_BLOCK_EMPTY:! `; } return [i18n_0]; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/switch_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/switch_template.js index 1de149232202..44b3dd58303d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/switch_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/blocks/switch_template.js @@ -33,7 +33,7 @@ export class MyApp { /** * @suppress {msgDescriptions} */ - const $MSG_ID_WITH_SUFFIX$ = goog.getMsg(" Content: {$startBlockCase}before{$startTagSpan}zero{$closeTagSpan}after{$closeBlockCase}{$startBlockCase_1}before{$startTagDiv}one{$closeTagDiv}after{$closeBlockCase}{$startBlockDefault}before{$startTagButton}otherwise{$closeTagButton}after{$closeBlockDefault}", { + const $MSG_ID_WITH_SUFFIX$ = /* @ts-ignore */ goog.getMsg(" Content: {$startBlockCase}before{$startTagSpan}zero{$closeTagSpan}after{$closeBlockCase}{$startBlockCase_1}before{$startTagDiv}one{$closeTagDiv}after{$closeBlockCase}{$startBlockDefault}before{$startTagButton}otherwise{$closeTagButton}after{$closeBlockDefault}", { "closeBlockCase": "[\uFFFD/*2:1\uFFFD|\uFFFD/*3:2\uFFFD]", "closeBlockDefault": "\uFFFD/*4:3\uFFFD", "closeTagButton": "\uFFFD/#1:3\uFFFD", @@ -62,6 +62,7 @@ export class MyApp { }); i18n_0 = $MSG_ID_WITH_SUFFIX$; } else { + /* @ts-ignore */ i18n_0 = $localize` Content: ${"\uFFFD*2:1\uFFFD"}:START_BLOCK_CASE:before${"\uFFFD#1:1\uFFFD"}:START_TAG_SPAN:zero${"\uFFFD/#1:1\uFFFD"}:CLOSE_TAG_SPAN:after${"[\uFFFD/*2:1\uFFFD|\uFFFD/*3:2\uFFFD]"}:CLOSE_BLOCK_CASE:${"\uFFFD*3:2\uFFFD"}:START_BLOCK_CASE_1:before${"\uFFFD#1:2\uFFFD"}:START_TAG_DIV:one${"\uFFFD/#1:2\uFFFD"}:CLOSE_TAG_DIV:after${"[\uFFFD/*2:1\uFFFD|\uFFFD/*3:2\uFFFD]"}:CLOSE_BLOCK_CASE:${"\uFFFD*4:3\uFFFD"}:START_BLOCK_DEFAULT:before${"\uFFFD#1:3\uFFFD"}:START_TAG_BUTTON:otherwise${"\uFFFD/#1:3\uFFFD"}:CLOSE_TAG_BUTTON:after${"\uFFFD/*4:3\uFFFD"}:CLOSE_BLOCK_DEFAULT:`; } i18n_0 = $r3$.ɵɵi18nPostprocess(i18n_0); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/GOLDEN_PARTIAL.js index 48a3157abdee..e68283ed4dc6 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/GOLDEN_PARTIAL.js @@ -676,6 +676,53 @@ export declare class MyModule { static ɵinj: i0.ɵɵInjectorDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: interpolation_complex_expressions_use_null.js + ****************************************************************************************************/ +import { Component, NgModule } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyComponent { + valueA; + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: false, selector: "my-component", ngImport: i0, template: ` +
    + `, isInline: true }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{ + type: Component, + args: [{ + selector: 'my-component', + template: ` +
    + `, + standalone: false + }] + }] }); +export class MyModule { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); + static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] }); + static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{ + type: NgModule, + args: [{ declarations: [MyComponent] }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: interpolation_complex_expressions_use_null.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyComponent { + valueA: any; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} +export declare class MyModule { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵinj: i0.ɵɵInjectorDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: i18n_root_node.js ****************************************************************************************************/ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/TEST_CASES.json index 81edf7df0f02..85c0a316ac59 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/TEST_CASES.json @@ -3,15 +3,10 @@ "cases": [ { "description": "should add the meaning and description as JsDoc comments and metadata blocks", - "inputFiles": [ - "meaning_description.ts" - ], + "inputFiles": ["meaning_description.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ], + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], "files": [ { "expected": "meaning_description_template.js", @@ -23,43 +18,28 @@ }, { "description": "should support i18n attributes on explicit elements", - "inputFiles": [ - "ng-template_basic.ts" - ], + "inputFiles": ["ng-template_basic.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ] + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"] } ] }, { "description": "should support i18n attributes on explicit with structural directives", - "inputFiles": [ - "ng-template_structural.ts" - ], + "inputFiles": ["ng-template_structural.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ] + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"] } ] }, { "description": "should support i18n attributes with interpolations on explicit elements", - "inputFiles": [ - "ng-template_interpolation.ts" - ], + "inputFiles": ["ng-template_interpolation.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ], + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], "files": [ { "expected": "ng-template_interpolation_template.js", @@ -71,15 +51,10 @@ }, { "description": "should support i18n attributes with interpolations on explicit elements with structural directives", - "inputFiles": [ - "ng-template_interpolation_structural.ts" - ], + "inputFiles": ["ng-template_interpolation_structural.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ], + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], "files": [ { "expected": "ng-template_interpolation_structural_template.js", @@ -91,57 +66,37 @@ }, { "description": "should not create translations for empty attributes", - "inputFiles": [ - "empty_attributes.ts" - ], + "inputFiles": ["empty_attributes.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ] + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"] } ] }, { "description": "should not create translations for bound attributes", - "inputFiles": [ - "bound_attributes.ts" - ], + "inputFiles": ["bound_attributes.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ] + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"] } ] }, { "description": "should translate static attributes", - "inputFiles": [ - "static_attributes.ts" - ], + "inputFiles": ["static_attributes.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ] + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"] } ] }, { "description": "should translate static attributes when used on an element with structural directive", - "inputFiles": [ - "static_attributes_structural.ts" - ], + "inputFiles": ["static_attributes_structural.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ], + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], "files": [ { "expected": "static_attributes_structural_template.js", @@ -153,15 +108,10 @@ }, { "description": "should support interpolation", - "inputFiles": [ - "interpolation_basic.ts" - ], + "inputFiles": ["interpolation_basic.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ], + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], "files": [ { "expected": "interpolation_basic_template.js", @@ -173,15 +123,10 @@ }, { "description": "should correctly bind to context in nested template", - "inputFiles": [ - "interpolation_nested_context.ts" - ], + "inputFiles": ["interpolation_nested_context.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ], + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], "files": [ { "expected": "interpolation_nested_context_template.js", @@ -193,15 +138,10 @@ }, { "description": "should support complex expressions in interpolation", - "inputFiles": [ - "interpolation_complex_expressions.ts" - ], + "inputFiles": ["interpolation_complex_expressions.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ], + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], "files": [ { "expected": "interpolation_complex_expressions_template.js", @@ -212,16 +152,30 @@ ] }, { - "description": "should work correctly when placed on i18n root node", - "inputFiles": [ - "i18n_root_node.ts" + "description": "should support complex expressions in interpolation with legacyOptionalChaining", + "inputFiles": ["interpolation_complex_expressions_use_null.ts"], + "compilationModeFilter": ["full compile", "instruction compile"], + "expectations": [ + { + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], + "files": [ + { + "expected": "interpolation_complex_expressions_template_use_null.js", + "generated": "interpolation_complex_expressions_use_null.js" + } + ] + } ], + "angularCompilerOptions": { + "legacyOptionalChaining": true + } + }, + { + "description": "should work correctly when placed on i18n root node", + "inputFiles": ["i18n_root_node.ts"], "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ], + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"], "files": [ { "expected": "i18n_root_node_template.js", @@ -233,18 +187,13 @@ }, { "description": "should sanitize ids and generate proper variable names", - "inputFiles": [ - "invalid_i18n_meta.ts" - ], + "inputFiles": ["invalid_i18n_meta.ts"], "angularCompilerOptions": { "i18nUseExternalIds": false }, "expectations": [ { - "extraChecks": [ - "verifyPlaceholdersIntegrity", - "verifyUniqueConsts" - ] + "extraChecks": ["verifyPlaceholdersIntegrity", "verifyUniqueConsts"] } ] } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_template.js index 20dca2cea363..5e4c573d2635 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_template.js @@ -14,8 +14,7 @@ template: function MyComponent_Template(rf, ctx) { $r3$.ɵɵelementEnd(); } if (rf & 2) { - let $tmp_0_0$; - $r3$.ɵɵi18nExp(($tmp_0_0$ = ctx.valueA.getRawValue()) == null ? null : $tmp_0_0$.getTitle()); - $r3$.ɵɵi18nApply(1); + i0.ɵɵi18nExp(ctx.valueA.getRawValue()?.getTitle()); + i0.ɵɵi18nApply(1); } } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_template_use_null.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_template_use_null.js new file mode 100644 index 000000000000..20dca2cea363 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_template_use_null.js @@ -0,0 +1,21 @@ +decls: 2, +vars: 1, +consts: () => { + __i18nMsg__('{$interpolation} title', [['interpolation', String.raw`\uFFFD0\uFFFD`]], {original_code: {'interpolation': '{{valueA.getRawValue()?.getTitle()}}'}}, {}) + return [ + ["title", $i18n_0$], + [__AttributeMarker.I18n__, "title"] + ]; +}, +template: function MyComponent_Template(rf, ctx) { + if (rf & 1) { + $r3$.ɵɵelementStart(0, "div", 1); + $r3$.ɵɵi18nAttributes(1, 0); + $r3$.ɵɵelementEnd(); + } + if (rf & 2) { + let $tmp_0_0$; + $r3$.ɵɵi18nExp(($tmp_0_0$ = ctx.valueA.getRawValue()) == null ? null : $tmp_0_0$.getTitle()); + $r3$.ɵɵi18nApply(1); + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_use_null.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_use_null.ts new file mode 100644 index 000000000000..0ec4551d8a7c --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/interpolation_complex_expressions_use_null.ts @@ -0,0 +1,16 @@ +import {Component, NgModule} from '@angular/core'; + +@Component({ + selector: 'my-component', + template: ` +
    + `, + standalone: false +}) +export class MyComponent { + valueA!: any; +} + +@NgModule({declarations: [MyComponent]}) +export class MyModule { +} diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/invalid_i18n_meta.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/invalid_i18n_meta.js index 8a9a5db2c27c..3bf281f448c5 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/invalid_i18n_meta.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/invalid_i18n_meta.js @@ -4,9 +4,10 @@ if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_1$ = goog.getMsg("Element title"); + const $MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_1$ = /* @ts-ignore */ goog.getMsg("Element title"); $I18N_0$ = $MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_1$; } else { + /* @ts-ignore */ $I18N_0$ = $localize`:@@ID.WITH.INVALID.CHARS:Element title`; } @@ -17,8 +18,9 @@ if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_ID_WITH_INVALID_CHARS_2$$APP_SPEC_TS_4$ = goog.getMsg(" Some content "); + const $MSG_EXTERNAL_ID_WITH_INVALID_CHARS_2$$APP_SPEC_TS_4$ = /* @ts-ignore */ goog.getMsg(" Some content "); $I18N_2$ = $MSG_EXTERNAL_ID_WITH_INVALID_CHARS_2$$APP_SPEC_TS_4$; } else { + /* @ts-ignore */ $I18N_2$ = $localize`:@@ID.WITH.INVALID.CHARS.2: Some content `; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/meaning_description_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/meaning_description_template.js index e83ba1c87571..596e11504097 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/meaning_description_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/element_attributes/meaning_description_template.js @@ -12,9 +12,10 @@ consts: /** * @desc [BACKUP_${MESSAGE}_ID:idH]`desc */ - const $MSG_EXTERNAL_idG$$APP_SPEC_TS_24$ = goog.getMsg("Title G"); + const $MSG_EXTERNAL_idG$$APP_SPEC_TS_24$ = /* @ts-ignore */ goog.getMsg("Title G"); $i18n_23$ = $MSG_EXTERNAL_idG$$APP_SPEC_TS_24$; } else { + /* @ts-ignore */ $i18n_23$ = $localize`:[BACKUP_$\{MESSAGE}_ID\:idH]\`desc@@idG:Title G`; } @@ -26,9 +27,10 @@ consts: /** * @desc Some text \' [BACKUP_MESSAGE_ID: xxx] */ - const $MSG_EXTERNAL_idG$$APP_SPEC_TS_21$ = goog.getMsg("Content H"); + const $MSG_EXTERNAL_idG$$APP_SPEC_TS_21$ = /* @ts-ignore */ goog.getMsg("Content H"); $i18n_7$ = $MSG_EXTERNAL_idG$$APP_SPEC_TS_21$; } else { + /* @ts-ignore */ $i18n_7$ = $localize`:Some text \\' [BACKUP_MESSAGE_ID\: xxx]:Content H`; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/attribute_interpolation.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/attribute_interpolation.js index c755cd012fc4..76c1662f6448 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/attribute_interpolation.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/attribute_interpolation.js @@ -4,9 +4,10 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_6301050568345677976__ATTRIBUTE_INTERPOLATION_TS_2$ = goog.getMsg("{VAR_SELECT, select, other {{START_TAG_SPAN}foo{CLOSE_TAG_SPAN}}}"); + const $MSG_EXTERNAL_6301050568345677976__ATTRIBUTE_INTERPOLATION_TS_2$ = /* @ts-ignore */ goog.getMsg("{VAR_SELECT, select, other {{START_TAG_SPAN}foo{CLOSE_TAG_SPAN}}}"); $i18n_1$ = $MSG_EXTERNAL_6301050568345677976__ATTRIBUTE_INTERPOLATION_TS_2$; } else { + /* @ts-ignore */ $i18n_1$ = $localize`{VAR_SELECT, select, other {{START_TAG_SPAN}foo{CLOSE_TAG_SPAN}}}`; } $i18n_1$ = $r3$.ɵɵi18nPostprocess($i18n_1$, { @@ -19,9 +20,10 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_369205108016154659__ATTRIBUTE_INTERPOLATION_TS_4$ = goog.getMsg("{VAR_SELECT, select, other {{INTERPOLATION}-{INTERPOLATION}}}"); + const $MSG_EXTERNAL_369205108016154659__ATTRIBUTE_INTERPOLATION_TS_4$ = /* @ts-ignore */ goog.getMsg("{VAR_SELECT, select, other {{INTERPOLATION}-{INTERPOLATION}}}"); $i18n_3$ = $MSG_EXTERNAL_369205108016154659__ATTRIBUTE_INTERPOLATION_TS_4$; } else { + /* @ts-ignore */ $i18n_3$ = $localize`{VAR_SELECT, select, other {{INTERPOLATION}-{INTERPOLATION}}}`; } $i18n_3$ = $r3$.ɵɵi18nPostprocess($i18n_3$, { @@ -33,7 +35,7 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_6009429127580785009__ATTRIBUTE_INTERPOLATION_TS_5$ = goog.getMsg( + const $MSG_EXTERNAL_6009429127580785009__ATTRIBUTE_INTERPOLATION_TS_5$ = /* @ts-ignore */ goog.getMsg( "{$startTagSpan}{$closeTagSpan}{$startTagSpan_1}{$icu}{$closeTagSpan}{$startTagSpan_1}{$icu_1}{$closeTagSpan}", { "closeTagSpan": "[\uFFFD/#2\uFFFD|\uFFFD/#3\uFFFD|\uFFFD/#4\uFFFD]", @@ -52,6 +54,7 @@ consts: () => { }); $i18n_0$ = $MSG_EXTERNAL_6009429127580785009__ATTRIBUTE_INTERPOLATION_TS_5$; } else { + /* @ts-ignore */ $i18n_0$ = $localize`${"\uFFFD#2\uFFFD"}:START_TAG_SPAN:${"[\uFFFD/#2\uFFFD|\uFFFD/#3\uFFFD|\uFFFD/#4\uFFFD]"}:CLOSE_TAG_SPAN:${"[\uFFFD#3\uFFFD|\uFFFD#4\uFFFD]"}:START_TAG_SPAN_1:${$i18n_1$}:ICU@@6051755734147382484:${"[\uFFFD/#2\uFFFD|\uFFFD/#3\uFFFD|\uFFFD/#4\uFFFD]"}:CLOSE_TAG_SPAN:${"[\uFFFD#3\uFFFD|\uFFFD#4\uFFFD]"}:START_TAG_SPAN_1:${$i18n_3$}:ICU_1@@7593934392904803263:${"[\uFFFD/#2\uFFFD|\uFFFD/#3\uFFFD|\uFFFD/#4\uFFFD]"}:CLOSE_TAG_SPAN:`; } $i18n_0$ = $r3$.ɵɵi18nPostprocess($i18n_0$); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/escape_quotes.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/escape_quotes.js index 2ee89dde2b96..b80bfa75aead 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/escape_quotes.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/escape_quotes.js @@ -3,12 +3,13 @@ if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_4166854826696768832$$APP_SPEC_TS_0$ = goog.getMsg("{VAR_SELECT, select, single {'single quotes'} double {\"double quotes\"} other {other}}"); + const $MSG_EXTERNAL_4166854826696768832$$APP_SPEC_TS_0$ = /* @ts-ignore */ goog.getMsg("{VAR_SELECT, select, single {'single quotes'} double {\"double quotes\"} other {other}}"); $I18N_0$ = $MSG_EXTERNAL_4166854826696768832$$APP_SPEC_TS_0$; } else { + /* @ts-ignore */ $I18N_0$ = $localize `{VAR_SELECT, select, single {'single quotes'} double {"double quotes"} other {other}}`; } $I18N_0$ = $r3$.ɵɵi18nPostprocess($I18N_0$, { "VAR_SELECT": "\uFFFD0\uFFFD" -}); \ No newline at end of file +}); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/root_icu_with_elements.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/root_icu_with_elements.js index c2790e592226..8b8a15625a6d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/root_icu_with_elements.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/root_icu_with_elements.js @@ -4,7 +4,7 @@ consts: () => { /** * @desc someText1 */ - const $MSG_EXTERNAL_4505060179465988919ICU_WHITESPACE_TS_1$ = goog.getMsg("{VAR_SELECT, select, WEBSITE {{START_TAG_STRONG}someText{CLOSE_TAG_STRONG}\n }}"); + const $MSG_EXTERNAL_4505060179465988919ICU_WHITESPACE_TS_1$ = /* @ts-ignore */ goog.getMsg("{VAR_SELECT, select, WEBSITE {{START_TAG_STRONG}someText{CLOSE_TAG_STRONG}\n }}"); i18n_0 = $MSG_EXTERNAL_4505060179465988919ICU_WHITESPACE_TS_1$; } else { … @@ -14,7 +14,7 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_4505060179465988919ICU_WHITESPACE_TS_3$ = goog.getMsg("{VAR_SELECT, select, WEBSITE {{START_TAG_STRONG}someText{CLOSE_TAG_STRONG}\n }}"); + const $MSG_EXTERNAL_4505060179465988919ICU_WHITESPACE_TS_3$ = /* @ts-ignore */ goog.getMsg("{VAR_SELECT, select, WEBSITE {{START_TAG_STRONG}someText{CLOSE_TAG_STRONG}\n }}"); i18n_1 = $MSG_EXTERNAL_4505060179465988919ICU_WHITESPACE_TS_3$; } else { … diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/shared_placeholder.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/shared_placeholder.js index 4cea015bd35c..f50ae693f7ba 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/shared_placeholder.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/icu_logic/shared_placeholder.js @@ -21,10 +21,11 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_APP_SPEC_TS_1$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); + const $MSG_APP_SPEC_TS_1$ = /* @ts-ignore */ goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_1$ = $MSG_APP_SPEC_TS_1$; } else { + /* @ts-ignore */ $I18N_1$ = $localize `{VAR_SELECT, select, male {male} female {female} other {other}}`; } $I18N_1$ = $r3$.ɵɵi18nPostprocess($I18N_1$, { @@ -36,10 +37,11 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_APP_SPEC_TS_2$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); + const $MSG_APP_SPEC_TS_2$ = /* @ts-ignore */ goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_2$ = $MSG_APP_SPEC_TS_2$; } else { + /* @ts-ignore */ $I18N_2$ = $localize `{VAR_SELECT, select, male {male} female {female} other {other}}`; } $I18N_2$ = $r3$.ɵɵi18nPostprocess($I18N_2$, { @@ -51,10 +53,11 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_APP_SPEC_TS__4$ = goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); + const $MSG_APP_SPEC_TS__4$ = /* @ts-ignore */ goog.getMsg("{VAR_SELECT, select, male {male} female {female} other {other}}"); $I18N_4$ = $MSG_APP_SPEC_TS__4$; } else { + /* @ts-ignore */ $I18N_4$ = $localize `{VAR_SELECT, select, male {male} female {female} other {other}}`; } $I18N_4$ = $r3$.ɵɵi18nPostprocess($I18N_4$, { @@ -66,7 +69,7 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_APP_SPEC_TS_0$ = goog.getMsg(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", { + const $MSG_APP_SPEC_TS_0$ = /* @ts-ignore */ goog.getMsg(" {$icu} {$startTagDiv} {$icu} {$closeTagDiv}{$startTagDiv_1} {$icu} {$closeTagDiv}", { "closeTagDiv": "[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]", "icu": "\uFFFDI18N_EXP_ICU\uFFFD", "startTagDiv": "\uFFFD#2\uFFFD", @@ -82,6 +85,7 @@ consts: () => { $I18N_0$ = $MSG_APP_SPEC_TS_0$; } else { + /* @ts-ignore */ $I18N_0$ = $localize ` ${"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU@@7670372064920373295: ${"\uFFFD#2\uFFFD"}:START_TAG_DIV: ${"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU@@7670372064920373295: ${"[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]"}:CLOSE_TAG_DIV:${"\uFFFD*3:1\uFFFD\uFFFD#1:1\uFFFD"}:START_TAG_DIV_1: ${"\uFFFDI18N_EXP_ICU\uFFFD"}:ICU@@7670372064920373295: ${"[\uFFFD/#2\uFFFD|\uFFFD/#1:1\uFFFD\uFFFD/*3:1\uFFFD]"}:CLOSE_TAG_DIV:`; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/localize_legacy_message_ids/legacy_disabled.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/localize_legacy_message_ids/legacy_disabled.js index 5cd67b37f498..bfb0faaab47d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/localize_legacy_message_ids/legacy_disabled.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/localize_legacy_message_ids/legacy_disabled.js @@ -2,5 +2,6 @@ let $I18N_0$; if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { … } else { + /* @ts-ignore */ $I18N_0$ = $localize`Some Message`; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/localize_legacy_message_ids/legacy_enabled.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/localize_legacy_message_ids/legacy_enabled.js index fb51d031f489..6cbf4fa9b9e6 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/localize_legacy_message_ids/legacy_enabled.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/localize_legacy_message_ids/legacy_enabled.js @@ -4,10 +4,11 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_2908931752694090721$$LEGACY_ENABLED_TS_1$ = goog.getMsg( + const $MSG_EXTERNAL_2908931752694090721$$LEGACY_ENABLED_TS_1$ = /* @ts-ignore */ goog.getMsg( "Some & attribute"); $i18n_0$ = $MSG_EXTERNAL_2908931752694090721$$LEGACY_ENABLED_TS_1$; } else { + /* @ts-ignore */ $i18n_0$ = $localize`:␟82ec661067f503a3357ecc159b2128325e9208cd␟2908931752694090721:Some & attribute`; } let $i18n_2$; @@ -15,10 +16,11 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_2720535395337591908$$LEGACY_ENABLED_TS_13$ = goog.getMsg( + const $MSG_EXTERNAL_2720535395337591908$$LEGACY_ENABLED_TS_13$ = /* @ts-ignore */ goog.getMsg( "\""); $i18n_2$ = $MSG_EXTERNAL_2720535395337591908$$LEGACY_ENABLED_TS_13$; } else { + /* @ts-ignore */ $i18n_2$ = $localize`:␟5b30d888e99e7c6cfc6265f89c39b5921805cd2e␟2720535395337591908:"`; } let $i18n_4$; @@ -26,10 +28,11 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_3600934704948217447$$LEGACY_ENABLED_TS_15$ = goog.getMsg( + const $MSG_EXTERNAL_3600934704948217447$$LEGACY_ENABLED_TS_15$ = /* @ts-ignore */ goog.getMsg( "\"\""); $i18n_4$ = $MSG_EXTERNAL_3600934704948217447$$LEGACY_ENABLED_TS_15$; } else { + /* @ts-ignore */ $i18n_4$ = $localize`:␟bdfdbea9161864191756930161fd41b8bc980fde␟3600934704948217447:""`; } let $i18n_6$; @@ -37,11 +40,12 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_2334195497629636162$$LEGACY_ENABLED_TS_5$ = goog.getMsg( + const $MSG_EXTERNAL_2334195497629636162$$LEGACY_ENABLED_TS_5$ = /* @ts-ignore */ goog.getMsg( "Some & {$interpolation} attribute", {"interpolation": "\uFFFD0\uFFFD"}, {original_code: {"interpolation": "{{'interpolated'}}"}}); $i18n_6$ = $MSG_EXTERNAL_2334195497629636162$$LEGACY_ENABLED_TS_5$; } else { + /* @ts-ignore */ $i18n_6$ = $localize`:␟57ebd20267116c04cc1dbd7be0b73bf56484f45d␟2334195497629636162:Some & ${"\uFFFD0\uFFFD"}:INTERPOLATION: attribute`; } let $i18n_8$; @@ -49,10 +53,11 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_4700340487900776701$$LEGACY_ENABLED_TS_3$ = goog.getMsg( + const $MSG_EXTERNAL_4700340487900776701$$LEGACY_ENABLED_TS_3$ = /* @ts-ignore */ goog.getMsg( "Some & message"); $i18n_8$ = $MSG_EXTERNAL_4700340487900776701$$LEGACY_ENABLED_TS_3$; } else { + /* @ts-ignore */ $i18n_8$ = $localize`:␟10adaf0ad7b8ba40200cd3c0e7c8d0f13280d522␟4700340487900776701:Some & message`; } let $i18n_10$; @@ -60,11 +65,12 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_3204054277547499090$$LEGACY_ENABLED_TS_7$ = goog.getMsg( + const $MSG_EXTERNAL_3204054277547499090$$LEGACY_ENABLED_TS_7$ = /* @ts-ignore */ goog.getMsg( "Some & {$interpolation} message", {"interpolation": "\uFFFD0\uFFFD"}, {original_code: {"interpolation": "{{'interpolated' }}"}}); $i18n_10$ = $MSG_EXTERNAL_3204054277547499090$$LEGACY_ENABLED_TS_7$; } else { + /* @ts-ignore */ $i18n_10$ = $localize`:␟28d558ca32556f1da67a333e3dada321a97212cd␟3204054277547499090:Some & ${"\uFFFD0\uFFFD"}:INTERPOLATION: message`; } let $i18n_12$; @@ -72,10 +78,11 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_2406634758623728945$$LEGACY_ENABLED_TS_9$ = goog.getMsg( + const $MSG_EXTERNAL_2406634758623728945$$LEGACY_ENABLED_TS_9$ = /* @ts-ignore */ goog.getMsg( "&"); $i18n_12$ = $MSG_EXTERNAL_2406634758623728945$$LEGACY_ENABLED_TS_9$; } else { + /* @ts-ignore */ $i18n_12$ = $localize`:␟0b3dff7b9382b6217ac97c99f9b04df04381bfdd␟2406634758623728945:&`; } let $i18n_14$; @@ -83,14 +90,15 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_4156372478368653226$$LEGACY_ENABLED_TS_11$ = goog.getMsg( + const $MSG_EXTERNAL_4156372478368653226$$LEGACY_ENABLED_TS_11$ = /* @ts-ignore */ goog.getMsg( "&\""); $i18n_14$ = $MSG_EXTERNAL_4156372478368653226$$LEGACY_ENABLED_TS_11$; } else { + /* @ts-ignore */ $i18n_14$ = $localize`:␟25b7cbf210e59a931423097cb7f2e1b72991a687␟4156372478368653226:&"`; } return [$i18n_8$, $i18n_10$, $i18n_12$, $i18n_14$, ["title", $i18n_6$], ["title", $i18n_0$], [6, "title"], ["title", $i18n_2$], ["title", $i18n_4$]]; -}, +}, template: function MyComponent_Template(rf, ctx) { if (rf & 1) { i0.ɵɵelement(0, "div", 5); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/namespaces/foreign_object.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/namespaces/foreign_object.js index 4f4ea3a0eadb..02cc7c69d344 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/namespaces/foreign_object.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/namespaces/foreign_object.js @@ -5,7 +5,7 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_7128002169381370313$$APP_SPEC_TS_1$ = goog.getMsg("{$startTagXhtmlDiv} Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}{$closeTagXhtmlDiv}", { + const $MSG_EXTERNAL_7128002169381370313$$APP_SPEC_TS_1$ = /* @ts-ignore */ goog.getMsg("{$startTagXhtmlDiv} Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}{$closeTagXhtmlDiv}", { "closeTagXhtmlDiv": "\uFFFD/#3\uFFFD", "closeTagXhtmlSpan": "\uFFFD/#4\uFFFD", "startTagXhtmlDiv": "\uFFFD#3\uFFFD", @@ -21,6 +21,7 @@ consts: () => { $I18N_0$ = $MSG_EXTERNAL_7128002169381370313$$APP_SPEC_TS_1$; } else { + /* @ts-ignore */ $I18N_0$ = $localize `${"\uFFFD#3\uFFFD"}:START_TAG__XHTML_DIV: Count: ${"\uFFFD#4\uFFFD"}:START_TAG__XHTML_SPAN:5${"\uFFFD/#4\uFFFD"}:CLOSE_TAG__XHTML_SPAN:${"\uFFFD/#3\uFFFD"}:CLOSE_TAG__XHTML_DIV:`; } return [ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/namespaces/namespaced_div.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/namespaces/namespaced_div.js index 9e31e57a0687..c9b1347646bc 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/namespaces/namespaced_div.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/namespaces/namespaced_div.js @@ -4,7 +4,7 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$ = goog.getMsg(" Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}", { + const $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$ = /* @ts-ignore */ goog.getMsg(" Count: {$startTagXhtmlSpan}5{$closeTagXhtmlSpan}", { "closeTagXhtmlSpan": "\uFFFD/#4\uFFFD", "startTagXhtmlSpan": "\uFFFD#4\uFFFD" }, { @@ -16,6 +16,7 @@ consts: () => { $I18N_0$ = $MSG_EXTERNAL_7428861019045796010$$APP_SPEC_TS_1$; } else { + /* @ts-ignore */ $I18N_0$ = $localize ` Count: ${"\uFFFD#4\uFFFD"}:START_TAG__XHTML_SPAN:5${"\uFFFD/#4\uFFFD"}:CLOSE_TAG__XHTML_SPAN:`; } return [ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/backtick_quotes.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/backtick_quotes.js index 49d1375fab75..0390969e511d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/backtick_quotes.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/backtick_quotes.js @@ -4,9 +4,10 @@ if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { /** * @suppress {msgDescriptions} */ - const $MSG_APP_SPEC_TS_1$ = goog.getMsg("`{$interpolation}`", { "interpolation": "\uFFFD0\uFFFD" }, { original_code: { "interpolation": "{{ count }}" } }); + const $MSG_APP_SPEC_TS_1$ = /* @ts-ignore */ goog.getMsg("`{$interpolation}`", { "interpolation": "\uFFFD0\uFFFD" }, { original_code: { "interpolation": "{{ count }}" } }); $I18N_0$ = $MSG_APP_SPEC_TS_1$; } else { + /* @ts-ignore */ $I18N_0$ = $localize `\`${"\uFFFD0\uFFFD"}:INTERPOLATION:\``; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/escape_quotes.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/escape_quotes.js index a9144938a1e0..b09a39f27c34 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/escape_quotes.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/escape_quotes.js @@ -4,9 +4,10 @@ if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_4924931801512133405$$APP_SPEC_TS_0$ = goog.getMsg("Some text 'with single quotes', \"with double quotes\", `with backticks` and without quotes."); + const $MSG_EXTERNAL_4924931801512133405$$APP_SPEC_TS_0$ = /* @ts-ignore */ goog.getMsg("Some text 'with single quotes', \"with double quotes\", `with backticks` and without quotes."); $I18N_0$ = $MSG_EXTERNAL_4924931801512133405$$APP_SPEC_TS_0$; } else { + /* @ts-ignore */ $I18N_0$ = $localize `Some text 'with single quotes', "with double quotes", \`with backticks\` and without quotes.`; } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/interpolation_complex_expressions.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/interpolation_complex_expressions.js index ee4b2d4c3aad..a58f5d070c73 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/interpolation_complex_expressions.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/interpolation_complex_expressions.js @@ -12,11 +12,8 @@ template: function MyComponent_Template(rf, ctx) { $r3$.ɵɵelementEnd(); } if (rf & 2) { - let $tmp_2_0$; - $r3$.ɵɵadvance(2); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(2, 3, ctx.valueA)) - (ctx.valueA == null ? null : ctx.valueA.a == null ? null : ctx.valueA.a.b) - (($tmp_2_0$ = ctx.valueA.getRawValue()) == null ? null : $tmp_2_0$.getTitle()); - $r3$.ɵɵi18nApply(1); + i0.ɵɵadvance(2); + i0.ɵɵi18nExp(i0.ɵɵpipeBind1(2, 3, ctx.valueA))(ctx.valueA?.a?.b)(ctx.valueA.getRawValue()?.getTitle()); + i0.ɵɵi18nApply(1); } } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/named_interpolations.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/named_interpolations.js index 241b27ae828e..bb0134a3420b 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/named_interpolations.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/nested_nodes/named_interpolations.js @@ -7,7 +7,7 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_7597881511811528589$$APP_SPEC_TS_0$ = goog.getMsg(" Named interpolation: {$phA} Named interpolation with spaces: {$phB} ", { + const $MSG_EXTERNAL_7597881511811528589$$APP_SPEC_TS_0$ = /* @ts-ignore */ goog.getMsg(" Named interpolation: {$phA} Named interpolation with spaces: {$phB} ", { "phB": "\uFFFD1\uFFFD", "phA": "\uFFFD0\uFFFD" }, { @@ -19,6 +19,7 @@ consts: () => { $I18N_0$ = $MSG_EXTERNAL_7597881511811528589$$APP_SPEC_TS_0$; } else { + /* @ts-ignore */ $I18N_0$ = $localize ` Named interpolation: ${"\uFFFD0\uFFFD"}:PH_A: Named interpolation with spaces: ${"\uFFFD1\uFFFD"}:PH_B: `; } return [ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/whitespace_preserving_mode/preserve_inner_content.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/whitespace_preserving_mode/preserve_inner_content.js index 20a5e764aef7..661446911d5c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/whitespace_preserving_mode/preserve_inner_content.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_i18n/whitespace_preserving_mode/preserve_inner_content.js @@ -5,7 +5,7 @@ consts: () => { /** * @suppress {msgDescriptions} */ - const $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$ = goog.getMsg("\n Some text\n {$startTagSpan}Text inside span{$closeTagSpan}\n ", { + const $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$ = /* @ts-ignore */ goog.getMsg("\n Some text\n {$startTagSpan}Text inside span{$closeTagSpan}\n ", { "closeTagSpan": "\uFFFD/#3\uFFFD", "startTagSpan": "\uFFFD#3\uFFFD" }, { @@ -17,6 +17,7 @@ consts: () => { $I18N_0$ = $MSG_EXTERNAL_963542717423364282$$APP_SPEC_TS_0$; } else { + /* @ts-ignore */ $I18N_0$ = $localize ` Some text ${"\uFFFD#3\uFFFD"}:START_TAG_SPAN:Text inside span${"\uFFFD/#3\uFFFD"}:CLOSE_TAG_SPAN: diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_inside_i18n_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_inside_i18n_template.js index 16f58d8091a9..468fd670c1de 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_inside_i18n_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_child_view_inside_i18n_template.js @@ -22,7 +22,7 @@ $r3$.ɵɵdefineComponent({ /** * @suppress {msgDescriptions} */ - const $MSG_ID_WITH_SUFFIX$ = goog.getMsg("{$startTagNgTemplate}The result is {$interpolation}{$closeTagNgTemplate}", { + const $MSG_ID_WITH_SUFFIX$ = /* @ts-ignore */ goog.getMsg("{$startTagNgTemplate}The result is {$interpolation}{$closeTagNgTemplate}", { "closeTagNgTemplate": "\uFFFD/*3:1\uFFFD", "interpolation": "\uFFFD0:1\uFFFD", "startTagNgTemplate": "\uFFFD*3:1\uFFFD" @@ -35,6 +35,7 @@ $r3$.ɵɵdefineComponent({ }); $i18n_0$ = $MSG_ID_WITH_SUFFIX$; } else { + /* @ts-ignore */ $i18n_0$ = $localize `${"\uFFFD*3:1\uFFFD"}:START_TAG_NG_TEMPLATE:The result is ${"\uFFFD0:1\uFFFD"}:INTERPOLATION:${"\uFFFD/*3:1\uFFFD"}:CLOSE_TAG_NG_TEMPLATE:`; } return [$i18n_0$]; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_i18n_and_child_view_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_i18n_and_child_view_template.js index 98e0418cc7a5..2a2e11f579c5 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_i18n_and_child_view_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_i18n_and_child_view_template.js @@ -22,7 +22,7 @@ $r3$.ɵɵdefineComponent({ /** * @suppress {msgDescriptions} */ - const $MSG_ID_WITH_SUFFIX$ = goog.getMsg(" The result is {$interpolation} {$startTagNgTemplate}To repeat, the result is {$interpolation}{$closeTagNgTemplate}", { + const $MSG_ID_WITH_SUFFIX$ = /* @ts-ignore */ goog.getMsg(" The result is {$interpolation} {$startTagNgTemplate}To repeat, the result is {$interpolation}{$closeTagNgTemplate}", { "closeTagNgTemplate": "\uFFFD/*3:1\uFFFD", "interpolation": "[\uFFFD0\uFFFD|\uFFFD0:1\uFFFD]", "startTagNgTemplate": "\uFFFD*3:1\uFFFD" @@ -35,6 +35,7 @@ $r3$.ɵɵdefineComponent({ }); $i18n_0$ = $MSG_ID_WITH_SUFFIX$; } else { + /* @ts-ignore */ $i18n_0$ = $localize ` The result is ${"[\uFFFD0\uFFFD|\uFFFD0:1\uFFFD]"}:INTERPOLATION: ${"\uFFFD*3:1\uFFFD"}:START_TAG_NG_TEMPLATE:To repeat, the result is ${"[\uFFFD0\uFFFD|\uFFFD0:1\uFFFD]"}:INTERPOLATION:${"\uFFFD/*3:1\uFFFD"}:CLOSE_TAG_NG_TEMPLATE:`; } $i18n_0$ = $r3$.ɵɵi18nPostprocess($i18n_0$); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_i18n_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_i18n_template.js index 000760f8bc14..63a5395586e5 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_i18n_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_in_i18n_template.js @@ -8,13 +8,14 @@ $r3$.ɵɵdefineComponent({ /** * @suppress {msgDescriptions} */ - const $MSG_ID_WITH_SUFFIX$ = goog.getMsg(" The result is {$interpolation} ", { + const $MSG_ID_WITH_SUFFIX$ = /* @ts-ignore */ goog.getMsg(" The result is {$interpolation} ", { "interpolation": "\uFFFD0\uFFFD" }, { original_code: { "interpolation": "{{result}}" } }); $i18n_0$ = $MSG_ID_WITH_SUFFIX$; } else { + /* @ts-ignore */ $i18n_0$ = $localize ` The result is ${"\uFFFD0\uFFFD"}:INTERPOLATION: `; } return [$i18n_0$]; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_preceded_by_i18n_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_preceded_by_i18n_template.js index 8d33e33bf122..92bb743bbc6a 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_preceded_by_i18n_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_let/let_preceded_by_i18n_template.js @@ -21,13 +21,14 @@ $r3$.ɵɵdefineComponent({ /** * @suppress {msgDescriptions} */ - const $MSG_ID_WITH_SUFFIX$ = goog.getMsg("Hello {$interpolation}", { + const $MSG_ID_WITH_SUFFIX$ = /* @ts-ignore */ goog.getMsg("Hello {$interpolation}", { "interpolation": "\uFFFD0\uFFFD" }, { original_code: { "interpolation": "{{value}}" } }); $i18n_0$ = $MSG_ID_WITH_SUFFIX$; } else { + /* @ts-ignore */ $i18n_0$ = $localize `Hello ${"\uFFFD0\uFFFD"}:INTERPOLATION:`; } return [$i18n_0$]; diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/GOLDEN_PARTIAL.js index f5fe565b683f..eee2977ca3c5 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/GOLDEN_PARTIAL.js @@ -108,8 +108,8 @@ export class MyComponent { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: false, selector: "my-component", ngImport: i0, template: `
    -
    - +
    +
    `, isInline: true }); } @@ -119,8 +119,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE selector: 'my-component', template: `
    -
    - +
    +
    `, standalone: false @@ -622,7 +622,7 @@ export class MyComponent { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: false, selector: "my-component", ngImport: i0, template: ` - + `, isInline: true }); } @@ -632,7 +632,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE selector: 'my-component', template: ` - + `, standalone: false @@ -720,7 +720,7 @@ import * as i0 from "@angular/core"; export class TestCmp { name = ''; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, deps: [], target: i0.ɵɵFactoryTarget.Component }); - static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: TestCmp, isStandalone: false, selector: "test-cmp", ngImport: i0, template: 'Name: ', isInline: true, dependencies: [{ kind: "directive", type: i0.forwardRef(() => NgModelDirective), selector: "[ngModel]", inputs: ["ngModel"], outputs: ["ngModelChanges"] }] }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: TestCmp, isStandalone: false, selector: "test-cmp", ngImport: i0, template: 'Name: ', isInline: true, dependencies: [{ kind: "directive", type: i0.forwardRef(() => NgModelDirective), selector: "[ngModel]", inputs: ["ngModel"], outputs: ["ngModelChange"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, decorators: [{ type: Component, @@ -732,9 +732,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE }] }); export class NgModelDirective { ngModel = ''; - ngModelChanges = new EventEmitter(); + ngModelChange = new EventEmitter(); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: NgModelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); - static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: NgModelDirective, isStandalone: false, selector: "[ngModel]", inputs: { ngModel: "ngModel" }, outputs: { ngModelChanges: "ngModelChanges" }, ngImport: i0 }); + static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: NgModelDirective, isStandalone: false, selector: "[ngModel]", inputs: { ngModel: "ngModel" }, outputs: { ngModelChange: "ngModelChange" }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: NgModelDirective, decorators: [{ type: Directive, @@ -744,7 +744,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE }] }], propDecorators: { ngModel: [{ type: Input - }], ngModelChanges: [{ + }], ngModelChange: [{ type: Output }] } }); export class AppModule { @@ -769,9 +769,9 @@ export declare class TestCmp { } export declare class NgModelDirective { ngModel: string; - ngModelChanges: EventEmitter; + ngModelChange: EventEmitter; static ɵfac: i0.ɵɵFactoryDeclaration; - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; } export declare class AppModule { static ɵfac: i0.ɵɵFactoryDeclaration; @@ -787,7 +787,7 @@ import * as i0 from "@angular/core"; export class TestCmp { name = ''; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, deps: [], target: i0.ɵɵFactoryTarget.Component }); - static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: TestCmp, isStandalone: false, selector: "test-cmp", ngImport: i0, template: 'Name: ', isInline: true, dependencies: [{ kind: "directive", type: i0.forwardRef(() => NgModelDirective), selector: "[ngModel]", inputs: ["ngModel"], outputs: ["ngModelChanges"] }] }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: TestCmp, isStandalone: false, selector: "test-cmp", ngImport: i0, template: 'Name: ', isInline: true, dependencies: [{ kind: "directive", type: i0.forwardRef(() => NgModelDirective), selector: "[ngModel]", inputs: ["ngModel"], outputs: ["ngModelChange"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestCmp, decorators: [{ type: Component, @@ -799,9 +799,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE }] }); export class NgModelDirective { ngModel = ''; - ngModelChanges = new EventEmitter(); + ngModelChange = new EventEmitter(); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: NgModelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); - static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: NgModelDirective, isStandalone: false, selector: "[ngModel]", inputs: { ngModel: "ngModel" }, outputs: { ngModelChanges: "ngModelChanges" }, ngImport: i0 }); + static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: NgModelDirective, isStandalone: false, selector: "[ngModel]", inputs: { ngModel: "ngModel" }, outputs: { ngModelChange: "ngModelChange" }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: NgModelDirective, decorators: [{ type: Directive, @@ -811,7 +811,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE }] }], propDecorators: { ngModel: [{ type: Input - }], ngModelChanges: [{ + }], ngModelChange: [{ type: Output }] } }); export class AppModule { @@ -836,9 +836,9 @@ export declare class TestCmp { } export declare class NgModelDirective { ngModel: string; - ngModelChanges: EventEmitter; + ngModelChange: EventEmitter; static ɵfac: i0.ɵɵFactoryDeclaration; - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration; } export declare class AppModule { static ɵfac: i0.ɵɵFactoryDeclaration; @@ -853,17 +853,17 @@ import { Component } from '@angular/core'; import * as i0 from "@angular/core"; export class MyComponent { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); - static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "my-component", host: { listeners: { "click": "$event.preventDefault(); $event.target.blur()" } }, ngImport: i0, template: ` -
    + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "my-component", host: { listeners: { "click": "$event.preventDefault(); $event.target" } }, ngImport: i0, template: ` +
    `, isInline: true }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{ type: Component, args: [{ selector: 'my-component', - host: { '(click)': '$event.preventDefault(); $event.target.blur()' }, + host: { '(click)': '$event.preventDefault(); $event.target' }, template: ` -
    +
    ` }] }] }); @@ -880,15 +880,15 @@ export declare class MyComponent { /**************************************************************************************************** * PARTIAL FILE: mixed_one_way_two_way_listener_order.js ****************************************************************************************************/ -import { Component, Directive, Input, Output } from '@angular/core'; +import { Component, Directive, EventEmitter, Input, Output } from '@angular/core'; import * as i0 from "@angular/core"; export class Dir { - a; - aChange; - b; - c; - cChange; - d; + a = ''; + aChange = new EventEmitter(); + b = new EventEmitter(); + c = ''; + cChange = new EventEmitter(); + d = new EventEmitter(); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Dir, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: Dir, isStandalone: true, selector: "[dir]", inputs: { a: "a", c: "c" }, outputs: { aChange: "aChange", b: "b", cChange: "cChange", d: "d" }, ngImport: i0 }); } @@ -929,14 +929,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE /**************************************************************************************************** * PARTIAL FILE: mixed_one_way_two_way_listener_order.d.ts ****************************************************************************************************/ +import { EventEmitter } from '@angular/core'; import * as i0 from "@angular/core"; export declare class Dir { - a: unknown; - aChange: unknown; - b: unknown; - c: unknown; - cChange: unknown; - d: unknown; + a: string; + aChange: EventEmitter; + b: EventEmitter; + c: string; + cChange: EventEmitter; + d: EventEmitter; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵdir: i0.ɵɵDirectiveDeclaration; } @@ -953,7 +954,8 @@ export declare class App { import { Component, Directive, model, signal } from '@angular/core'; import * as i0 from "@angular/core"; export class NgModelDirective { - ngModel = model.required(...(ngDevMode ? [{ debugName: "ngModel" }] : [])); + ngModel = model.required(/* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "ngModel" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: NgModelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "0.0.0-PLACEHOLDER", type: NgModelDirective, isStandalone: true, selector: "[ngModel]", inputs: { ngModel: { classPropertyName: "ngModel", publicName: "ngModel", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { ngModel: "ngModelChange" }, ngImport: i0 }); } @@ -1005,7 +1007,8 @@ export declare class TestCmp { import { Component, Directive, model } from '@angular/core'; import * as i0 from "@angular/core"; export class NgModelDirective { - ngModel = model('', ...(ngDevMode ? [{ debugName: "ngModel" }] : [])); + ngModel = model('', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "ngModel" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: NgModelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "0.0.0-PLACEHOLDER", type: NgModelDirective, isStandalone: true, selector: "[ngModel]", inputs: { ngModel: { classPropertyName: "ngModel", publicName: "ngModel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { ngModel: "ngModelChange" }, ngImport: i0 }); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/implicit_receiver_keyed_write_inside_template.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/implicit_receiver_keyed_write_inside_template.ts index 9aa1649dac54..6b493208f70b 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/implicit_receiver_keyed_write_inside_template.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/implicit_receiver_keyed_write_inside_template.ts @@ -1,10 +1,10 @@ -import {Component, NgModule} from '@angular/core'; +import { Component, NgModule } from '@angular/core'; @Component({ selector: 'my-component', template: ` - + `, standalone: false diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/mixed_one_way_two_way_listener_order.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/mixed_one_way_two_way_listener_order.ts index 6be355bb8ce8..e845dd13672c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/mixed_one_way_two_way_listener_order.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/mixed_one_way_two_way_listener_order.ts @@ -1,16 +1,16 @@ -import {Component, Directive, Input, Output} from '@angular/core'; +import { Component, Directive, EventEmitter, Input, Output } from '@angular/core'; @Directive({selector: '[dir]'}) export class Dir { - @Input() a: unknown; - @Output() aChange: unknown; + @Input() a: string = ''; + @Output() aChange = new EventEmitter(); - @Output() b: unknown; + @Output() b = new EventEmitter(); - @Input() c: unknown; - @Output() cChange: unknown; + @Input() c: string = ''; + @Output() cChange = new EventEmitter(); - @Output() d: unknown; + @Output() d = new EventEmitter(); } @Component({ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/multiple_statements.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/multiple_statements.js index 0d00fc64187d..58a3d6c1bf5d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/multiple_statements.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/multiple_statements.js @@ -1,13 +1,13 @@ hostBindings: function MyComponent_HostBindings(rf, ctx) { if (rf & 1) { - $r3$.ɵɵlistener("click", function MyComponent_click_HostBindingHandler($event) { $event.preventDefault(); return $event.target.blur(); }); + $r3$.ɵɵlistener("click", function MyComponent_click_HostBindingHandler($event) { $event.preventDefault(); return $event.target; }); } } … template: function MyComponent_Template(rf, ctx) { if (rf & 1) { $r3$.ɵɵdomElementStart(0, "div", 0); - $r3$.ɵɵdomListener("click", function MyComponent_Template_div_click_0_listener($event) { $event.preventDefault(); return $event.target.blur(); }); + $r3$.ɵɵdomListener("click", function MyComponent_Template_div_click_0_listener($event) { $event.preventDefault(); return $event.target; }); $r3$.ɵɵdomElementEnd(); } } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/multiple_statements.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/multiple_statements.ts index c19d27e3990f..db78a6d7f232 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/multiple_statements.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/multiple_statements.ts @@ -1,10 +1,10 @@ -import {Component} from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'my-component', - host: {'(click)': '$event.preventDefault(); $event.target.blur()'}, + host: {'(click)': '$event.preventDefault(); $event.target'}, template: ` -
    +
    ` }) export class MyComponent { diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/nested_two_way.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/nested_two_way.ts index beda41c54d81..3848e4eb603c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/nested_two_way.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/nested_two_way.ts @@ -1,4 +1,4 @@ -import {Component, Directive, EventEmitter, Input, NgModule, Output} from '@angular/core'; +import { Component, Directive, EventEmitter, Input, NgModule, Output } from '@angular/core'; @Component({ selector: 'test-cmp', @@ -15,7 +15,7 @@ export class TestCmp { }) export class NgModelDirective { @Input() ngModel: string = ''; - @Output() ngModelChanges: EventEmitter = new EventEmitter(); + @Output() ngModelChange: EventEmitter = new EventEmitter(); } @NgModule({declarations: [TestCmp, NgModelDirective]}) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/nested_two_way_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/nested_two_way_template.js index 1566be1ea290..d61182c6906d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/nested_two_way_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/nested_two_way_template.js @@ -9,8 +9,10 @@ function TestCmp_ng_template_1_Template(rf, ctx) { return $r3$.ɵɵresetView($event); }); $r3$.ɵɵelementEnd(); + $r3$.ɵɵcontrolCreate(); } if (rf & 2) { const $ctx_r0$ = $r3$.ɵɵnextContext(); $r3$.ɵɵtwoWayProperty("ngModel", $ctx_r0$.name); + $r3$.ɵɵcontrol(); } } \ No newline at end of file diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/shared_snapshot_listeners.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/shared_snapshot_listeners.ts index 870d70776ac8..05849e95a41d 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/shared_snapshot_listeners.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/shared_snapshot_listeners.ts @@ -1,11 +1,11 @@ -import {Component, NgModule} from '@angular/core'; +import { Component, NgModule } from '@angular/core'; @Component({ selector: 'my-component', template: `
    -
    - +
    +
    `, standalone: false diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/shared_snapshot_listeners_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/shared_snapshot_listeners_template.js index e762e6abbd3a..db56ee07eb13 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/shared_snapshot_listeners_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/shared_snapshot_listeners_template.js @@ -5,14 +5,14 @@ function MyComponent_div_0_Template(rf, ctx) { $r3$.ɵɵlistener("click", function MyComponent_div_0_Template_div_click_1_listener() { $r3$.ɵɵrestoreView($s$); const $comp$ = $r3$.ɵɵnextContext(); - return $i0$.ɵɵresetView($comp$.onClick($comp$.foo)); + return $i0$.ɵɵresetView($comp$.onClick(1)); }); $r3$.ɵɵelementEnd(); $r3$.ɵɵelementStart(2, "button", 1); $r3$.ɵɵlistener("click", function MyComponent_div_0_Template_button_click_2_listener() { $r3$.ɵɵrestoreView($s$); const $comp2$ = $r3$.ɵɵnextContext(); - return $i0$.ɵɵresetView($comp2$.onClick2($comp2$.bar)); + return $i0$.ɵɵresetView($comp2$.onClick2(2)); }); $r3$.ɵɵelementEnd()(); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/simple_two_way.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/simple_two_way.ts index 0247b2bec3cf..71ee5b2154e3 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/simple_two_way.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/simple_two_way.ts @@ -1,4 +1,4 @@ -import {Component, Directive, EventEmitter, Input, NgModule, Output} from '@angular/core'; +import { Component, Directive, EventEmitter, Input, NgModule, Output } from '@angular/core'; @Component({ selector: 'test-cmp', @@ -15,7 +15,7 @@ export class TestCmp { }) export class NgModelDirective { @Input() ngModel: string = ''; - @Output() ngModelChanges: EventEmitter = new EventEmitter(); + @Output() ngModelChange: EventEmitter = new EventEmitter(); } @NgModule({declarations: [TestCmp, NgModelDirective]}) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/simple_two_way_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/simple_two_way_template.js index 6b4ecd0b4f33..52b0d3f69466 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/simple_two_way_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/simple_two_way_template.js @@ -7,8 +7,10 @@ function TestCmp_Template(rf, ctx) { return $event; }); $r3$.ɵɵelementEnd(); + $r3$.ɵɵcontrolCreate(); } if (rf & 2) { $r3$.ɵɵadvance(); $r3$.ɵɵtwoWayProperty("ngModel", ctx.name); + $r3$.ɵɵcontrol(); } } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/two_way_binding_to_signal_loop_variable_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/two_way_binding_to_signal_loop_variable_template.js index 27844f0b4825..44f9199f958f 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/two_way_binding_to_signal_loop_variable_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/two_way_binding_to_signal_loop_variable_template.js @@ -8,10 +8,12 @@ function TestCmp_For_1_Template(rf, ctx) { return $r3$.ɵɵresetView($event); }); $r3$.ɵɵelementEnd(); + $r3$.ɵɵcontrolCreate(); } if (rf & 2) { const $name_r2$ = ctx.$implicit; $r3$.ɵɵtwoWayProperty("ngModel", $name_r2$); + $r3$.ɵɵcontrol(); } } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/two_way_to_any_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/two_way_to_any_template.js index 5f560f31d553..e57ec47f83ee 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/two_way_to_any_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_listener/two_way_to_any_template.js @@ -6,8 +6,10 @@ function TestCmp_Template(rf, ctx) { return $event; }); $r3$.ɵɵelementEnd(); + $r3$.ɵɵcontrolCreate(); } if (rf & 2) { $r3$.ɵɵtwoWayProperty("ngModel", ctx.value); + $r3$.ɵɵcontrol(); } } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js index bfdc9627560e..9f9dbd1f2e52 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/GOLDEN_PARTIAL.js @@ -308,7 +308,7 @@ import * as i0 from "@angular/core"; export class MyComponent { expr = true; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); - static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "ng-component", host: { properties: { "class.text-primary/80": "expr", "class.data-active:text-green-300/80": "expr", "class.data-[size='large'": "expr" } }, ngImport: i0, template: ``, isInline: true }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "ng-component", host: { properties: { "class.text-primary/80": "expr", "class.data-active:text-green-300/80": "expr", "class.data-[size='large']:p-8": "expr" } }, ngImport: i0, template: ``, isInline: true }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{ type: Component, diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js index def9a9d5a7a8..a683e16b8905 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_styling/class_bindings/host_class_binding_special_chars.js @@ -3,7 +3,7 @@ $r3$.ɵɵdefineComponent({ hostVars: 6, hostBindings: function MyComponent_HostBindings(rf, ctx) { if (rf & 2) { - $r3$.ɵɵclassProp("text-primary/80", ctx.expr)("data-active:text-green-300/80", ctx.expr)("data-[size='large'", ctx.expr); + $r3$.ɵɵclassProp("text-primary/80", ctx.expr)("data-active:text-green-300/80", ctx.expr)("data-[size='large']:p-8", ctx.expr); } }, … diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/GOLDEN_PARTIAL.js index 9b268bd6f947..25b70ac8e29b 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/GOLDEN_PARTIAL.js @@ -873,6 +873,51 @@ export declare class MyModule { static ɵinj: i0.ɵɵInjectorDeclaration; } +/**************************************************************************************************** + * PARTIAL FILE: nested_ternary_operation_use_null.js + ****************************************************************************************************/ +import { Component, NgModule } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyComponent { + a; + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); + static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: false, selector: "my-component", ngImport: i0, template: ` + {{a?.b ? 1 : 2 }}`, isInline: true }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{ + type: Component, + args: [{ + selector: 'my-component', + template: ` + {{a?.b ? 1 : 2 }}`, + standalone: false + }] + }] }); +export class MyModule { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); + static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] }); + static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, decorators: [{ + type: NgModule, + args: [{ declarations: [MyComponent] }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: nested_ternary_operation_use_null.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyComponent { + a: any; + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; +} +export declare class MyModule { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵinj: i0.ɵɵInjectorDeclaration; +} + /**************************************************************************************************** * PARTIAL FILE: shorthand_property_declaration.js ****************************************************************************************************/ diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/TEST_CASES.json index 943c53a6bf53..6c12573e896c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/TEST_CASES.json +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/TEST_CASES.json @@ -3,9 +3,7 @@ "cases": [ { "description": "should correctly bind to context in nested template", - "inputFiles": [ - "nested_template_context.ts" - ], + "inputFiles": ["nested_template_context.ts"], "expectations": [ { "files": [ @@ -20,9 +18,7 @@ }, { "description": "should correctly bind to context in nested template with many bindings", - "inputFiles": [ - "nested_template_context_many_bindings.ts" - ], + "inputFiles": ["nested_template_context_many_bindings.ts"], "expectations": [ { "files": [ @@ -37,9 +33,7 @@ }, { "description": "should correctly bind to implicit receiver in template", - "inputFiles": [ - "implicit_receiver.ts" - ], + "inputFiles": ["implicit_receiver.ts"], "expectations": [ { "files": [ @@ -54,9 +48,7 @@ }, { "description": "should support ngFor context variables", - "inputFiles": [ - "ng_for_context_variables.ts" - ], + "inputFiles": ["ng_for_context_variables.ts"], "expectations": [ { "files": [ @@ -71,9 +63,7 @@ }, { "description": "should support ngFor context variables in parent views", - "inputFiles": [ - "ng_for_parent_context_variables.ts" - ], + "inputFiles": ["ng_for_parent_context_variables.ts"], "expectations": [ { "files": [ @@ -88,9 +78,7 @@ }, { "description": "should correctly skip contexts as needed", - "inputFiles": [ - "template_context_skip.ts" - ], + "inputFiles": ["template_context_skip.ts"], "expectations": [ { "files": [ @@ -105,9 +93,7 @@ }, { "description": "should support ", - "inputFiles": [ - "ng_template.ts" - ], + "inputFiles": ["ng_template.ts"], "expectations": [ { "files": [ @@ -122,9 +108,7 @@ }, { "description": "should support local refs on ", - "inputFiles": [ - "ng_template_local_ref.ts" - ], + "inputFiles": ["ng_template_local_ref.ts"], "expectations": [ { "files": [ @@ -139,9 +123,7 @@ }, { "description": "should support directive outputs on ", - "inputFiles": [ - "ng_template_output.ts" - ], + "inputFiles": ["ng_template_output.ts"], "expectations": [ { "files": [ @@ -156,9 +138,7 @@ }, { "description": "should allow directive inputs as an interpolated prop on ", - "inputFiles": [ - "ng_template_interpolated_prop.ts" - ], + "inputFiles": ["ng_template_interpolated_prop.ts"], "expectations": [ { "files": [ @@ -173,9 +153,7 @@ }, { "description": "should allow directive inputs as an interpolated prop on (with structural directives)", - "inputFiles": [ - "ng_template_interpolated_prop_with_structural_directive.ts" - ], + "inputFiles": ["ng_template_interpolated_prop_with_structural_directive.ts"], "expectations": [ { "files": [ @@ -199,60 +177,34 @@ }, { "description": "should create unique template function names even for similar nested template structures", - "inputFiles": [ - "unique_template_function_names.ts" - ], + "inputFiles": ["unique_template_function_names.ts"], "expectations": [ { - "extraChecks": [ - [ - "verifyUniqueFunctions", - "Template", - 16 - ] - ] + "extraChecks": [["verifyUniqueFunctions", "Template", 16]] } ] }, { "description": "should create unique template function names for ng-content templates", - "inputFiles": [ - "unique_template_function_names_ng_content.ts" - ], + "inputFiles": ["unique_template_function_names_ng_content.ts"], "expectations": [ { - "extraChecks": [ - [ - "verifyUniqueFunctions", - "Template", - 4 - ] - ] + "extraChecks": [["verifyUniqueFunctions", "Template", 4]] } ] }, { "description": "should create unique listener function names even for similar nested template structures", - "inputFiles": [ - "unique_listener_function_names.ts" - ], + "inputFiles": ["unique_listener_function_names.ts"], "expectations": [ { - "extraChecks": [ - [ - "verifyUniqueFunctions", - "listener", - 3 - ] - ] + "extraChecks": [["verifyUniqueFunctions", "listener", 3]] } ] }, { "description": "should support pipes in template bindings", - "inputFiles": [ - "template_binding_pipe.ts" - ], + "inputFiles": ["template_binding_pipe.ts"], "expectations": [ { "files": [ @@ -267,9 +219,7 @@ }, { "description": "should safely nest ternary operations", - "inputFiles": [ - "nested_ternary_operation.ts" - ], + "inputFiles": ["nested_ternary_operation.ts"], "expectations": [ { "files": [ @@ -283,10 +233,27 @@ ] }, { - "description": "should handle shorthand property declarations in templates", - "inputFiles": [ - "shorthand_property_declaration.ts" + "description": "should safely nest ternary operations with legacyOptionalChaining", + "inputFiles": ["nested_ternary_operation_use_null.ts"], + "compilationModeFilter": ["full compile", "instruction compile"], + "expectations": [ + { + "files": [ + { + "expected": "nested_ternary_operation_template_use_null.js", + "generated": "nested_ternary_operation_use_null.js" + } + ], + "failureMessage": "Incorrect template" + } ], + "angularCompilerOptions": { + "legacyOptionalChaining": true + } + }, + { + "description": "should handle shorthand property declarations in templates", + "inputFiles": ["shorthand_property_declaration.ts"], "expectations": [ { "files": [ @@ -301,9 +268,7 @@ }, { "description": "should handle a bindings on plain ng-template elements", - "inputFiles": [ - "ng_template_bindings.ts" - ], + "inputFiles": ["ng_template_bindings.ts"], "expectations": [ { "failureMessage": "Incorrect template" @@ -312,9 +277,7 @@ }, { "description": "should allow self-closing custom elements in templates", - "inputFiles": [ - "self_closing_tags.ts" - ], + "inputFiles": ["self_closing_tags.ts"], "expectations": [ { "files": [ @@ -329,9 +292,7 @@ }, { "description": "should not confuse self-closing tag for an end tag", - "inputFiles": [ - "self_closing_tags_nested.ts" - ], + "inputFiles": ["self_closing_tags_nested.ts"], "expectations": [ { "files": [ @@ -346,9 +307,7 @@ }, { "description": "should handle structural directive on ng-template", - "inputFiles": [ - "template_with_structural_directive.ts" - ], + "inputFiles": ["template_with_structural_directive.ts"], "expectations": [ { "failureMessage": "Incorrect template" @@ -357,9 +316,7 @@ }, { "description": "should handle self-closing structural directives", - "inputFiles": [ - "self_closing_structural_directives.ts" - ], + "inputFiles": ["self_closing_structural_directives.ts"], "expectations": [ { "failureMessage": "Incorrect template" @@ -368,9 +325,7 @@ }, { "description": "should handle ngFor context variables when used in bindings", - "inputFiles": [ - "ng_for_context_in_attr_binding.ts" - ], + "inputFiles": ["ng_for_context_in_attr_binding.ts"], "expectations": [ { "failureMessage": "Incorrect template" @@ -379,9 +334,7 @@ }, { "description": "should handle attribute bindings inside an ng-template", - "inputFiles": [ - "attr_binding_on_structural_inside_ng_template.ts" - ], + "inputFiles": ["attr_binding_on_structural_inside_ng_template.ts"], "expectations": [ { "failureMessage": "Incorrect template" @@ -390,9 +343,7 @@ }, { "description": "should handle implict contezt in ng-template", - "inputFiles": [ - "ng_template_implicit.ts" - ], + "inputFiles": ["ng_template_implicit.ts"], "expectations": [ { "failureMessage": "Incorrect template" @@ -401,9 +352,7 @@ }, { "description": "should break large element creation chains", - "inputFiles": [ - "create_many_elements.ts" - ], + "inputFiles": ["create_many_elements.ts"], "expectations": [ { "failureMessage": "Incorrect template" diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_template.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_template.js index c8f40370207a..30317fdafb06 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_template.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_template.js @@ -1,2 +1,2 @@ … -i0.ɵɵtextInterpolate1(" ", (ctx.a == null ? null : ctx.a.b) ? 1 : 2) +i0.ɵɵtextInterpolate1(" ", ctx.a?.b ? 1 : 2); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_template_use_null.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_template_use_null.js new file mode 100644 index 000000000000..c8f40370207a --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_template_use_null.js @@ -0,0 +1,2 @@ +… +i0.ɵɵtextInterpolate1(" ", (ctx.a == null ? null : ctx.a.b) ? 1 : 2) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_use_null.ts b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_use_null.ts new file mode 100644 index 000000000000..e48ea24e5fd4 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_template/nested_ternary_operation_use_null.ts @@ -0,0 +1,15 @@ +import {Component, NgModule} from '@angular/core'; + +@Component({ + selector: 'my-component', + template: ` + {{a?.b ? 1 : 2 }}`, + standalone: false +}) +export class MyComponent { + a!: any; +} + +@NgModule({declarations: [MyComponent]}) +export class MyModule { +} diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/service_decorator/GOLDEN_PARTIAL.js new file mode 100644 index 000000000000..686178c642f1 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/GOLDEN_PARTIAL.js @@ -0,0 +1,123 @@ +/**************************************************************************************************** + * PARTIAL FILE: basic_service.js + ****************************************************************************************************/ +import { Service } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyService { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Service }); + static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, decorators: [{ + type: Service + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: basic_service.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyService { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵprov: i0.ɵɵInjectableDeclaration; +} + +/**************************************************************************************************** + * PARTIAL FILE: service_with_factory.js + ****************************************************************************************************/ +import { Service } from '@angular/core'; +import * as i0 from "@angular/core"; +class Alternate { +} +export class MyService { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Service }); + static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, factory: () => new Alternate() }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, decorators: [{ + type: Service, + args: [{ factory: () => new Alternate() }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: service_with_factory.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyService { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵprov: i0.ɵɵInjectableDeclaration; +} + +/**************************************************************************************************** + * PARTIAL FILE: not_provided_service.js + ****************************************************************************************************/ +import { Service } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyService { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Service }); + static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, autoProvided: false }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, decorators: [{ + type: Service, + args: [{ autoProvided: false }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: not_provided_service.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyService { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵprov: i0.ɵɵInjectableDeclaration; +} + +/**************************************************************************************************** + * PARTIAL FILE: explicitly_provided_service.js + ****************************************************************************************************/ +import { Service } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyService { + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Service }); + static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, decorators: [{ + type: Service, + args: [{ autoProvided: true }] + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: explicitly_provided_service.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyService { + static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵprov: i0.ɵɵInjectableDeclaration; +} + +/**************************************************************************************************** + * PARTIAL FILE: generic_service.js + ****************************************************************************************************/ +import { Service } from '@angular/core'; +import * as i0 from "@angular/core"; +export class MyService { + getOne() { + return null; + } + getTwo() { + return null; + } + static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, deps: [], target: i0.ɵɵFactoryTarget.Service }); + static ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService }); +} +i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyService, decorators: [{ + type: Service + }] }); + +/**************************************************************************************************** + * PARTIAL FILE: generic_service.d.ts + ****************************************************************************************************/ +import * as i0 from "@angular/core"; +export declare class MyService { + getOne(): T; + getTwo(): V; + static ɵfac: i0.ɵɵFactoryDeclaration, never>; + static ɵprov: i0.ɵɵInjectableDeclaration>; +} + diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/TEST_CASES.json b/packages/compiler-cli/test/compliance/test_cases/service_decorator/TEST_CASES.json new file mode 100644 index 000000000000..3d7736292c87 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/TEST_CASES.json @@ -0,0 +1,80 @@ +{ + "$schema": "../test_case_schema.json", + "cases": [ + { + "description": "should compile a basic service", + "inputFiles": ["basic_service.ts"], + "expectations": [ + { + "files": [ + { + "expected": "basic_service.js", + "generated": "basic_service.js" + } + ], + "failureMessage": "Incorrect service" + } + ] + }, + { + "description": "should compile a service with a factory", + "inputFiles": ["service_with_factory.ts"], + "expectations": [ + { + "files": [ + { + "expected": "service_with_factory.js", + "generated": "service_with_factory.js" + } + ], + "failureMessage": "Incorrect service" + } + ] + }, + { + "description": "should compile a service with `autoProvided: false`", + "inputFiles": ["not_provided_service.ts"], + "expectations": [ + { + "files": [ + { + "expected": "not_provided_service.js", + "generated": "not_provided_service.js" + } + ], + "failureMessage": "Incorrect service" + } + ] + }, + { + "description": "should compile a service with `autoProvided: true`", + "inputFiles": ["explicitly_provided_service.ts"], + "expectations": [ + { + "files": [ + { + "expected": "explicitly_provided_service.js", + "generated": "explicitly_provided_service.js" + } + ], + "failureMessage": "Incorrect service" + } + ] + }, + { + "description": "should compile a service with generic arguments", + "inputFiles": ["generic_service.ts"], + "expectations": [ + { + "files": [ + { + "expected": "generic_service.js", + "generated": "generic_service.js" + } + ], + "failureMessage": "Incorrect service" + } + ] + } + ] +} diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/basic_service.js b/packages/compiler-cli/test/compliance/test_cases/service_decorator/basic_service.js new file mode 100644 index 000000000000..b8266ebd43ea --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/basic_service.js @@ -0,0 +1,8 @@ +export class MyService { + static ɵfac = function MyService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MyService)(); }; + static ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineService({ token: MyService, factory: MyService.ɵfac }); +} + +(() => { (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadata(MyService, [{ + type: Service +}], null, null); })(); diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/basic_service.ts b/packages/compiler-cli/test/compliance/test_cases/service_decorator/basic_service.ts new file mode 100644 index 000000000000..785a8c81f1f2 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/basic_service.ts @@ -0,0 +1,4 @@ +import {Service} from '@angular/core'; + +@Service() +export class MyService {} diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/explicitly_provided_service.js b/packages/compiler-cli/test/compliance/test_cases/service_decorator/explicitly_provided_service.js new file mode 100644 index 000000000000..35a8dce64944 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/explicitly_provided_service.js @@ -0,0 +1,9 @@ +export class MyService { + static ɵfac = function MyService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MyService)(); }; + static ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineService({ token: MyService, factory: MyService.ɵfac }); +} + +(() => { (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadata(MyService, [{ + type: Service, + args: [{ autoProvided: true }] +}], null, null); })(); diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/explicitly_provided_service.ts b/packages/compiler-cli/test/compliance/test_cases/service_decorator/explicitly_provided_service.ts new file mode 100644 index 000000000000..767c036e9f63 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/explicitly_provided_service.ts @@ -0,0 +1,4 @@ +import {Service} from '@angular/core'; + +@Service({autoProvided: true}) +export class MyService {} diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/generic_service.js b/packages/compiler-cli/test/compliance/test_cases/service_decorator/generic_service.js new file mode 100644 index 000000000000..09ff0733696a --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/generic_service.js @@ -0,0 +1,6 @@ +export class MyService { + … + static ɵfac = function MyService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MyService)(); }; + static ɵprov = /*@__PURE__*/ i0.ɵɵdefineService({ token: MyService, factory: MyService.ɵfac }); +} + diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/generic_service.ts b/packages/compiler-cli/test/compliance/test_cases/service_decorator/generic_service.ts new file mode 100644 index 000000000000..336de2b1d411 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/generic_service.ts @@ -0,0 +1,12 @@ +import {Service} from '@angular/core'; + +@Service() +export class MyService { + getOne(): T { + return null!; + } + + getTwo(): V { + return null!; + } +} diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/not_provided_service.js b/packages/compiler-cli/test/compliance/test_cases/service_decorator/not_provided_service.js new file mode 100644 index 000000000000..ed83abc0f2d4 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/not_provided_service.js @@ -0,0 +1,9 @@ +export class MyService { + static ɵfac = function MyService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MyService)(); }; + static ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineService({ token: MyService, factory: MyService.ɵfac, autoProvided: false }); +} + +(() => { (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadata(MyService, [{ + type: Service, + args: [{ autoProvided: false }] +}], null, null); })(); diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/not_provided_service.ts b/packages/compiler-cli/test/compliance/test_cases/service_decorator/not_provided_service.ts new file mode 100644 index 000000000000..a24833cf6165 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/not_provided_service.ts @@ -0,0 +1,4 @@ +import {Service} from '@angular/core'; + +@Service({autoProvided: false}) +export class MyService {} diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/service_with_factory.js b/packages/compiler-cli/test/compliance/test_cases/service_decorator/service_with_factory.js new file mode 100644 index 000000000000..025baf142427 --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/service_with_factory.js @@ -0,0 +1,11 @@ +class Alternate {} + +export class MyService { + static ɵfac = function MyService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MyService)(); }; + static ɵprov = /*@__PURE__*/ $r3$.ɵɵdefineService({ token: MyService, factory: () => (() => new Alternate())() }); +} + +(() => { (typeof ngDevMode === "undefined" || ngDevMode) && $r3$.ɵsetClassMetadata(MyService, [{ + type: Service, + args: [{ factory: () => new Alternate() }] +}], null, null); })(); diff --git a/packages/compiler-cli/test/compliance/test_cases/service_decorator/service_with_factory.ts b/packages/compiler-cli/test/compliance/test_cases/service_decorator/service_with_factory.ts new file mode 100644 index 000000000000..dd77e267957c --- /dev/null +++ b/packages/compiler-cli/test/compliance/test_cases/service_decorator/service_with_factory.ts @@ -0,0 +1,6 @@ +import {Service} from '@angular/core'; + +class Alternate {} + +@Service({factory: () => new Alternate()}) +export class MyService {} diff --git a/packages/compiler-cli/test/compliance/test_cases/signal_inputs/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/signal_inputs/GOLDEN_PARTIAL.js index 5eebfafda258..8787359935db 100644 --- a/packages/compiler-cli/test/compliance/test_cases/signal_inputs/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/signal_inputs/GOLDEN_PARTIAL.js @@ -4,8 +4,10 @@ import { Directive, input } from '@angular/core'; import * as i0 from "@angular/core"; export class TestDir { - counter = input(0, ...(ngDevMode ? [{ debugName: "counter" }] : [])); - name = input.required(...(ngDevMode ? [{ debugName: "name" }] : [])); + counter = input(0, /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "counter" }] : /* istanbul ignore next */ [])); + name = input.required(/* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestDir, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "0.0.0-PLACEHOLDER", type: TestDir, isStandalone: true, inputs: { counter: { classPropertyName: "counter", publicName: "counter", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); } @@ -31,8 +33,10 @@ export declare class TestDir { import { Component, input } from '@angular/core'; import * as i0 from "@angular/core"; export class TestComp { - counter = input(0, ...(ngDevMode ? [{ debugName: "counter" }] : [])); - name = input.required(...(ngDevMode ? [{ debugName: "name" }] : [])); + counter = input(0, /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "counter" }] : /* istanbul ignore next */ [])); + name = input.required(/* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComp, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "0.0.0-PLACEHOLDER", type: TestComp, isStandalone: true, selector: "ng-component", inputs: { counter: { classPropertyName: "counter", publicName: "counter", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: 'Works', isInline: true }); } @@ -63,9 +67,10 @@ function convertToBoolean(value) { return value === true || value !== ''; } export class TestDir { - counter = input(0, ...(ngDevMode ? [{ debugName: "counter" }] : [])); - signalWithTransform = input(false, { ...(ngDevMode ? { debugName: "signalWithTransform" } : {}), transform: convertToBoolean }); - signalWithTransformAndAlias = input(false, { ...(ngDevMode ? { debugName: "signalWithTransformAndAlias" } : {}), alias: 'publicNameSignal', transform: convertToBoolean }); + counter = input(0, /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "counter" }] : /* istanbul ignore next */ [])); + signalWithTransform = input(false, { ...(ngDevMode ? { debugName: "signalWithTransform" } : /* istanbul ignore next */ {}), transform: convertToBoolean }); + signalWithTransformAndAlias = input(false, { ...(ngDevMode ? { debugName: "signalWithTransformAndAlias" } : /* istanbul ignore next */ {}), alias: 'publicNameSignal', transform: convertToBoolean }); decoratorInput = true; decoratorInputWithAlias = true; decoratorInputWithTransformAndAlias = true; @@ -110,7 +115,7 @@ function convertToBoolean(value) { return value === true || value !== ''; } export class TestDir { - name = input.required({ ...(ngDevMode ? { debugName: "name" } : {}), transform: convertToBoolean }); + name = input.required({ ...(ngDevMode ? { debugName: "name" } : /* istanbul ignore next */ {}), transform: convertToBoolean }); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestDir, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "0.0.0-PLACEHOLDER", type: TestDir, isStandalone: true, inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); } @@ -139,10 +144,10 @@ const toBoolean = (v) => v === true || v !== ''; // Note: `@Input` non-signal inputs did not support transform function "builders" and generics. const complexTransform = (defaultVal) => (v) => v || defaultVal; export class TestDir { - name = input.required({ ...(ngDevMode ? { debugName: "name" } : {}), transform: (v) => v === true || v !== '' }); - name2 = input.required({ ...(ngDevMode ? { debugName: "name2" } : {}), transform: toBoolean }); - genericTransform = input.required({ ...(ngDevMode ? { debugName: "genericTransform" } : {}), transform: complexTransform(1) }); - genericTransform2 = input.required({ ...(ngDevMode ? { debugName: "genericTransform2" } : {}), transform: complexTransform(null) }); + name = input.required({ ...(ngDevMode ? { debugName: "name" } : /* istanbul ignore next */ {}), transform: (v) => v === true || v !== '' }); + name2 = input.required({ ...(ngDevMode ? { debugName: "name2" } : /* istanbul ignore next */ {}), transform: toBoolean }); + genericTransform = input.required({ ...(ngDevMode ? { debugName: "genericTransform" } : /* istanbul ignore next */ {}), transform: complexTransform(1) }); + genericTransform2 = input.required({ ...(ngDevMode ? { debugName: "genericTransform2" } : /* istanbul ignore next */ {}), transform: complexTransform(null) }); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestDir, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "0.0.0-PLACEHOLDER", type: TestDir, isStandalone: true, inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, name2: { classPropertyName: "name2", publicName: "name2", isSignal: true, isRequired: true, transformFunction: null }, genericTransform: { classPropertyName: "genericTransform", publicName: "genericTransform", isSignal: true, isRequired: true, transformFunction: null }, genericTransform2: { classPropertyName: "genericTransform2", publicName: "genericTransform2", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0 }); } diff --git a/packages/compiler-cli/test/compliance/test_cases/signal_queries/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/signal_queries/GOLDEN_PARTIAL.js index 3c244771c8ec..1dae31ac0add 100644 --- a/packages/compiler-cli/test/compliance/test_cases/signal_queries/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/signal_queries/GOLDEN_PARTIAL.js @@ -7,15 +7,21 @@ export class SomeToken { } const nonAnalyzableRefersToString = 'a, b, c'; export class TestDir { - query1 = viewChild('locatorA', ...(ngDevMode ? [{ debugName: "query1" }] : [])); - query2 = viewChildren('locatorB', ...(ngDevMode ? [{ debugName: "query2" }] : [])); - query3 = contentChild('locatorC', ...(ngDevMode ? [{ debugName: "query3" }] : [])); - query4 = contentChildren('locatorD', ...(ngDevMode ? [{ debugName: "query4" }] : [])); - query5 = viewChild(forwardRef(() => SomeToken), ...(ngDevMode ? [{ debugName: "query5" }] : [])); - query6 = viewChildren(SomeToken, ...(ngDevMode ? [{ debugName: "query6" }] : [])); - query7 = viewChild('locatorE', { ...(ngDevMode ? { debugName: "query7" } : {}), read: SomeToken }); - query8 = contentChildren('locatorF, locatorG', { ...(ngDevMode ? { debugName: "query8" } : {}), descendants: true }); - query9 = contentChildren(nonAnalyzableRefersToString, { ...(ngDevMode ? { debugName: "query9" } : {}), descendants: true }); + query1 = viewChild('locatorA', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query1" }] : /* istanbul ignore next */ [])); + query2 = viewChildren('locatorB', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query2" }] : /* istanbul ignore next */ [])); + query3 = contentChild('locatorC', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query3" }] : /* istanbul ignore next */ [])); + query4 = contentChildren('locatorD', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query4" }] : /* istanbul ignore next */ [])); + query5 = viewChild(forwardRef(() => SomeToken), /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query5" }] : /* istanbul ignore next */ [])); + query6 = viewChildren(SomeToken, /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query6" }] : /* istanbul ignore next */ [])); + query7 = viewChild('locatorE', { ...(ngDevMode ? { debugName: "query7" } : /* istanbul ignore next */ {}), read: SomeToken }); + query8 = contentChildren('locatorF, locatorG', { ...(ngDevMode ? { debugName: "query8" } : /* istanbul ignore next */ {}), descendants: true }); + query9 = contentChildren(nonAnalyzableRefersToString, { ...(ngDevMode ? { debugName: "query9" } : /* istanbul ignore next */ {}), descendants: true }); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestDir, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "0.0.0-PLACEHOLDER", type: TestDir, isStandalone: true, queries: [{ propertyName: "query3", first: true, predicate: ["locatorC"], descendants: true, isSignal: true }, { propertyName: "query4", predicate: ["locatorD"], isSignal: true }, { propertyName: "query8", predicate: ["locatorF, locatorG"], descendants: true, isSignal: true }, { propertyName: "query9", predicate: nonAnalyzableRefersToString, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "query1", first: true, predicate: ["locatorA"], descendants: true, isSignal: true }, { propertyName: "query2", predicate: ["locatorB"], descendants: true, isSignal: true }, { propertyName: "query5", first: true, predicate: i0.forwardRef(() => SomeToken), descendants: true, isSignal: true }, { propertyName: "query6", predicate: SomeToken, descendants: true, isSignal: true }, { propertyName: "query7", first: true, predicate: ["locatorE"], descendants: true, read: SomeToken, isSignal: true }], ngImport: i0 }); } @@ -50,10 +56,14 @@ export declare class TestDir { import { Component, contentChild, contentChildren, viewChild, viewChildren } from '@angular/core'; import * as i0 from "@angular/core"; export class TestComp { - query1 = viewChild('locatorA', ...(ngDevMode ? [{ debugName: "query1" }] : [])); - query2 = viewChildren('locatorB', ...(ngDevMode ? [{ debugName: "query2" }] : [])); - query3 = contentChild('locatorC', ...(ngDevMode ? [{ debugName: "query3" }] : [])); - query4 = contentChildren('locatorD', ...(ngDevMode ? [{ debugName: "query4" }] : [])); + query1 = viewChild('locatorA', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query1" }] : /* istanbul ignore next */ [])); + query2 = viewChildren('locatorB', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query2" }] : /* istanbul ignore next */ [])); + query3 = contentChild('locatorC', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query3" }] : /* istanbul ignore next */ [])); + query4 = contentChildren('locatorD', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "query4" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestComp, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "0.0.0-PLACEHOLDER", type: TestComp, isStandalone: true, selector: "ng-component", queries: [{ propertyName: "query3", first: true, predicate: ["locatorC"], descendants: true, isSignal: true }, { propertyName: "query4", predicate: ["locatorD"], isSignal: true }], viewQueries: [{ propertyName: "query1", first: true, predicate: ["locatorA"], descendants: true, isSignal: true }, { propertyName: "query2", predicate: ["locatorB"], descendants: true, isSignal: true }], ngImport: i0, template: 'Works', isInline: true }); } @@ -84,9 +94,11 @@ import { ContentChild, contentChild, Directive, ViewChild, viewChild } from '@an import * as i0 from "@angular/core"; export class TestDir { decoratorViewChild; - signalViewChild = viewChild('locator1', ...(ngDevMode ? [{ debugName: "signalViewChild" }] : [])); + signalViewChild = viewChild('locator1', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "signalViewChild" }] : /* istanbul ignore next */ [])); decoratorContentChild; - signalContentChild = contentChild('locator2', ...(ngDevMode ? [{ debugName: "signalContentChild" }] : [])); + signalContentChild = contentChild('locator2', /* @ts-ignore */ + ...(ngDevMode ? [{ debugName: "signalContentChild" }] : /* istanbul ignore next */ [])); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: TestDir, deps: [], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "0.0.0-PLACEHOLDER", type: TestDir, isStandalone: true, queries: [{ propertyName: "signalContentChild", first: true, predicate: ["locator2"], descendants: true, isSignal: true }, { propertyName: "decoratorContentChild", first: true, predicate: ["locator2"], descendants: true }], viewQueries: [{ propertyName: "signalViewChild", first: true, predicate: ["locator1"], descendants: true, isSignal: true }, { propertyName: "decoratorViewChild", first: true, predicate: ["locator1"], descendants: true }], ngImport: i0 }); } diff --git a/packages/compiler-cli/test/compliance/test_cases/test_case_schema.json b/packages/compiler-cli/test/compliance/test_cases/test_case_schema.json index a9a8a7e8587f..ac25d626e229 100644 --- a/packages/compiler-cli/test/compliance/test_cases/test_case_schema.json +++ b/packages/compiler-cli/test/compliance/test_cases/test_case_schema.json @@ -27,13 +27,15 @@ "full compile", "linked compile", "local compile", - "declaration-only emit" + "declaration-only emit", + "instruction compile" ] }, "default": [ "full compile", "linked compile", - "declaration-only emit" + "declaration-only emit", + "instruction compile" ] }, "inputFiles": { diff --git a/packages/compiler-cli/test/compliance/test_helpers/compile_test.ts b/packages/compiler-cli/test/compliance/test_helpers/compile_test.ts index f1a01734197e..a7e432c3b5b9 100644 --- a/packages/compiler-cli/test/compliance/test_helpers/compile_test.ts +++ b/packages/compiler-cli/test/compliance/test_helpers/compile_test.ts @@ -62,7 +62,11 @@ export function compileTest( ): CompileResult { const rootDir = getRootDirectory(fs); const outDir = getBuildOutputDirectory(fs); - const options = getOptions(rootDir, outDir, compilerOptions, angularCompilerOptions); + const options = getOptions(rootDir, outDir, compilerOptions, { + ...angularCompilerOptions, + // TODO: Do we want to enable strictTemplates for compliance tests? + strictTemplates: false, + }); const rootNames = files.map((f) => fs.resolve(f)); const host = new NgtscTestCompilerHost(fs, options); const {diagnostics, emitResult} = performCompilation({rootNames, host, options}); diff --git a/packages/compiler-cli/test/compliance/test_helpers/expect_emit.ts b/packages/compiler-cli/test/compliance/test_helpers/expect_emit.ts index c48b51ba1eec..2e8c066918cd 100644 --- a/packages/compiler-cli/test/compliance/test_helpers/expect_emit.ts +++ b/packages/compiler-cli/test/compliance/test_helpers/expect_emit.ts @@ -26,7 +26,7 @@ const TOKEN = new RegExp( type Piece = string | RegExp; -const SKIP = /(?:.|\n|\r)*/; +const SKIP = /(?:.|\n|\r)*?/; const ERROR_CONTEXT_WIDTH = 30; // Transform the expected output to set of tokens @@ -126,58 +126,90 @@ export function expectEmit( .replace(/\/\/\s*NOTE.*?\n/g, ''); const pieces = tokenize(expected); - const {regexp, groups} = buildMatcher(pieces); - const matches = source.match(regexp); - if (matches === null) { - let last: number = 0; - for (let i = 1; i < pieces.length; i++) { - const {regexp} = buildMatcher(pieces.slice(0, i)); - const m = source.match(regexp); - const expectedPiece = pieces[i - 1] == IDENTIFIER ? '' : pieces[i - 1]; - if (!m) { - // display at most `contextLength` characters of the line preceding the error location - const contextLength = 50; - const fullContext = source.substring(source.lastIndexOf('\n', last) + 1, last); - const context = - fullContext.length > contextLength - ? `...${fullContext.slice(-contextLength)}` - : fullContext; - throw new Error( - `${RED}${description}:\n${RESET}${BLUE}Failed to find${RESET} "${expectedPiece}"\n` + - `${BLUE}After ${RESET}"${context}"\n` + - `${BLUE}In generated file:${RESET}\n\n` + - `${source.slice(0, last)}` + - `${RED}[[[ <<<<---HERE expected "${GREEN}${expectedPiece}${RED}" ]]]${RESET}` + - `${source.slice(last)}`, - ); - } else { - last = (m.index || 0) + m[0].length; + + // Group pieces into chunks separated by SKIP. + const chunks: {pieces: (string | RegExp)[]; skipBefore: boolean}[] = []; + let currentChunk: (string | RegExp)[] = []; + let hasSkip = false; + for (const piece of pieces) { + if (piece === SKIP) { + if (currentChunk.length > 0 || hasSkip) { + chunks.push({pieces: currentChunk, skipBefore: hasSkip}); } + hasSkip = true; + currentChunk = []; + } else { + currentChunk.push(piece); } + } + if (currentChunk.length > 0 || chunks.length === 0) { + chunks.push({pieces: currentChunk, skipBefore: hasSkip}); + } - throw new Error( - `Test helper failure: Expected expression failed but the reporting logic could not find where it failed in: ${source}`, - ); - } else { - if (assertIdentifiers) { - // It might be possible to add the constraints in the original regexp (see `buildMatcher`) - // by transforming the assertion regexps when using anchoring, grouping, back references, - // flags, ... - // - // Checking identifiers after they have matched allows for a simple and flexible - // implementation. - // The overall performance are not impacted when `assertIdentifiers` is empty. - const ids = Object.keys(assertIdentifiers); - for (let i = 0; i < ids.length; i++) { - const id = ids[i]; - if (groups.has(id)) { - const name = matches[groups.get(id) as number]; - const regexp = assertIdentifiers[id]; - if (!regexp.test(name)) { - throw Error( - `${description}: The matching identifier "${id}" is "${name}" which doesn't match ${regexp}`, - ); - } + const extractedGroups = new Map(); + let lastIndex = 0; + + for (let i = 0; i < chunks.length; i++) { + const chunk = chunks[i]; + if (chunk.pieces.length === 0) continue; + + const {regexp, newGroups} = buildChunkMatcher(chunk.pieces, extractedGroups, 'g'); + regexp.lastIndex = lastIndex; + + let m = regexp.exec(source); + + if (!m) { + let errLast = lastIndex; + for (let j = 1; j <= chunk.pieces.length; j++) { + const subPieces = chunk.pieces.slice(0, j); + const {regexp: subRegexp} = buildChunkMatcher(subPieces, extractedGroups, 'g'); + subRegexp.lastIndex = lastIndex; + + let subMatch = subRegexp.exec(source); + + const expectedPiece = chunk.pieces[j - 1] == IDENTIFIER ? '' : chunk.pieces[j - 1]; + if (!subMatch) { + const contextLength = 50; + const fullContext = source.substring(source.lastIndexOf('\n', errLast) + 1, errLast); + const context = + fullContext.length > contextLength + ? `...${fullContext.slice(-contextLength)}` + : fullContext; + throw new Error( + `${RED}${description}:\n${RESET}${BLUE}Failed to find${RESET} "${expectedPiece}"\n` + + `${BLUE}After ${RESET}"${context}"\n` + + `${BLUE}In generated file:${RESET}\n\n` + + `${source.slice(0, errLast)}` + + `${RED}[[[ <<<<---HERE expected "${GREEN}${expectedPiece}${RED}" ]]]${RESET}` + + `${source.slice(errLast)}`, + ); + } else { + errLast = subMatch.index + subMatch[0].length; + } + } + + throw new Error( + `Test helper failure: Expected expression failed but the reporting logic could not find where it failed in: ${source}`, + ); + } + + for (const [id, idx] of newGroups.entries()) { + extractedGroups.set(id, m[idx]); + } + lastIndex = m.index + m[0].length; + } + + if (assertIdentifiers) { + const ids = Object.keys(assertIdentifiers); + for (let i = 0; i < ids.length; i++) { + const id = ids[i]; + if (extractedGroups.has(id)) { + const name = extractedGroups.get(id) as string; + const regexp = assertIdentifiers[id]; + if (!regexp.test(name)) { + throw Error( + `${description}: The matching identifier "${id}" is "${name}" which doesn't match ${regexp}`, + ); } } } @@ -192,27 +224,36 @@ const MATCHING_IDENT = /^\$.*\$$/; * * It returns: * - the `regexp` to be used to match the generated code, - * - the `groups` which maps `$...$` identifier to their position in the regexp matches. + * - the `newGroups` which maps `$...$` identifier to their position in the regexp matches. */ -function buildMatcher(pieces: (string | RegExp)[]): {regexp: RegExp; groups: Map} { +function buildChunkMatcher( + pieces: (string | RegExp)[], + knownGroups: Map, + flag: string, +): {regexp: RegExp; newGroups: Map} { const results: string[] = []; let first = true; - let group = 0; + let groupCounter = 0; + const newGroups = new Map(); - const groups = new Map(); for (const piece of pieces) { if (!first) results.push(`\\s${typeof piece === 'string' && IDENT_LIKE.test(piece) ? '+' : '*'}`); first = false; + if (typeof piece === 'string') { if (MATCHING_IDENT.test(piece)) { - const matchGroup = groups.get(piece); - if (!matchGroup) { - results.push('(' + IDENTIFIER.source + ')'); - const newGroup = ++group; - groups.set(piece, newGroup); + if (knownGroups.has(piece)) { + results.push(escapeRegExp(knownGroups.get(piece)!)); } else { - results.push(`\\${matchGroup}`); + const matchGroup = newGroups.get(piece); + if (!matchGroup) { + results.push('(' + IDENTIFIER.source + ')'); + groupCounter++; + newGroups.set(piece, groupCounter); + } else { + results.push(`\\${matchGroup}`); + } } } else { results.push(escapeRegExp(piece)); @@ -222,7 +263,7 @@ function buildMatcher(pieces: (string | RegExp)[]): {regexp: RegExp; groups: Map } } return { - regexp: new RegExp(results.join('')), - groups, + regexp: new RegExp(results.join(''), flag), + newGroups, }; } diff --git a/packages/compiler-cli/test/compliance/test_helpers/get_compliance_tests.ts b/packages/compiler-cli/test/compliance/test_helpers/get_compliance_tests.ts index 75a25fdee9d5..cd3fef979609 100644 --- a/packages/compiler-cli/test/compliance/test_helpers/get_compliance_tests.ts +++ b/packages/compiler-cli/test/compliance/test_helpers/get_compliance_tests.ts @@ -50,7 +50,7 @@ export function* getComplianceTests(absTestConfigPath: AbsoluteFsPath): Generato test, 'compilationModeFilter', realTestPath, - ['linked compile', 'full compile', 'declaration-only emit'], + ['linked compile', 'full compile', 'declaration-only emit', 'instruction compile'], ) as CompilationMode[]; yield { @@ -297,7 +297,8 @@ export type CompilationMode = | 'linked compile' | 'full compile' | 'local compile' - | 'declaration-only emit'; + | 'declaration-only emit' + | 'instruction compile'; export interface Expectation { /** The message to display if this expectation fails. */ @@ -342,7 +343,7 @@ export type ConfigOptions = Record; */ export interface TestCaseJson { description: string; - compilationModeFilter?: ('fulll compile' | 'linked compile')[]; + compilationModeFilter?: CompilationMode[]; inputFiles?: string[]; expectations?: { failureMessage?: string; diff --git a/packages/compiler-cli/test/compliance/test_helpers/i18n_helpers.ts b/packages/compiler-cli/test/compliance/test_helpers/i18n_helpers.ts index bce436ac212d..d252e23daa4b 100644 --- a/packages/compiler-cli/test/compliance/test_helpers/i18n_helpers.ts +++ b/packages/compiler-cli/test/compliance/test_helpers/i18n_helpers.ts @@ -33,10 +33,11 @@ export function i18nMsg( let ${varName}; if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) { ${i18nMsgClosureMeta(meta)} - const $MSG_EXTERNAL_${msgIndex}$ = goog.getMsg("${message}"${closurePlaceholders}${closureOptions}); + const $MSG_EXTERNAL_${msgIndex}$ = /* @ts-ignore */ goog.getMsg("${message}"${closurePlaceholders}${closureOptions}); ${varName} = $MSG_EXTERNAL_${msgIndex}$; } else { + /* @ts-ignore */ ${varName} = $localize \`${i18nMsgLocalizeMeta(meta)}${locMessageWithPlaceholders}\`; }`; } diff --git a/packages/compiler-cli/test/compliance/test_helpers/test_runner.ts b/packages/compiler-cli/test/compliance/test_helpers/test_runner.ts index 20445e92922b..4e356ab47820 100644 --- a/packages/compiler-cli/test/compliance/test_helpers/test_runner.ts +++ b/packages/compiler-cli/test/compliance/test_helpers/test_runner.ts @@ -52,6 +52,7 @@ export function runTests( isLocalCompilation?: boolean; emitDeclarationOnly?: boolean; skipMappingChecks?: boolean; + checkErrorsOnly?: boolean; } = {}, ) { describe(`compliance tests (${type})`, () => { @@ -85,6 +86,8 @@ export function runTests( expectation.expectedErrors, errors, ); + } else if (options.checkErrorsOnly) { + checkNoUnexpectedErrors(test.relativePath, errors); } else if (!!options.emitDeclarationOnly) { checkNoUnexpectedErrors(test.relativePath, errors); checkTypeDeclarations(fs, emittedFiles); diff --git a/packages/compiler-cli/test/ngtsc/authoring_inputs_spec.ts b/packages/compiler-cli/test/ngtsc/authoring_inputs_spec.ts index 358de9ad349d..6122cfd3720f 100644 --- a/packages/compiler-cli/test/ngtsc/authoring_inputs_spec.ts +++ b/packages/compiler-cli/test/ngtsc/authoring_inputs_spec.ts @@ -21,7 +21,7 @@ runInEachFileSystem(() => { beforeEach(() => { env = NgtscTestEnvironment.setup(testFiles); - env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true}); + env.tsconfig({strictTemplates: true}); }); it('should handle a basic, primitive valued input', () => { @@ -255,9 +255,8 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toBe(`Type 'number' is not assignable to type 'string'.`); - expect(diags[1].messageText).toBe(`Type 'string' is not assignable to type 'number'.`); }); describe('type checking', () => { diff --git a/packages/compiler-cli/test/ngtsc/authoring_models_spec.ts b/packages/compiler-cli/test/ngtsc/authoring_models_spec.ts index 1dc192d4f625..d6359255929d 100644 --- a/packages/compiler-cli/test/ngtsc/authoring_models_spec.ts +++ b/packages/compiler-cli/test/ngtsc/authoring_models_spec.ts @@ -21,7 +21,7 @@ runInEachFileSystem(() => { beforeEach(() => { env = NgtscTestEnvironment.setup(testFiles); - env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true}); + env.tsconfig({strictTemplates: true}); }); it('should declare an input/output pair for a field initialized to a model()', () => { @@ -294,9 +294,8 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toBe(`Type 'boolean' is not assignable to type 'number'.`); - expect(diags[1].messageText).toBe(`Type 'number' is not assignable to type 'boolean'.`); }); it('should check a signal value bound to a model input via a two-way binding', () => { @@ -323,9 +322,8 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toBe(`Type 'boolean' is not assignable to type 'number'.`); - expect(diags[1].messageText).toBe(`Type 'number' is not assignable to type 'boolean'.`); }); it('should check two-way binding of a signal to a decorator-based input/output pair', () => { @@ -353,9 +351,8 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toBe(`Type 'boolean' is not assignable to type 'number'.`); - expect(diags[1].messageText).toBe(`Type 'number' is not assignable to type 'boolean'.`); }); it('should not allow a non-writable signal to be assigned to a model', () => { @@ -382,13 +379,10 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toBe( `Type 'InputSignal' is not assignable to type 'number'.`, ); - expect(diags[1].messageText).toBe( - `Type 'number' is not assignable to type 'InputSignal'.`, - ); }); it('should allow a model signal to be bound to another model signal', () => { @@ -501,17 +495,12 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toEqual( jasmine.objectContaining({ messageText: `Type '{ id: number; }' is not assignable to type '{ id: string; }'.`, }), ); - expect(diags[1].messageText).toEqual( - jasmine.objectContaining({ - messageText: `Type '{ id: string; }' is not assignable to type '{ id: number; }'.`, - }), - ); }); it('should check generic two-way model binding with a signal value', () => { @@ -538,17 +527,12 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toEqual( jasmine.objectContaining({ messageText: `Type '{ id: number; }' is not assignable to type '{ id: string; }'.`, }), ); - expect(diags[1].messageText).toEqual( - jasmine.objectContaining({ - messageText: `Type '{ id: string; }' is not assignable to type '{ id: number; }'.`, - }), - ); }); it('should report unwrapped signals assigned to a model in a one-way binding', () => { diff --git a/packages/compiler-cli/test/ngtsc/component_indexing_spec.ts b/packages/compiler-cli/test/ngtsc/component_indexing_spec.ts index 5914c6d923fc..bfc62a216dc4 100644 --- a/packages/compiler-cli/test/ngtsc/component_indexing_spec.ts +++ b/packages/compiler-cli/test/ngtsc/component_indexing_spec.ts @@ -83,8 +83,6 @@ runInEachFileSystem(() => { target: null, }, ]), - usedComponents: new Set(), - isInline: true, file: new ParseSourceFile(componentContent, testSourceFile), }); }); @@ -116,8 +114,6 @@ runInEachFileSystem(() => { target: null, }, ]), - usedComponents: new Set(), - isInline: false, file: new ParseSourceFile('{{foo}}', testTemplateFile), }); }); @@ -153,8 +149,6 @@ runInEachFileSystem(() => { target: null, }, ]), - usedComponents: new Set(), - isInline: false, file: new ParseSourceFile(' \n {{foo}}', testTemplateFile), }); }); @@ -205,12 +199,6 @@ runInEachFileSystem(() => { const testImportComp = indexedComps.find((cmp) => cmp.name === 'TestImportCmp'); expect(testComp).toBeDefined(); expect(testImportComp).toBeDefined(); - - expect(testComp!.template.usedComponents.size).toBe(0); - expect(testImportComp!.template.usedComponents.size).toBe(1); - - const [usedComp] = Array.from(testImportComp!.template.usedComponents); - expect(indexed.get(usedComp)).toEqual(testComp); }); }); }); diff --git a/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts b/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts index a7ded6f46b5d..4fb520c9e37a 100644 --- a/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts +++ b/packages/compiler-cli/test/ngtsc/debug_transform_spec.ts @@ -31,7 +31,7 @@ const minifiedProdBuildOptions = { }; function cleanNewLines(contents: string) { - return contents.replace(/\n/g, ' ').replace(/\s+/g, ' '); + return contents.replace(/\s*\n\s*/g, ' '); } runInEachFileSystem(() => { @@ -69,8 +69,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `signal('Hello World', ...(ngDevMode ? [{ debugName: "testSignal" }] : []))`, + expect(cleanNewLines(jsContents)).toContain( + `signal('Hello World', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testSignal" }] : /* istanbul ignore next */ []))`, ); }); @@ -88,7 +88,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('signal("Hello World")'); + expect(cleanNewLines(builtContent)).toContain('signal( "Hello World" )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -103,7 +103,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain(`signal("Hello World", { debugName: "testSignal" });`); + expect(cleanNewLines(builtContent)).toContain( + `signal( "Hello World", { debugName: "testSignal" } )`, + ); }); it('should insert debug info into signal function that already has custom options', async () => { @@ -117,8 +119,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `signal('Hello World', { ...(ngDevMode ? { debugName: "testSignal" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `signal('Hello World', { ...(ngDevMode ? { debugName: "testSignal" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -135,7 +137,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; - expect(builtContent).toContain(`signal("Hello World", { equal });`); + expect(cleanNewLines(builtContent)).toContain(`signal("Hello World", { equal })`); expect(builtContent).not.toContain('ngDevMode'); expect(builtContent).not.toContain('debugName'); }); @@ -159,8 +161,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `signal('Hello World', ...(ngDevMode ? [{ debugName: "testSignal" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `signal('Hello World', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testSignal" }] : /* istanbul ignore next */ [])`, ); }); @@ -183,7 +185,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('signal("Hello World")'); + expect(cleanNewLines(builtContent)).toContain('signal( "Hello World" )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -204,7 +206,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain(`signal("Hello World", { debugName: "testSignal" });`); + expect(cleanNewLines(builtContent)).toContain( + `signal( "Hello World", { debugName: "testSignal" } )`, + ); }); it('should insert debug info into signal function that already has custom options', async () => { @@ -224,8 +228,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `signal('Hello World', { ...(ngDevMode ? { debugName: "testSignal" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `signal('Hello World', { ...(ngDevMode ? { debugName: "testSignal" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -249,7 +253,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; - expect(builtContent).toContain(`signal("Hello World", { equal });`); + expect(cleanNewLines(builtContent)).toContain(`signal("Hello World", { equal })`); expect(builtContent).not.toContain('ngDevMode'); expect(builtContent).not.toContain('debugName'); }); @@ -276,8 +280,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `signal('Hello World', ...(ngDevMode ? [{ debugName: "testSignal" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `signal('Hello World', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testSignal" }] : /* istanbul ignore next */ [])`, ); }); @@ -303,7 +307,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('signal("Hello World")'); + expect(cleanNewLines(builtContent)).toContain('signal( "Hello World" )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -327,7 +331,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain(`signal("Hello World", { debugName: "testSignal" });`); + expect(cleanNewLines(builtContent)).toContain( + `signal( "Hello World", { debugName: "testSignal" } )`, + ); }); it('should insert debug info into signal function that already has custom options', async () => { @@ -350,8 +356,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `signal('Hello World', { ...(ngDevMode ? { debugName: "testSignal" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `signal('Hello World', { ...(ngDevMode ? { debugName: "testSignal" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -378,7 +384,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; - expect(builtContent).toContain(`signal("Hello World", { equal });`); + expect(cleanNewLines(builtContent)).toContain(`signal("Hello World", { equal });`); expect(builtContent).not.toContain('ngDevMode'); expect(builtContent).not.toContain('debugName'); }); @@ -410,8 +416,8 @@ runInEachFileSystem(() => { ); env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `computed(() => testSignal(), ...(ngDevMode ? [{ debugName: "testComputed" }] : []))`, + expect(cleanNewLines(jsContents)).toContain( + `computed(() => testSignal(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testComputed" }] : /* istanbul ignore next */ []))`, ); }); @@ -429,7 +435,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('computed(() => testSignal())'); + expect(cleanNewLines(builtContent)).toContain('computed( () => testSignal() )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -444,8 +450,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `computed(() => testSignal(), { debugName: "testComputed" })`, + expect(cleanNewLines(builtContent)).toContain( + `computed( () => testSignal(), { debugName: "testComputed" } )`, ); }); @@ -461,8 +467,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `computed(() => testSignal(), { ...(ngDevMode ? { debugName: "testComputed" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `computed(() => testSignal(), { ...(ngDevMode ? { debugName: "testComputed" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -481,7 +487,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; - expect(builtContent).toContain(`testComputed = computed(() => testSignal(), { equal })`); + expect(cleanNewLines(builtContent)).toContain( + `testComputed = computed(() => testSignal(), { equal })`, + ); expect(builtContent).not.toContain('ngDevMode'); expect(builtContent).not.toContain('debugName'); }); @@ -501,8 +509,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `testComputed = computed(() => testSignal(), { debugName: "testComputed", equal });`, + expect(cleanNewLines(builtContent)).toContain( + `testComputed = computed(() => testSignal(), { debugName: "testComputed", equal })`, ); }); }); @@ -526,7 +534,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('computed(() => this.testSignal())'); + expect(cleanNewLines(builtContent)).toContain('computed( () => this.testSignal() )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -546,8 +554,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `computed(() => this.testSignal(), { debugName: "testComputed" })`, + expect(cleanNewLines(builtContent)).toContain( + `computed( () => this.testSignal(), { debugName: "testComputed" } )`, ); }); @@ -568,8 +576,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `computed(() => this.testSignal(), { ...(ngDevMode ? { debugName: "testComputed" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `computed(() => this.testSignal(), { ...(ngDevMode ? { debugName: "testComputed" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -593,7 +601,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; - expect(builtContent).toContain(`computed(() => this.testSignal(), { equal })`); + expect(cleanNewLines(builtContent)).toContain( + `computed(() => this.testSignal(), { equal })`, + ); expect(builtContent).not.toContain('ngDevMode'); expect(builtContent).not.toContain('debugName'); }); @@ -617,8 +627,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `computed(() => this.testSignal(), { debugName: "testComputed", equal });`, + expect(cleanNewLines(builtContent)).toContain( + `computed(() => this.testSignal(), { debugName: "testComputed", equal })`, ); }); }); @@ -648,7 +658,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('computed(() => this.testSignal())'); + expect(cleanNewLines(builtContent)).toContain('computed( () => this.testSignal() )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -674,8 +684,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `computed(() => this.testSignal(), { debugName: "testComputed" })`, + expect(cleanNewLines(builtContent)).toContain( + `computed( () => this.testSignal(), { debugName: "testComputed" } )`, ); }); @@ -700,8 +710,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `computed(() => this.testSignal(), { ...(ngDevMode ? { debugName: "testComputed" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `computed(() => this.testSignal(), { ...(ngDevMode ? { debugName: "testComputed" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -728,7 +738,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; - expect(builtContent).toContain(`computed(() => this.testSignal(), { equal })`); + expect(cleanNewLines(builtContent)).toContain( + `computed(() => this.testSignal(), { equal })`, + ); expect(builtContent).not.toContain('ngDevMode'); expect(builtContent).not.toContain('debugName'); }); @@ -756,8 +768,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `computed(() => this.testSignal(), { debugName: "testComputed", equal });`, + expect(cleanNewLines(builtContent)).toContain( + `computed(() => this.testSignal(), { debugName: "testComputed", equal })`, ); }); }); @@ -799,11 +811,11 @@ runInEachFileSystem(() => { ); env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `model('Hello World', ...(ngDevMode ? [{ debugName: "testModel" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `model('Hello World', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testModel" }] : /* istanbul ignore next */ []))`, ); - expect(jsContents).toContain( - `model(...(ngDevMode ? [undefined, { debugName: "testModel2" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `model(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "testModel2" }] : /* istanbul ignore next */ []))`, ); }); @@ -824,7 +836,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('model("Hello World")'); + expect(cleanNewLines(builtContent)).toContain('model( "Hello World" )'); }); describe('.required', () => { @@ -844,8 +856,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `model.required(...(ngDevMode ? [{ debugName: "testModel" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `model.required(/* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testModel" }] : /* istanbul ignore next */ []))`, ); }); @@ -865,8 +877,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `model.required({ ...(ngDevMode ? { debugName: "testModel" } : {}), alias: 'testModelAlias' })`, + expect(cleanNewLines(jsContents)).toContain( + `model.required({ ...(ngDevMode ? { debugName: "testModel" } : /* istanbul ignore next */ {}), alias: 'testModelAlias' })`, ); }); @@ -906,7 +918,9 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain(`model.required({ debugName: "testModel" });`); + expect(cleanNewLines(builtContent)).toContain( + `model.required( { debugName: "testModel" } )`, + ); }); it('should tree-shake away debug info if in prod mode with custom options', async () => { @@ -945,8 +959,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `model.required({ debugName: "testModel", alias: "testModelAlias" });`, + expect(cleanNewLines(builtContent)).toContain( + `model.required({ debugName: "testModel", alias: "testModelAlias" })`, ); }); }); @@ -989,8 +1003,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `input(...(ngDevMode ? [undefined, { debugName: "testInput" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `input(/* @ts-ignore */ ...(ngDevMode ? [undefined, { debugName: "testInput" }] : /* istanbul ignore next */ []))`, ); }); @@ -1032,8 +1046,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `input.required(...(ngDevMode ? [{ debugName: "testInput" }] : []))`, + expect(cleanNewLines(jsContents)).toContain( + `input.required(/* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testInput" }] : /* istanbul ignore next */ []))`, ); }); @@ -1053,8 +1067,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `input.required({ ...(ngDevMode ? { debugName: "testInput" } : {}), alias: 'testInputAlias' })`, + expect(cleanNewLines(jsContents)).toContain( + `input.required({ ...(ngDevMode ? { debugName: "testInput" } : /* istanbul ignore next */ {}), alias: 'testInputAlias' })`, ); }); @@ -1096,7 +1110,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain(`input.required({ debugName: "testInput" });`); + expect(cleanNewLines(builtContent)).toContain( + `input.required( { debugName: "testInput" } )`, + ); }); it('should tree-shake away debug info if in prod mode with custom options', async () => { @@ -1137,8 +1153,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `input.required({ debugName: "testInput", alias: "testInputAlias" });`, + expect(cleanNewLines(builtContent)).toContain( + `input.required({ debugName: "testInput", alias: "testInputAlias" })`, ); }); }); @@ -1190,11 +1206,11 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `viewChild('foo', ...(ngDevMode ? [{ debugName: "testViewChild" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `viewChild('foo', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testViewChild" }] : /* istanbul ignore next */ []))`, ); - expect(jsContents).toContain( - `viewChild(ChildComponent, ...(ngDevMode ? [{ debugName: "testViewChildComponent" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `viewChild(ChildComponent, /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testViewChildComponent" }] : /* istanbul ignore next */ []))`, ); }); @@ -1224,8 +1240,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain(`viewChild("foo")`); - expect(builtContent).toContain(`viewChild(ChildComponent)`); + expect(cleanNewLines(builtContent)).toContain(`viewChild( "foo" )`); + expect(cleanNewLines(builtContent)).toContain(`viewChild( ChildComponent )`); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -1253,9 +1269,11 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain(`viewChild("foo", { debugName: "testViewChild" })`); - expect(builtContent).toContain( - `viewChild(ChildComponent, { debugName: "testViewChildComponent" })`, + expect(cleanNewLines(builtContent)).toContain( + `viewChild( "foo", { debugName: "testViewChild" } )`, + ); + expect(cleanNewLines(builtContent)).toContain( + `viewChild( ChildComponent, { debugName: "testViewChildComponent" } )`, ); }); @@ -1297,7 +1315,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( `viewChild("foo", { debugName: "testViewChild", read: ElementRef })`, ); }); @@ -1339,8 +1357,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `viewChildren('foo', ...(ngDevMode ? [{ debugName: "testViewChildren" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `viewChildren('foo', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testViewChildren" }] : /* istanbul ignore next */ []))`, ); }); @@ -1362,7 +1380,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('viewChildren("foo")'); + expect(cleanNewLines(builtContent)).toContain('viewChildren( "foo" )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -1382,7 +1400,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain(`viewChildren("foo", { debugName: "testViewChildren" })`); + expect(cleanNewLines(builtContent)).toContain( + `viewChildren( "foo", { debugName: "testViewChildren" } )`, + ); }); it('should tree-shake away debug info if in prod mode with existing options', async () => { @@ -1403,7 +1423,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('viewChildren("foo", { read: ElementRef })'); + expect(cleanNewLines(builtContent)).toContain('viewChildren("foo", { read: ElementRef })'); }); it('should not tree-shake away debug info if in dev mode with existing options', async () => { @@ -1423,7 +1443,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( `viewChildren("foo", { debugName: "testViewChild", read: ElementRef })`, ); }); @@ -1465,8 +1485,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `contentChild('foo', ...(ngDevMode ? [{ debugName: "testContentChild" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `contentChild('foo', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testContentChild" }] : /* istanbul ignore next */ []))`, ); }); @@ -1488,7 +1508,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('contentChild("foo")'); + expect(cleanNewLines(builtContent)).toContain('contentChild( "foo" )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -1508,7 +1528,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain(`contentChild("foo", { debugName: "testContentChild" })`); + expect(cleanNewLines(builtContent)).toContain( + `contentChild( "foo", { debugName: "testContentChild" } )`, + ); }); it('should tree-shake away debug info if in prod mode with existing options', async () => { @@ -1529,7 +1551,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('contentChild("foo", { read: ElementRef })'); + expect(cleanNewLines(builtContent)).toContain('contentChild("foo", { read: ElementRef })'); }); it('should not tree-shake away debug info if in dev mode with existing options', async () => { @@ -1549,7 +1571,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( `contentChild("foo", { debugName: "testContentChild", read: ElementRef })`, ); }); @@ -1593,8 +1615,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `contentChildren('foo', ...(ngDevMode ? [{ debugName: "testContentChildren" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `contentChildren('foo', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testContentChildren" }] : /* istanbul ignore next */ []))`, ); }); @@ -1616,7 +1638,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('contentChildren("foo")'); + expect(cleanNewLines(builtContent)).toContain('contentChildren( "foo" )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -1636,8 +1658,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `contentChildren("foo", { debugName: "testContentChildren" })`, + expect(cleanNewLines(builtContent)).toContain( + `contentChildren( "foo", { debugName: "testContentChildren" } )`, ); }); @@ -1659,7 +1681,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('contentChildren("foo", { read: ElementRef })'); + expect(cleanNewLines(builtContent)).toContain( + 'contentChildren("foo", { read: ElementRef })', + ); }); it('should not tree-shake away debug info if in dev mode with existing options', async () => { @@ -1679,7 +1703,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( `contentChildren("foo", { debugName: "testContentChildren", read: ElementRef })`, ); }); @@ -1723,8 +1747,8 @@ runInEachFileSystem(() => { ); env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `effect(() => this.testSignal(), ...(ngDevMode ? [{ debugName: "testEffect" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `effect(() => this.testSignal(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testEffect" }] : /* istanbul ignore next */ []))`, ); }); @@ -1745,7 +1769,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('effect(() => this.testSignal())'); + expect(cleanNewLines(builtContent)).toContain('effect( () => this.testSignal() )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -1766,8 +1790,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `effect(() => this.testSignal(), { debugName: "testEffect" })`, + expect(cleanNewLines(builtContent)).toContain( + `effect( () => this.testSignal(), { debugName: "testEffect" } )`, ); }); @@ -1790,7 +1814,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( 'effect(() => this.testSignal(), { manualCleanup: !0, allowSignalWrites: !0 })', ); }); @@ -1813,7 +1837,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( `effect(() => this.testSignal(), { debugName: "testEffect", manualCleanup: !0, allowSignalWrites: !0 })`, ); }); @@ -1844,8 +1868,8 @@ runInEachFileSystem(() => { ); env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `linkedSignal(() => testSignal(), ...(ngDevMode ? [{ debugName: "testLinkedSignal" }] : [])`, + expect(cleanNewLines(jsContents)).toContain( + `linkedSignal(() => testSignal(), /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testLinkedSignal" }] : /* istanbul ignore next */ []))`, ); }); @@ -1863,7 +1887,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('linkedSignal(() => testSignal())'); + expect(cleanNewLines(builtContent)).toContain('linkedSignal( () => testSignal() )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -1878,8 +1902,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `linkedSignal(() => testSignal(), { debugName: "testLinkedSignal" })`, + expect(cleanNewLines(builtContent)).toContain( + `linkedSignal( () => testSignal(), { debugName: "testLinkedSignal" } )`, ); }); @@ -1895,8 +1919,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `linkedSignal(() => testSignal(), { ...(ngDevMode ? { debugName: "testLinkedSignal" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `linkedSignal(() => testSignal(), { ...(ngDevMode ? { debugName: "testLinkedSignal" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -1915,7 +1939,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( `testLinkedSignal = linkedSignal(() => testSignal(), { equal })`, ); expect(builtContent).not.toContain('ngDevMode'); @@ -1937,7 +1961,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( `testLinkedSignal = linkedSignal(() => testSignal(), { debugName: "testLinkedSignal", equal });`, ); }); @@ -1958,7 +1982,7 @@ runInEachFileSystem(() => { const jsContents = cleanNewLines(env.getContents('test.js')); expect(jsContents).toContain( - 'testLinkedSignal = linkedSignal({ ...(ngDevMode ? { debugName: "testLinkedSignal" } : {}), ' + + 'testLinkedSignal = linkedSignal({ ...(ngDevMode ? { debugName: "testLinkedSignal" } : /* istanbul ignore next */ {}), ' + 'source: testSignal, ' + 'computation: (src, prev) => src ' + '})', @@ -2039,7 +2063,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('linkedSignal(() => this.testSignal())'); + expect(cleanNewLines(builtContent)).toContain('linkedSignal( () => this.testSignal() )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -2059,8 +2083,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `linkedSignal(() => this.testSignal(), { debugName: "testLinkedSignal" })`, + expect(cleanNewLines(builtContent)).toContain( + `linkedSignal( () => this.testSignal(), { debugName: "testLinkedSignal" } )`, ); }); @@ -2081,8 +2105,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `linkedSignal(() => this.testSignal(), { ...(ngDevMode ? { debugName: "testLinkedSignal" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `linkedSignal(() => this.testSignal(), { ...(ngDevMode ? { debugName: "testLinkedSignal" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -2131,7 +2155,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( + expect(cleanNewLines(builtContent)).toContain( `this.testLinkedSignal = linkedSignal(() => this.testSignal(), { debugName: "testLinkedSignal", equal });`, ); }); @@ -2157,7 +2181,7 @@ runInEachFileSystem(() => { const jsContents = cleanNewLines(env.getContents('test.js')); expect(jsContents).toContain( - 'linkedSignal({ ...(ngDevMode ? { debugName: "testLinkedSignal" } : {}), ' + + 'linkedSignal({ ...(ngDevMode ? { debugName: "testLinkedSignal" } : /* istanbul ignore next */ {}), ' + 'source: this.testSignal, ' + 'computation: (src, prev) => src ' + '})', @@ -2251,7 +2275,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; expect(builtContent).not.toContain('debugName'); - expect(builtContent).toContain('linkedSignal(() => this.testSignal())'); + expect(cleanNewLines(builtContent)).toContain('linkedSignal( () => this.testSignal() )'); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -2276,8 +2300,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; - expect(builtContent).toContain( - `linkedSignal(() => this.testSignal(), { debugName: "testLinkedSignal" })`, + expect(cleanNewLines(builtContent)).toContain( + `linkedSignal( () => this.testSignal(), { debugName: "testLinkedSignal" } )`, ); }); @@ -2303,8 +2327,8 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain( - `linkedSignal(() => this.testSignal(), { ...(ngDevMode ? { debugName: "testLinkedSignal" } : {}), equal: () => true })`, + expect(cleanNewLines(jsContents)).toContain( + `linkedSignal(() => this.testSignal(), { ...(ngDevMode ? { debugName: "testLinkedSignal" } : /* istanbul ignore next */ {}), equal: () => true })`, ); }); @@ -2394,7 +2418,7 @@ runInEachFileSystem(() => { const jsContents = cleanNewLines(env.getContents('test.js')); expect(jsContents).toContain( - 'linkedSignal({ ...(ngDevMode ? { debugName: "testLinkedSignal" } : {}), ' + + 'linkedSignal({ ...(ngDevMode ? { debugName: "testLinkedSignal" } : /* istanbul ignore next */ {}), ' + 'source: this.testSignal, ' + 'computation: (src, prev) => src ' + '})', @@ -2509,7 +2533,7 @@ runInEachFileSystem(() => { const jsContents = cleanNewLines(env.getContents('test.js')); expect(jsContents).toContain( 'resource({ ' + - '...(ngDevMode ? { debugName: "testResource" } : {}), ' + + '...(ngDevMode ? { debugName: "testResource" } : /* istanbul ignore next */ {}), ' + `defaultValue: 'foo', ` + `loader: async () => 'bar' ` + '})', @@ -2587,7 +2611,7 @@ runInEachFileSystem(() => { const jsContents = cleanNewLines(env.getContents('test.js')); expect(jsContents).toContain( 'resource({ ' + - '...(ngDevMode ? { debugName: "testResource" } : {}), ' + + '...(ngDevMode ? { debugName: "testResource" } : /* istanbul ignore next */ {}), ' + `defaultValue: 'foo', ` + `loader: async () => 'bar' ` + '})', @@ -2678,7 +2702,7 @@ runInEachFileSystem(() => { const jsContents = cleanNewLines(env.getContents('test.js')); expect(jsContents).toContain( 'resource({ ' + - '...(ngDevMode ? { debugName: "testResource" } : {}), ' + + '...(ngDevMode ? { debugName: "testResource" } : /* istanbul ignore next */ {}), ' + `defaultValue: 'foo', ` + `loader: async () => 'bar' ` + '})', @@ -2775,9 +2799,9 @@ runInEachFileSystem(() => { `, ); env.driveMain(); - const jsContents = cleanNewLines(env.getContents('test.js')); - expect(jsContents).toContain( - `httpResource(() => '/api', ...(ngDevMode ? [{ debugName: "testHttpResource" }] : []))`, + const jsContents = env.getContents('test.js'); + expect(cleanNewLines(jsContents)).toContain( + `httpResource(() => '/api', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testHttpResource" }] : /* istanbul ignore next */ []))`, ); }); @@ -2796,7 +2820,7 @@ runInEachFileSystem(() => { const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; const contentWoNewLines = cleanNewLines(builtContent); expect(contentWoNewLines).not.toContain('debugName'); - expect(contentWoNewLines).toContain(`testHttpResource = httpResource(() => "/api")`); + expect(contentWoNewLines).toContain(`testHttpResource = httpResource( () => "/api" )`); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -2813,9 +2837,7 @@ runInEachFileSystem(() => { const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; const contentWoNewLines = cleanNewLines(builtContent); expect(contentWoNewLines).toContain( - 'testHttpResource = httpResource(() => "/api", { ' + - 'debugName: "testHttpResource" ' + - '})', + 'testHttpResource = httpResource( () => "/api", { debugName: "testHttpResource" } )', ); }); }); @@ -2837,7 +2859,7 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = cleanNewLines(env.getContents('test.js')); expect(jsContents).toContain( - `httpResource(() => '/api', ...(ngDevMode ? [{ debugName: "testHttpResource" }] : []))`, + `httpResource(() => '/api', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testHttpResource" }] : /* istanbul ignore next */ []))`, ); }); @@ -2860,7 +2882,7 @@ runInEachFileSystem(() => { const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; const contentWoNewLines = cleanNewLines(builtContent); expect(contentWoNewLines).not.toContain('debugName'); - expect(contentWoNewLines).toContain(`testHttpResource = httpResource(() => "/api")`); + expect(contentWoNewLines).toContain(`testHttpResource = httpResource( () => "/api" )`); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -2882,9 +2904,7 @@ runInEachFileSystem(() => { const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; const contentWoNewLines = cleanNewLines(builtContent); expect(contentWoNewLines).toContain( - 'testHttpResource = httpResource(() => "/api", { ' + - 'debugName: "testHttpResource" ' + - '})', + 'testHttpResource = httpResource( () => "/api", { debugName: "testHttpResource" } )', ); }); }); @@ -2909,7 +2929,7 @@ runInEachFileSystem(() => { env.driveMain(); const jsContents = cleanNewLines(env.getContents('test.js')); expect(jsContents).toContain( - `httpResource(() => '/api', ...(ngDevMode ? [{ debugName: "testHttpResource" }] : []))`, + `httpResource(() => '/api', /* @ts-ignore */ ...(ngDevMode ? [{ debugName: "testHttpResource" }] : /* istanbul ignore next */ []))`, ); }); @@ -2935,7 +2955,7 @@ runInEachFileSystem(() => { const builtContent = (await esbuild.transform(jsContents, minifiedProdBuildOptions)).code; const contentWoNewLines = cleanNewLines(builtContent); expect(contentWoNewLines).not.toContain('debugName'); - expect(contentWoNewLines).toContain(`testHttpResource = httpResource(() => "/api")`); + expect(contentWoNewLines).toContain(`testHttpResource = httpResource( () => "/api" )`); }); it('should not tree-shake away debug info if in dev mode', async () => { @@ -2960,9 +2980,7 @@ runInEachFileSystem(() => { const builtContent = (await esbuild.transform(jsContents, minifiedDevBuildOptions)).code; const contentWoNewLines = cleanNewLines(builtContent); expect(contentWoNewLines).toContain( - 'testHttpResource = httpResource(() => "/api", { ' + - 'debugName: "testHttpResource" ' + - '})', + 'testHttpResource = httpResource( () => "/api", { debugName: "testHttpResource" } )', ); }); }); diff --git a/packages/compiler-cli/test/ngtsc/defer_spec.ts b/packages/compiler-cli/test/ngtsc/defer_spec.ts index 07398f1d0c92..c7fbadd1c262 100644 --- a/packages/compiler-cli/test/ngtsc/defer_spec.ts +++ b/packages/compiler-cli/test/ngtsc/defer_spec.ts @@ -15,6 +15,10 @@ import {NgtscTestEnvironment} from './env'; const testFiles = loadStandardTestFiles(); +function cleanNewLines(contents: string) { + return contents.replace(/\s*\n\s*/g, ' '); +} + runInEachFileSystem(() => { describe('ngtsc @defer block', () => { let env!: NgtscTestEnvironment; @@ -69,7 +73,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA), LocalDep]'); + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.CmpA), LocalDep]', + ); // The `CmpA` symbol wasn't referenced elsewhere, so it can be defer-loaded // via dynamic imports and an original import can be removed. @@ -119,6 +125,85 @@ runInEachFileSystem(() => { ); }); + it('should include incremental hydration runtime activator when `@defer` uses hydrate triggers', () => { + env.write( + 'cmp-a.ts', + ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'cmp-a', + template: 'CmpA!' + }) + export class CmpA {} + `, + ); + + env.write( + '/test.ts', + ` + import { Component } from '@angular/core'; + import { CmpA } from './cmp-a'; + + @Component({ + selector: 'test-cmp', + imports: [CmpA], + template: \` + @defer (hydrate on idle) { + + } + \`, + }) + export class TestCmp {} + `, + ); + + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('i0.ɵɵenableIncrementalHydrationRuntime'); + expect(jsContents).toContain('i0.ɵɵdeferHydrateOnIdle()'); + }); + + it('should NOT include incremental hydration runtime activator when `@defer` has no hydrate triggers', () => { + env.write( + 'cmp-a.ts', + ` + import { Component } from '@angular/core'; + + @Component({ + selector: 'cmp-a', + template: 'CmpA!' + }) + export class CmpA {} + `, + ); + + env.write( + '/test.ts', + ` + import { Component } from '@angular/core'; + import { CmpA } from './cmp-a'; + + @Component({ + selector: 'test-cmp', + imports: [CmpA], + template: \` + @defer (on idle) { + + } + \`, + }) + export class TestCmp {} + `, + ); + + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).not.toContain('ɵɵenableIncrementalHydrationRuntime'); + }); + describe('imports', () => { it('should retain regular imports when symbol is eagerly referenced', () => { env.write( @@ -278,10 +363,8 @@ runInEachFileSystem(() => { // Both `CmpA` and `CmpB` were used inside the defer block and were not // referenced elsewhere, so we generate dynamic imports and drop a regular one. - expect(jsContents).toContain( - '() => [' + - 'import("./cmp-a").then(m => m.CmpA), ' + - 'import("./cmp-a").then(m => m.CmpB)]', + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.CmpA), /* @ts-ignore */ import("./cmp-a").then(m => m.CmpB)]', ); expect(jsContents).not.toContain('import { CmpA, CmpB }'); }); @@ -324,7 +407,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]'); + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.CmpA)]', + ); // The `CmpA` symbol wasn't referenced elsewhere, so it can be defer-loaded // via dynamic imports and an original import can be removed. @@ -373,7 +458,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]'); + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.CmpA)]', + ); expect(jsContents).not.toContain('import { CmpA }'); }); @@ -420,7 +507,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]'); + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.CmpA)]', + ); expect(jsContents).not.toContain('import { CmpA }'); }); @@ -467,7 +556,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]'); + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.CmpA)]', + ); expect(jsContents).not.toContain('import { CmpA }'); }); @@ -514,7 +605,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain('() => [import("./cmp-a").then(m => m.CmpA)]'); + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.CmpA)]', + ); expect(jsContents).not.toContain('import { CmpA }'); }); @@ -564,8 +657,8 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain( - '() => [import("./cmps").then(m => m.CmpA), import("./cmps").then(m => m.CmpB)]', + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmps").then(m => m.CmpA), /* @ts-ignore */ import("./cmps").then(m => m.CmpB)]', ); expect(jsContents).not.toContain('import { CmpA }'); expect(jsContents).not.toContain('import { CmpB }'); @@ -610,11 +703,11 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain( - 'const TestCmp_Defer_1_DepsFn = () => [import("./cmp-a").then(m => m.default), LocalDep];', + expect(cleanNewLines(jsContents)).toContain( + 'const TestCmp_Defer_1_DepsFn = () => [/* @ts-ignore */ import("./cmp-a").then(m => m.default), LocalDep];', ); - expect(jsContents).toContain( - 'i0.ɵsetClassMetadataAsync(TestCmp, () => [import("./cmp-a").then(m => m.default)]', + expect(cleanNewLines(jsContents)).toContain( + 'i0.ɵsetClassMetadataAsync(TestCmp, () => [/* @ts-ignore */ import("./cmp-a").then(m => m.default)]', ); // The `CmpA` symbol wasn't referenced elsewhere, so it can be defer-loaded // via dynamic imports and an original import can be removed. @@ -673,7 +766,9 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain('() => [import("./cmp").then(m => m.Cmp)]'); + expect(cleanNewLines(jsContents)).toContain( + '() => [/* @ts-ignore */ import("./cmp").then(m => m.Cmp)]', + ); expect(jsContents).not.toContain('import { Cmp }'); }); @@ -918,13 +1013,13 @@ runInEachFileSystem(() => { // Expect that all deferrableImports in local compilation mode // are located in a single function (since we can't detect in // the local mode which components belong to which block). - expect(jsContents).toContain( - 'const AppCmp_For_1_Conditional_0_Defer_1_DepsFn = () => [' + - 'import("./deferred-a").then(m => m.DeferredCmpA), ' + + expect(cleanNewLines(jsContents)).toContain( + 'const AppCmp_For_1_Conditional_0_Defer_1_DepsFn = () => [/* @ts-ignore */ ' + + 'import("./deferred-a").then(m => m.DeferredCmpA), /* @ts-ignore */ ' + 'import("./pipe-a").then(m => m.PipeA)];', ); - expect(jsContents).toContain( - 'const AppCmp_For_1_Conditional_0_Defer_4_DepsFn = () => [' + + expect(cleanNewLines(jsContents)).toContain( + 'const AppCmp_For_1_Conditional_0_Defer_4_DepsFn = () => [/* @ts-ignore */ ' + 'import("./deferred-b").then(m => m.DeferredCmpB)];', ); @@ -938,10 +1033,10 @@ runInEachFileSystem(() => { expect(jsContents).toContain('ɵɵdefer(4, 3, AppCmp_For_1_Conditional_0_Defer_4_DepsFn);'); // Expect `ɵsetClassMetadataAsync` to contain dynamic imports too. - expect(jsContents).toContain( - 'ɵsetClassMetadataAsync(AppCmp, () => [' + - 'import("./deferred-a").then(m => m.DeferredCmpA), ' + - 'import("./pipe-a").then(m => m.PipeA), ' + + expect(cleanNewLines(jsContents)).toContain( + 'ɵsetClassMetadataAsync(AppCmp, () => [/* @ts-ignore */ ' + + 'import("./deferred-a").then(m => m.DeferredCmpA), /* @ts-ignore */ ' + + 'import("./pipe-a").then(m => m.PipeA), /* @ts-ignore */ ' + 'import("./deferred-b").then(m => m.DeferredCmpB)], ' + '(DeferredCmpA, PipeA, DeferredCmpB) => {', ); @@ -1023,15 +1118,13 @@ runInEachFileSystem(() => { // Expect that all deferrableImports to become dynamic imports. // Other imported symbols remain eager. - expect(jsContents).toContain( - 'const AppCmp_Defer_1_DepsFn = () => [' + - 'import("./deferred-a").then(m => m.DeferredCmpA), ' + - 'EagerCmpA];', + expect(cleanNewLines(jsContents)).toContain( + 'const AppCmp_Defer_1_DepsFn = () => [/* @ts-ignore */ ' + + 'import("./deferred-a").then(m => m.DeferredCmpA), EagerCmpA];', ); - expect(jsContents).toContain( - 'const AppCmp_Defer_4_DepsFn = () => [' + - 'import("./deferred-b").then(m => m.DeferredCmpB), ' + - 'EagerCmpA];', + expect(cleanNewLines(jsContents)).toContain( + 'const AppCmp_Defer_4_DepsFn = () => [/* @ts-ignore */ ' + + 'import("./deferred-b").then(m => m.DeferredCmpB), EagerCmpA];', ); // Make sure there are no eager imports present in the output. @@ -1046,9 +1139,9 @@ runInEachFileSystem(() => { expect(jsContents).toContain('ɵɵdefer(4, 3, AppCmp_Defer_4_DepsFn);'); // Expect `ɵsetClassMetadataAsync` to contain dynamic imports too. - expect(jsContents).toContain( - 'ɵsetClassMetadataAsync(AppCmp, () => [' + - 'import("./deferred-a").then(m => m.DeferredCmpA), ' + + expect(cleanNewLines(jsContents)).toContain( + 'ɵsetClassMetadataAsync(AppCmp, () => [/* @ts-ignore */ ' + + 'import("./deferred-a").then(m => m.DeferredCmpA), /* @ts-ignore */ ' + 'import("./deferred-b").then(m => m.DeferredCmpB)], ' + '(DeferredCmpA, DeferredCmpB) => {', ); @@ -1379,14 +1472,10 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain( - // ngDevMode check is present + expect(cleanNewLines(jsContents)).toContain( '(() => { (typeof ngDevMode === "undefined" || ngDevMode) && ' + - // Main `setClassMetadataAsync` call 'i0.ɵsetClassMetadataAsync(TestCmp, ' + - // Dependency loading function (note: no local `LocalDep` here) - '() => [import("./cmp-a").then(m => m.CmpA)], ' + - // Callback that invokes `setClassMetadata` at the end + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.CmpA)], ' + 'CmpA => { i0.ɵsetClassMetadata(TestCmp', ); }); @@ -1494,14 +1583,13 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain('ɵɵdefer(1, 0, TestCmp_Defer_1_DepsFn)'); - expect(jsContents).toContain( - // ngDevMode check is present + expect(cleanNewLines(jsContents)).toContain( '(() => { (typeof ngDevMode === "undefined" || ngDevMode) && ' + // Main `setClassMetadataAsync` call 'i0.ɵsetClassMetadataAsync(TestCmp, ' + // Dependency loading function (note: no local `LocalDep` here) - '() => [import("./cmp-a").then(m => m.default)], ' + // Callback that invokes `setClassMetadata` at the end + '() => [/* @ts-ignore */ import("./cmp-a").then(m => m.default)], ' + 'CmpA => { i0.ɵsetClassMetadata(TestCmp', ); }); diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/function_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/function_doc_extraction_spec.ts index 63f810850368..99b7e6ebb7a5 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/function_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/function_doc_extraction_spec.ts @@ -126,6 +126,56 @@ runInEachFileSystem(() => { expect(numberOverloadEntry.returnType).toBe('number'); }); + it('should use overload docs when implementation has no docs', () => { + env.write( + 'index.ts', + ` + /** + * Overload docs. + * + * @publicApi + */ + export function ident(value: boolean): boolean + export function ident(value: boolean|number): boolean|number { + return value; + } + `, + ); + + const docs = env.driveDocsExtraction('index.ts') as FunctionEntry[]; + const [functionEntry] = docs; + + expect(functionEntry.description).toContain('Overload docs.'); + expect(functionEntry.rawComment).toContain('Overload docs.'); + expect(functionEntry.jsdocTags.some((tag) => tag.name === 'publicApi')).toBeTrue(); + expect(functionEntry.implementation.description).toBe(''); + }); + + it('should prefer implementation docs over overload docs', () => { + env.write( + 'index.ts', + ` + /** Overload docs. */ + export function ident(value: boolean): boolean + /** + * Implementation docs. + * + * @publicApi + */ + export function ident(value: boolean|number): boolean|number { + return value; + } + `, + ); + + const docs = env.driveDocsExtraction('index.ts') as FunctionEntry[]; + const [functionEntry] = docs; + + expect(functionEntry.description).toContain('Implementation docs.'); + expect(functionEntry.rawComment).toContain('Implementation docs.'); + expect(functionEntry.jsdocTags.some((tag) => tag.name === 'publicApi')).toBeTrue(); + }); + it('should extract function generics', () => { env.write( 'index.ts', diff --git a/packages/compiler-cli/test/ngtsc/env.ts b/packages/compiler-cli/test/ngtsc/env.ts index 24ac6666d65e..f0c3c8f340dc 100644 --- a/packages/compiler-cli/test/ngtsc/env.ts +++ b/packages/compiler-cli/test/ngtsc/env.ts @@ -6,16 +6,16 @@ * found in the LICENSE file at https://angular.dev/license */ +import ts from 'typescript'; import { + createCompilerHost, + createProgram, CustomTransformers, defaultGatherDiagnostics, Program, - createCompilerHost, - createProgram, } from '../../index'; import {DocEntry} from '../../src/ngtsc/docs'; import * as api from '../../src/transformers/api'; -import ts from 'typescript'; import {mainXi18n} from '../../src/extract_i18n'; import {main, mainDiagnosticsForTest, readNgcCommandLineAndConfiguration} from '../../src/main'; @@ -229,6 +229,11 @@ export class NgtscTestEnvironment { compilerOptions?: TsCompilerOptions, files?: string[], ): void { + // TODO: all tests should have template that pass strictness + if (!('strictTemplates' in extraOpts)) { + extraOpts['strictTemplates'] = false; + } + let tsconfig: {[key: string]: any} = { extends: './tsconfig-base.json', angularCompilerOptions: extraOpts, diff --git a/packages/compiler-cli/test/ngtsc/host_bindings_type_check_spec.ts b/packages/compiler-cli/test/ngtsc/host_bindings_type_check_spec.ts index d1aa2a8797af..127492bc81ac 100644 --- a/packages/compiler-cli/test/ngtsc/host_bindings_type_check_spec.ts +++ b/packages/compiler-cli/test/ngtsc/host_bindings_type_check_spec.ts @@ -278,7 +278,7 @@ runInEachFileSystem(() => { expect(getDiagnosticSourceCode(diags[0])).toBe('$event'); }); - it('should check host animation event listeners', () => { + it('should check legacy host animation event listeners', () => { env.write( 'test.ts', ` @@ -301,6 +301,56 @@ runInEachFileSystem(() => { expect(getDiagnosticSourceCode(diags[0])).toBe('handleEvent'); }); + it('should check animation event listeners in `host` object', () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + + @Component({ + template: '', + selector: 'button[foo]', + host: {'(animate.leave)': 'handleEvent($event)'}, + }) + export class Comp { + handleEvent(event: Event) {} + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect((diags[0].messageText as ts.DiagnosticMessageChain).messageText).toContain( + `Argument of type 'AnimationCallbackEvent' is not assignable to parameter of type 'Event'.`, + ); + expect(getDiagnosticSourceCode(diags[0])).toBe('$event'); + }); + + it('should check animation event listeners in `@HostListener`', () => { + env.write( + 'test.ts', + ` + import {Component, HostListener} from '@angular/core'; + + @Component({ + template: '', + selector: 'button[foo]', + }) + export class Comp { + @HostListener('animate.leave', ['$event']) + handleEvent(event: Event) {} + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect((diags[0].messageText as ts.DiagnosticMessageChain).messageText).toContain( + `Argument of type 'AnimationCallbackEvent' is not assignable to parameter of type 'Event'.`, + ); + expect(getDiagnosticSourceCode(diags[0])).toBe('$event'); + }); + it('should not leak @let from the template into the host bindings', () => { env.write( 'test.ts', @@ -579,7 +629,7 @@ runInEachFileSystem(() => { expect(getDiagnosticSourceCode(diags[1])).toBe('handleClick'); }); - it('should report diagnostic on the entire initializer of property binding if node contains escaped string', () => { + it('should report diagnostic on the entire expression of property binding if node contains escaped string', () => { env.write( 'test.ts', ` @@ -599,10 +649,10 @@ runInEachFileSystem(() => { const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); expect(diags[0].messageText).toBe(`Property 'doesNotExist' does not exist on type 'Dir'.`); - expect(getDiagnosticSourceCode(diags[0])).toBe(`'prefix + \\'123\\' + doesNotExist'`); + expect(getDiagnosticSourceCode(diags[0])).toBe(`prefix + \\'123\\' + doesNotExist`); }); - it('should report diagnostic on the entire initializer of event binding if node contains escaped string', () => { + it('should report diagnostic on the entire expression of event binding if node contains escaped string', () => { env.write( 'test.ts', ` @@ -624,7 +674,7 @@ runInEachFileSystem(() => { expect(diags[0].messageText).toBe( `Argument of type 'string' is not assignable to parameter of type 'number'.`, ); - expect(getDiagnosticSourceCode(diags[0])).toBe(`'handleClick(\\'foo\\')'`); + expect(getDiagnosticSourceCode(diags[0])).toBe(`handleClick(\\'foo\\')`); }); it('should preserve diagnostic location of nodes that occur before escaped string', () => { diff --git a/packages/compiler-cli/test/ngtsc/host_directives_spec.ts b/packages/compiler-cli/test/ngtsc/host_directives_spec.ts index 64569f788b84..519ce707ca65 100644 --- a/packages/compiler-cli/test/ngtsc/host_directives_spec.ts +++ b/packages/compiler-cli/test/ngtsc/host_directives_spec.ts @@ -519,6 +519,266 @@ runInEachFileSystem(() => { expect(messages).toEqual([]); }); + describe('de-duplication', () => { + it('should expose original inputs if a directive matches both as a host directive and through the template', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Input} from '@angular/core'; + + @Directive({selector: '[dir]'}) + export class HostDir { + @Input() value = 0; + @Input({alias: 'otherAlias'}) other = false; + } + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, inputs: ['value: valueAlias']}] + }) + export class Dir {} + + @Component({ + selector: 'my-comp', + template: '
    ', + imports: [HostDir, Dir] + }) + export class App { + greeting = 'hi'; + } + `, + ); + + // First diagnostic checks that the input is available under the original name. + // Second diagnostic checks that an input that would otherwise be hidden by a host directive is available. + // Third diagnostic checks that the host directive alias does not apply. + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(3); + expect(diags[0].messageText).toBe(`Type 'string' is not assignable to type 'number'.`); + expect(diags[1].messageText).toBe(`Type 'string' is not assignable to type 'boolean'.`); + expect(diags[2].messageText).toBe( + `Can't bind to 'valueAlias' since it isn't a known property of 'div'.`, + ); + }); + + it('should expose original outputs if a directive matches both as a host directive and through the template', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Output, EventEmitter} from '@angular/core'; + + @Directive({selector: '[dir]'}) + export class HostDir { + @Output() eventOne = new EventEmitter(); + @Output('twoAlias') eventTwo = new EventEmitter(); + } + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, outputs: ['eventOne: oneAlias']}] + }) + export class Dir {} + + @Component({ + selector: 'my-comp', + template: '
    ', + imports: [HostDir, Dir] + }) + export class App { + expectsString(value: string) {} + } + `, + ); + + // First diagnostic checks that the output is available under the original name. + // Second diagnostic checks that an output that would otherwise be hidden by a host directive is available. + // Third diagnostic checks that the host directive alias does not apply. + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(3); + expect(diags[0].messageText).toBe( + `Argument of type 'number' is not assignable to parameter of type 'string'.`, + ); + expect(diags[1].messageText).toBe( + `Argument of type 'boolean' is not assignable to parameter of type 'string'.`, + ); + expect(diags[2].messageText).toBe( + `Argument of type 'Event' is not assignable to parameter of type 'string'.`, + ); + }); + + it('should combine inputs configuration if host directive is exposed multiple times with non-conflicting configurations', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Input} from '@angular/core'; + + @Directive() + export class HostDir { + @Input() value = 0; + @Input({alias: 'otherInput'}) other = false; + } + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, inputs: ['value']}] + }) + export class DirOne {} + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, inputs: ['otherInput: otherAlias']}] + }) + export class DirTwo {} + + @Component({ + selector: 'my-comp', + template: '
    ', + imports: [DirOne, DirTwo] + }) + export class App { + greeting = 'hi'; + } + `, + ); + + const diagnostics = env.driveDiagnostics(); + expect(diagnostics.length).toBe(2); + expect(diagnostics[0].messageText).toBe( + `Type 'string' is not assignable to type 'number'.`, + ); + expect(diagnostics[1].messageText).toBe( + `Type 'string' is not assignable to type 'boolean'.`, + ); + }); + + it('should combine inputs configuration if host directive is exposed multiple times with identical configurations', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Input} from '@angular/core'; + + @Directive() + export class HostDir { + @Input() value = 0; + } + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, inputs: ['value']}] + }) + export class DirOne {} + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, inputs: ['value']}] + }) + export class DirTwo {} + + @Component({ + selector: 'my-comp', + template: '
    ', + imports: [DirOne, DirTwo] + }) + export class App { + greeting = 'hi'; + } + `, + ); + + const diagnostics = env.driveDiagnostics(); + expect(diagnostics.length).toBe(1); + expect(diagnostics[0].messageText).toBe( + `Type 'string' is not assignable to type 'number'.`, + ); + }); + + it('should combine output configuration if host directive is exposed multiple times with non-conflicting configurations', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Output, EventEmitter} from '@angular/core'; + + @Directive() + export class HostDir { + @Output() myEvent = new EventEmitter(); + @Output('otherOutput') myOtherEvent = new EventEmitter(); + } + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, outputs: ['myEvent']}] + }) + export class DirOne {} + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, outputs: ['otherOutput: otherAlias']}] + }) + export class DirTwo {} + + @Component({ + selector: 'my-comp', + template: '
    ', + imports: [DirOne, DirTwo] + }) + export class App { + expectsString(value: string) {} + } + `, + ); + + const diagnostics = env.driveDiagnostics(); + expect(diagnostics.length).toBe(2); + expect(diagnostics[0].messageText).toBe( + `Argument of type 'number' is not assignable to parameter of type 'string'.`, + ); + expect(diagnostics[1].messageText).toBe( + `Argument of type 'boolean' is not assignable to parameter of type 'string'.`, + ); + }); + + it('should combine outputs configuration if host directive is exposed multiple times with identical configurations', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Output, EventEmitter} from '@angular/core'; + + @Directive() + export class HostDir { + @Output() myEvent = new EventEmitter(); + } + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, outputs: ['myEvent']}] + }) + export class DirOne {} + + @Directive({ + selector: '[dir]', + hostDirectives: [{directive: HostDir, outputs: ['myEvent']}] + }) + export class DirTwo {} + + @Component({ + selector: 'my-comp', + template: '
    ', + imports: [DirOne, DirTwo] + }) + export class App { + expectsString(value: string) {} + } + `, + ); + + const diagnostics = env.driveDiagnostics(); + expect(diagnostics.length).toBe(1); + expect(diagnostics[0].messageText).toBe( + `Argument of type 'number' is not assignable to parameter of type 'string'.`, + ); + }); + }); + describe('validations', () => { it('should produce a diagnostic if a host directive is not standalone', () => { env.write( @@ -1079,6 +1339,149 @@ runInEachFileSystem(() => { const diags = env.driveDiagnostics(); expect(diags.length).toBe(0); }); + + it('should produce a diagnostic if an input is exposed under multiple names in a chain of host directives', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Input} from '@angular/core'; + + @Directive() + class DuplicateHostDir { + @Input() inp: any; + } + + @Directive({hostDirectives: [{directive: DuplicateHostDir, inputs: ['inp: alias']}]}) + class HostOne {} + + @Directive({hostDirectives: [HostOne, {directive: DuplicateHostDir, inputs: ['inp']}]}) + class HostTwo {} + + @Directive({ + selector: '[dir]', + hostDirectives: [HostTwo, {directive: DuplicateHostDir, inputs: ['inp: alias2']}], + }) + class Dir {} + + @Component({ + template: '
    ', + imports: [Dir], + }) + class App {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain( + 'Input declared in DuplicateHostDir.inp is exposed under the following conflicting names: "alias", "inp", "alias2"', + ); + }); + + it('should produce a diagnostic if an aliased input is exposed under multiple names in a chain of host directives', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Input} from '@angular/core'; + + @Directive() + class DuplicateHostDir { + @Input({alias: 'foo'}) inp: any; + } + + @Directive({hostDirectives: [{directive: DuplicateHostDir, inputs: ['foo']}]}) + class HostDir {} + + @Directive({ + selector: '[dir]', + hostDirectives: [HostDir, {directive: DuplicateHostDir, inputs: ['foo: alias']}], + }) + class Dir {} + + @Component({ + template: '
    ', + imports: [Dir], + }) + class App {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain( + 'Input declared in DuplicateHostDir.inp is exposed under the following conflicting names: "foo", "alias"', + ); + }); + + it('should produce a diagnostic if an output is exposed under multiple names in a chain of host directives', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Output, EventEmitter} from '@angular/core'; + + @Directive() + class DuplicateHostDir { + @Output() myEvent = new EventEmitter(); + } + + @Directive({hostDirectives: [{directive: DuplicateHostDir, outputs: ['myEvent']}]}) + class HostDir {} + + @Directive({ + selector: '[dir]', + hostDirectives: [HostDir, {directive: DuplicateHostDir, outputs: ['myEvent: alias']}], + }) + class Dir {} + + @Component({ + template: '
    ', + imports: [Dir], + }) + class App {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain( + 'Output declared in DuplicateHostDir.myEvent is exposed under the following conflicting names: "myEvent", "alias"', + ); + }); + + it('should produce a diagnostic if an aliased output is exposed under multiple names in a chain of host directives', () => { + env.write( + 'test.ts', + ` + import {Directive, Component, Output, EventEmitter} from '@angular/core'; + + @Directive() + class DuplicateHostDir { + @Output('foo') myEvent = new EventEmitter(); + } + + @Directive({hostDirectives: [{directive: DuplicateHostDir, outputs: ['foo']}]}) + class HostDir {} + + @Directive({ + selector: '[dir]', + hostDirectives: [HostDir, {directive: DuplicateHostDir, outputs: ['foo: alias']}], + }) + class Dir {} + + @Component({ + template: '
    ', + imports: [Dir], + }) + class App {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain( + 'Output declared in DuplicateHostDir.myEvent is exposed under the following conflicting names: "foo", "alias"', + ); + }); }); }); }); diff --git a/packages/compiler-cli/test/ngtsc/incremental_spec.ts b/packages/compiler-cli/test/ngtsc/incremental_spec.ts index b54bb1d54a72..9ebf52da4824 100644 --- a/packages/compiler-cli/test/ngtsc/incremental_spec.ts +++ b/packages/compiler-cli/test/ngtsc/incremental_spec.ts @@ -630,7 +630,7 @@ runInEachFileSystem(() => { }); it('should compile incrementally with template type-checking turned on', () => { - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true}); env.write( 'main.ts', ` @@ -652,7 +652,7 @@ runInEachFileSystem(() => { // This test verifies that ambient types declared in node_modules/@types are still available // in incremental compilations. In the below code, the usage of `require` should be valid // in the original program and the incremental program. - env.tsconfig({fullTemplateTypeCheck: true}, {types: ['node']}); + env.tsconfig({strictTemplates: true}, {types: ['node']}); env.write('node_modules/@types/node/index.d.ts', 'declare var require: any;'); env.write( 'main.ts', @@ -673,7 +673,7 @@ runInEachFileSystem(() => { // https://github.com/angular/angular/pull/26036 it('should handle redirected source files', () => { - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true}); // This file structure has an identical version of "a" under the root node_modules and inside // of "b". Because their package.json file indicates it is the exact same version of "a", @@ -710,7 +710,7 @@ runInEachFileSystem(() => { }); it('should allow incremental compilation with redirected source files', () => { - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true}); // This file structure has an identical version of "a" under the root node_modules and inside // of "b". Because their package.json file indicates it is the exact same version of "a", diff --git a/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts b/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts index a766fe6b6387..86ec8e95ff16 100644 --- a/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts +++ b/packages/compiler-cli/test/ngtsc/local_compilation_spec.ts @@ -16,6 +16,10 @@ import {NgtscTestEnvironment, TsConfigOptions} from './env'; const testFiles = loadStandardTestFiles(); +function cleanNewLines(contents: string) { + return contents.replace(/\s*\n\s*/g, ' '); +} + runInEachFileSystem(() => { describe('local compilation', () => { let env!: NgtscTestEnvironment; @@ -1044,7 +1048,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `MainComponent.ɵfac = function MainComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MainComponent)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`, + `MainComponent.ɵfac = function MainComponent_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || MainComponent)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`, ); }); @@ -1080,7 +1084,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `MainComponent.ɵfac = function MainComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MainComponent)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`, + `MainComponent.ɵfac = function MainComponent_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || MainComponent)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`, ); }); @@ -1120,7 +1124,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `MainDirective.ɵfac = function MainDirective_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MainDirective)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`, + `MainDirective.ɵfac = function MainDirective_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || MainDirective)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`, ); }); @@ -1153,7 +1157,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `MainDirective.ɵfac = function MainDirective_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MainDirective)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`, + `MainDirective.ɵfac = function MainDirective_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || MainDirective)(i0.ɵɵdirectiveInject(i1.SomeService1), i0.ɵɵdirectiveInject(SomeService2), i0.ɵɵdirectiveInject(i2.SomeService3), i0.ɵɵdirectiveInject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN)); };`, ); }); @@ -1192,7 +1196,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `MainPipe.ɵfac = function MainPipe_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MainPipe)(i0.ɵɵdirectiveInject(i1.SomeService1, 16), i0.ɵɵdirectiveInject(SomeService2, 16), i0.ɵɵdirectiveInject(i2.SomeService3, 16), i0.ɵɵdirectiveInject(i3.nested.SomeService4, 16), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN, 16)); };`, + `MainPipe.ɵfac = function MainPipe_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || MainPipe)(i0.ɵɵdirectiveInject(i1.SomeService1, 16), i0.ɵɵdirectiveInject(SomeService2, 16), i0.ɵɵdirectiveInject(i2.SomeService3, 16), i0.ɵɵdirectiveInject(i3.nested.SomeService4, 16), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN, 16)); };`, ); }); @@ -1225,7 +1229,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `MainPipe.ɵfac = function MainPipe_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MainPipe)(i0.ɵɵdirectiveInject(i1.SomeService1, 16), i0.ɵɵdirectiveInject(SomeService2, 16), i0.ɵɵdirectiveInject(i2.SomeService3, 16), i0.ɵɵdirectiveInject(i3.nested.SomeService4, 16), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN, 16)); };`, + `MainPipe.ɵfac = function MainPipe_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || MainPipe)(i0.ɵɵdirectiveInject(i1.SomeService1, 16), i0.ɵɵdirectiveInject(SomeService2, 16), i0.ɵɵdirectiveInject(i2.SomeService3, 16), i0.ɵɵdirectiveInject(i3.nested.SomeService4, 16), i0.ɵɵinjectAttribute('title'), i0.ɵɵdirectiveInject(MESSAGE_TOKEN, 16)); };`, ); }); @@ -1260,7 +1264,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `MainService.ɵfac = function MainService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MainService)(i0.ɵɵinject(i1.SomeService1), i0.ɵɵinject(SomeService2), i0.ɵɵinject(i2.SomeService3), i0.ɵɵinject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵinject(MESSAGE_TOKEN)); };`, + `MainService.ɵfac = function MainService_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || MainService)(i0.ɵɵinject(i1.SomeService1), i0.ɵɵinject(SomeService2), i0.ɵɵinject(i2.SomeService3), i0.ɵɵinject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵinject(MESSAGE_TOKEN)); };`, ); }); @@ -1294,7 +1298,7 @@ runInEachFileSystem(() => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `MainModule.ɵfac = function MainModule_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || MainModule)(i0.ɵɵinject(i1.SomeService1), i0.ɵɵinject(SomeService2), i0.ɵɵinject(i2.SomeService3), i0.ɵɵinject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵinject(MESSAGE_TOKEN)); };`, + `MainModule.ɵfac = function MainModule_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || MainModule)(i0.ɵɵinject(i1.SomeService1), i0.ɵɵinject(SomeService2), i0.ɵɵinject(i2.SomeService3), i0.ɵɵinject(i3.nested.SomeService4), i0.ɵɵinjectAttribute('title'), i0.ɵɵinject(MESSAGE_TOKEN)); };`, ); }); @@ -2213,9 +2217,9 @@ runInEachFileSystem(() => { // Expect that all deferrableImports in local compilation mode // are located in a single function (since we can't detect in // the local mode which components belong to which block). - expect(jsContents).toContain( - 'const AppCmp_DeferFn = () => [' + - 'import("./deferred-a").then(m => m.DeferredCmpA), ' + + expect(cleanNewLines(jsContents)).toContain( + 'const AppCmp_DeferFn = () => [/* @ts-ignore */ ' + + 'import("./deferred-a").then(m => m.DeferredCmpA), /* @ts-ignore */ ' + 'import("./deferred-b").then(m => m.DeferredCmpB)];', ); @@ -2228,9 +2232,9 @@ runInEachFileSystem(() => { expect(jsContents).toContain('ɵɵdefer(4, 3, AppCmp_DeferFn);'); // Expect `ɵsetClassMetadataAsync` to contain dynamic imports too. - expect(jsContents).toContain( - 'ɵsetClassMetadataAsync(AppCmp, () => [' + - 'import("./deferred-a").then(m => m.DeferredCmpA), ' + + expect(cleanNewLines(jsContents)).toContain( + 'ɵsetClassMetadataAsync(AppCmp, () => [/* @ts-ignore */ ' + + 'import("./deferred-a").then(m => m.DeferredCmpA), /* @ts-ignore */ ' + 'import("./deferred-b").then(m => m.DeferredCmpB)], ' + '(DeferredCmpA, DeferredCmpB) => {', ); @@ -2360,9 +2364,9 @@ runInEachFileSystem(() => { // are located in a single function (since we can't detect in // the local mode which components belong to which block). // Eager dependencies are **not* included here. - expect(jsContents).toContain( - 'const AppCmp_DeferFn = () => [' + - 'import("./deferred-a").then(m => m.DeferredCmpA), ' + + expect(cleanNewLines(jsContents)).toContain( + 'const AppCmp_DeferFn = () => [/* @ts-ignore */ ' + + 'import("./deferred-a").then(m => m.DeferredCmpA), /* @ts-ignore */ ' + 'import("./deferred-b").then(m => m.DeferredCmpB)];', ); @@ -2378,9 +2382,9 @@ runInEachFileSystem(() => { expect(jsContents).toContain('ɵɵdefer(4, 3, AppCmp_DeferFn);'); // Expect `ɵsetClassMetadataAsync` to contain dynamic imports too. - expect(jsContents).toContain( - 'ɵsetClassMetadataAsync(AppCmp, () => [' + - 'import("./deferred-a").then(m => m.DeferredCmpA), ' + + expect(cleanNewLines(jsContents)).toContain( + 'ɵsetClassMetadataAsync(AppCmp, () => [/* @ts-ignore */ ' + + 'import("./deferred-a").then(m => m.DeferredCmpA), /* @ts-ignore */ ' + 'import("./deferred-b").then(m => m.DeferredCmpB)], ' + '(DeferredCmpA, DeferredCmpB) => {', ); @@ -2448,12 +2452,12 @@ runInEachFileSystem(() => { // Expect that we generate 2 different defer functions // (one for each component). - expect(jsContents).toContain( - 'const AppCmpA_DeferFn = () => [' + + expect(cleanNewLines(jsContents)).toContain( + 'const AppCmpA_DeferFn = () => [/* @ts-ignore */ ' + 'import("./deferred-deps").then(m => m.DeferredCmpA)]', ); - expect(jsContents).toContain( - 'const AppCmpB_DeferFn = () => [' + + expect(cleanNewLines(jsContents)).toContain( + 'const AppCmpB_DeferFn = () => [/* @ts-ignore */ ' + 'import("./deferred-deps").then(m => m.DeferredCmpB)]', ); @@ -2465,12 +2469,12 @@ runInEachFileSystem(() => { expect(jsContents).toContain('ɵɵdefer(1, 0, AppCmpB_DeferFn)'); // Expect `ɵsetClassMetadataAsync` to contain dynamic imports too. - expect(jsContents).toContain( - 'ɵsetClassMetadataAsync(AppCmpA, () => [' + + expect(cleanNewLines(jsContents)).toContain( + 'ɵsetClassMetadataAsync(AppCmpA, () => [/* @ts-ignore */ ' + 'import("./deferred-deps").then(m => m.DeferredCmpA)]', ); - expect(jsContents).toContain( - 'ɵsetClassMetadataAsync(AppCmpB, () => [' + + expect(cleanNewLines(jsContents)).toContain( + 'ɵsetClassMetadataAsync(AppCmpB, () => [/* @ts-ignore */ ' + 'import("./deferred-deps").then(m => m.DeferredCmpB)]', ); }, diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index 22d6b43d90d3..c3af979eb74a 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -162,7 +162,7 @@ runInEachFileSystem((os: string) => { expect(jsContents).toContain('Dep.ɵprov ='); expect(jsContents).toContain('Service.ɵprov ='); expect(jsContents).toContain( - 'Service.ɵfac = function Service_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep)); };', + 'Service.ɵfac = function Service_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep)); };', ); expect(jsContents).toContain("providedIn: 'root' })"); expect(jsContents).not.toContain('__decorate'); @@ -494,7 +494,9 @@ runInEachFileSystem((os: string) => { expect(jsContents).toContain( 'factory: function Service_Factory(__ngFactoryType__) { let __ngConditionalFactory__ = null; if (__ngFactoryType__) {', ); - expect(jsContents).toContain('return new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep));'); + expect(jsContents).toContain( + '/* @ts-ignore */\nreturn new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep));', + ); expect(jsContents).toContain( '__ngConditionalFactory__ = ((dep) => new Service(dep))(i0.ɵɵinject(Dep));', ); @@ -532,7 +534,9 @@ runInEachFileSystem((os: string) => { expect(jsContents).toContain( 'factory: function Service_Factory(__ngFactoryType__) { let __ngConditionalFactory__ = null; if (__ngFactoryType__) {', ); - expect(jsContents).toContain('return new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep));'); + expect(jsContents).toContain( + '/* @ts-ignore */\nreturn new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep));', + ); expect(jsContents).toContain( '__ngConditionalFactory__ = ((dep) => new Service(dep))(i0.ɵɵinject(Dep, 10));', ); @@ -569,7 +573,7 @@ runInEachFileSystem((os: string) => { expect(jsContents).toContain('Service.ɵprov ='); expect(jsContents).toContain('Mod.ɵmod ='); expect(jsContents).toContain( - 'Service.ɵfac = function Service_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep)); };', + 'Service.ɵfac = function Service_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep)); };', ); expect(jsContents).toContain('providedIn: i0.forwardRef(() => Mod) })'); expect(jsContents).not.toContain('__decorate'); @@ -626,7 +630,7 @@ runInEachFileSystem((os: string) => { expect(jsContents).toContain( `Service.ɵfac = function Service_Factory(__ngFactoryType__) { ` + - `return new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep), i0.ɵɵinject(OptionalDep, 8)); };`, + `/* @ts-ignore */\nreturn new (__ngFactoryType__ || Service)(i0.ɵɵinject(Dep), i0.ɵɵinject(OptionalDep, 8)); };`, ); }); @@ -1049,7 +1053,7 @@ runInEachFileSystem((os: string) => { it('should still perform schema checks in embedded views', () => { env.tsconfig({ - 'fullTemplateTypeCheck': false, + 'strictTemplates': false, 'annotateForClosureCompiler': true, }); env.write( @@ -1446,7 +1450,7 @@ runInEachFileSystem((os: string) => { class DirectiveA {} @Component({ - selector: 'comp', + selector: 'comp-a', template: '...', standalone: false, }) @@ -1467,7 +1471,7 @@ runInEachFileSystem((os: string) => { class DirectiveB {} @Component({ - selector: 'comp', + selector: 'comp-b', template: '...', standalone: false, }) @@ -1477,7 +1481,8 @@ runInEachFileSystem((os: string) => { selector: 'app', template: \`
    - + + \`, standalone: false, }) @@ -1552,7 +1557,7 @@ runInEachFileSystem((os: string) => { class DirectiveA {} @Component({ - selector: 'comp', + selector: 'comp-a', template: '...', standalone: false, }) @@ -1573,7 +1578,7 @@ runInEachFileSystem((os: string) => { class DirectiveB {} @Component({ - selector: 'comp', + selector: 'comp-b', template: '...', standalone: false, }) @@ -1591,7 +1596,8 @@ runInEachFileSystem((os: string) => { selector: 'app', template: \`
    - + + \`, standalone: false, }) @@ -5123,7 +5129,7 @@ runInEachFileSystem((os: string) => { env.driveMain(); const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - `FooCmp.ɵfac = function FooCmp_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FooCmp)(i0.ɵɵinjectAttribute("test"), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.Injector), i0.ɵɵdirectiveInject(i0.Renderer2), i0.ɵɵdirectiveInject(i0.TemplateRef), i0.ɵɵdirectiveInject(i0.ViewContainerRef)); }`, + `FooCmp.ɵfac = function FooCmp_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || FooCmp)(i0.ɵɵinjectAttribute("test"), i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i0.ElementRef), i0.ɵɵdirectiveInject(i0.Injector), i0.ɵɵdirectiveInject(i0.Renderer2), i0.ɵɵdirectiveInject(i0.TemplateRef), i0.ɵɵdirectiveInject(i0.ViewContainerRef)); }`, ); }); @@ -5981,7 +5987,7 @@ runInEachFileSystem((os: string) => { expect(messageText).toContain("Value is of type 'string'."); }); - it('should handle `changeDetection` field', () => { + it('should handle `changeDetection` field with the "default" value: OnPush', () => { env.write( `test.ts`, ` @@ -5997,7 +6003,27 @@ runInEachFileSystem((os: string) => { env.driveMain(); const jsContents = env.getContents('test.js'); - expect(jsContents).toContain('changeDetection: 0'); + // because this default value is implicit + expect(jsContents).not.toContain('changeDetection: 0'); + }); + + it('should handle `changeDetection` field', () => { + env.write( + `test.ts`, + ` + import {Component, ChangeDetectionStrategy} from '@angular/core'; + @Component({ + selector: 'comp-a', + template: '...', + changeDetection: ChangeDetectionStrategy.Eager + }) + class CompA {} + `, + ); + + env.driveMain(); + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('changeDetection: 1'); }); it('should throw if `changeDetection` contains invalid value', () => { @@ -6168,7 +6194,7 @@ runInEachFileSystem((os: string) => { const jsContents = env.getContents('test.js'); expect(jsContents).toContain( - 'function Base_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || Base)(i0.ɵɵinject(Dep)); }', + 'function Base_Factory(__ngFactoryType__) { /* @ts-ignore */\nreturn new (__ngFactoryType__ || Base)(i0.ɵɵinject(Dep)); }', ); expect(jsContents).toContain( '(() => { let ɵChild_BaseFactory; return function Child_Factory(__ngFactoryType__) { return (ɵChild_BaseFactory || (ɵChild_BaseFactory = i0.ɵɵgetInheritedFactory(Child)))(__ngFactoryType__ || Child); }; })();', @@ -9376,6 +9402,41 @@ runInEachFileSystem((os: string) => { const diags = env.driveDiagnostics(); expect(diags.length).toBe(0); }); + + it('should emit `declare` fields without runtime initialization in decorated classes', () => { + env.tsconfig(); + env.write( + 'test.ts', + ` + import {Directive} from '@angular/core'; + + function Log(target: any, key: string): void {} + + @Directive({selector: '[child]'}) + export class Child { + @Log declare value: string; + } + `, + ); + + env.driveMain(); + + const jsContents = trim(env.getContents('test.js')); + expect(jsContents).toContain( + trim(` + import { Directive } from '@angular/core'; + import * as i0 from "@angular/core"; + function Log(target, key) { } + export class Child { + } + Child.ɵfac = function Child_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || Child)(); }; + Child.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: Child, selectors: [["", "child", ""]] }); + __decorate([ + Log + ], Child.prototype, "value", void 0); + `), + ); + }); }); describe('SVG animation processing', () => { @@ -9728,7 +9789,8 @@ runInEachFileSystem((os: string) => { import {Directive} from '@angular/core'; @Directive({ - selector: '[test]' + selector: '[test]', + standalone: false, }) class TestDirective {} `, @@ -9740,6 +9802,24 @@ runInEachFileSystem((os: string) => { expect(jsContents).toContain('Directive({'); }); + it('should emit directive definitions for non-exported standalone classes by default', () => { + env.write( + 'test.ts', + ` + import {Directive} from '@angular/core'; + + @Directive({ + selector: '[test]' + }) + class TestDirective {} + `, + ); + env.driveMain(); + const jsContents = env.getContents('test.js'); + + expect(jsContents).toContain('defineDirective('); + }); + it('should not emit component definitions for non-exported classes if configured', () => { env.write( 'test.ts', @@ -9748,7 +9828,8 @@ runInEachFileSystem((os: string) => { @Component({ selector: 'test', - template: 'hello' + template: 'hello', + standalone: false, }) class TestComponent {} `, @@ -9760,6 +9841,25 @@ runInEachFileSystem((os: string) => { expect(jsContents).toContain('Component({'); }); + it('should emit component definitions for non-exported standalone classes by default', () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'test', + template: 'hello' + }) + class TestComponent {} + `, + ); + env.driveMain(); + const jsContents = env.getContents('test.js'); + + expect(jsContents).toContain('defineComponent('); + }); + it('should not emit module definitions for non-exported classes if configured', () => { env.write( 'test.ts', @@ -9779,6 +9879,44 @@ runInEachFileSystem((os: string) => { expect(jsContents).toContain('NgModule({'); }); + it('should not emit pipe definitions for non-exported classes if configured', () => { + env.write( + 'test.ts', + ` + import {Pipe} from '@angular/core'; + + @Pipe({ + name: 'test', + standalone: false, + }) + class TestPipe {} + `, + ); + env.driveMain(); + const jsContents = env.getContents('test.js'); + + expect(jsContents).not.toContain('definePipe('); + expect(jsContents).toContain('Pipe({'); + }); + + it('should emit pipe definitions for non-exported standalone classes by default', () => { + env.write( + 'test.ts', + ` + import {Pipe} from '@angular/core'; + + @Pipe({ + name: 'test' + }) + class TestPipe {} + `, + ); + env.driveMain(); + const jsContents = env.getContents('test.js'); + + expect(jsContents).toContain('definePipe('); + }); + it('should still compile a class that is indirectly exported', () => { env.write( 'test.ts', @@ -9788,6 +9926,7 @@ runInEachFileSystem((os: string) => { @Component({ selector: 'test-cmp', template: 'Test Cmp', + standalone: false, }) class TestCmp {} @@ -10788,6 +10927,56 @@ runInEachFileSystem((os: string) => { expect(codes).toEqual([ngErrorCode(ErrorCode.NGMODULE_BOOTSTRAP_IS_STANDALONE)]); }); + [true, false].forEach((strictTemplates) => { + it(`[strictTemplates: ${strictTemplates}] should compile a component with a complex generic`, () => { + env.tsconfig({strictTemplates}); + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + + @Component({ + selector: 'app-root', + template: '', + }) + export class App< + T extends object = object, + TOptions extends { [K in keyof T]?: T[K] } = object + > {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + + // See #67704. + it(`[strictTemplates: ${strictTemplates}] should compile a directive with a generic that has type parameters`, () => { + env.tsconfig({strictTemplates}); + env.write( + 'test.ts', + ` + import {Directive} from '@angular/core'; + + type Foo = {prop: T}; + + @Directive({ + host: { + '[class.some-class]': 'foo || bar' // Only necessary to enable type checking. + }, + }) + export class TestDir ? V : never> { + foo?: T; + bar?: U; + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + }); + describe('InjectorDef emit optimizations for standalone', () => { it('should not filter components out of NgModule.imports', () => { env.write( @@ -11190,6 +11379,24 @@ runInEachFileSystem((os: string) => { 'static ngAcceptInputType_element: HTMLElement | i0.ElementRef;', ); }); + + it('should compile an input with a transform function and whose name needs to be quoted', () => { + env.write( + '/test.ts', + ` + import {Directive, Input} from '@angular/core'; + + @Directive({selector: '[dir]'}) + export class Dir { + @Input({transform: (value: string) => value}) 'aria-label': string = ''; + } + `, + ); + + env.driveMain(); + const dtsContents = env.getContents('test.d.ts'); + expect(dtsContents).toContain('static "ngAcceptInputType_aria-label": string;'); + }); }); describe('debug info', () => { @@ -11445,6 +11652,30 @@ runInEachFileSystem((os: string) => { `The pipe 'TestPipe' appears in 'imports', but is not standalone`, ); }); + + it('should not recurse when a non-standalone component is both declared and imported', () => { + env.write( + '/test.ts', + ` + import {Component, NgModule} from '@angular/core'; + + @Component({standalone: false, template: ''}) + export class TestComp {} + + @NgModule({ + declarations: [TestComp], + imports: [TestComp], + }) + export class TestModule {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain( + `The component 'TestComp' appears in 'imports', but is not standalone`, + ); + }); }); }); diff --git a/packages/compiler-cli/test/ngtsc/scope_spec.ts b/packages/compiler-cli/test/ngtsc/scope_spec.ts index 605d1141e001..ff8cb2d6b662 100644 --- a/packages/compiler-cli/test/ngtsc/scope_spec.ts +++ b/packages/compiler-cli/test/ngtsc/scope_spec.ts @@ -360,7 +360,7 @@ runInEachFileSystem(() => { }); it('should not produce component template type-check errors if its module is invalid', () => { - env.tsconfig({'fullTemplateTypeCheck': true}); + env.tsconfig({'strictTemplates': true}); // Set up 3 files, each of which declare an NgModule that's invalid in some way. This will // produce a bunch of diagnostics related to the issues with the modules. Each module also diff --git a/packages/compiler-cli/test/ngtsc/selectorless_spec.ts b/packages/compiler-cli/test/ngtsc/selectorless_spec.ts index 34d47eb8cafe..89b7dd9b0676 100644 --- a/packages/compiler-cli/test/ngtsc/selectorless_spec.ts +++ b/packages/compiler-cli/test/ngtsc/selectorless_spec.ts @@ -12,6 +12,10 @@ import {NgtscTestEnvironment} from './env'; const testFiles = loadStandardTestFiles(); +function cleanNewLines(contents: string) { + return contents.replace(/\s*\n\s*/g, ' '); +} + runInEachFileSystem(() => { describe('selectorless', () => { let env!: NgtscTestEnvironment; @@ -1112,10 +1116,10 @@ runInEachFileSystem(() => { expect(jsContents).not.toContain('import { DepComp'); expect(jsContents).not.toContain('import { DepDir'); expect(jsContents).not.toContain('import { DepPipe'); - expect(jsContents).toContain( - 'const Comp_Defer_1_DepsFn = () => [import("./dep-comp").then(m => m.DepComp), ' + - 'import("./dep-dir").then(m => m.DepDir), ' + - 'import("./dep-pipe").then(m => m.DepPipe)];', + expect(cleanNewLines(jsContents)).toContain( + 'const Comp_Defer_1_DepsFn = () => [/* @ts-ignore */ import("./dep-comp").then(m => m.DepComp), ' + + '/* @ts-ignore */ import("./dep-dir").then(m => m.DepDir), ' + + '/* @ts-ignore */ import("./dep-pipe").then(m => m.DepPipe)];', ); expect(jsContents).toContain('ɵɵdefer(1, 0, Comp_Defer_1_DepsFn);'); }); diff --git a/packages/compiler-cli/test/ngtsc/service_spec.ts b/packages/compiler-cli/test/ngtsc/service_spec.ts new file mode 100644 index 000000000000..7b04d35db987 --- /dev/null +++ b/packages/compiler-cli/test/ngtsc/service_spec.ts @@ -0,0 +1,127 @@ +/*! + * @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 {runInEachFileSystem} from '../../src/ngtsc/file_system/testing'; +import {loadStandardTestFiles} from '../../src/ngtsc/testing'; +import {NgtscTestEnvironment} from './env'; + +const testFiles = loadStandardTestFiles(); + +runInEachFileSystem(() => { + describe('@Service decorator', () => { + let env!: NgtscTestEnvironment; + + beforeEach(() => { + env = NgtscTestEnvironment.setup(testFiles); + env.tsconfig(); + }); + + // More thorough compilation tests are in `packages/compiler-cli/test/compliance/test_cases/service_decorator`. + it('should compile an @Service class', () => { + env.write( + 'test.ts', + ` + import {Service} from '@angular/core'; + + @Service() + export class TestService {} + `, + ); + env.driveMain(); + + const jsContents = env.getContents('test.js'); + const dtsContents = env.getContents('test.d.ts'); + expect(jsContents).not.toContain('__decorate'); + expect(jsContents).toContain('TestService.ɵfac ='); + expect(jsContents).toContain('TestService.ɵprov ='); + expect(dtsContents).toContain('static ɵfac: i0.ɵɵFactoryDeclaration;'); + expect(dtsContents).toContain('static ɵprov: i0.ɵɵInjectableDeclaration;'); + }); + + it('should report if an @Service class has another Angular decorator', () => { + env.write( + 'test.ts', + ` + import {Service, Pipe} from '@angular/core'; + + @Service() + @Pipe({name: 'foo'}) + export class TestService {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toBe( + 'Cannot apply more than one Angular decorator on an @Service class.', + ); + }); + + it('should report if an @Service class uses constructor-based DI from its own constructor', () => { + env.write( + 'test.ts', + ` + import {Service, ApplicationRef} from '@angular/core'; + + @Service() + export class TestService { + constructor(appRef: ApplicationRef) {} + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toBe( + '@Service class cannot use constructor dependency injection. Use the `inject` function instead.', + ); + }); + + it('should report if an @Service class uses constructor-based DI from an inherited constructor', () => { + env.write( + 'grandparent.ts', + ` + import {Injectable, ApplicationRef} from '@angular/core'; + + @Injectable() + export class GrandparentService { + constructor(appRef: ApplicationRef) {} + } + `, + ); + + env.write( + 'parent.ts', + ` + import {Injectable} from '@angular/core'; + import {GrandparentService} from './grandparent'; + + @Injectable() + export class ParentService extends GrandparentService {} + `, + ); + + env.write( + 'test.ts', + ` + import {Service} from '@angular/core'; + import {ParentService} from './parent'; + + @Service() + export class TestService extends ParentService {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toBe( + '@Service class cannot use constructor dependency injection. Use the `inject` function instead.', + ); + }); + }); +}); diff --git a/packages/compiler-cli/test/ngtsc/signal_forms_spec.ts b/packages/compiler-cli/test/ngtsc/signal_forms_spec.ts index 3392b885ddf2..5d14e126f328 100644 --- a/packages/compiler-cli/test/ngtsc/signal_forms_spec.ts +++ b/packages/compiler-cli/test/ngtsc/signal_forms_spec.ts @@ -106,7 +106,7 @@ runInEachFileSystem(() => { expect(extractMessage(diags[0])).toBe(`Type 'null' is not assignable to type 'string'.`); }); - it('should infer an input without a `type` as a string field', () => { + it('should reject a numeric field without null on a bare input', () => { env.write( 'test.ts', ` @@ -123,6 +123,120 @@ runInEachFileSystem(() => { `, ); + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(extractMessage(diags[0])).toContain( + `is not assignable to type '{ (): string; set: (v: string) => void; } | { (): number | null; set: (v: number | null) => void; }'`, + ); + }); + + it('should allow a string field on a bare input', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + import {FormField, form} from '@angular/forms/signals'; + + @Component({ + template: '', + imports: [FormField] + }) + export class Comp { + f = form(signal('')); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + + it('should allow a number|null field on a text input', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + import {FormField, form} from '@angular/forms/signals'; + + @Component({ + template: '', + imports: [FormField] + }) + export class Comp { + f = form(signal(42)); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + + it('should reject a string|null field on a text input', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + import {FormField, form} from '@angular/forms/signals'; + + @Component({ + template: '', + imports: [FormField] + }) + export class Comp { + f = form(signal(null)); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(extractMessage(diags[0])).toContain( + `is not assignable to type '{ (): string; set: (v: string) => void; } | { (): number | null; set: (v: number | null) => void; }'`, + ); + }); + + it('should reject a boolean field on a text input', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + import {FormField, form} from '@angular/forms/signals'; + + @Component({ + template: '', + imports: [FormField] + }) + export class Comp { + f = form(signal(true)); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(extractMessage(diags[0])).toContain( + `is not assignable to type '{ (): string; set: (v: string) => void; } | { (): number | null; set: (v: number | null) => void; }'`, + ); + }); + + it('should reject a number field on a textarea', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + import {FormField, form} from '@angular/forms/signals'; + + @Component({ + template: '', + imports: [FormField] + }) + export class Comp { + f = form(signal(0)); + } + `, + ); + const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); expect(extractMessage(diags[0])).toBe(`Type 'number' is not assignable to type 'string'.`); @@ -152,6 +266,54 @@ runInEachFileSystem(() => { ); }); + it('should allow min/max bindings on date inputs', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + import {FormField, form} from '@angular/forms/signals'; + + @Component({ + template: '', + imports: [FormField] + }) + export class Comp { + f = form(signal(new Date('2026-01-15'))); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + + it('should prohibit min/max bindings on non-date inputs', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + import {FormField, form} from '@angular/forms/signals'; + + @Component({ + template: '', + imports: [FormField] + }) + export class Comp { + f = form(signal(5)); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(2); + expect(extractMessage(diags[0])).toBe( + `Setting the 'min' attribute is not allowed on nodes using the '[formField]' directive`, + ); + expect(extractMessage(diags[1])).toBe( + `Setting the 'max' attribute is not allowed on nodes using the '[formField]' directive`, + ); + }); + it('should infer the type of a custom value control', () => { env.write( 'test.ts', diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index c8a72403dc16..44addc76d163 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -29,7 +29,7 @@ runInEachFileSystem(() => { beforeEach(() => { env = NgtscTestEnvironment.setup(testFiles); - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true}); env.write( 'node_modules/@angular/animations/index.d.ts', ` @@ -117,7 +117,7 @@ runInEachFileSystem(() => { it('should check regular attributes that are directive inputs', () => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, strictInputTypes: true, strictAttributeTypes: true, }); @@ -155,9 +155,10 @@ runInEachFileSystem(() => { expect(diags[0].code).toBeGreaterThan(0); }); - it('should produce diagnostics when mapping to multiple fields and bound types are incorrect', () => { + // This is not supported at runtime + xit('should produce diagnostics when mapping to multiple fields and bound types are incorrect', () => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, strictInputTypes: true, strictAttributeTypes: true, }); @@ -197,7 +198,7 @@ runInEachFileSystem(() => { it('should support inputs and outputs with names that are not JavaScript identifiers', () => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, strictInputTypes: true, strictOutputEventTypes: true, }); @@ -241,7 +242,8 @@ runInEachFileSystem(() => { ); }); - it('should support one input property mapping to multiple fields', () => { + /** This is not supported at runtime */ + xit('should support one input property mapping to multiple fields', () => { env.write( 'test.ts', ` @@ -274,7 +276,7 @@ runInEachFileSystem(() => { }); it('should check event bindings', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictOutputEventTypes: true}); + env.tsconfig({strictTemplates: true, strictOutputEventTypes: true}); env.write( 'test.ts', ` @@ -305,18 +307,17 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(3); + expect(diags.length).toBe(4); expect(diags[0].messageText).toEqual( `Argument of type 'number' is not assignable to parameter of type 'string'.`, ); expect(diags[1].messageText).toEqual( `Property 'updated' does not exist on type 'TestCmp'. Did you mean 'update'?`, ); - // Disabled because `checkTypeOfDomEvents` is disabled by default - // expect(diags[2].messageText) - // .toEqual( - // `Argument of type 'FocusEvent' is not assignable to parameter of type 'string'.`); - expect(diags[2].messageText).toEqual(`Property 'focused' does not exist on type 'TestCmp'.`); + expect(diags[2].messageText).toEqual( + `Argument of type 'FocusEvent' is not assignable to parameter of type 'string'.`, + ); + expect(diags[3].messageText).toEqual(`Property 'focused' does not exist on type 'TestCmp'.`); }); // https://github.com/angular/angular/issues/35073 @@ -542,7 +543,7 @@ runInEachFileSystem(() => { }); it('should type check a two-way binding to a generic property', () => { - env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true}); + env.tsconfig({strictTemplates: true}); env.write( 'test.ts', ` @@ -565,21 +566,16 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toEqual( jasmine.objectContaining({ messageText: `Type '{ id: number; }' is not assignable to type '{ id: string; }'.`, }), ); - expect(diags[1].messageText).toEqual( - jasmine.objectContaining({ - messageText: `Type '{ id: string; }' is not assignable to type '{ id: number; }'.`, - }), - ); }); it('should use the setter type when assigning using a two-way binding to an input with different getter and setter types', () => { - env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true}); + env.tsconfig({strictTemplates: true}); env.write( 'test.ts', ` @@ -614,7 +610,7 @@ runInEachFileSystem(() => { }); it('should type check a two-way binding to a function value', () => { - env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true}); + env.tsconfig({strictTemplates: true}); env.write( 'test.ts', ` @@ -639,21 +635,16 @@ runInEachFileSystem(() => { ); const diags = env.driveDiagnostics(); - expect(diags.length).toBe(2); + expect(diags.length).toBe(1); expect(diags[0].messageText).toEqual( jasmine.objectContaining({ messageText: `Type '(val: string) => number' is not assignable to type 'TestFn'.`, }), ); - expect(diags[1].messageText).toEqual( - jasmine.objectContaining({ - messageText: `Type 'TestFn' is not assignable to type '(val: string) => number'.`, - }), - ); }); it('should be able to cast to any in a two-way binding', () => { - env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true}); + env.tsconfig({strictTemplates: true}); env.write( 'test.ts', ` @@ -679,34 +670,6 @@ runInEachFileSystem(() => { expect(diags.length).toBe(0); }); - it('should type check a two-way binding to input/output pair where the input has a wider type than the output', () => { - env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true}); - env.write( - 'test.ts', - ` - import {Component, Directive, Input, Output, EventEmitter} from '@angular/core'; - - @Directive({selector: '[dir]'}) - export class Dir { - @Input() value: string | number; - @Output() valueChange = new EventEmitter(); - } - - @Component({ - template: '
    ', - imports: [Dir], - }) - export class App { - value = 'hello'; - } - `, - ); - - const diags = env.driveDiagnostics(); - expect(diags.length).toBe(1); - expect(diags[0].messageText).toBe(`Type 'number' is not assignable to type 'string'.`); - }); - it('should check the fallback content of ng-content', () => { env.write( 'test.ts', @@ -867,7 +830,7 @@ runInEachFileSystem(() => { }); it('should check expressions and their type when enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); @@ -889,7 +852,7 @@ runInEachFileSystem(() => { }); it('should check expressions but not their type when not enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: false}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); @@ -933,7 +896,7 @@ runInEachFileSystem(() => { it('should check expressions and their nullability when enabled', () => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, strictInputTypes: true, strictNullInputTypes: true, }); @@ -962,7 +925,7 @@ runInEachFileSystem(() => { }); it('should check expressions but not their nullability when not enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true, strictNullInputTypes: false}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); @@ -1006,7 +969,7 @@ runInEachFileSystem(() => { it('should infer result type for safe navigation expressions when enabled', () => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, strictInputTypes: true, strictNullInputTypes: true, strictSafeNavigationTypes: true, @@ -1037,7 +1000,8 @@ runInEachFileSystem(() => { it('should not infer result type for safe navigation expressions when not enabled', () => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, + strictSafeNavigationTypes: false, strictInputTypes: true, }); @@ -1047,6 +1011,39 @@ runInEachFileSystem(() => { `Property 'invalid' does not exist on type 'TestCmp'.`, ); }); + + it('should narrow the type of safe navigation expressions in an if guard when enabled', () => { + env.tsconfig({ + fullTemplateTypeCheck: true, + strictInputTypes: true, + strictNullInputTypes: true, + strictSafeNavigationTypes: true, + }); + + env.write( + 'test.ts', + ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'test', + template: '@if (user?.isMember) { {{user.isMember}} }', + standalone: false, + }) + class TestCmp { + user?: {isMember: boolean}; + } + + @NgModule({ + declarations: [TestCmp], + }) + class Module {} + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); }); describe('strictOutputEventTypes', () => { @@ -1082,7 +1079,7 @@ runInEachFileSystem(() => { }); it('should expressions and infer type of $event when enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictOutputEventTypes: true}); + env.tsconfig({strictTemplates: true, strictOutputEventTypes: true}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); @@ -1108,7 +1105,7 @@ runInEachFileSystem(() => { }); it('should check expressions but not infer type of $event when not enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true, strictOutputEventTypes: false}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); @@ -1143,7 +1140,7 @@ runInEachFileSystem(() => { }); it('should check expressions and let $event be of type AnimationEvent when enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictOutputEventTypes: true}); + env.tsconfig({strictTemplates: true, strictOutputEventTypes: true}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); @@ -1169,7 +1166,7 @@ runInEachFileSystem(() => { }); it('should check expressions and let $event be of type any when not enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true, strictOutputEventTypes: false}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); @@ -1202,7 +1199,7 @@ runInEachFileSystem(() => { }); it('should infer the type of DOM references when enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictDomLocalRefTypes: true}); + env.tsconfig({strictTemplates: true, strictDomLocalRefTypes: true}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); @@ -1222,7 +1219,7 @@ runInEachFileSystem(() => { }); it('should let the type of DOM references be any when not enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true, strictDomLocalRefTypes: false}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(0); @@ -1262,7 +1259,7 @@ runInEachFileSystem(() => { it('should produce an error for text attributes when enabled', () => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, strictInputTypes: true, strictAttributeTypes: true, }); @@ -1283,7 +1280,7 @@ runInEachFileSystem(() => { }); it('should not produce an error for text attributes when not enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictAttributeTypes: false, strictInputTypes: true}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(0); @@ -1315,7 +1312,7 @@ runInEachFileSystem(() => { }); it('should check expressions and infer type of $event when enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictDomEventTypes: true}); + env.tsconfig({strictTemplates: true, strictDomEventTypes: true}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); @@ -1341,7 +1338,7 @@ runInEachFileSystem(() => { }); it('should check expressions but not infer type of $event when not enabled', () => { - env.tsconfig({fullTemplateTypeCheck: true}); + env.tsconfig({strictTemplates: true, strictDomEventTypes: false}); const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); @@ -1489,7 +1486,7 @@ runInEachFileSystem(() => { }); it('should report an error inside the NgFor template', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); env.write( 'test.ts', ` @@ -1728,7 +1725,7 @@ runInEachFileSystem(() => { }); it('should allow the implicit value of an NgFor to be invoked', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); env.write( 'test.ts', ` @@ -1866,8 +1863,8 @@ runInEachFileSystem(() => { expect(getSourceCodeForDiagnostic(diags[0])).toBe('unknown'); }); - it('should report an error with an unknown pipe even if `fullTemplateTypeCheck` is disabled', () => { - env.tsconfig({fullTemplateTypeCheck: false}); + it('should report an error with an unknown pipe even if `strictTemplates` is disabled', () => { + env.tsconfig({strictTemplates: false}); env.write( 'test.ts', ` @@ -1954,7 +1951,7 @@ runInEachFileSystem(() => { it('should constrain types using type parameter bounds', () => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, strictInputTypes: true, strictContextGenerics: true, }); @@ -2018,7 +2015,7 @@ runInEachFileSystem(() => { }); it("should be treated as 'any' without strictTemplates", () => { - env.tsconfig({fullTemplateTypeCheck: true, strictTemplates: false}); + env.tsconfig(); const diags = env.driveDiagnostics(); expect(diags.length).toBe(0); @@ -2036,7 +2033,7 @@ runInEachFileSystem(() => { }); it('should properly type-check inherited directives', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); env.write( 'test.ts', ` @@ -2088,7 +2085,7 @@ runInEachFileSystem(() => { }); it('should properly type-check inherited directives from external libraries', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); env.write( 'node_modules/external/index.d.ts', @@ -2180,8 +2177,13 @@ runInEachFileSystem(() => { `, ); const diags = env.driveDiagnostics(); - expect(diags.length).toEqual(1); - expect(getSourceCodeForDiagnostic(diags[0])).toEqual('y = !y'); + expect(diags.length).toEqual(2); + expect(getSourceCodeForDiagnostic(diags[0])).toEqual('y'); + expect(getSourceCodeForDiagnostic(diags[1])).toEqual('y = !y'); + expect(diags[0].messageText).toEqual(`Type 'false' is not assignable to type 'true'.`); + expect(diags[1].messageText).toEqual( + `Cannot use variable 'y' as the left-hand side of an assignment expression. Template variables are read-only.`, + ); }); it('should detect a duplicate variable declaration', () => { @@ -2232,7 +2234,7 @@ runInEachFileSystem(() => { '_useHostForImportGeneration': true, // Because the tsconfig is overridden, template type-checking needs to be turned back on // explicitly as well. - 'fullTemplateTypeCheck': true, + 'strictTemplates': true, }); // 'alpha' declares the directive which will ultimately be imported. @@ -2294,7 +2296,7 @@ runInEachFileSystem(() => { describe('input coercion', () => { beforeEach(() => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); env.write( 'node_modules/@angular/material/index.d.ts', ` @@ -3044,7 +3046,7 @@ runInEachFileSystem(() => { }); it('should type check a two-way binding to an input with a transform', () => { - env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true}); + env.tsconfig({strictTemplates: true}); env.write( 'test.ts', ` @@ -3140,7 +3142,7 @@ runInEachFileSystem(() => { describe('with strictInputAccessModifiers', () => { beforeEach(() => { env.tsconfig({ - fullTemplateTypeCheck: true, + strictTemplates: true, strictInputTypes: true, strictInputAccessModifiers: true, }); @@ -3205,7 +3207,7 @@ runInEachFileSystem(() => { describe('with strict inputs', () => { beforeEach(() => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); }); it('should not produce diagnostics for correct inputs which assign to readonly, private, or protected fields', () => { @@ -3253,7 +3255,7 @@ runInEachFileSystem(() => { }); it('should not produce diagnostics for undeclared inputs', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); env.write( 'test.ts', ` @@ -3287,7 +3289,7 @@ runInEachFileSystem(() => { }); it('should produce diagnostics for invalid expressions when assigned into an undeclared input', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); env.write( 'test.ts', ` @@ -3321,7 +3323,7 @@ runInEachFileSystem(() => { }); it('should not produce diagnostics for undeclared inputs inherited from a base class', () => { - env.tsconfig({fullTemplateTypeCheck: true, strictInputTypes: true}); + env.tsconfig({strictTemplates: true, strictInputTypes: true}); env.write( 'test.ts', ` @@ -3482,6 +3484,31 @@ runInEachFileSystem(() => { `Argument of type 'number' is not assignable to parameter of type 'string'.`, ); }); + + it('should check template literals with escaped characters', () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + + @Component({ + template: '{{\\\`Hello \\\\\`\${check(name)}\\\\\`\\\`}}', + }) + export class Main { + name = 'test'; + check(input: number): string { + return String(input); + } + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toBe( + `Argument of type 'string' is not assignable to parameter of type 'number'.`, + ); + }); }); describe('tagged template literals', () => { @@ -3584,7 +3611,7 @@ runInEachFileSystem(() => { describe('legacy schema checking with the DOM schema', () => { beforeEach(() => { - env.tsconfig({fullTemplateTypeCheck: false}); + env.tsconfig({strictTemplates: false}); }); it('should check for unknown elements', () => { @@ -4098,35 +4125,6 @@ runInEachFileSystem(() => { describe('option compatibility verification', () => { beforeEach(() => env.write('index.ts', `export const a = 1;`)); - - it('should error if "fullTemplateTypeCheck" is false when "strictTemplates" is true', () => { - env.tsconfig({fullTemplateTypeCheck: false, strictTemplates: true}); - - const diags = env.driveDiagnostics(); - expect(diags.length).toBe(1); - expect(diags[0].messageText).toContain( - 'Angular compiler option "strictTemplates" is enabled, however "fullTemplateTypeCheck" is disabled.', - ); - }); - it('should not error if "fullTemplateTypeCheck" is false when "strictTemplates" is false', () => { - env.tsconfig({fullTemplateTypeCheck: false, strictTemplates: false}); - - const diags = env.driveDiagnostics(); - expect(diags.length).toBe(0); - }); - it('should not error if "fullTemplateTypeCheck" is not set when "strictTemplates" is true', () => { - env.tsconfig({strictTemplates: true}); - - const diags = env.driveDiagnostics(); - expect(diags.length).toBe(0); - }); - it('should not error if "fullTemplateTypeCheck" is true set when "strictTemplates" is true', () => { - env.tsconfig({strictTemplates: true}); - - const diags = env.driveDiagnostics(); - expect(diags.length).toBe(0); - }); - it('should error if "strictTemplates" is false when "extendedDiagnostics" is configured', () => { env.tsconfig({strictTemplates: false, extendedDiagnostics: {}}); @@ -4151,6 +4149,7 @@ runInEachFileSystem(() => { it('should error if "extendedDiagnostics.defaultCategory" is set to an unknown value', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { defaultCategory: 'does-not-exist', }, @@ -4172,6 +4171,7 @@ suppress }); it('should not error if "extendedDiagnostics.defaultCategory" is set to a known value', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { defaultCategory: DiagnosticCategoryLabel.Error, }, @@ -4183,6 +4183,7 @@ suppress it('should error if "extendedDiagnostics.checks" contains an unknown check', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { checks: { doesNotExist: DiagnosticCategoryLabel.Error, @@ -4198,6 +4199,7 @@ suppress }); it('should not error if "extendedDiagnostics.checks" contains all known checks', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { checks: { [invalidBananaInBoxFactory.name]: DiagnosticCategoryLabel.Error, @@ -4211,6 +4213,7 @@ suppress it('should error if "extendedDiagnostics.checks" contains an unknown diagnostic category', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { checks: { [invalidBananaInBoxFactory.name]: 'does-not-exist', @@ -4234,6 +4237,7 @@ suppress }); it('should not error if "extendedDiagnostics.checks" contains all known diagnostic categories', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { checks: { [invalidBananaInBoxFactory.name]: DiagnosticCategoryLabel.Error, @@ -4269,7 +4273,7 @@ suppress it('should accept a program with a flat index', () => { // This test asserts that flat indices don't have any negative interactions with the // generation of template type-checking code in the program. - env.tsconfig({fullTemplateTypeCheck: true, flatModuleOutFile: 'flat.js'}); + env.tsconfig({strictTemplates: true, flatModuleOutFile: 'flat.js'}); expect(env.driveDiagnostics()).toEqual([]); }); @@ -4915,7 +4919,7 @@ suppress @Component({ template: \` - @defer (prefetch when isVisible() || does_not_exist) {Hello} + @defer (on idle; prefetch when isVisible() || does_not_exist) {Hello} \`, }) export class Main { @@ -4940,7 +4944,7 @@ suppress @Component({ template: \` - @defer (hydrate when isVisible() || does_not_exist) {Hello} + @defer (on idle; hydrate when isVisible() || does_not_exist) {Hello} \`, }) export class Main { @@ -4957,6 +4961,66 @@ suppress ]); }); + it('should check that functions are invoked in `when` trigger', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + + @Component({ + template: \`@defer (when flag) {Hello}\`, + }) + export class Main { + flag = signal(false); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain('always return true'); + }); + + it('should check that functions are invoked in `prefetch when` trigger', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + + @Component({ + template: \`@defer (on idle; prefetch when flag) {Hello}\`, + }) + export class Main { + flag = signal(false); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain('always return true'); + }); + + it('should check that functions are invoked in `hydrate when` trigger', () => { + env.write( + 'test.ts', + ` + import {Component, signal} from '@angular/core'; + + @Component({ + template: \`@defer (hydrate when flag) {Hello}\`, + }) + export class Main { + flag = signal(false); + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain('always return true'); + }); + it('should report if a deferred trigger reference does not exist', () => { env.write( 'test.ts', @@ -5820,8 +5884,8 @@ suppress describe('for loop blocks', () => { beforeEach(() => { - // `fullTemplateTypeCheck: true` is necessary so content inside `ng-template` is checked. - env.tsconfig({fullTemplateTypeCheck: true}); + // `strictTemplates: true` is necessary so content inside `ng-template` is checked. + env.tsconfig({strictTemplates: true}); }); it('should check bindings inside of for loop blocks', () => { @@ -6509,6 +6573,32 @@ suppress `Type 'number' is not assignable to type 'string'.`, ]); }); + + it('should type check a @for loop without a `track` expression', () => { + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + + @Component({ + template: \` + @for (item of items) { + {{does_not_exist}} + } + \`, + }) + export class Main { + items = []; + } + `, + ); + + const diags = env.driveDiagnostics(); + expect(diags.map((d) => ts.flattenDiagnosticMessageText(d.messageText, ''))).toEqual([ + `Property 'does_not_exist' does not exist on type 'Main'.`, + `@for loop must have a "track" expression`, + ]); + }); }); describe('control flow content projection diagnostics', () => { @@ -6957,6 +7047,7 @@ suppress it('should allow the content projection diagnostic to be disabled individually', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { checks: { controlFlowPreventingContentProjection: DiagnosticCategoryLabel.Suppress, @@ -6995,6 +7086,7 @@ suppress it('should allow the content projection diagnostic to be disabled via `defaultCategory`', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { defaultCategory: DiagnosticCategoryLabel.Suppress, }, @@ -8145,6 +8237,7 @@ suppress it('should be able to opt out for checking for unused imports via the tsconfig', () => { env.tsconfig({ + strictTemplates: true, extendedDiagnostics: { checks: { unusedStandaloneImports: DiagnosticCategoryLabel.Suppress, @@ -8704,5 +8797,115 @@ suppress expect(diags.length).toBe(0); }); }); + + describe('multiple matching components', () => { + it('should report an error when multiple components match the same element', () => { + env.tsconfig({strictTemplates: true}); + env.write( + 'test.ts', + ` + import {Component, NgModule} from '@angular/core'; + + @Component({ + selector: 'my-comp', + template: '', + standalone: false, + }) + export class CompA {} + + @Component({ + selector: 'my-comp', + template: '', + standalone: false, + }) + export class CompB {} + + @Component({ + selector: 'test', + template: '', + standalone: false, + }) + export class TestCmp {} + + @NgModule({ + declarations: [TestCmp, CompA, CompB], + }) + export class Module {} + `, + ); + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].code).toBe(ngErrorCode(ErrorCode.MULTIPLE_MATCHING_COMPONENTS)); + expect(diags[0].messageText).toContain( + 'Multiple components match node with tagname my-comp', + ); + }); + + it('should report an error when multiple components with attribute selectors match the same element', () => { + env.tsconfig({strictTemplates: true}); + env.write( + 'test.ts', + ` + import {Component} from '@angular/core'; + + + @Component({ + selector: '[stroked-button]', + template: '', + }) + export class StrokedBtn {} + + @Component({ + selector: '[raised-button]', + template: '', + }) + export class RaisedBtn {} + + @Component({ + selector: 'app-root', + template: '', + imports: [StrokedBtn, RaisedBtn], + }) + export class App {} + `, + ); + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].code).toBe(ngErrorCode(ErrorCode.MULTIPLE_MATCHING_COMPONENTS)); + expect(diags[0].messageText).toContain( + 'Multiple components match node with tagname button', + ); + }); + + it('should not report an error when a single component and directives match', () => { + env.tsconfig({strictTemplates: true}); + env.write( + 'test.ts', + ` + import {Component, Directive} from '@angular/core'; + + @Component({ + selector: 'my-comp', + template: '', + }) + export class CompA {} + + @Directive({ + selector: 'my-comp', + }) + export class DirB {} + + @Component({ + selector: 'test', + template: '', + imports: [CompA, DirB], + }) + export class TestCmp {} + `, + ); + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + }); }); }); diff --git a/packages/compiler/package.json b/packages/compiler/package.json index d5b71f18910d..5a97b762dc4a 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -5,7 +5,7 @@ "author": "angular", "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": "^22.22.0 || ^24.13.1 || >=26.0.0" }, "dependencies": { "tslib": "^2.3.0" diff --git a/packages/compiler/src/compiler.ts b/packages/compiler/src/compiler.ts index 1d478d083380..b3a7d8c7b4b4 100644 --- a/packages/compiler/src/compiler.ts +++ b/packages/compiler/src/compiler.ts @@ -48,16 +48,24 @@ export * from './expression_parser/lexer'; export * from './expression_parser/parser'; export * from './i18n/index'; export * from './injectable_compiler_2'; +export * from './service_compiler'; export {publishFacade} from './jit_compiler_facade'; export * from './ml_parser/ast'; export * from './ml_parser/html_parser'; export * from './ml_parser/html_tags'; +export { + ClassPropertyMapping, + ClassPropertyName, + InputOrOutput, + BindingPropertyName, +} from './property_mapping'; +export {MatchSource} from './render3/view/t2_api'; export {LexerRange} from './ml_parser/lexer'; export {ParseTreeResult, TreeError} from './ml_parser/parser'; export * from './ml_parser/tags'; export {TokenType as LexerTokenType} from './ml_parser/tokens'; export * from './ml_parser/xml_parser'; -export {EmitterVisitorContext} from './output/abstract_emitter'; +export {EmitterVisitorContext, AbstractEmitterVisitor} from './output/abstract_emitter'; export { ArrayType, ArrowFunctionExpr, @@ -135,6 +143,7 @@ export { export {compileDeclareDirectiveFromMetadata} from './render3/partial/directive'; export {compileDeclareFactoryFunction} from './render3/partial/factory'; export {compileDeclareInjectableFromMetadata} from './render3/partial/injectable'; +export {compileDeclareServiceFromMetadata} from './render3/partial/service'; export {compileDeclareInjectorFromMetadata} from './render3/partial/injector'; export {compileDeclareNgModuleFromMetadata} from './render3/partial/ng_module'; export {compileDeclarePipeFromMetadata} from './render3/partial/pipe'; @@ -220,6 +229,7 @@ export { MaybeForwardRefExpression, R3CompiledExpression, R3Reference, + isUnsafeObjectKey, } from './render3/util'; export * from './render3/view/api'; export { @@ -257,6 +267,16 @@ export {FactoryTarget} from './compiler_facade_interface'; export {QueryFlags} from './render3/view/query_generation'; export {setEnableTemplateSourceLocations} from './render3/view/config'; +export * from './typecheck/api'; +export * from './typecheck/host_bindings'; +export {CommentTriviaType, ExpressionIdentifier} from './typecheck/comments'; +export {OutOfBandDiagnosticRecorder, OutOfBandDiagnosticCategory} from './typecheck/oob'; +export {DomSchemaChecker} from './typecheck/schema'; +export {generateTypeCheckBlock} from './typecheck/type_check_block'; +export {TcbExpr} from './typecheck/ops/codegen'; +export {TcbGenericContextBehavior} from './typecheck/ops/context'; +export {LEGACY_OPTIONAL_CHAINING_DEFAULT} from './legacy_optional_chaining_default'; + // This file only reexports content of the `src` folder. Keep it that way. // This function call has a global side effects and publishes the compiler into global namespace for diff --git a/packages/compiler/src/compiler_facade_interface.ts b/packages/compiler/src/compiler_facade_interface.ts index f0c74e137ceb..81c75e56c024 100644 --- a/packages/compiler/src/compiler_facade_interface.ts +++ b/packages/compiler/src/compiler_facade_interface.ts @@ -96,6 +96,16 @@ export interface CompilerFacade { sourceMapUrl: string, meta: R3DeclareFactoryFacade, ): any; + compileService( + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3ServiceMetadataFacade, + ): any; + compileServiceDeclaration( + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + meta: R3DeclareServiceFacade, + ): any; createParseSourceSpan(kind: string, typeName: string, sourceUrl: string): ParseSourceSpan; @@ -123,6 +133,7 @@ export enum FactoryTarget { Injectable = 2, Pipe = 3, NgModule = 4, + Service = 5, } export interface R3DependencyMetadataFacade { @@ -163,6 +174,14 @@ export interface R3InjectableMetadataFacade { deps?: R3DependencyMetadataFacade[]; } +export interface R3ServiceMetadataFacade { + name: string; + type: Type; + typeArgumentCount: number; + autoProvided?: boolean; + factory?: OpaqueValue; +} + export interface R3NgModuleMetadataFacade { type: Type; bootstrap: Function[]; @@ -205,6 +224,7 @@ export interface R3DirectiveMetadataFacade { isStandalone: boolean; hostDirectives: R3HostDirectiveMetadataFacade[] | null; isSignal: boolean; + legacyOptionalChaining: boolean; } export interface R3ComponentMetadataFacade extends R3DirectiveMetadataFacade { @@ -258,6 +278,7 @@ export interface R3DeclareDirectiveFacade { isStandalone?: boolean; isSignal?: boolean; hostDirectives?: R3HostDirectiveMetadataFacade[] | null; + legacyOptionalChaining?: boolean; } export interface R3DeclareComponentFacade extends R3DeclareDirectiveFacade { @@ -343,6 +364,12 @@ export interface R3DeclareInjectableFacade { deps?: R3DeclareDependencyMetadataFacade[]; } +export interface R3DeclareServiceFacade { + type: Type; + autoProvided?: boolean; + factory?: OpaqueValue; +} + export enum ViewEncapsulation { Emulated = 0, // Historically the 1 value was for `Native` encapsulation which has been removed as of v11. diff --git a/packages/compiler/src/expression_parser/parser.ts b/packages/compiler/src/expression_parser/parser.ts index c0de70d43d4a..e6d57a058d25 100644 --- a/packages/compiler/src/expression_parser/parser.ts +++ b/packages/compiler/src/expression_parser/parser.ts @@ -17,8 +17,8 @@ import {ParseError, ParseSourceSpan} from '../parse_util'; import { AbsoluteSourceSpan, ArrowFunction, - ArrowFunctionParameter, ArrowFunctionIdentifierParameter, + ArrowFunctionParameter, AST, ASTWithSource, Binary, @@ -969,7 +969,7 @@ class _ParseAST { let result = this.parseAdditive(); while ( this.next.type == TokenType.Operator || - this.next.isKeywordIn() || // Should be invoked. This is bug here that will be fixed by #65249 when the breaking change window opens. + this.next.isKeywordIn() || this.next.isKeywordInstanceOf() ) { const operator = this.next.strValue; @@ -1144,9 +1144,6 @@ class _ParseAST { } else if (this.next.isKeywordFalse()) { this.advance(); return new LiteralPrimitive(this.span(start), this.sourceSpan(start), false); - } else if (this.next.isKeywordIn()) { - this.advance(); - return new LiteralPrimitive(this.span(start), this.sourceSpan(start), 'in'); } else if (this.next.isKeywordThis()) { this.advance(); return new ThisReceiver(this.span(start), this.sourceSpan(start)); diff --git a/packages/compiler/src/injectable_compiler_2.ts b/packages/compiler/src/injectable_compiler_2.ts index f3e24d7b69ba..1ace5fe5963a 100644 --- a/packages/compiler/src/injectable_compiler_2.ts +++ b/packages/compiler/src/injectable_compiler_2.ts @@ -140,20 +140,18 @@ export function compileInjectable( .callFn([injectableProps.toLiteralMap()], undefined, true); return { expression, - type: createInjectableType(meta), + type: createInjectableType(meta.type.type, meta.typeArgumentCount), statements: result.statements, }; } -export function createInjectableType(meta: R3InjectableMetadata) { +export function createInjectableType(type: o.Expression, typeArgumentCount: number) { return new o.ExpressionType( - o.importExpr(Identifiers.InjectableDeclaration, [ - typeWithParameters(meta.type.type, meta.typeArgumentCount), - ]), + o.importExpr(Identifiers.InjectableDeclaration, [typeWithParameters(type, typeArgumentCount)]), ); } -function delegateToFactory( +export function delegateToFactory( type: o.WrappedNodeExpr, useType: o.WrappedNodeExpr, unwrapForwardRefs: boolean, diff --git a/packages/compiler/src/jit_compiler_facade.ts b/packages/compiler/src/jit_compiler_facade.ts index ed623ee9cc36..641379c8c014 100644 --- a/packages/compiler/src/jit_compiler_facade.ts +++ b/packages/compiler/src/jit_compiler_facade.ts @@ -25,6 +25,7 @@ import { R3DeclarePipeDependencyFacade, R3DeclarePipeFacade, R3DeclareQueryMetadataFacade, + R3DeclareServiceFacade, R3DependencyMetadataFacade, R3DirectiveMetadataFacade, R3FactoryDefMetadataFacade, @@ -33,6 +34,7 @@ import { R3NgModuleMetadataFacade, R3PipeMetadataFacade, R3QueryMetadataFacade, + R3ServiceMetadataFacade, R3TemplateDependencyFacade, } from './compiler_facade_interface'; import {ConstantPool} from './constant_pool'; @@ -45,6 +47,7 @@ import { ViewEncapsulation, } from './core'; import {compileInjectable} from './injectable_compiler_2'; +import {LEGACY_OPTIONAL_CHAINING_DEFAULT} from './legacy_optional_chaining_default'; import { DeclareVarStmt, Expression, @@ -103,6 +106,7 @@ import {R3TargetBinder} from './render3/view/t2_binder'; import {makeBindingParser, parseTemplate} from './render3/view/template'; import {ResourceLoader} from './resource_loader'; import {DomElementSchemaRegistry} from './schema/dom_element_schema_registry'; +import {compileService} from './service_compiler'; import {getJitStandaloneDefaultForVersion} from './util'; export class CompilerFacadeImpl implements CompilerFacade { @@ -269,6 +273,44 @@ export class CompilerFacadeImpl implements CompilerFacade { return this.compileDirectiveFromMeta(angularCoreEnv, sourceMapUrl, meta); } + compileService( + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + facade: R3ServiceMetadataFacade, + ): any { + const {expression, statements} = compileService( + { + name: facade.name, + type: wrapReference(facade.type), + typeArgumentCount: facade.typeArgumentCount, + autoProvided: facade.autoProvided, + factory: facade.factory ? wrapExpression(facade, 'factory') : undefined, + }, + /* resolveForwardRefs */ true, + ); + + return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements); + } + + compileServiceDeclaration( + angularCoreEnv: CoreEnvironment, + sourceMapUrl: string, + facade: R3DeclareServiceFacade, + ): any { + const {expression, statements} = compileService( + { + name: facade.type.name, + type: wrapReference(facade.type), + typeArgumentCount: 0, + autoProvided: facade.autoProvided, + factory: facade.factory ? wrapExpression(facade, 'factory') : undefined, + }, + /* resolveForwardRefs */ true, + ); + + return this.jitExpression(expression, angularCoreEnv, sourceMapUrl, statements); + } + private compileDirectiveFromMeta( angularCoreEnv: CoreEnvironment, sourceMapUrl: string, @@ -568,6 +610,7 @@ function convertDeclareDirectiveFacadeToMetadata( declaration.isStandalone ?? getJitStandaloneDefaultForVersion(declaration.version), isSignal: declaration.isSignal ?? false, hostDirectives, + legacyOptionalChaining: declaration.legacyOptionalChaining ?? LEGACY_OPTIONAL_CHAINING_DEFAULT, }; } @@ -666,13 +709,14 @@ function convertDeclareComponentFacadeToMetadata( decl.viewProviders !== undefined ? new WrappedNodeExpr(decl.viewProviders) : null, animations: decl.animations !== undefined ? new WrappedNodeExpr(decl.animations) : null, defer, - changeDetection: decl.changeDetection ?? ChangeDetectionStrategy.Default, + changeDetection: decl.changeDetection ?? ChangeDetectionStrategy.OnPush, encapsulation: decl.encapsulation ?? ViewEncapsulation.Emulated, declarationListEmitMode: DeclarationListEmitMode.ClosureResolved, relativeContextFilePath: '', i18nUseExternalIds: true, relativeTemplatePath: null, hasDirectiveDependencies, + legacyOptionalChaining: decl.legacyOptionalChaining ?? LEGACY_OPTIONAL_CHAINING_DEFAULT, }; } @@ -864,12 +908,6 @@ function extractHostBindings( // First parse the declarations from the metadata. const bindings = parseHostBindings(host || {}); - // After that check host bindings for errors - const errors = verifyHostBindings(bindings, sourceSpan); - if (errors.length) { - throw new Error(errors.map((error: ParseError) => error.msg).join('\n')); - } - // Next, loop over the properties of the object, looking for @HostBinding and @HostListener. for (const field in propMetadata) { if (propMetadata.hasOwnProperty(field)) { @@ -889,6 +927,12 @@ function extractHostBindings( } } + // After that check host bindings for errors + const errors = verifyHostBindings(bindings, sourceSpan); + if (errors.length) { + throw new Error(errors.map((error: ParseError) => error.msg).join('\n')); + } + return bindings; } diff --git a/packages/zone.js/karma-build-sauce-mocha.conf.js b/packages/compiler/src/legacy_optional_chaining_default.ts similarity index 51% rename from packages/zone.js/karma-build-sauce-mocha.conf.js rename to packages/compiler/src/legacy_optional_chaining_default.ts index 081ca7b0a961..11ce3c1e8aa1 100644 --- a/packages/zone.js/karma-build-sauce-mocha.conf.js +++ b/packages/compiler/src/legacy_optional_chaining_default.ts @@ -6,7 +6,9 @@ * found in the LICENSE file at https://angular.dev/license */ -module.exports = function (config) { - require('./karma-dist-mocha.conf.js')(config); - require('./sauce.conf')(config); -}; +/** + * Whether legacy optional chaining is opt-in (false) or opt-out (true). + * + * This is extracted in order to be patched in G3. + */ +export const LEGACY_OPTIONAL_CHAINING_DEFAULT = false; diff --git a/packages/compiler/src/ml_parser/lexer.ts b/packages/compiler/src/ml_parser/lexer.ts index 3c8b04ab61b4..2800f3642087 100644 --- a/packages/compiler/src/ml_parser/lexer.ts +++ b/packages/compiler/src/ml_parser/lexer.ts @@ -299,14 +299,6 @@ class _Tokenizer { this._beginToken(TokenType.BLOCK_OPEN_START, start); const startToken = this._endToken([this._getBlockName()]); - if (startToken.parts[0] === 'default never' && this._attemptCharCode(chars.$SEMICOLON)) { - this._beginToken(TokenType.BLOCK_OPEN_END); - this._endToken([]); - this._beginToken(TokenType.BLOCK_CLOSE); - this._endToken([]); - return; - } - if (this._cursor.peek() === chars.$LPAREN) { // Advance past the opening paren. this._cursor.advance(); @@ -324,6 +316,14 @@ class _Tokenizer { } } + if (startToken.parts[0] === 'default never' && this._attemptCharCode(chars.$SEMICOLON)) { + this._beginToken(TokenType.BLOCK_OPEN_END); + this._endToken([]); + this._beginToken(TokenType.BLOCK_CLOSE); + this._endToken([]); + return; + } + if (this._attemptCharCode(chars.$LBRACE)) { this._beginToken(TokenType.BLOCK_OPEN_END); this._endToken([]); @@ -423,8 +423,8 @@ class _Tokenizer { const endChar = this._cursor.peek(); if (endChar === chars.$SEMICOLON) { this._beginToken(TokenType.LET_END); - this._endToken([]); this._cursor.advance(); + this._endToken([]); } else { startToken.type = TokenType.INCOMPLETE_LET; startToken.sourceSpan = this._cursor.getSpan(start); @@ -804,6 +804,28 @@ class _Tokenizer { return [prefix, name]; } + private _consumeSingleLineComment() { + this._attemptCharCodeUntilFn((code) => chars.isNewLine(code) || code === chars.$EOF); + this._attemptCharCodeUntilFn(isNotWhitespace); + } + + private _consumeMultiLineComment() { + this._attemptCharCodeUntilFn((code) => { + if (code === chars.$EOF) { + return true; + } + if (code === chars.$STAR) { + const next = this._cursor.clone(); + next.advance(); + return next.peek() === chars.$SLASH; + } + return false; + }); + if (this._attemptStr('*/')) { + this._attemptCharCodeUntilFn(isNotWhitespace); + } + } + private _consumeTagOpen(start: CharacterCursor) { let tagName: string; let prefix: string; @@ -840,7 +862,21 @@ class _Tokenizer { this._attemptCharCodeUntilFn(isNotWhitespace); } - while (!isAttributeTerminator(this._cursor.peek())) { + while (true) { + if (this._attemptStr('//')) { + this._consumeSingleLineComment(); + continue; + } + + if (this._attemptStr('/*')) { + this._consumeMultiLineComment(); + continue; + } + + if (isAttributeTerminator(this._cursor.peek())) { + break; + } + if (this._selectorlessEnabled && this._cursor.peek() === chars.$AT) { const start = this._cursor.clone(); const nameStart = start.clone(); @@ -1431,7 +1467,12 @@ function isDigitEntityEnd(code: number): boolean { } function isNamedEntityEnd(code: number): boolean { - return code === chars.$SEMICOLON || code === chars.$EOF || !chars.isAsciiLetter(code); + // Named entities may contain digits (e.g. ¹, ½, ▓). + return ( + code === chars.$SEMICOLON || + code === chars.$EOF || + !(chars.isAsciiLetter(code) || chars.isDigit(code)) + ); } function isExpansionCaseStart(peek: number): boolean { diff --git a/packages/compiler/src/ml_parser/parser.ts b/packages/compiler/src/ml_parser/parser.ts index 88ea7c9f8607..a0a697191656 100644 --- a/packages/compiler/src/ml_parser/parser.ts +++ b/packages/compiler/src/ml_parser/parser.ts @@ -843,7 +843,7 @@ class _TreeBuilder { endToken = this._advance(); } - const end = endToken.sourceSpan.fullStart; + const end = endToken.sourceSpan.end; const span = new ParseSourceSpan( startToken.sourceSpan.start, end, diff --git a/packages/compiler/src/output/abstract_emitter.ts b/packages/compiler/src/output/abstract_emitter.ts index adcff8205d66..237126c40caf 100644 --- a/packages/compiler/src/output/abstract_emitter.ts +++ b/packages/compiler/src/output/abstract_emitter.ts @@ -11,14 +11,15 @@ import {ParseSourceSpan} from '../parse_util'; import * as o from './output_ast'; import {SourceMapGenerator} from './source_map'; -const _SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n|\r|\$/g; -const _LEGAL_IDENTIFIER_RE = /^[$A-Z_][0-9A-Z_$]*$/i; -const _INDENT_WITH = ' '; +const SINGLE_QUOTE_ESCAPE_STRING_RE = /'|\\|\n|\r/g; +const LEGAL_IDENTIFIER_RE = /^[$A-Z_][0-9A-Z_$]*$/i; +const INDENT_WITH = ' '; -class _EmittedLine { +class EmittedLine { partsLength = 0; - parts: string[] = []; - srcSpans: (ParseSourceSpan | null)[] = []; + readonly parts: string[] = []; + readonly srcSpans: (ParseSourceSpan | null)[] = []; + constructor(public indent: number) {} } @@ -61,17 +62,17 @@ export class EmitterVisitorContext { return new EmitterVisitorContext(0); } - private _lines: _EmittedLine[]; + private _lines: EmittedLine[]; constructor(private _indent: number) { - this._lines = [new _EmittedLine(_indent)]; + this._lines = [new EmittedLine(_indent)]; } /** * @internal strip this from published d.ts files due to * https://github.com/microsoft/TypeScript/issues/36216 */ - private get _currentLine(): _EmittedLine { + private get _currentLine(): EmittedLine { return this._lines[this._lines.length - 1]; } @@ -84,7 +85,7 @@ export class EmitterVisitorContext { } lineLength(): number { - return this._currentLine.indent * _INDENT_WITH.length + this._currentLine.partsLength; + return this._currentLine.indent * INDENT_WITH.length + this._currentLine.partsLength; } print(from: {sourceSpan: ParseSourceSpan | null} | null, part: string, newLine: boolean = false) { @@ -94,7 +95,7 @@ export class EmitterVisitorContext { this._currentLine.srcSpans.push((from && from.sourceSpan) || null); } if (newLine) { - this._lines.push(new _EmittedLine(this._indent)); + this._lines.push(new EmittedLine(this._indent)); } } @@ -120,7 +121,7 @@ export class EmitterVisitorContext { toSource(): string { return this.sourceLines - .map((l) => (l.parts.length > 0 ? _createIndent(l.indent) + l.parts.join('') : '')) + .map((l) => (l.parts.length > 0 ? INDENT_WITH.repeat(l.indent) + l.parts.join('') : '')) .join('\n'); } @@ -148,7 +149,7 @@ export class EmitterVisitorContext { const spans = line.srcSpans; const parts = line.parts; - let col0 = line.indent * _INDENT_WITH.length; + let col0 = line.indent * INDENT_WITH.length; let spanIdx = 0; // skip leading parts without source spans while (spanIdx < spans.length && !spans[spanIdx]) { @@ -187,7 +188,7 @@ export class EmitterVisitorContext { spanOf(line: number, column: number): ParseSourceSpan | null { const emittedLine = this._lines[line]; if (emittedLine) { - let columnsLeft = column - _createIndent(emittedLine.indent).length; + let columnsLeft = column - INDENT_WITH.repeat(emittedLine.indent).length; for (let partIndex = 0; partIndex < emittedLine.parts.length; partIndex++) { const part = emittedLine.parts[partIndex]; if (part.length > columnsLeft) { @@ -203,7 +204,7 @@ export class EmitterVisitorContext { * @internal strip this from published d.ts files due to * https://github.com/microsoft/TypeScript/issues/36216 */ - private get sourceLines(): _EmittedLine[] { + private get sourceLines(): EmittedLine[] { if (this._lines.length && this._lines[this._lines.length - 1].parts.length === 0) { return this._lines.slice(0, -1); } @@ -211,46 +212,33 @@ export class EmitterVisitorContext { } } -export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.ExpressionVisitor { +export abstract class AbstractEmitterVisitor + implements o.StatementVisitor, o.ExpressionVisitor, o.TypeVisitor +{ private lastIfCondition: o.Expression | null = null; - constructor(private _escapeDollarInStrings: boolean) {} + constructor( + protected readonly printComments: boolean, + protected readonly printTypes: boolean, + ) {} - protected printLeadingComments(stmt: o.Statement, ctx: EmitterVisitorContext): void { - if (stmt.leadingComments === undefined) { - return; - } - for (const comment of stmt.leadingComments) { - if (comment instanceof o.JSDocComment) { - ctx.print(stmt, `/*${comment.toString()}*/`, comment.trailingNewline); - } else { - if (comment.multiline) { - ctx.print(stmt, `/* ${comment.text} */`, comment.trailingNewline); - } else { - comment.text.split('\n').forEach((line) => { - ctx.println(stmt, `// ${line}`); - }); - } - } - } - } + abstract visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): void; + abstract visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): void; - visitExpressionStmt(stmt: o.ExpressionStatement, ctx: EmitterVisitorContext): any { + visitExpressionStmt(stmt: o.ExpressionStatement, ctx: EmitterVisitorContext): void { this.printLeadingComments(stmt, ctx); stmt.expr.visitExpression(this, ctx); ctx.println(stmt, ';'); - return null; } - visitReturnStmt(stmt: o.ReturnStatement, ctx: EmitterVisitorContext): any { + visitReturnStmt(stmt: o.ReturnStatement, ctx: EmitterVisitorContext): void { this.printLeadingComments(stmt, ctx); ctx.print(stmt, `return `); stmt.value.visitExpression(this, ctx); ctx.println(stmt, ';'); - return null; } - visitIfStmt(stmt: o.IfStmt, ctx: EmitterVisitorContext): any { + visitIfStmt(stmt: o.IfStmt, ctx: EmitterVisitorContext): void { this.printLeadingComments(stmt, ctx); ctx.print(stmt, `if (`); this.lastIfCondition = stmt.condition; // We can skip redundant parentheses for the condition. @@ -276,13 +264,27 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex } } ctx.println(stmt, `}`); - return null; } - abstract visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any; + visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): void { + const varKind = stmt.hasModifier(o.StmtModifier.Final) ? 'const' : 'let'; - visitInvokeFunctionExpr(expr: o.InvokeFunctionExpr, ctx: EmitterVisitorContext): any { - const shouldParenthesize = expr.fn instanceof o.ArrowFunctionExpr; + this.printLeadingComments(stmt, ctx); + ctx.print(stmt, `${varKind} ${stmt.name}`); + stmt.type?.visitType(this, ctx); + + if (stmt.value) { + ctx.print(stmt, ' = '); + stmt.value.visitExpression(this, ctx); + } + + ctx.println(stmt, `;`); + } + + visitInvokeFunctionExpr(expr: o.InvokeFunctionExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(expr, ctx); + + const shouldParenthesize = this.shouldParenthesize(expr.fn, expr); if (shouldParenthesize) { ctx.print(expr.fn, '('); @@ -291,20 +293,22 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex if (shouldParenthesize) { ctx.print(expr.fn, ')'); } - ctx.print(expr, `(`); + ctx.print(expr, expr.isOptional ? '?.(' : '('); this.visitAllExpressions(expr.args, ctx, ','); - ctx.print(expr, `)`); - return null; + ctx.print(expr, ')'); } + visitTaggedTemplateLiteralExpr( expr: o.TaggedTemplateLiteralExpr, ctx: EmitterVisitorContext, - ): any { + ): void { + this.printLeadingComments(expr, ctx); expr.tag.visitExpression(this, ctx); expr.template.visitExpression(this, ctx); - return null; } + visitTemplateLiteralExpr(expr: o.TemplateLiteralExpr, ctx: EmitterVisitorContext) { + this.printLeadingComments(expr, ctx); ctx.print(expr, '`'); for (let i = 0; i < expr.elements.length; i++) { expr.elements[i].visitExpression(this, ctx); @@ -317,52 +321,58 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex } ctx.print(expr, '`'); } + visitTemplateLiteralElementExpr(expr: o.TemplateLiteralElementExpr, ctx: EmitterVisitorContext) { + this.printLeadingComments(expr, ctx); ctx.print(expr, expr.rawText); } - visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): any { - throw new Error('Abstract emitter cannot visit WrappedNodeExpr.'); - } - visitTypeofExpr(expr: o.TypeofExpr, ctx: EmitterVisitorContext): any { + + visitTypeofExpr(expr: o.TypeofExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(expr, ctx); ctx.print(expr, 'typeof '); expr.expr.visitExpression(this, ctx); } - visitVoidExpr(expr: o.VoidExpr, ctx: EmitterVisitorContext): any { + + visitVoidExpr(expr: o.VoidExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(expr, ctx); ctx.print(expr, 'void '); expr.expr.visitExpression(this, ctx); } - visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): any { + + visitReadVarExpr(ast: o.ReadVarExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, ast.name); - return null; } - visitInstantiateExpr(ast: o.InstantiateExpr, ctx: EmitterVisitorContext): any { + + visitInstantiateExpr(ast: o.InstantiateExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, `new `); ast.classExpr.visitExpression(this, ctx); ctx.print(ast, `(`); this.visitAllExpressions(ast.args, ctx, ','); ctx.print(ast, `)`); - return null; } - visitLiteralExpr(ast: o.LiteralExpr, ctx: EmitterVisitorContext): any { + visitLiteralExpr(ast: o.LiteralExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); const value = ast.value; if (typeof value === 'string') { - ctx.print(ast, escapeIdentifier(value, this._escapeDollarInStrings)); + ctx.print(ast, escapeIdentifier(value)!); } else { ctx.print(ast, `${value}`); } - return null; } visitRegularExpressionLiteral( ast: o.RegularExpressionLiteralExpr, ctx: EmitterVisitorContext, - ): any { + ): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, `/${ast.body}/${ast.flags || ''}`); - return null; } - visitLocalizedString(ast: o.LocalizedString, ctx: EmitterVisitorContext): any { + visitLocalizedString(ast: o.LocalizedString, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); const head = ast.serializeI18nHead(); ctx.print(ast, '$localize `' + head.raw); for (let i = 1; i < ast.messageParts.length; i++) { @@ -371,36 +381,98 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex ctx.print(ast, `}${ast.serializeI18nTemplatePart(i).raw}`); } ctx.print(ast, '`'); - return null; } - abstract visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any; - - visitConditionalExpr(ast: o.ConditionalExpr, ctx: EmitterVisitorContext): any { + visitConditionalExpr(ast: o.ConditionalExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, `(`); ast.condition.visitExpression(this, ctx); - ctx.print(ast, '? '); + ctx.print(ast, ' ? '); ast.trueCase.visitExpression(this, ctx); - ctx.print(ast, ': '); - ast.falseCase!.visitExpression(this, ctx); + ctx.print(ast, ' : '); + ast.falseCase?.visitExpression(this, ctx); ctx.print(ast, `)`); - return null; } - visitDynamicImportExpr(ast: o.DynamicImportExpr, ctx: EmitterVisitorContext) { - ctx.print(ast, `import(${ast.url})`); + visitDynamicImportExpr(ast: o.DynamicImportExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); + ctx.print(ast, `import(`); + + if (typeof ast.url === 'string') { + ctx.print(ast, escapeIdentifier(ast.url, true)!); + } else { + ast.url.visitExpression(this, ctx); + } + + ctx.print(ast, `)`); } - visitNotExpr(ast: o.NotExpr, ctx: EmitterVisitorContext): any { + visitNotExpr(ast: o.NotExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, '!'); ast.condition.visitExpression(this, ctx); - return null; } - abstract visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any; - abstract visitArrowFunctionExpr(ast: o.ArrowFunctionExpr, context: any): any; - abstract visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, context: any): any; - visitUnaryOperatorExpr(ast: o.UnaryOperatorExpr, ctx: EmitterVisitorContext): any { + visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); + ctx.print(ast, `function${ast.name ? ' ' + ast.name : ''}(`); + this.visitParams(ast.params, ctx); + ctx.print(ast, `)`); + ast.type?.visitType(this, ctx); + ctx.print(ast, ` {`); + ctx.println(ast); + ctx.incIndent(); + this.visitAllStatements(ast.statements, ctx); + ctx.decIndent(); + ctx.println(ast, `}`); + } + + visitArrowFunctionExpr(ast: o.ArrowFunctionExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); + ctx.print(ast, '('); + this.visitParams(ast.params, ctx); + ctx.print(ast, ')'); + ast.type?.visitType(this, ctx); + ctx.print(ast, ' => '); + + if (Array.isArray(ast.body)) { + ctx.print(ast, `{`); + ctx.println(ast); + ctx.incIndent(); + this.visitAllStatements(ast.body, ctx); + ctx.decIndent(); + ctx.println(ast, `}`); + } else { + const shouldParenthesize = this.shouldParenthesize(ast.body, ast); + + if (shouldParenthesize) { + ctx.print(ast, '('); + } + + ast.body.visitExpression(this, ctx); + + if (shouldParenthesize) { + ctx.print(ast, ')'); + } + } + } + + visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): void { + this.printLeadingComments(stmt, ctx); + ctx.print(stmt, `function ${stmt.name}(`); + this.visitParams(stmt.params, ctx); + ctx.print(stmt, `)`); + stmt.type?.visitType(this, ctx); + ctx.print(stmt, ` {`); + ctx.println(stmt); + ctx.incIndent(); + this.visitAllStatements(stmt.statements, ctx); + ctx.decIndent(); + ctx.println(stmt, `}`); + } + + visitUnaryOperatorExpr(ast: o.UnaryOperatorExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); let opStr: string; switch (ast.operator) { case o.UnaryOperator.Plus: @@ -417,10 +489,10 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex ctx.print(ast, opStr); ast.expr.visitExpression(this, ctx); if (parens) ctx.print(ast, `)`); - return null; } - visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, ctx: EmitterVisitorContext): any { + visitBinaryOperatorExpr(ast: o.BinaryOperatorExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); const operator = BINARY_OPERATORS.get(ast.operator); if (!operator) { throw new Error(`Unknown operator ${ast.operator}`); @@ -431,29 +503,32 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex ctx.print(ast, ` ${operator} `); ast.rhs.visitExpression(this, ctx); if (parens) ctx.print(ast, `)`); - return null; } - visitReadPropExpr(ast: o.ReadPropExpr, ctx: EmitterVisitorContext): any { + visitReadPropExpr(ast: o.ReadPropExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ast.receiver.visitExpression(this, ctx); - ctx.print(ast, `.`); + ctx.print(ast, ast.isOptional ? `?.` : `.`); ctx.print(ast, ast.name); - return null; } - visitReadKeyExpr(ast: o.ReadKeyExpr, ctx: EmitterVisitorContext): any { + + visitReadKeyExpr(ast: o.ReadKeyExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ast.receiver.visitExpression(this, ctx); - ctx.print(ast, `[`); + ctx.print(ast, ast.isOptional ? `?.[` : `[`); ast.index.visitExpression(this, ctx); ctx.print(ast, `]`); - return null; } - visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: EmitterVisitorContext): any { + + visitLiteralArrayExpr(ast: o.LiteralArrayExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, `[`); - this.visitAllExpressions(ast.entries, ctx, ','); + this.visitAllExpressions(ast.entries, ctx, ', '); ctx.print(ast, `]`); - return null; } - visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: EmitterVisitorContext): any { + + visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, `{`); this.visitAllObjects( (entry) => { @@ -461,36 +536,117 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex ctx.print(ast, '...'); entry.expression.visitExpression(this, ctx); } else { - ctx.print( - ast, - `${escapeIdentifier(entry.key, this._escapeDollarInStrings, entry.quoted)}:`, - ); + ctx.print(ast, `${escapeIdentifier(entry.key, entry.quoted)}: `); entry.value.visitExpression(this, ctx); } }, ast.entries, ctx, - ',', + ', ', ); ctx.print(ast, `}`); - return null; } - visitCommaExpr(ast: o.CommaExpr, ctx: EmitterVisitorContext): any { + + visitCommaExpr(ast: o.CommaExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, '('); - this.visitAllExpressions(ast.parts, ctx, ','); + this.visitAllExpressions(ast.parts, ctx, ', '); ctx.print(ast, ')'); - return null; } - visitParenthesizedExpr(ast: o.ParenthesizedExpr, ctx: EmitterVisitorContext): any { + + visitParenthesizedExpr(ast: o.ParenthesizedExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); // We parenthesize everything regardless of an explicit ParenthesizedExpr, so we can just visit // the inner expression. // TODO: Do we *need* to parenthesize everything? ast.expr.visitExpression(this, ctx); } - visitSpreadElementExpr(ast: o.SpreadElementExpr, ctx: EmitterVisitorContext) { + + visitSpreadElementExpr(ast: o.SpreadElementExpr, ctx: EmitterVisitorContext): void { + this.printLeadingComments(ast, ctx); ctx.print(ast, '...'); ast.expression.visitExpression(this, ctx); } + + visitBuiltinType(type: o.BuiltinType, ctx: EmitterVisitorContext): void { + if (!this.printTypes) { + return; + } + + switch (type.name) { + case o.BuiltinTypeName.Bool: + ctx.print(null, ': boolean'); + break; + case o.BuiltinTypeName.Dynamic: + ctx.print(null, ': any'); + break; + case o.BuiltinTypeName.Int: + case o.BuiltinTypeName.Number: + ctx.print(null, ': number'); + break; + case o.BuiltinTypeName.String: + ctx.print(null, ': string'); + break; + case o.BuiltinTypeName.None: + ctx.print(null, ': void'); + break; + case o.BuiltinTypeName.Inferred: + // Emits nothing + break; + case o.BuiltinTypeName.Function: + ctx.print(null, ': Function'); + break; + default: + ctx.print(null, ': any'); + break; + } + } + + visitExpressionType(type: o.ExpressionType, ctx: EmitterVisitorContext): void { + if (!this.printTypes) { + return; + } + + ctx.print(null, ': '); + type.value.visitExpression(this, ctx); + + if (type.typeParams && type.typeParams.length > 0) { + ctx.print(null, '<'); + this.visitAllObjects((param) => param.visitType(this, ctx), type.typeParams, ctx, ','); + ctx.print(null, '>'); + } + } + + visitArrayType(type: o.ArrayType, ctx: EmitterVisitorContext): void { + if (!this.printTypes) { + return; + } + + ctx.print(null, ': '); + type.of.visitType(this, ctx); + ctx.print(null, '[]'); + } + + visitMapType(type: o.MapType, ctx: EmitterVisitorContext): void { + if (!this.printTypes) { + return; + } + + ctx.print(null, ': { [key: string]: '); + + if (type.valueType) { + type.valueType.visitType(this, ctx); + } else { + ctx.print(null, 'any'); + } + + ctx.print(null, '}'); + } + + visitTransplantedType(type: o.TransplantedType, ctx: EmitterVisitorContext): void { + throw new Error('TransplantedType nodes are not supported'); + } + visitAllExpressions( expressions: o.Expression[], ctx: EmitterVisitorContext, @@ -532,20 +688,63 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex visitAllStatements(statements: o.Statement[], ctx: EmitterVisitorContext): void { statements.forEach((stmt) => stmt.visitStatement(this, ctx)); } + + protected visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void { + this.visitAllObjects( + (param) => { + ctx.print(null, param.name); + param.type?.visitType(this, ctx); + }, + params, + ctx, + ', ', + ); + } + + protected shouldParenthesize( + expression: o.Expression, + containingExpression: o.Expression, + ): boolean { + // Note: this method is protected so consumers can override it, e.g. in case a + // `WrappedNodeExpr` wraps an expression that needs to be parenthesized. + return ( + // e.g. `(() => foo)()` or `(function() {})()`. + ((expression instanceof o.ArrowFunctionExpr || expression instanceof o.FunctionExpr) && + containingExpression instanceof o.InvokeFunctionExpr) || + // e.g. `() => ({a: 1, b: 2})` + (expression instanceof o.LiteralMapExpr && + containingExpression instanceof o.ArrowFunctionExpr) + ); + } + + protected printLeadingComments( + node: o.Expression | o.Statement, + ctx: EmitterVisitorContext, + ): void { + if (!this.printComments || node.leadingComments === undefined) { + return; + } + for (const comment of node.leadingComments) { + if (comment instanceof o.JSDocComment) { + ctx.print(node, `/*${comment.toString()}*/`, comment.trailingNewline); + } else { + if (comment.multiline) { + ctx.print(node, `/* ${comment.text} */`, comment.trailingNewline); + } else { + comment.text.split('\n').forEach((line) => ctx.println(node, `// ${line}`)); + } + } + } + } } -export function escapeIdentifier( - input: string, - escapeDollar: boolean, - alwaysQuote: boolean = true, -): any { +export function escapeIdentifier(input: string, alwaysQuote: boolean = true): string | null { if (input == null) { return null; } - const body = input.replace(_SINGLE_QUOTE_ESCAPE_STRING_RE, (...match: string[]) => { - if (match[0] == '$') { - return escapeDollar ? '\\$' : '$'; - } else if (match[0] == '\n') { + + const body = input.replace(SINGLE_QUOTE_ESCAPE_STRING_RE, (...match: string[]) => { + if (match[0] == '\n') { return '\\n'; } else if (match[0] == '\r') { return '\\r'; @@ -553,14 +752,7 @@ export function escapeIdentifier( return `\\${match[0]}`; } }); - const requiresQuotes = alwaysQuote || !_LEGAL_IDENTIFIER_RE.test(body); - return requiresQuotes ? `'${body}'` : body; -} -function _createIndent(count: number): string { - let res = ''; - for (let i = 0; i < count; i++) { - res += _INDENT_WITH; - } - return res; + const requiresQuotes = alwaysQuote || !LEGAL_IDENTIFIER_RE.test(body); + return requiresQuotes ? `'${body}'` : body; } diff --git a/packages/compiler/src/output/abstract_js_emitter.ts b/packages/compiler/src/output/abstract_js_emitter.ts index 27e2ef5efbd5..a3b7d82dca54 100644 --- a/packages/compiler/src/output/abstract_js_emitter.ts +++ b/packages/compiler/src/output/abstract_js_emitter.ts @@ -26,26 +26,26 @@ const makeTemplateObjectPolyfill = export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { constructor() { - super(false); + super(false /* printComments */, false /* emitTypes */); } - override visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): any { + override visitWrappedNodeExpr(ast: o.WrappedNodeExpr, ctx: EmitterVisitorContext): void { throw new Error('Cannot emit a WrappedNodeExpr in Javascript.'); } - override visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any { + override visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): void { ctx.print(stmt, `var ${stmt.name}`); if (stmt.value) { ctx.print(stmt, ' = '); stmt.value.visitExpression(this, ctx); } ctx.println(stmt, `;`); - return null; } + override visitTaggedTemplateLiteralExpr( ast: o.TaggedTemplateLiteralExpr, ctx: EmitterVisitorContext, - ): any { + ): void { // The following convoluted piece of code is effectively the downlevelled equivalent of // ``` // tag`...` @@ -57,19 +57,16 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { const elements = ast.template.elements; ast.tag.visitExpression(this, ctx); ctx.print(ast, `(${makeTemplateObjectPolyfill}(`); - ctx.print(ast, `[${elements.map((part) => escapeIdentifier(part.text, false)).join(', ')}], `); - ctx.print( - ast, - `[${elements.map((part) => escapeIdentifier(part.rawText, false)).join(', ')}])`, - ); + ctx.print(ast, `[${elements.map((part) => escapeIdentifier(part.text)).join(', ')}], `); + ctx.print(ast, `[${elements.map((part) => escapeIdentifier(part.rawText)).join(', ')}])`); ast.template.expressions.forEach((expression) => { ctx.print(ast, ', '); expression.visitExpression(this, ctx); }); ctx.print(ast, ')'); - return null; } - override visitTemplateLiteralExpr(expr: o.TemplateLiteralExpr, ctx: EmitterVisitorContext): any { + + override visitTemplateLiteralExpr(expr: o.TemplateLiteralExpr, ctx: EmitterVisitorContext): void { ctx.print(expr, '`'); for (let i = 0; i < expr.elements.length; i++) { expr.elements[i].visitExpression(this, ctx); @@ -82,61 +79,15 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { } ctx.print(expr, '`'); } + override visitTemplateLiteralElementExpr( expr: o.TemplateLiteralElementExpr, ctx: EmitterVisitorContext, - ): any { + ): void { ctx.print(expr, expr.rawText); - return null; - } - override visitFunctionExpr(ast: o.FunctionExpr, ctx: EmitterVisitorContext): any { - ctx.print(ast, `function${ast.name ? ' ' + ast.name : ''}(`); - this._visitParams(ast.params, ctx); - ctx.println(ast, `) {`); - ctx.incIndent(); - this.visitAllStatements(ast.statements, ctx); - ctx.decIndent(); - ctx.print(ast, `}`); - return null; } - override visitArrowFunctionExpr(ast: o.ArrowFunctionExpr, ctx: EmitterVisitorContext): any { - ctx.print(ast, '('); - this._visitParams(ast.params, ctx); - ctx.print(ast, ') =>'); - - if (Array.isArray(ast.body)) { - ctx.println(ast, `{`); - ctx.incIndent(); - this.visitAllStatements(ast.body, ctx); - ctx.decIndent(); - ctx.print(ast, `}`); - } else { - const isObjectLiteral = ast.body instanceof o.LiteralMapExpr; - - if (isObjectLiteral) { - ctx.print(ast, '('); - } - - ast.body.visitExpression(this, ctx); - if (isObjectLiteral) { - ctx.print(ast, ')'); - } - } - - return null; - } - override visitDeclareFunctionStmt(stmt: o.DeclareFunctionStmt, ctx: EmitterVisitorContext): any { - ctx.print(stmt, `function ${stmt.name}(`); - this._visitParams(stmt.params, ctx); - ctx.println(stmt, `) {`); - ctx.incIndent(); - this.visitAllStatements(stmt.statements, ctx); - ctx.decIndent(); - ctx.println(stmt, `}`); - return null; - } - override visitLocalizedString(ast: o.LocalizedString, ctx: EmitterVisitorContext): any { + override visitLocalizedString(ast: o.LocalizedString, ctx: EmitterVisitorContext): void { // The following convoluted piece of code is effectively the downlevelled equivalent of // ``` // $localize `...` @@ -150,17 +101,12 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { for (let i = 1; i < ast.messageParts.length; i++) { parts.push(ast.serializeI18nTemplatePart(i)); } - ctx.print(ast, `[${parts.map((part) => escapeIdentifier(part.cooked, false)).join(', ')}], `); - ctx.print(ast, `[${parts.map((part) => escapeIdentifier(part.raw, false)).join(', ')}])`); + ctx.print(ast, `[${parts.map((part) => escapeIdentifier(part.cooked)).join(', ')}], `); + ctx.print(ast, `[${parts.map((part) => escapeIdentifier(part.raw)).join(', ')}])`); ast.expressions.forEach((expression) => { ctx.print(ast, ', '); expression.visitExpression(this, ctx); }); ctx.print(ast, ')'); - return null; - } - - private _visitParams(params: o.FnParam[], ctx: EmitterVisitorContext): void { - this.visitAllObjects((param) => ctx.print(null, param.name), params, ctx, ','); } } diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index cf12cc811ffe..966f2cbf9f32 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -195,7 +195,11 @@ export abstract class Expression { public type: Type | null; public sourceSpan: ParseSourceSpan | null; - constructor(type: Type | null | undefined, sourceSpan?: ParseSourceSpan | null) { + constructor( + type: Type | null | undefined, + sourceSpan?: ParseSourceSpan | null, + public leadingComments?: LeadingComment[], + ) { this.type = type || null; this.sourceSpan = sourceSpan || null; } @@ -227,14 +231,16 @@ export abstract class Expression { params: Expression[], sourceSpan?: ParseSourceSpan | null, pure?: boolean, + leadingComments?: LeadingComment[], ): InvokeFunctionExpr { - return new InvokeFunctionExpr(this, params, null, sourceSpan, pure); + return new InvokeFunctionExpr(this, params, null, sourceSpan, pure, leadingComments); } instantiate( params: Expression[], type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ): InstantiateExpr { return new InstantiateExpr(this, params, type, sourceSpan); } @@ -243,6 +249,7 @@ export abstract class Expression { trueCase: Expression, falseCase: Expression | null = null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ): ConditionalExpr { return new ConditionalExpr(this, trueCase, falseCase, null, sourceSpan); } @@ -310,8 +317,8 @@ export abstract class Expression { return new BinaryOperatorExpr(BinaryOperator.NullishCoalesce, this, rhs, null, sourceSpan); } - toStmt(): Statement { - return new ExpressionStatement(this, null); + toStmt(leadingComments?: LeadingComment[]): Statement { + return new ExpressionStatement(this, null, leadingComments); } } @@ -320,8 +327,9 @@ export class ReadVarExpr extends Expression { public name: string, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -350,8 +358,9 @@ export class TypeofExpr extends Expression { public expr: Expression, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override visitExpression(visitor: ExpressionVisitor, context: any) { @@ -376,8 +385,9 @@ export class VoidExpr extends Expression { public expr: Expression, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override visitExpression(visitor: ExpressionVisitor, context: any) { @@ -402,8 +412,9 @@ export class WrappedNodeExpr extends Expression { public node: T, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -430,8 +441,10 @@ export class InvokeFunctionExpr extends Expression { type?: Type | null, sourceSpan?: ParseSourceSpan | null, public pure = false, + leadingComments?: LeadingComment[], + public isOptional = false, ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } // An alias for fn, which allows other logic to handle calls and property reads together. @@ -463,6 +476,8 @@ export class InvokeFunctionExpr extends Expression { this.type, this.sourceSpan, this.pure, + [], + this.isOptional, ); } } @@ -473,8 +488,9 @@ export class TaggedTemplateLiteralExpr extends Expression { public template: TemplateLiteralExpr, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -509,8 +525,9 @@ export class InstantiateExpr extends Expression { public args: Expression[], type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -544,8 +561,9 @@ export class RegularExpressionLiteralExpr extends Expression { public body: string, public flags: string | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(null, sourceSpan); + super(null, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -572,8 +590,9 @@ export class LiteralExpr extends Expression { public value: number | string | boolean | null | undefined, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -598,8 +617,9 @@ export class TemplateLiteralExpr extends Expression { public elements: TemplateLiteralElementExpr[], public expressions: Expression[], sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(null, sourceSpan); + super(null, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -632,8 +652,9 @@ export class TemplateLiteralElementExpr extends Expression { readonly text: string, sourceSpan?: ParseSourceSpan | null, rawText?: string, + leadingComments?: LeadingComment[], ) { - super(STRING_TYPE, sourceSpan); + super(STRING_TYPE, sourceSpan, leadingComments); // If `rawText` is not provided, "fake" the raw string by escaping the following sequences: // - "\" would otherwise indicate that the next character is a control character. @@ -699,8 +720,9 @@ export class LocalizedString extends Expression { readonly placeHolderNames: PlaceholderPiece[], readonly expressions: Expression[], sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(STRING_TYPE, sourceSpan); + super(STRING_TYPE, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -851,8 +873,9 @@ export class ExternalExpr extends Expression { type?: Type | null, public typeParams: Type[] | null = null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -893,8 +916,9 @@ export class ConditionalExpr extends Expression { public falseCase: Expression | null = null, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type || trueCase.type, sourceSpan); + super(type || trueCase.type, sourceSpan, leadingComments); this.trueCase = trueCase; } @@ -931,8 +955,9 @@ export class DynamicImportExpr extends Expression { public url: string | Expression, sourceSpan?: ParseSourceSpan | null, public urlComment?: string, + leadingComments?: LeadingComment[], ) { - super(null, sourceSpan); + super(null, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -960,8 +985,9 @@ export class NotExpr extends Expression { constructor( public condition: Expression, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(BOOL_TYPE, sourceSpan); + super(BOOL_TYPE, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -1003,8 +1029,9 @@ export class FunctionExpr extends Expression { type?: Type | null, sourceSpan?: ParseSourceSpan | null, public name?: string | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression | Statement): boolean { @@ -1055,8 +1082,9 @@ export class ArrowFunctionExpr extends Expression { public body: Expression | Statement[], type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -1105,8 +1133,9 @@ export class UnaryOperatorExpr extends Expression { type?: Type | null, sourceSpan?: ParseSourceSpan | null, public parens: boolean = true, + leadingComments?: LeadingComment[], ) { - super(type || NUMBER_TYPE, sourceSpan); + super(type || NUMBER_TYPE, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -1141,8 +1170,9 @@ export class ParenthesizedExpr extends Expression { public expr: Expression, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override visitExpression(visitor: ExpressionVisitor, context: any) { @@ -1171,8 +1201,9 @@ export class BinaryOperatorExpr extends Expression { public rhs: Expression, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type || lhs.type, sourceSpan); + super(type || lhs.type, sourceSpan, leadingComments); this.lhs = lhs; } @@ -1226,8 +1257,13 @@ export class ReadPropExpr extends Expression { public name: string, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], + /** + * Whether the property access uses the optional-chaining operator (`?.`). + */ + public isOptional = false, ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } // An alias for name, which allows other logic to handle property reads and keyed reads together. @@ -1237,7 +1273,10 @@ export class ReadPropExpr extends Expression { override isEquivalent(e: Expression): boolean { return ( - e instanceof ReadPropExpr && this.receiver.isEquivalent(e.receiver) && this.name === e.name + e instanceof ReadPropExpr && + this.receiver.isEquivalent(e.receiver) && + this.name === e.name && + this.isOptional === e.isOptional ); } @@ -1260,7 +1299,14 @@ export class ReadPropExpr extends Expression { } override clone(): ReadPropExpr { - return new ReadPropExpr(this.receiver.clone(), this.name, this.type, this.sourceSpan); + return new ReadPropExpr( + this.receiver.clone(), + this.name, + this.type, + this.sourceSpan, + [], + this.isOptional, + ); } } @@ -1270,15 +1316,21 @@ export class ReadKeyExpr extends Expression { public index: Expression, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], + /** + * Whether the property access uses the optional-chaining operator (`?.[`). + */ + public isOptional = false, ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { return ( e instanceof ReadKeyExpr && this.receiver.isEquivalent(e.receiver) && - this.index.isEquivalent(e.index) + this.index.isEquivalent(e.index) && + this.isOptional === e.isOptional ); } @@ -1301,14 +1353,26 @@ export class ReadKeyExpr extends Expression { } override clone(): ReadKeyExpr { - return new ReadKeyExpr(this.receiver.clone(), this.index.clone(), this.type, this.sourceSpan); + return new ReadKeyExpr( + this.receiver.clone(), + this.index.clone(), + this.type, + this.sourceSpan, + [], + this.isOptional, + ); } } export class LiteralArrayExpr extends Expression { public entries: Expression[]; - constructor(entries: Expression[], type?: Type | null, sourceSpan?: ParseSourceSpan | null) { - super(type, sourceSpan); + constructor( + entries: Expression[], + type?: Type | null, + sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], + ) { + super(type, sourceSpan, leadingComments); this.entries = entries; } @@ -1376,8 +1440,9 @@ export class LiteralMapExpr extends Expression { public entries: LiteralMapEntry[], type?: MapType | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(type, sourceSpan); + super(type, sourceSpan, leadingComments); if (type) { this.valueType = type.valueType; } @@ -1405,8 +1470,9 @@ export class CommaExpr extends Expression { constructor( public parts: Expression[], sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(parts[parts.length - 1].type, sourceSpan); + super(parts[parts.length - 1].type, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -1430,8 +1496,9 @@ export class SpreadElementExpr extends Expression { constructor( public expression: Expression, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ) { - super(null, sourceSpan); + super(null, sourceSpan, leadingComments); } override isEquivalent(e: Expression): boolean { @@ -1858,8 +1925,9 @@ export function variable( name: string, type?: Type | null, sourceSpan?: ParseSourceSpan | null, + leadingComments?: LeadingComment[], ): ReadVarExpr { - return new ReadVarExpr(name, type, sourceSpan); + return new ReadVarExpr(name, type, sourceSpan, leadingComments); } export function importExpr( diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/property_mapping.ts b/packages/compiler/src/property_mapping.ts similarity index 98% rename from packages/compiler-cli/src/ngtsc/metadata/src/property_mapping.ts rename to packages/compiler/src/property_mapping.ts index 1423b6628f96..f4f200ea6a36 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/property_mapping.ts +++ b/packages/compiler/src/property_mapping.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.dev/license */ -import {InputOutputPropertySet} from '@angular/compiler'; - /** * The name of a class property that backs an input or output declared by a directive or component. * @@ -53,9 +51,7 @@ export interface InputOrOutput { * Allows bidirectional querying of the mapping - looking up all inputs/outputs with a given * property name, or mapping from a specific class property to its binding property name. */ -export class ClassPropertyMapping - implements InputOutputPropertySet -{ +export class ClassPropertyMapping { /** * Mapping from class property names to the single `InputOrOutput` for that class property. */ diff --git a/packages/compiler/src/render3/partial/api.ts b/packages/compiler/src/render3/partial/api.ts index 1c90823a2452..003e39e657fc 100644 --- a/packages/compiler/src/render3/partial/api.ts +++ b/packages/compiler/src/render3/partial/api.ts @@ -222,7 +222,7 @@ export interface R3DeclareComponentMetadata extends R3DeclareDirectiveMetadata { /** * Strategy used for detecting changes in the component. - * Defaults to `ChangeDetectionStrategy.Default`. + * Defaults to `ChangeDetectionStrategy.OnPush`. */ changeDetection?: ChangeDetectionStrategy; @@ -585,3 +585,23 @@ export interface R3DeclareHostDirectiveMetadata { inputs?: string[]; outputs?: string[]; } + +/** + * Describes the shape of the object that the `ɵɵngDeclareService()` function accepts. + * + * This interface serves primarily as documentation, as conformance to this interface is not + * enforced during linking. + */ +export interface R3DeclareServiceMetadata extends R3PartialDeclaration { + /** + * Determines whether the service should be provided automatically or if the user + * is responsible for providing it. + */ + autoProvided?: boolean; + + /** + * If provided, an expression that evaluates to a function to use when creating an instance of + * this injectable. + */ + factory?: o.Expression; +} diff --git a/packages/compiler/src/render3/partial/directive.ts b/packages/compiler/src/render3/partial/directive.ts index 087dff973816..4d676e1da60c 100644 --- a/packages/compiler/src/render3/partial/directive.ts +++ b/packages/compiler/src/render3/partial/directive.ts @@ -10,16 +10,12 @@ import {Identifiers as R3} from '../r3_identifiers'; import { convertFromMaybeForwardRefExpression, generateForwardRef, + isUnsafeObjectKey, R3CompiledExpression, } from '../util'; import {R3DirectiveMetadata, R3HostMetadata, R3QueryMetadata} from '../view/api'; import {createDirectiveType, createHostDirectivesMappingArray} from '../view/compiler'; -import { - asLiteral, - conditionallyCreateDirectiveBindingLiteral, - DefinitionMap, - UNSAFE_OBJECT_KEY_NAME_REGEXP, -} from '../view/util'; +import {asLiteral, conditionallyCreateDirectiveBindingLiteral, DefinitionMap} from '../view/util'; import {R3DeclareDirectiveMetadata, R3DeclareQueryMetadata} from './api'; import {toOptionalLiteralMap} from './util'; @@ -286,7 +282,7 @@ function createInputsPartialMetadata(inputs: R3DirectiveMetadata['inputs']): o.E return { key: declaredName, // put quotes around keys that contain potentially unsafe characters - quoted: UNSAFE_OBJECT_KEY_NAME_REGEXP.test(declaredName), + quoted: isUnsafeObjectKey(declaredName), value: o.literalMap([ {key: 'classPropertyName', quoted: false, value: asLiteral(value.classPropertyName)}, {key: 'publicName', quoted: false, value: asLiteral(value.bindingPropertyName)}, @@ -340,7 +336,7 @@ function legacyInputsPartialMetadata(inputs: R3DirectiveMetadata['inputs']): o.E return { key: declaredName, // put quotes around keys that contain potentially unsafe characters - quoted: UNSAFE_OBJECT_KEY_NAME_REGEXP.test(declaredName), + quoted: isUnsafeObjectKey(declaredName), value: result, }; }), diff --git a/packages/compiler/src/render3/partial/injectable.ts b/packages/compiler/src/render3/partial/injectable.ts index 99e9a9ccc337..af40037b16b9 100644 --- a/packages/compiler/src/render3/partial/injectable.ts +++ b/packages/compiler/src/render3/partial/injectable.ts @@ -32,7 +32,7 @@ export function compileDeclareInjectableFromMetadata( const definitionMap = createInjectableDefinitionMap(meta); const expression = o.importExpr(R3.declareInjectable).callFn([definitionMap.toLiteralMap()]); - const type = createInjectableType(meta); + const type = createInjectableType(meta.type.type, meta.typeArgumentCount); return {expression, type, statements: []}; } diff --git a/packages/compiler/src/render3/partial/service.ts b/packages/compiler/src/render3/partial/service.ts new file mode 100644 index 000000000000..7dabaf4aa2f7 --- /dev/null +++ b/packages/compiler/src/render3/partial/service.ts @@ -0,0 +1,59 @@ +/** + * @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 {R3ServiceMetadata} from '../../service_compiler'; +import {createInjectableType} from '../../injectable_compiler_2'; +import * as o from '../../output/output_ast'; +import {Identifiers as R3} from '../r3_identifiers'; +import {R3CompiledExpression} from '../util'; +import {DefinitionMap} from '../view/util'; +import {R3DeclareServiceMetadata} from './api'; + +/** + * Every time we make a breaking change to the declaration interface or partial-linker behavior, we + * must update this constant to prevent old partial-linkers from incorrectly processing the + * declaration. + * + * Do not include any prerelease in these versions as they are ignored. + */ +const MINIMUM_PARTIAL_LINKER_VERSION = '22.0.0'; + +/** + * Compile a Service declaration defined by the `R3ServiceMetadata`. + */ +export function compileDeclareServiceFromMetadata(meta: R3ServiceMetadata): R3CompiledExpression { + const definitionMap = createServiceDefinitionMap(meta); + + const expression = o.importExpr(R3.declareService).callFn([definitionMap.toLiteralMap()]); + const type = createInjectableType(meta.type.type, meta.typeArgumentCount); + + return {expression, type, statements: []}; +} + +/** + * Gathers the declaration fields for a Service into a `DefinitionMap`. + */ +export function createServiceDefinitionMap( + meta: R3ServiceMetadata, +): DefinitionMap { + const definitionMap = new DefinitionMap(); + + definitionMap.set('minVersion', o.literal(MINIMUM_PARTIAL_LINKER_VERSION)); + definitionMap.set('version', o.literal('0.0.0-PLACEHOLDER')); + definitionMap.set('ngImport', o.importExpr(R3.core)); + definitionMap.set('type', meta.type.value); + + if (meta.autoProvided === false) { + definitionMap.set('autoProvided', o.literal(false)); + } + + if (meta.factory !== undefined) { + definitionMap.set('factory', meta.factory); + } + + return definitionMap; +} diff --git a/packages/compiler/src/render3/r3_ast.ts b/packages/compiler/src/render3/r3_ast.ts index d570bb139501..8c7fa4a79098 100644 --- a/packages/compiler/src/render3/r3_ast.ts +++ b/packages/compiler/src/render3/r3_ast.ts @@ -208,7 +208,18 @@ export class BoundDeferredTrigger extends DeferredTrigger { export class NeverDeferredTrigger extends DeferredTrigger {} -export class IdleDeferredTrigger extends DeferredTrigger {} +export class IdleDeferredTrigger extends DeferredTrigger { + constructor( + nameSpan: ParseSourceSpan, + sourceSpan: ParseSourceSpan, + prefetchSpan: ParseSourceSpan | null, + onSourceSpan: ParseSourceSpan | null, + hydrateSpan: ParseSourceSpan | null, + public timeout: number | null, + ) { + super(nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan); + } +} export class ImmediateDeferredTrigger extends DeferredTrigger {} @@ -460,6 +471,7 @@ export class SwitchBlockCaseGroup extends BlockNode implements Node { export class SwitchExhaustiveCheck extends BlockNode implements Node { constructor( + public expression: AST | null, sourceSpan: ParseSourceSpan, startSourceSpan: ParseSourceSpan, endSourceSpan: ParseSourceSpan | null, @@ -477,8 +489,8 @@ export class ForLoopBlock extends BlockNode implements Node { constructor( public item: Variable, public expression: ASTWithSource, - public trackBy: ASTWithSource, - public trackKeywordSpan: ParseSourceSpan, + public trackBy: ASTWithSource | null, + public trackKeywordSpan: ParseSourceSpan | null, public contextVariables: Variable[], public children: Node[], public empty: ForLoopBlockEmpty | null, @@ -803,9 +815,8 @@ export class RecursiveVisitor implements Visitor { visitAll(this, block.branches); } visitIfBlockBranch(block: IfBlockBranch): void { - const blockItems = block.children; - block.expressionAlias && blockItems.push(block.expressionAlias); - visitAll(this, blockItems); + visitAll(this, block.children); + block.expressionAlias?.visit(this); } visitContent(content: Content): void { visitAll(this, content.children); diff --git a/packages/compiler/src/render3/r3_class_metadata_compiler.ts b/packages/compiler/src/render3/r3_class_metadata_compiler.ts index adc42e902ec6..07632fb2e4a8 100644 --- a/packages/compiler/src/render3/r3_class_metadata_compiler.ts +++ b/packages/compiler/src/render3/r3_class_metadata_compiler.ts @@ -8,7 +8,7 @@ import * as o from '../output/output_ast'; import {Identifiers as R3} from './r3_identifiers'; -import {devOnlyGuardedExpression} from './util'; +import {devOnlyGuardedExpression, tsIgnoreComment} from './util'; import {R3DeferPerComponentDependency} from './view/api'; export type CompileClassMetadataFn = (metadata: R3ClassMetadata) => o.Expression; @@ -150,7 +150,13 @@ export function compileComponentMetadataAsyncResolver( ); // e.g. `import('./cmp-a').then(...)` - return new o.DynamicImportExpr(importPath).prop('then').callFn([innerFn]); + return new o.DynamicImportExpr(importPath) + .prop('then') + .callFn([innerFn], undefined, undefined, [ + // Necessary, because we might not generate extensions for the path + // and TS may try to enforce it based on the compiler options. + tsIgnoreComment(), + ]); }); // e.g. `() => [ ... ];` diff --git a/packages/compiler/src/render3/r3_control_flow.ts b/packages/compiler/src/render3/r3_control_flow.ts index 78fe0434da8c..98ea387c7e30 100644 --- a/packages/compiler/src/render3/r3_control_flow.ts +++ b/packages/compiler/src/render3/r3_control_flow.ts @@ -183,35 +183,40 @@ export function createForLoop( } if (params !== null) { + // The `for` block has a main span that includes the `empty` branch. For only the span of the + // main `for` body, use `mainSourceSpan`. + const endSpan = empty?.endSourceSpan ?? ast.endSourceSpan; + const sourceSpan = new ParseSourceSpan( + ast.sourceSpan.start, + endSpan?.end ?? ast.sourceSpan.end, + ); + let trackExpression: ASTWithSource | null; + let trackKeywordSpan: ParseSourceSpan | null; + if (params.trackBy === null) { - // TODO: We should not fail here, and instead try to produce some AST for the language - // service. + trackExpression = trackKeywordSpan = null; errors.push(new ParseError(ast.startSourceSpan, '@for loop must have a "track" expression')); } else { - // The `for` block has a main span that includes the `empty` branch. For only the span of the - // main `for` body, use `mainSourceSpan`. - const endSpan = empty?.endSourceSpan ?? ast.endSourceSpan; - const sourceSpan = new ParseSourceSpan( - ast.sourceSpan.start, - endSpan?.end ?? ast.sourceSpan.end, - ); + trackExpression = params.trackBy.expression; + trackKeywordSpan = params.trackBy.keywordSpan; validateTrackByExpression(params.trackBy.expression, params.trackBy.keywordSpan, errors); - node = new t.ForLoopBlock( - params.itemName, - params.expression, - params.trackBy.expression, - params.trackBy.keywordSpan, - params.context, - html.visitAll(visitor, ast.children, ast.children), - empty, - sourceSpan, - ast.sourceSpan, - ast.startSourceSpan, - endSpan, - ast.nameSpan, - ast.i18n, - ); } + + node = new t.ForLoopBlock( + params.itemName, + params.expression, + trackExpression, + trackKeywordSpan, + params.context, + html.visitAll(visitor, ast.children, ast.children), + empty, + sourceSpan, + ast.sourceSpan, + ast.startSourceSpan, + endSpan, + ast.nameSpan, + ast.i18n, + ); } return {node, errors}; @@ -264,6 +269,10 @@ export function createSwitchBlock( if (isCase) { expression = parseBlockParameterToBinding(node.parameters[0], bindingParser); } else if (node.name === 'default never') { + if (node.parameters.length > 0) { + expression = parseBlockParameterToBinding(node.parameters[0], bindingParser); + } + if ( node.children.length > 0 || (node.endSourceSpan !== null && @@ -287,6 +296,7 @@ export function createSwitchBlock( } exhaustiveCheck = new t.SwitchExhaustiveCheck( + expression, node.sourceSpan, node.startSourceSpan, node.endSourceSpan, diff --git a/packages/compiler/src/render3/r3_deferred_triggers.ts b/packages/compiler/src/render3/r3_deferred_triggers.ts index 834ceec0b7c6..f790f8218971 100644 --- a/packages/compiler/src/render3/r3_deferred_triggers.ts +++ b/packages/compiler/src/render3/r3_deferred_triggers.ts @@ -488,11 +488,26 @@ function createIdleTrigger( onSourceSpan: ParseSourceSpan | null, hydrateSpan: ParseSourceSpan | null, ): t.IdleDeferredTrigger { - if (parameters.length > 0) { - throw new Error(`"${OnTriggerType.IDLE}" trigger cannot have parameters`); + if (parameters.length > 1) { + throw new Error(`"${OnTriggerType.IDLE}" trigger can only have zero or one parameters`); + } + + let timeout: number | null = null; + if (parameters[0]) { + timeout = parseDeferredTime(parameters[0].expression); + if (timeout === null) { + throw new Error(`Could not parse time value of trigger "${OnTriggerType.IDLE}"`); + } } - return new t.IdleDeferredTrigger(nameSpan, sourceSpan, prefetchSpan, onSourceSpan, hydrateSpan); + return new t.IdleDeferredTrigger( + nameSpan, + sourceSpan, + prefetchSpan, + onSourceSpan, + hydrateSpan, + timeout, + ); } function createTimerTrigger( diff --git a/packages/compiler/src/render3/r3_factory.ts b/packages/compiler/src/render3/r3_factory.ts index 43d910babb65..fe131688a4ea 100644 --- a/packages/compiler/src/render3/r3_factory.ts +++ b/packages/compiler/src/render3/r3_factory.ts @@ -10,7 +10,7 @@ import {InjectFlags} from '../core'; import * as o from '../output/output_ast'; import {Identifiers as R3} from '../render3/r3_identifiers'; -import {R3CompiledExpression, R3Reference, typeWithParameters} from './util'; +import {R3CompiledExpression, R3Reference, tsIgnoreComment, typeWithParameters} from './util'; /** * Metadata required by the factory generator to generate a `factory` function for a type. @@ -117,6 +117,19 @@ export function compileFactoryFunction(meta: R3FactoryMetadata): R3CompiledExpre : t; let ctorExpr: o.Expression | null = null; + + // If the factory has invalid dependencies (e.g. trying to inject an interface), we normally mark + // the `deps` as invalid so we can emit an invalid factory. In some environments we may not + // be able to determine if the dependency is invalid, because that depends on information in + // other files. To ensure that cases like that still compile, we need to add a `@ts-ignore` + // comment which allows the code to compile and then error at runtime. Note that it's important + // to put the comment on a statement, because it includes a new line which may break `return` + // statements if the comment is set on the expression. + const factoryComments = + meta.deps !== null && meta.deps !== 'invalid' && meta.deps.length > 0 + ? [tsIgnoreComment()] + : undefined; + if (meta.deps !== null) { // There is a constructor (either explicitly or implicitly defined). if (meta.deps !== 'invalid') { @@ -133,12 +146,13 @@ export function compileFactoryFunction(meta: R3FactoryMetadata): R3CompiledExpre function makeConditionalFactory(nonCtorExpr: o.Expression): o.ReadVarExpr { const r = o.variable('__ngConditionalFactory__'); - body.push(new o.DeclareVarStmt(r.name, o.NULL_EXPR, o.INFERRED_TYPE)); + body.push(new o.DeclareVarStmt(r.name, o.NULL_EXPR, o.DYNAMIC_TYPE)); const ctorStmt = ctorExpr !== null - ? r.set(ctorExpr).toStmt() + ? r.set(ctorExpr).toStmt(factoryComments) : o.importExpr(R3.invalidFactory).callFn([]).toStmt(); - body.push(o.ifStmt(t, [ctorStmt], [r.set(nonCtorExpr).toStmt()])); + // Always add a `ts-ignore` on the alternate factory. + body.push(o.ifStmt(t, [ctorStmt], [r.set(nonCtorExpr).toStmt([tsIgnoreComment()])])); return r; } @@ -173,7 +187,7 @@ export function compileFactoryFunction(meta: R3FactoryMetadata): R3CompiledExpre body.push(new o.ReturnStatement(baseFactory.callFn([typeForCtor]))); } else { // This is straightforward factory, just return it. - body.push(new o.ReturnStatement(retExpr)); + body.push(new o.ReturnStatement(retExpr, null, factoryComments)); } let factoryFn: o.Expression = o.fn( @@ -188,7 +202,13 @@ export function compileFactoryFunction(meta: R3FactoryMetadata): R3CompiledExpre // There is a base factory variable so wrap its declaration along with the factory function into // an IIFE. factoryFn = o - .arrowFn([], [new o.DeclareVarStmt(baseFactoryVar.name!), new o.ReturnStatement(factoryFn)]) + .arrowFn( + [], + [ + new o.DeclareVarStmt(baseFactoryVar.name!, undefined, o.DYNAMIC_TYPE), + new o.ReturnStatement(factoryFn), + ], + ) .callFn([], /* sourceSpan */ undefined, /* pure */ true); } diff --git a/packages/compiler/src/render3/r3_hmr_compiler.ts b/packages/compiler/src/render3/r3_hmr_compiler.ts index 6250c838219c..82c830b1b79a 100644 --- a/packages/compiler/src/render3/r3_hmr_compiler.ts +++ b/packages/compiler/src/render3/r3_hmr_compiler.ts @@ -78,7 +78,10 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression { ]); // (m) => m.default && ɵɵreplaceMetadata(...) - const replaceCallback = o.arrowFn([new o.FnParam(moduleName)], defaultRead.and(replaceCall)); + const replaceCallback = o.arrowFn( + [new o.FnParam(moduleName, o.DYNAMIC_TYPE)], + defaultRead.and(replaceCall), + ); // getReplaceMetadataURL(id, timestamp, import.meta.url) const url = o @@ -94,7 +97,7 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression { // } const importCallback = new o.DeclareFunctionStmt( importCallbackName, - [new o.FnParam(timestampName)], + [new o.FnParam(timestampName, o.DYNAMIC_TYPE)], [ // The vite-ignore special comment is required to prevent Vite from generating a superfluous // warning for each usage within the development code. If Vite provides a method to @@ -110,7 +113,7 @@ export function compileHmrInitializer(meta: R3HmrMetadata): o.Expression { // (d) => d.id === id && Cmp_HmrLoad(d.timestamp) const updateCallback = o.arrowFn( - [new o.FnParam(dataName)], + [new o.FnParam(dataName, o.DYNAMIC_TYPE)], o .variable(dataName) .prop('id') @@ -173,7 +176,7 @@ export function compileHmrUpdateCallback( const body: o.Statement[] = []; for (const local of meta.localDependencies) { - params.push(new o.FnParam(local.name)); + params.push(new o.FnParam(local.name, o.DYNAMIC_TYPE)); } // Declare variables that read out the individual namespaces. diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 45c1bf00473d..82d96103762f 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -187,6 +187,10 @@ export class Identifiers { name: 'ɵɵdeferEnableTimerScheduling', moduleName: CORE, }; + static enableIncrementalHydrationRuntime: o.ExternalReference = { + name: 'ɵɵenableIncrementalHydrationRuntime', + moduleName: CORE, + }; static conditionalCreate: o.ExternalReference = {name: 'ɵɵconditionalCreate', moduleName: CORE}; static conditionalBranchCreate: o.ExternalReference = { @@ -307,6 +311,9 @@ export class Identifiers { moduleName: CORE, }; + static defineService: o.ExternalReference = {name: 'ɵɵdefineService', moduleName: CORE}; + static declareService: o.ExternalReference = {name: 'ɵɵngDeclareService', moduleName: CORE}; + static resolveWindow: o.ExternalReference = {name: 'ɵɵresolveWindow', moduleName: CORE}; static resolveDocument: o.ExternalReference = {name: 'ɵɵresolveDocument', moduleName: CORE}; static resolveBody: o.ExternalReference = {name: 'ɵɵresolveBody', moduleName: CORE}; diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index c38c30a12286..bf268b1cb31a 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -591,10 +591,11 @@ class HtmlAstToIvyAst implements html.Visitor { // Note that validation is skipped and property mapping is disabled // due to the fact that we need to make sure a given prop is not an // input of a directive and directive matching happens at runtime. + const isAttrOn = prop.name.toLowerCase().startsWith('attr.on'); const bep = this.bindingParser.createBoundElementProperty( elementName, prop, - /* skipValidation */ true, + /* skipValidation */ !isAttrOn, /* mapPropertyName */ false, ); bound.push(t.BoundAttribute.fromBoundElementProperty(bep, i18n)); @@ -619,7 +620,6 @@ class HtmlAstToIvyAst implements html.Visitor { for (const attribute of attrs) { let hasBinding = false; - const normalizedName = normalizeAttributeName(attribute.name); // `*attr` defines template bindings let isTemplateBinding = false; @@ -628,7 +628,7 @@ class HtmlAstToIvyAst implements html.Visitor { i18nAttrsMeta[attribute.name] = attribute.i18n; } - if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) { + if (attribute.name.startsWith(TEMPLATE_ATTR_PREFIX)) { // *-attributes if (elementHasInlineTemplate) { this.reportError( @@ -639,7 +639,7 @@ class HtmlAstToIvyAst implements html.Visitor { isTemplateBinding = true; elementHasInlineTemplate = true; const templateValue = attribute.value; - const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length); + const templateKey = attribute.name.substring(TEMPLATE_ATTR_PREFIX.length); const parsedVariables: ParsedVariable[] = []; const absoluteValueOffset = attribute.valueSpan @@ -705,7 +705,7 @@ class HtmlAstToIvyAst implements html.Visitor { variables: t.Variable[], references: t.Reference[], ) { - const name = normalizeAttributeName(attribute.name); + const name = attribute.name; const value = attribute.value; const srcSpan = attribute.sourceSpan; const absoluteOffset = attribute.valueSpan @@ -715,8 +715,7 @@ class HtmlAstToIvyAst implements html.Visitor { function createKeySpan(srcSpan: ParseSourceSpan, prefix: string, identifier: string) { // We need to adjust the start location for the keySpan to account for the removed 'data-' // prefix from `normalizeAttributeName`. - const normalizationAdjustment = attribute.name.length - name.length; - const keySpanStart = srcSpan.start.moveBy(prefix.length + normalizationAdjustment); + const keySpanStart = srcSpan.start.moveBy(prefix.length); const keySpanEnd = keySpanStart.moveBy(identifier.length); return new ParseSourceSpan(keySpanStart, keySpanEnd, keySpanStart, identifier); } @@ -1254,10 +1253,6 @@ class NonBindableVisitor implements html.Visitor { const NON_BINDABLE_VISITOR = new NonBindableVisitor(); -function normalizeAttributeName(attrName: string): string { - return /^data-/i.test(attrName) ? attrName.substring(5) : attrName; -} - function addEvents(events: ParsedEvent[], boundEvents: t.BoundEvent[]) { boundEvents.push(...events.map((e) => t.BoundEvent.fromParsedEvent(e))); } diff --git a/packages/compiler/src/render3/util.ts b/packages/compiler/src/render3/util.ts index e12dd0d23418..a2d38f2771ed 100644 --- a/packages/compiler/src/render3/util.ts +++ b/packages/compiler/src/render3/util.ts @@ -11,6 +11,9 @@ import * as o from '../output/output_ast'; import {Identifiers} from './r3_identifiers'; +/** Regex that includes unsafe characters in an object literal property name. */ +const UNSAFE_OBJECT_KEY_NAME_REGEXP = /[-.]/; + export function typeWithParameters(type: o.Expression, numParams: number): o.ExpressionType { if (numParams === 0) { return o.expressionType(type); @@ -46,7 +49,7 @@ export function prepareSyntheticListenerName(name: string, phase: string) { } export function getSafePropertyAccessString(accessor: string, name: string): string { - const escapedName = escapeIdentifier(name, false, false); + const escapedName = escapeIdentifier(name, false); return escapedName !== name ? `${accessor}[${escapedName}]` : `${accessor}.${name}`; } @@ -89,6 +92,14 @@ export function refsToArray(refs: R3Reference[], shouldForwardDeclare: boolean): return shouldForwardDeclare ? o.arrowFn([], values) : values; } +export function tsIgnoreComment(): o.LeadingComment { + return o.leadingComment('@ts-ignore', true, true); +} + +export function isUnsafeObjectKey(key: string): boolean { + return UNSAFE_OBJECT_KEY_NAME_REGEXP.test(key); +} + /** * Describes an expression that may have been wrapped in a `forwardRef()` guard. * diff --git a/packages/compiler/src/render3/view/api.ts b/packages/compiler/src/render3/view/api.ts index 471c9209caf9..216825b0f8b2 100644 --- a/packages/compiler/src/render3/view/api.ts +++ b/packages/compiler/src/render3/view/api.ts @@ -121,6 +121,11 @@ export interface R3DirectiveMetadata { * Additional directives applied to the directive host. */ hostDirectives: R3HostDirectiveMetadata[] | null; + + /** + * Whether null should be used instead of undefined for optional chaining. + */ + legacyOptionalChaining: boolean; } /** diff --git a/packages/compiler/src/render3/view/compiler.ts b/packages/compiler/src/render3/view/compiler.ts index 77ac761b57ac..2a68935853c8 100644 --- a/packages/compiler/src/render3/view/compiler.ts +++ b/packages/compiler/src/render3/view/compiler.ts @@ -8,7 +8,6 @@ import {ConstantPool} from '../../constant_pool'; import * as core from '../../core'; -import {CssSelector} from '../../directive_matching'; import * as o from '../../output/output_ast'; import {ParseError, ParseSourceSpan} from '../../parse_util'; import {ShadowCss} from '../../shadow_css'; @@ -17,7 +16,7 @@ import {emitHostBindingFunction, emitTemplateFn, transform} from '../../template import {ingestComponent, ingestHostBinding} from '../../template/pipeline/src/ingest'; import {BindingParser} from '../../template_parser/binding_parser'; import {Identifiers as R3} from '../r3_identifiers'; -import {R3CompiledExpression, typeWithParameters} from '../util'; +import {R3CompiledExpression, tsIgnoreComment, typeWithParameters} from '../util'; import { DeclarationListEmitMode, @@ -36,7 +35,6 @@ import {asLiteral, conditionallyCreateDirectiveBindingLiteral, DefinitionMap} fr const COMPONENT_VARIABLE = '%COMP%'; const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`; const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`; -const ANIMATE_LEAVE = `animate.leave`; function baseDirectiveFields( meta: R3DirectiveMetadata, @@ -80,6 +78,7 @@ function baseDirectiveFields( meta.selector || '', meta.name, definitionMap, + meta.legacyOptionalChaining, ), ); @@ -103,16 +102,6 @@ function baseDirectiveFields( return definitionMap; } -function hasAnimationHostBinding( - meta: R3DirectiveMetadata | R3ComponentMetadata, -): boolean { - return ( - meta.host.attributes[ANIMATE_LEAVE] !== undefined || - meta.host.properties[ANIMATE_LEAVE] !== undefined || - meta.host.listeners[ANIMATE_LEAVE] !== undefined - ); -} - /** * Add features to the definition map. */ @@ -194,28 +183,6 @@ export function compileComponentFromMetadata( const definitionMap = baseDirectiveFields(meta, constantPool, bindingParser); addFeatures(definitionMap, meta); - const selector = meta.selector && CssSelector.parse(meta.selector); - const firstSelector = selector && selector[0]; - - // e.g. `attr: ["class", ".my.app"]` - // This is optional an only included if the first selector of a component specifies attributes. - if (firstSelector) { - const selectorAttributes = firstSelector.getAttrs(); - if (selectorAttributes.length) { - definitionMap.set( - 'attrs', - constantPool.getConstLiteral( - o.literalArr( - selectorAttributes.map((value) => - value != null ? o.literal(value) : o.literal(undefined), - ), - ), - /* forceShared */ true, - ), - ); - } - } - // e.g. `template: function MyComponent_Template(_ctx, _cm) {...}` const templateTypeName = meta.name; @@ -248,6 +215,7 @@ export function compileComponentFromMetadata( allDeferrableDepsFn, meta.relativeTemplatePath, getTemplateSourceLocationsEnabled(), + meta.legacyOptionalChaining, ); // Then the IR is transformed to prepare it for code generation. @@ -339,7 +307,7 @@ export function compileComponentFromMetadata( if (meta.changeDetection !== null) { if ( typeof meta.changeDetection === 'number' && - meta.changeDetection !== core.ChangeDetectionStrategy.Default + meta.changeDetection !== core.ChangeDetectionStrategy.OnPush ) { // changeDetection is resolved during analysis. Only set it if not the default. definitionMap.set('changeDetection', o.literal(meta.changeDetection)); @@ -487,6 +455,7 @@ function createHostBindingsFunction( selector: string, name: string, definitionMap: DefinitionMap, + legacyOptionalChaining: boolean, ): o.Expression | null { const bindings = bindingParser.createBoundHostProperties( hostBindingsMetadata.properties, @@ -521,6 +490,7 @@ function createHostBindingsFunction( properties: bindings, events: eventBindings, attributes: hostBindingsMetadata.attributes, + legacyOptionalChaining: legacyOptionalChaining, }, bindingParser, constantPool, @@ -537,38 +507,42 @@ function createHostBindingsFunction( return emitHostBindingFunction(hostJob); } -const HOST_REG_EXP = /^(?:\[([^\]]+)\])|(?:\(([^\)]+)\))$/; -// Represents the groups in the above regex. -const enum HostBindingGroup { - // group 1: "prop" from "[prop]", or "attr.role" from "[attr.role]", or @anim from [@anim] - Binding = 1, - - // group 2: "event" from "(event)" - Event = 2, -} - // Defines Host Bindings structure that contains attributes, listeners, and properties, // parsed from the `host` object defined for a Type. export interface ParsedHostBindings { - attributes: {[key: string]: o.Expression}; - listeners: {[key: string]: string}; - properties: {[key: string]: string}; + attributes: Record; + listeners: Record; + properties: Record; specialAttributes: {styleAttr?: string; classAttr?: string}; } export function parseHostBindings(host: { [key: string]: string | o.Expression; }): ParsedHostBindings { - const attributes: {[key: string]: o.Expression} = {}; - const listeners: {[key: string]: string} = {}; - const properties: {[key: string]: string} = {}; + const attributes: Record = {}; + const listeners: Record = {}; + const properties: Record = {}; const specialAttributes: {styleAttr?: string; classAttr?: string} = {}; for (const key of Object.keys(host)) { const value = host[key]; - const matches = key.match(HOST_REG_EXP); - if (matches === null) { + if (key.startsWith('(') && key.endsWith(')')) { + if (typeof value !== 'string') { + // TODO(alxhub): make this a diagnostic. + throw new Error(`Event binding must be string`); + } + listeners[key.slice(1, -1)] = value; + } else if (key.startsWith('[') && key.endsWith(']')) { + if (typeof value !== 'string') { + // TODO(alxhub): make this a diagnostic. + throw new Error(`Property binding must be string`); + } + // synthetic properties (the ones that have a `@` as a prefix) + // are still treated the same as regular properties. Therefore + // there is no point in storing them in a separate map. + properties[key.slice(1, -1)] = value; + } else { switch (key) { case 'class': if (typeof value !== 'string') { @@ -591,21 +565,6 @@ export function parseHostBindings(host: { attributes[key] = value; } } - } else if (matches[HostBindingGroup.Binding] != null) { - if (typeof value !== 'string') { - // TODO(alxhub): make this a diagnostic. - throw new Error(`Property binding must be string`); - } - // synthetic properties (the ones that have a `@` as a prefix) - // are still treated the same as regular properties. Therefore - // there is no point in storing them in a separate map. - properties[matches[HostBindingGroup.Binding]] = value; - } else if (matches[HostBindingGroup.Event] != null) { - if (typeof value !== 'string') { - // TODO(alxhub): make this a diagnostic. - throw new Error(`Event binding must be string`); - } - listeners[matches[HostBindingGroup.Event]] = value; } } @@ -629,9 +588,41 @@ export function verifyHostBindings( const bindingParser = makeBindingParser(); bindingParser.createDirectiveHostEventAsts(bindings.listeners, sourceSpan); bindingParser.createBoundHostProperties(bindings.properties, sourceSpan); + + validateNoEventBindings(bindings, bindingParser, sourceSpan); + return bindingParser.errors; } +/** + * Validates that there are no event attribute bindings in the host bindings. + * @param bindings - Map of host bindings for the component. + * @param bindingParser - Binding parser used to create the binding expression. + * @param sourceSpan - Source span where the host bindings were defined. + */ +function validateNoEventBindings( + bindings: ParsedHostBindings, + bindingParser: BindingParser, + sourceSpan: ParseSourceSpan, +): void { + for (const prop in bindings.properties) { + const isAttr = prop.startsWith('attr.'); + const boundName = isAttr ? prop.slice(5) : prop; + + if (boundName.toLowerCase().startsWith('on')) { + const errorType = isAttr ? 'attribute' : 'property'; + const suggestion = `(${boundName.slice(2)})=...`; + + let msg = `Binding to event ${errorType} '${boundName}' is disallowed for security reasons, please use ${suggestion}`; + if (!isAttr) { + msg += `\nIf '${prop}' is a directive input, make sure the directive is imported by the current module.`; + } + + bindingParser.errors.push(new ParseError(sourceSpan, msg)); + } + } +} + function compileStyles(styles: string[], selector: string, hostSelector: string): string[] { const shadowCss = new ShadowCss(); return styles.map((style) => { @@ -769,7 +760,13 @@ export function compileDeferResolverFunction( ); // Dynamic import, e.g. `import('./a').then(...)`. - const importExpr = new o.DynamicImportExpr(dep.importPath!).prop('then').callFn([innerFn]); + const importExpr = new o.DynamicImportExpr(dep.importPath!) + .prop('then') + .callFn([innerFn], undefined, undefined, [ + // Necessary, because we might not generate extensions for the path + // and TS may try to enforce it based on the compiler options. + tsIgnoreComment(), + ]); depExpressions.push(importExpr); } else { // Non-deferrable symbol, just use a reference to the type. Note that it's important to @@ -787,7 +784,13 @@ export function compileDeferResolverFunction( ); // Dynamic import, e.g. `import('./a').then(...)`. - const importExpr = new o.DynamicImportExpr(importPath).prop('then').callFn([innerFn]); + const importExpr = new o.DynamicImportExpr(importPath) + .prop('then') + .callFn([innerFn], undefined, undefined, [ + // Necessary, because we might not generate extensions for the path + // and TS may try to enforce it based on the compiler options. + tsIgnoreComment(), + ]); depExpressions.push(importExpr); } } diff --git a/packages/compiler/src/render3/view/i18n/get_msg_utils.ts b/packages/compiler/src/render3/view/i18n/get_msg_utils.ts index 3a54ffa68c42..c0514686c744 100644 --- a/packages/compiler/src/render3/view/i18n/get_msg_utils.ts +++ b/packages/compiler/src/render3/view/i18n/get_msg_utils.ts @@ -8,14 +8,12 @@ import * as i18n from '../../../i18n/i18n_ast'; import {mapLiteral} from '../../../output/map_util'; import * as o from '../../../output/output_ast'; +import {tsIgnoreComment} from '../../util'; import {serializeIcuNode} from './icu_serializer'; import {i18nMetaToJSDoc} from './meta'; import {formatI18nPlaceholderName, formatI18nPlaceholderNamesInMap} from './util'; -/** Closure uses `goog.getMsg(message)` to lookup translations */ -const GOOG_GET_MSG = 'goog.getMsg'; - /** * Generates a `goog.getMsg()` statement and reassignment. The template: * @@ -100,7 +98,9 @@ export function createGoogleGetMsgStatements( // I18N_X = MSG_...; const googGetMsgStmt = new o.DeclareVarStmt( closureVar.name, - o.variable(GOOG_GET_MSG).callFn(args), + o.variable('goog').prop('getMsg').callFn(args, null, undefined, [ + tsIgnoreComment(), // `goog` might not be available externally. + ]), o.INFERRED_TYPE, o.StmtModifier.Final, ); diff --git a/packages/compiler/src/render3/view/i18n/localize_utils.ts b/packages/compiler/src/render3/view/i18n/localize_utils.ts index 74cc0befbdab..430a5e7fec88 100644 --- a/packages/compiler/src/render3/view/i18n/localize_utils.ts +++ b/packages/compiler/src/render3/view/i18n/localize_utils.ts @@ -8,6 +8,7 @@ import * as i18n from '../../../i18n/i18n_ast'; import * as o from '../../../output/output_ast'; import {ParseLocation, ParseSourceSpan} from '../../../parse_util'; +import {tsIgnoreComment} from '../../util'; import {serializeIcuNode} from './icu_serializer'; import {formatI18nPlaceholderName} from './util'; @@ -28,7 +29,11 @@ export function createLocalizeStatements( sourceSpan, ); const variableInitialization = variable.set(localizedString); - return [new o.ExpressionStatement(variableInitialization)]; + return [ + new o.ExpressionStatement(variableInitialization, null, [ + tsIgnoreComment(), // `$localize` might not be available internally. + ]), + ]; } /** diff --git a/packages/compiler/src/render3/view/query_generation.ts b/packages/compiler/src/render3/view/query_generation.ts index c32e94f5c876..6d803e0d0260 100644 --- a/packages/compiler/src/render3/view/query_generation.ts +++ b/packages/compiler/src/render3/view/query_generation.ts @@ -217,7 +217,7 @@ export function createViewQueriesFunction( const viewQueryFnName = name ? `${name}_Query` : null; return o.fn( - [new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)], + [new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, o.DYNAMIC_TYPE)], [ renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements), renderFlagCheckIfStmt(core.RenderFlags.Update, collapseAdvanceStatements(updateStatements)), @@ -282,8 +282,8 @@ export function createContentQueriesFunction( return o.fn( [ new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), - new o.FnParam(CONTEXT_NAME, null), - new o.FnParam('dirIndex', null), + new o.FnParam(CONTEXT_NAME, o.DYNAMIC_TYPE), + new o.FnParam('dirIndex', o.NUMBER_TYPE), ], [ renderFlagCheckIfStmt(core.RenderFlags.Create, createStatements), diff --git a/packages/compiler/src/render3/view/t2_api.ts b/packages/compiler/src/render3/view/t2_api.ts index b6629381b4ff..a2ca23659496 100644 --- a/packages/compiler/src/render3/view/t2_api.ts +++ b/packages/compiler/src/render3/view/t2_api.ts @@ -7,6 +7,7 @@ */ import {AST} from '../../expression_parser/ast'; +import {ClassPropertyMapping} from '../../property_mapping'; import { BoundAttribute, BoundEvent, @@ -58,6 +59,18 @@ export type TemplateEntity = Reference | Variable | LetDeclaration; /** Nodes that can have directives applied to them. */ export type DirectiveOwner = Element | Template | Component | Directive | HostElement; +/** Information about a host directive binding that was exposed under conflicting aliases. */ +export interface ConflictingHostDirectiveBinding { + /** Metadata of the directive that the binding belongs to. */ + directive: DirectiveT; + /** Name of the class member that the binding would write into. */ + classPropertyName: string; + /** Aliases that caused the conflict. */ + conflictingAliases: Set; + /** Type of the binding. */ + kind: 'input' | 'output'; +} + /* * t2 is the replacement for the `TemplateDefinitionBuilder`. It handles the operations of * analyzing Angular templates, extracting semantic info, and ultimately producing a template @@ -78,16 +91,6 @@ export interface Target { }; } -/** - * A data structure which can indicate whether a given property name is present or not. - * - * This is used to represent the set of inputs or outputs present on a directive, and allows the - * binder to query for the presence of a mapping for property names. - */ -export interface InputOutputPropertySet { - hasBindingPropertyName(propertyName: string): boolean; -} - /** * A data structure which captures the animation trigger names that are statically resolvable * and whether some names could not be statically evaluated. @@ -107,6 +110,14 @@ export interface DirectiveMeta { */ name: string; + /** Reference to the directive declaration site. */ + ref: { + /** Key that uniquely identifies the reference. */ + key: string; + + // Normally we have some more fields here depending on where the reference originated from. + }; + /** The selector for the directive or `null` if there isn't one. */ selector: string | null; @@ -120,14 +131,14 @@ export interface DirectiveMeta { * * Goes from property names to field names. */ - inputs: InputOutputPropertySet; + inputs: ClassPropertyMapping; /** * Set of outputs which this directive claims. * * Goes from property names to field names. */ - outputs: InputOutputPropertySet; + outputs: ClassPropertyMapping; /** * Name under which the directive is exported, if any (exportAs in Angular). @@ -156,6 +167,20 @@ export interface DirectiveMeta { * Only includes the legacy animation names. */ animationTriggerNames: LegacyAnimationTriggerNames | null; + + /** Tracks how the directive was matched. */ + matchSource: MatchSource; +} + +/** + * Possible ways that a directive can be matched. + */ +export enum MatchSource { + /** The directive was matched by its selector. */ + Selector, + + /** The directive was applied as a host directive. */ + HostDirective, } /** @@ -282,4 +307,12 @@ export interface BoundTarget { * @param name Name of the component/directive. */ referencedDirectiveExists(name: string): boolean; + + /** + * Returns any cases of conflicting host bindings that were detected during directive matching. + * @param node Node for which to look up the conflicting bindings. + */ + getConflictingHostDirectiveBindings( + node: DirectiveOwner, + ): ConflictingHostDirectiveBinding[] | null; } diff --git a/packages/compiler/src/render3/view/t2_binder.ts b/packages/compiler/src/render3/view/t2_binder.ts index 549ef8a14884..38d23acefdb9 100644 --- a/packages/compiler/src/render3/view/t2_binder.ts +++ b/packages/compiler/src/render3/view/t2_binder.ts @@ -55,8 +55,10 @@ import { import {CombinedRecursiveAstVisitor} from '../../combined_visitor'; import { BoundTarget, + ConflictingHostDirectiveBinding, DirectiveMeta, DirectiveOwner, + MatchSource, ReferenceTarget, ScopedNode, Target, @@ -65,6 +67,7 @@ import { } from './t2_api'; import {parseTemplate} from './template'; import {createCssSelectorFromNode} from './util'; +import {ClassPropertyMapping, ClassPropertyName, InputOrOutput} from '../../property_mapping'; /** * Computes a difference between full list (first argument) and @@ -133,6 +136,7 @@ export function findMatchingDirectivesAndPipes(template: string, directiveSelect return false; }, }, + matchSource: MatchSource.Selector, } as unknown as DirectiveMeta; matcher.addSelectables(CssSelector.parse(selector), [fakeDirective]); } @@ -189,6 +193,10 @@ export class R3TargetBinder implements TargetB const usedPipes = new Set(); const eagerPipes = new Set(); const deferBlocks: DeferBlockScopes = []; + const conflictingHostDirectiveBindings = new Map< + DirectiveOwner, + ConflictingHostDirectiveBinding[] + >(); if (target.template) { // First, parse the template into a `Scope` structure. This operation captures the syntactic @@ -211,6 +219,7 @@ export class R3TargetBinder implements TargetB missingDirectives, bindings, references, + conflictingHostDirectiveBindings, ); // Finally, run the TemplateBinder to bind references, variables, and other entities within the @@ -257,6 +266,7 @@ export class R3TargetBinder implements TargetB usedPipes, eagerPipes, deferBlocks, + conflictingHostDirectiveBindings, ); } } @@ -511,6 +521,10 @@ class DirectiveBinder implements Visitor { private missingDirectives: Set, private bindings: BindingsMap, private references: ReferenceMap, + private conflictingHostDirectiveBindings: Map< + DirectiveOwner, + ConflictingHostDirectiveBinding[] + >, ) {} /** @@ -533,6 +547,10 @@ class DirectiveBinder implements Visitor { missingDirectives: Set, bindings: BindingsMap, references: ReferenceMap, + conflictingHostDirectiveBindings: Map< + DirectiveOwner, + ConflictingHostDirectiveBinding[] + >, ): void { const matcher = new DirectiveBinder( directiveMatcher, @@ -541,6 +559,7 @@ class DirectiveBinder implements Visitor { missingDirectives, bindings, references, + conflictingHostDirectiveBindings, ); matcher.ingest(template); } @@ -663,8 +682,9 @@ class DirectiveBinder implements Visitor { node.children.forEach((child) => child.visit(this)); } - private trackMatchedDirectives(node: DirectiveOwner, directives: DirectiveT[]): void { - if (directives.length > 0) { + private trackMatchedDirectives(node: DirectiveOwner, matchedDirectives: DirectiveT[]) { + if (matchedDirectives.length > 0) { + const directives = this.dedupeAndMergeDirectives(node, matchedDirectives); this.directives.set(node, directives); if (!this.isInDeferBlock) { this.eagerDirectives.push(...directives); @@ -672,6 +692,117 @@ class DirectiveBinder implements Visitor { } } + private dedupeAndMergeDirectives(node: DirectiveOwner, matches: DirectiveT[]): DirectiveT[] { + if (matches.length === 0 || matches.every((dir) => dir.matchSource === MatchSource.Selector)) { + return matches; + } + + const selectorMatches = new Set(); + const hostDirectives = new Map(); + const mergedHostDirectives = new Map(); + + for (const dir of matches) { + if (dir.matchSource === MatchSource.Selector) { + selectorMatches.add(dir.ref.key); + } else { + if (!hostDirectives.has(dir.ref.key)) { + hostDirectives.set(dir.ref.key, []); + } + hostDirectives.get(dir.ref.key)!.push(dir); + } + } + + for (const [key, directives] of hostDirectives.entries()) { + // Filter out host directives that also matched through the template. + if (selectorMatches.has(key)) { + continue; + } + + if (directives.length === 1) { + // Based on the prior loop, we should always have at least one directive. + mergedHostDirectives.set(key, directives[0]); + continue; + } + + const inputs: Record = {}; + const outputs: Record = {}; + + // Merge the bindings for all duplicate host directives. + for (const dir of directives) { + this.mergeMapping(node, dir, 'input', inputs, dir.inputs); + this.mergeMapping(node, dir, 'output', outputs, dir.outputs); + } + + mergedHostDirectives.set(key, { + ...directives[0], + inputs: ClassPropertyMapping.fromMappedObject(inputs), + outputs: ClassPropertyMapping.fromMappedObject(outputs), + }); + } + + return matches.reduce((result, dir) => { + if (dir.matchSource === MatchSource.Selector) { + result.push(dir); + } else if (mergedHostDirectives.has(dir.ref.key)) { + result.push(mergedHostDirectives.get(dir.ref.key)!); + mergedHostDirectives.delete(dir.ref.key); + } + return result; + }, [] as DirectiveT[]); + } + + private mergeMapping( + node: DirectiveOwner, + directive: DirectiveT, + kind: 'input' | 'output', + accumulator: Record, + bindings: ClassPropertyMapping, + ) { + for (const binding of bindings) { + const existing = accumulator[binding.classPropertyName]; + + // Untracked binding, track it. + if (!existing) { + accumulator[binding.classPropertyName] = binding; + continue; + } + + // If the binding is already tracked, but is equivalent to the existing binding, we can keep it. + if ( + existing.bindingPropertyName === binding.bindingPropertyName && + existing.classPropertyName === binding.classPropertyName && + existing.isSignal === binding.isSignal + ) { + continue; + } + + // Otherwise track the binding as conflicting so it can be reported later. + if (!this.conflictingHostDirectiveBindings.has(node)) { + this.conflictingHostDirectiveBindings.set(node, []); + } + + const conflictsForNode = this.conflictingHostDirectiveBindings.get(node)!; + let conflict = conflictsForNode.find( + (current) => + current.directive.ref.key === directive.ref.key && + current.kind === kind && + current.classPropertyName === binding.classPropertyName, + ); + + if (!conflict) { + conflict = { + directive, + kind, + classPropertyName: existing.classPropertyName, + conflictingAliases: new Set([existing.bindingPropertyName]), + }; + conflictsForNode.push(conflict); + } + + conflict.conflictingAliases.add(binding.bindingPropertyName); + } + } + private trackSelectorlessMatchesAndDirectives( node: Component | Directive, directives: DirectiveT[], @@ -863,7 +994,7 @@ class TemplateBinder extends CombinedRecursiveAstVisitor { } else if (nodeOrNodes instanceof ForLoopBlock) { this.visitNode(nodeOrNodes.item); nodeOrNodes.contextVariables.forEach((v) => this.visitNode(v)); - nodeOrNodes.trackBy.visit(this); + nodeOrNodes.trackBy?.visit(this); nodeOrNodes.children.forEach(this.visitNode); this.nestingLevel.set(nodeOrNodes, this.level); } else if (nodeOrNodes instanceof DeferredBlock) { @@ -953,7 +1084,7 @@ class TemplateBinder extends CombinedRecursiveAstVisitor { } override visitSwitchExhaustiveCheck(block: SwitchExhaustiveCheck) { - // There are no bindings/references in the exhaustive check block. + block.expression?.visit(this); } override visitForLoopBlock(block: ForLoopBlock) { @@ -1062,6 +1193,10 @@ class R3BoundTarget implements BoundTarget, private eagerPipes: Set, rawDeferred: DeferBlockScopes, + private conflictingHostDirectiveBindings: Map< + DirectiveOwner, + ConflictingHostDirectiveBinding[] + >, ) { this.deferredBlocks = rawDeferred.map((current) => current[0]); this.deferredScopes = new Map(rawDeferred); @@ -1211,6 +1346,12 @@ class R3BoundTarget implements BoundTarget[] | null { + return this.conflictingHostDirectiveBindings.get(node) || null; + } + /** * Finds an entity with a specific name in a scope. * @param rootNode Root node of the scope. diff --git a/packages/compiler/src/render3/view/util.ts b/packages/compiler/src/render3/view/util.ts index 2247d569c63f..853bdf1c24f3 100644 --- a/packages/compiler/src/render3/view/util.ts +++ b/packages/compiler/src/render3/view/util.ts @@ -14,16 +14,7 @@ import {CssSelector} from '../../directive_matching'; import * as t from '../r3_ast'; import {isI18nAttribute} from './i18n/util'; - -/** - * Checks whether an object key contains potentially unsafe chars, thus the key should be wrapped in - * quotes. Note: we do not wrap all keys into quotes, as it may have impact on minification and may - * not work in some cases when object keys are mangled by a minifier. - * - * TODO(FW-1136): this is a temporary solution, we need to come up with a better way of working with - * inputs that contain potentially unsafe chars. - */ -export const UNSAFE_OBJECT_KEY_NAME_REGEXP = /[-.]/; +import {isUnsafeObjectKey} from '../util'; /** Name of the temporary to use during data binding */ export const TEMPORARY_NAME = '_t'; @@ -147,7 +138,7 @@ export function conditionallyCreateDirectiveBindingLiteral( return { key: minifiedName, // put quotes around keys that contain potentially unsafe characters - quoted: UNSAFE_OBJECT_KEY_NAME_REGEXP.test(minifiedName), + quoted: isUnsafeObjectKey(minifiedName), value: expressionValue, }; }), diff --git a/packages/compiler/src/schema/dom_security_schema.ts b/packages/compiler/src/schema/dom_security_schema.ts index beeadef3332c..26a4f8bf16a9 100644 --- a/packages/compiler/src/schema/dom_security_schema.ts +++ b/packages/compiler/src/schema/dom_security_schema.ts @@ -126,17 +126,27 @@ export function SECURITY_SCHEMA(): {[k: string]: SecurityContext} { ]); // Keep this in sync with SECURITY_SENSITIVE_ELEMENTS in packages/core/src/sanitization/sanitization.ts - // Unknown is the internal tag name for unknown elements example used for host-bindings. + // The `unknown` elements refer to cases when we need to validate the input/binding in a directive (host bindings) + // and the directive can be applied to multiple different elements (with different tag names). In this case we generate + // a special instruction that an attribute might potentially be security-sensitive and defer the actual security check + // to runtime, when we apply that directive to a concrete elements, thus we can check the combination of tag+attribute + // against the set that requires sanitization. // These are unsafe as `attributeName` can be `href` or `xlink:href` // See: http://b/463880509#comment7 - registerContext(SecurityContext.ATTRIBUTE_NO_BINDING, [ 'animate|attributeName', + 'animate|values', + 'animate|to', + 'animate|from', + 'set|to', 'set|attributeName', 'animateMotion|attributeName', 'animateTransform|attributeName', 'unknown|attributeName', + 'unknown|values', + 'unknown|to', + 'unknown|from', 'iframe|sandbox', 'iframe|allow', diff --git a/packages/compiler/src/schema/trusted_types_sinks.ts b/packages/compiler/src/schema/trusted_types_sinks.ts index 49d03aa7fab6..0d69ccee47f9 100644 --- a/packages/compiler/src/schema/trusted_types_sinks.ts +++ b/packages/compiler/src/schema/trusted_types_sinks.ts @@ -11,7 +11,7 @@ * tags use '*'. * * Extracted from, and should be kept in sync with - * https://w3c.github.io/webappsec-trusted-types/dist/spec/#integrations + * https://www.w3.org/TR/trusted-types/#integrations */ const TRUSTED_TYPES_SINKS = new Set([ // NOTE: All strings in this set *must* be lowercase! @@ -25,6 +25,7 @@ const TRUSTED_TYPES_SINKS = new Set([ // TrustedScriptURL 'embed|src', + 'iframe|src', 'object|codebase', 'object|data', ]); diff --git a/packages/compiler/src/service_compiler.ts b/packages/compiler/src/service_compiler.ts new file mode 100644 index 000000000000..2ae2b90a07b7 --- /dev/null +++ b/packages/compiler/src/service_compiler.ts @@ -0,0 +1,58 @@ +/** + * @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 {createInjectableType, delegateToFactory} from './injectable_compiler_2'; +import * as o from './output/output_ast'; +import {Identifiers} from './render3/r3_identifiers'; +import {R3CompiledExpression, R3Reference} from './render3/util'; +import {DefinitionMap} from './render3/view/util'; + +export interface R3ServiceMetadata { + name: string; + type: R3Reference; + typeArgumentCount: number; + autoProvided?: boolean; + factory?: o.Expression; +} + +export function compileService( + meta: R3ServiceMetadata, + resolveForwardRefs: boolean, +): R3CompiledExpression { + const def = new DefinitionMap<{ + token: o.Expression; + factory: o.Expression; + autoProvided: o.Expression; + }>(); + def.set('token', meta.type.value); + def.set( + 'factory', + meta.factory === undefined + ? delegateToFactory( + meta.type.value as o.WrappedNodeExpr, + meta.type.value as o.WrappedNodeExpr, + resolveForwardRefs, + ) + : o.arrowFn([], meta.factory.callFn([])), + ); + + // Only generate providedIn property if it's different from the default. + if (meta.autoProvided === false) { + def.set('autoProvided', o.literal(false)); + } + + const expression = o + .importExpr(Identifiers.defineService) + .callFn([def.toLiteralMap()], undefined, true); + + return { + expression, + type: createInjectableType(meta.type.type, meta.typeArgumentCount), + statements: [], + }; +} diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index cadfaf55e85c..7671c5bab0f1 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -186,24 +186,18 @@ export class ShadowCss { // Replace non hash comments with empty lines. // This is done so that we do not leak any sensitive data in comments. const newLinesMatches = m.match(_newLinesRe); - comments.push((newLinesMatches?.join('') ?? '') + '\n'); + comments.push(newLinesMatches?.join('') ?? ''); } return COMMENT_PLACEHOLDER; }); - cssText = this._insertDirectives(cssText); const scopedCssText = this._scopeCssText(cssText, selector, hostSelector); // Add back comments at the original position. let commentIdx = 0; return scopedCssText.replace(_commentWithHashPlaceHolderRe, () => comments[commentIdx++]); } - private _insertDirectives(cssText: string): string { - cssText = this._insertPolyfillDirectivesInCssText(cssText); - return this._insertPolyfillRulesInCssText(cssText); - } - /** * Process styles to add scope to keyframes. * @@ -367,7 +361,7 @@ export class ShadowCss { unscopedKeyframesSet: ReadonlySet, ): CssRule { let content = rule.content.replace( - /((?:^|\s+|;)(?:-webkit-)?animation\s*:\s*),*([^;]+)/g, + /((?:^|\s+|;)(?:-webkit-)?animation\s*:\s*)([^;]+)/g, (_, start, animationDeclarations) => start + animationDeclarations.replace( @@ -410,48 +404,6 @@ export class ShadowCss { return {...rule, content}; } - /* - * Process styles to convert native ShadowDOM rules that will trip - * up the css parser; we rely on decorating the stylesheet with inert rules. - * - * For example, we convert this rule: - * - * polyfill-next-selector { content: ':host menu-item'; } - * ::content menu-item { - * - * to this: - * - * scopeName menu-item { - * - **/ - private _insertPolyfillDirectivesInCssText(cssText: string): string { - return cssText.replace(_cssContentNextSelectorRe, function (...m: string[]) { - return m[2] + '{'; - }); - } - - /* - * Process styles to add rules which will only apply under the polyfill - * - * For example, we convert this rule: - * - * polyfill-rule { - * content: ':host menu-item'; - * ... - * } - * - * to this: - * - * scopeName menu-item {...} - * - **/ - private _insertPolyfillRulesInCssText(cssText: string): string { - return cssText.replace(_cssContentRuleRe, (...m: string[]) => { - const rule = m[0].replace(m[1], '').replace(m[2], ''); - return m[4] + rule; - }); - } - /* Ensure styles are scoped. Pseudo-scoping takes a rule like: * * .foo {... } @@ -461,46 +413,17 @@ export class ShadowCss { * scopeName .foo { ... } */ private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string { - const unscopedRules = this._extractUnscopedRulesFromCssText(cssText); // replace :host and :host-context with -shadowcsshost and -shadowcsshostcontext respectively cssText = this._insertPolyfillHostInCssText(cssText); cssText = this._convertColonHost(cssText); cssText = this._convertColonHostContext(cssText); - cssText = this._convertShadowDOMSelectors(cssText); if (scopeSelector) { cssText = this._scopeKeyframesRelatedCss(cssText, scopeSelector); cssText = this._scopeSelectors(cssText, scopeSelector, hostSelector); } - cssText = cssText + '\n' + unscopedRules; return cssText.trim(); } - /* - * Process styles to add rules which will only apply under the polyfill - * and do not process via CSSOM. (CSSOM is destructive to rules on rare - * occasions, e.g. -webkit-calc on Safari.) - * For example, we convert this rule: - * - * @polyfill-unscoped-rule { - * content: 'menu-item'; - * ... } - * - * to this: - * - * menu-item {...} - * - **/ - private _extractUnscopedRulesFromCssText(cssText: string): string { - let r = ''; - let m: RegExpExecArray | null; - _cssContentUnscopedRuleRe.lastIndex = 0; - while ((m = _cssContentUnscopedRuleRe.exec(cssText)) !== null) { - const rule = m[0].replace(m[2], '').replace(m[1], m[4]); - r += rule + '\n\n'; - } - return r; - } - /* * convert a rule like :host(.foo) > .bar { } * @@ -511,20 +434,20 @@ export class ShadowCss { private _convertColonHost(cssText: string): string { return cssText.replace(_cssColonHostRe, (_, hostSelectors: string, otherSelectors: string) => { if (hostSelectors) { - const convertedSelectors: string[] = []; - for (const hostSelector of this._splitOnTopLevelCommas(hostSelectors, true)) { - const trimmedHostSelector = hostSelector.trim(); - if (!trimmedHostSelector) break; - const convertedSelector = + const parts = [...this._splitOnTopLevelCommas(hostSelectors, true)]; + if (parts.length > 1) { + return ':host(' + hostSelectors + ')' + otherSelectors; + } + const trimmedHostSelector = parts[0].trim(); + if (trimmedHostSelector) { + return ( _polyfillHostNoCombinator + trimmedHostSelector.replace(_polyfillHost, '') + - otherSelectors; - convertedSelectors.push(convertedSelector); + otherSelectors + ); } - return convertedSelectors.join(','); - } else { - return _polyfillHostNoCombinator + otherSelectors; } + return _polyfillHostNoCombinator + otherSelectors; }); } @@ -666,14 +589,6 @@ export class ShadowCss { }); } - /* - * Convert combinators like ::shadow and pseudo-elements like ::content - * by replacing with space. - */ - private _convertShadowDOMSelectors(cssText: string): string { - return _shadowDOMSelectorsRe.reduce((result, pattern) => result.replace(pattern, ' '), cssText); - } - // change a selector like 'div' to 'name div' private _scopeSelectors(cssText: string, scopeSelector: string, hostSelector: string): string { return processRules(cssText, (rule: CssRule) => { @@ -1062,11 +977,6 @@ class SafeSelector { const _cssScopedPseudoFunctionPrefix = '(:(where|is)\\()?'; const _cssPrefixWithPseudoSelectorFunction = /:(where|is)\(/gi; -const _cssContentNextSelectorRe = - /polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim; -const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim; -const _cssContentUnscopedRuleRe = - /(polyfill-unscoped-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim; const _polyfillHost = '-shadowcsshost'; // note: :host-context pre-processed to -shadowcsshostcontext. const _polyfillHostContext = '-shadowcsscontext'; @@ -1081,7 +991,7 @@ const nthRegex = new RegExp(String.raw`(:nth-[-\w]+)` + _parenSuffix, 'g'); const _cssColonHostRe = new RegExp(_polyfillHost + _parenSuffix + '?([^,{]*)', 'gim'); // note: :host-context patterns are terminated with `{`, as opposed to :host which // is both `{` and `,` because :host-context handles top-level commas differently. -const _hostContextPattern = _polyfillHostContext + _parenSuffix + '?([^{]*)'; +const _hostContextPattern = _polyfillHostContext + _parenSuffix + '([^{]*)'; const _cssColonHostContextReGlobal = new RegExp( `${_cssScopedPseudoFunctionPrefix}(${_hostContextPattern})`, 'gim', @@ -1092,13 +1002,6 @@ const _polyfillHostNoCombinatorOutsidePseudoFunction = new RegExp( 'g', ); const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s,]*)/; -const _shadowDOMSelectorsRe = [ - /::shadow/g, - /::content/g, - // Deprecated selectors - /\/shadow-deep\//g, - /\/shadow\//g, -]; // The deep combinator is deprecated in the CSS spec // Support for `>>>`, `deep`, `::ng-deep` is then also deprecated and will be removed in the future. @@ -1106,8 +1009,8 @@ const _shadowDOMSelectorsRe = [ const _shadowDeepSelectors = /(?:>>>)|(?:\/deep\/)|(?:::ng-deep)/g; const _selectorReSuffix = '([>\\s~+[.,{:][\\s\\S]*)?$'; const _polyfillHostRe = /-shadowcsshost/gim; -const _colonHostRe = /:host/gim; -const _colonHostContextRe = /:host-context/gim; +const _colonHostRe = /:host(?!\-context)/gim; +const _colonHostContextRe = /:host-context(?=\(\s*[^)\s])/gim; const _newLinesRe = /\r?\n/g; const _commentRe = /\/\*[\s\S]*?\*\//g; diff --git a/packages/compiler/src/template/pipeline/ir/src/enums.ts b/packages/compiler/src/template/pipeline/ir/src/enums.ts index 6ba41f644a20..c10097f9e833 100644 --- a/packages/compiler/src/template/pipeline/ir/src/enums.ts +++ b/packages/compiler/src/template/pipeline/ir/src/enums.ts @@ -190,6 +190,13 @@ export enum OpKind { */ ProjectionDef, + /** + * Emit a top-level call to the `ɵɵenableIncrementalHydrationRuntime` instruction. + * This op is inserted once per view (before the first `Defer` op with hydrate triggers) + * to activate the incremental hydration runtime for that view. + */ + EnableIncrementalHydrationRuntime, + /** * Create a content projection slot. */ @@ -404,9 +411,9 @@ export enum ExpressionKind { SafeKeyedRead, /** - * A safe function call requiring expansion into a null check. + * Wraps an expression to indicate that it should be evaluated with legacy null-safe navigation semantics. */ - SafeInvokeFunction, + SafeNavigationMigration, /** * An intermediate expression that will be expanded from a safe read into an explicit ternary. diff --git a/packages/compiler/src/template/pipeline/ir/src/expression.ts b/packages/compiler/src/template/pipeline/ir/src/expression.ts index b58ac5f74b4e..4c3a094b5b58 100644 --- a/packages/compiler/src/template/pipeline/ir/src/expression.ts +++ b/packages/compiler/src/template/pipeline/ir/src/expression.ts @@ -9,8 +9,8 @@ import * as o from '../../../../output/output_ast'; import type {ParseSourceSpan} from '../../../../parse_util'; -import {CONTEXT_NAME} from '../../../../render3/view/util'; import * as t from '../../../../render3/r3_ast'; +import {CONTEXT_NAME} from '../../../../render3/view/util'; import {ExpressionKind, OpKind} from './enums'; import {SlotHandle} from './handle'; import {OpList, type XrefId} from './operations'; @@ -43,7 +43,7 @@ export type Expression = | PipeBindingVariadicExpr | SafePropertyReadExpr | SafeKeyedReadExpr - | SafeInvokeFunctionExpr + | SafeNavigationMigrationExpr | EmptyExpr | AssignTemporaryExpr | ReadTemporaryExpr @@ -767,46 +767,42 @@ export class SafeKeyedReadExpr extends ExpressionBase { } } -export class SafeInvokeFunctionExpr extends ExpressionBase { - override readonly kind = ExpressionKind.SafeInvokeFunction; +/** + * Wraps an expression to indicate that it should be evaluated with legacy null-safe navigation semantics. + * This is used to implement the `$safeNavigationMigration` builtin function. + */ +export class SafeNavigationMigrationExpr extends ExpressionBase { + override readonly kind = ExpressionKind.SafeNavigationMigration; - constructor( - public receiver: o.Expression, - public args: o.Expression[], - ) { + constructor(public expr: o.Expression) { super(); } - override visitExpression(visitor: o.ExpressionVisitor, context: any): any { - this.receiver.visitExpression(visitor, context); - for (const a of this.args) { - a.visitExpression(visitor, context); - } + override visitExpression(visitor: o.ExpressionVisitor, context: any): void { + this.expr.visitExpression(visitor, context); } - override isEquivalent(): boolean { - return false; + override isEquivalent(e: o.Expression): boolean { + return e instanceof SafeNavigationMigrationExpr && this.expr.isEquivalent(e.expr); } override isConstant(): boolean { - return false; + return this.expr.isConstant(); } override transformInternalExpressions( transform: ExpressionTransform, flags: VisitorContextFlag, ): void { - this.receiver = transformExpressionsInExpression(this.receiver, transform, flags); - for (let i = 0; i < this.args.length; i++) { - this.args[i] = transformExpressionsInExpression(this.args[i], transform, flags); - } + this.expr = transformExpressionsInExpression( + this.expr, + transform, + flags | VisitorContextFlag.InSafeNavigationMigration, + ); } - override clone(): SafeInvokeFunctionExpr { - return new SafeInvokeFunctionExpr( - this.receiver.clone(), - this.args.map((a) => a.clone()), - ); + override clone(): SafeNavigationMigrationExpr { + return new SafeNavigationMigrationExpr(this.expr.clone()); } } @@ -1124,6 +1120,7 @@ export enum VisitorContextFlag { None = 0b0000, InChildOperation = 0b0001, InArrowFunctionOperation = 0b0010, + InSafeNavigationMigration = 0b0100, } function transformExpressionsInInterpolation( @@ -1290,6 +1287,7 @@ export function transformExpressionsInOp( case OpKind.Pipe: case OpKind.Projection: case OpKind.ProjectionDef: + case OpKind.EnableIncrementalHydrationRuntime: case OpKind.Template: case OpKind.Text: case OpKind.I18nAttributes: diff --git a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts index ac6ab218f8a6..c104e9d2a6e8 100644 --- a/packages/compiler/src/template/pipeline/ir/src/ops/create.ts +++ b/packages/compiler/src/template/pipeline/ir/src/ops/create.ts @@ -57,6 +57,7 @@ export type CreateOp = | VariableOp | NamespaceOp | ProjectionDefOp + | EnableIncrementalHydrationRuntimeOp | ProjectionOp | ExtractedAttributeOp | DeferOp @@ -1127,6 +1128,27 @@ export function createProjectionDefOp(def: o.Expression | null): ProjectionDefOp }; } +/** + * An op that emits a top-level call to the `ɵɵenableIncrementalHydrationRuntime` + * instruction. This op is inserted once per view (before the first `Defer` op + * with hydrate triggers) to activate the incremental hydration runtime. + */ +export interface EnableIncrementalHydrationRuntimeOp extends Op { + kind: OpKind.EnableIncrementalHydrationRuntime; + + sourceSpan: ParseSourceSpan | null; +} + +export function createEnableIncrementalHydrationRuntimeOp( + sourceSpan: ParseSourceSpan | null, +): EnableIncrementalHydrationRuntimeOp { + return { + kind: OpKind.EnableIncrementalHydrationRuntime, + sourceSpan, + ...NEW_OP, + }; +} + /** * An op that creates a content projection slot. */ @@ -1382,6 +1404,8 @@ interface DeferTriggerWithTargetBase extends DeferTriggerBase { interface DeferIdleTrigger extends DeferTriggerBase { kind: DeferTriggerKind.Idle; + + timeout: number | null; } interface DeferImmediateTrigger extends DeferTriggerBase { diff --git a/packages/compiler/src/template/pipeline/src/compilation.ts b/packages/compiler/src/template/pipeline/src/compilation.ts index abdc21eaf605..0a1753507d8a 100644 --- a/packages/compiler/src/template/pipeline/src/compilation.ts +++ b/packages/compiler/src/template/pipeline/src/compilation.ts @@ -35,6 +35,7 @@ export abstract class CompilationJob { readonly componentName: string, readonly pool: ConstantPool, readonly mode: TemplateCompilationMode, + readonly legacyOptionalChaining: boolean, ) {} kind: CompilationJobKind = CompilationJobKind.Both; @@ -84,8 +85,9 @@ export class ComponentCompilationJob extends CompilationJob { readonly allDeferrableDepsFn: o.ReadVarExpr | null, readonly relativeTemplatePath: string | null, readonly enableDebugLocations: boolean, + legacyOptionalChaining: boolean, ) { - super(componentName, pool, mode); + super(componentName, pool, mode, legacyOptionalChaining); this.root = new ViewCompilationUnit(this, this.allocateXrefId(), null); this.views.set(this.root.xref, this.root); } @@ -263,8 +265,13 @@ export class ViewCompilationUnit extends CompilationUnit { * Compilation-in-progress of a host binding, which contains a single unit for that host binding. */ export class HostBindingCompilationJob extends CompilationJob { - constructor(componentName: string, pool: ConstantPool, mode: TemplateCompilationMode) { - super(componentName, pool, mode); + constructor( + componentName: string, + pool: ConstantPool, + mode: TemplateCompilationMode, + legacyOptionalChaining: boolean, + ) { + super(componentName, pool, mode, legacyOptionalChaining); this.root = new HostBindingCompilationUnit(this); } diff --git a/packages/compiler/src/template/pipeline/src/emit.ts b/packages/compiler/src/template/pipeline/src/emit.ts index 1437a13b5a6d..00529edc9943 100644 --- a/packages/compiler/src/template/pipeline/src/emit.ts +++ b/packages/compiler/src/template/pipeline/src/emit.ts @@ -9,8 +9,8 @@ import * as o from '../../../../src/output/output_ast'; import {ConstantPool} from '../../../constant_pool'; -import * as ir from '../ir'; import {CONTEXT_NAME, RENDER_FLAGS} from '../../../render3/view/util'; +import * as ir from '../ir'; import { CompilationJob, @@ -30,16 +30,19 @@ import {chain} from './phases/chaining'; import {collapseSingletonInterpolations} from './phases/collapse_singleton_interpolations'; import {generateConditionalExpressions} from './phases/conditionals'; import {collectElementConsts} from './phases/const_collection'; +import {specializeControlProperties} from './phases/control_directives'; import {convertAnimations} from './phases/convert_animations'; import {convertI18nBindings} from './phases/convert_i18n_bindings'; import {createI18nContexts} from './phases/create_i18n_contexts'; import {deduplicateTextBindings} from './phases/deduplicate_text_bindings'; import {configureDeferInstructions} from './phases/defer_configs'; +import {insertIncrementalHydrationRuntime} from './phases/insert_incremental_hydration_runtime'; import {resolveDeferTargetNames} from './phases/defer_resolve_targets'; import {collapseEmptyInstructions} from './phases/empty_elements'; import {expandSafeReads} from './phases/expand_safe_reads'; import {extractI18nMessages} from './phases/extract_i18n_messages'; import {generateAdvance} from './phases/generate_advance'; +import {generateArrowFunctions} from './phases/generate_arrow_functions'; import {generateLocalLetReferences} from './phases/generate_local_let_references'; import {generateProjectionDefs} from './phases/generate_projection_def'; import {generateVariables} from './phases/generate_variables'; @@ -74,6 +77,7 @@ import {resolveI18nElementPlaceholders} from './phases/resolve_i18n_element_plac import {resolveI18nExpressionPlaceholders} from './phases/resolve_i18n_expression_placeholders'; import {resolveNames} from './phases/resolve_names'; import {resolveSanitizers} from './phases/resolve_sanitizers'; +import {removeSafeNavigationMigration} from './phases/safe_navigation_migration'; import {saveAndRestoreView} from './phases/save_restore_view'; import {allocateSlots} from './phases/slot_allocation'; import {optimizeStoreLet} from './phases/store_let_optimization'; @@ -86,8 +90,6 @@ import {transformTwoWayBindingSet} from './phases/transform_two_way_binding_set' import {countVariables} from './phases/var_counting'; import {optimizeVariables} from './phases/variable_optimization'; import {wrapI18nIcus} from './phases/wrap_icus'; -import {generateArrowFunctions} from './phases/generate_arrow_functions'; -import {specializeControlProperties} from './phases/control_directives'; type Phase = | { @@ -124,6 +126,7 @@ const phases: Phase[] = [ {kind: Kind.Tmpl, fn: generateConditionalExpressions}, {kind: Kind.Tmpl, fn: createPipes}, {kind: Kind.Tmpl, fn: configureDeferInstructions}, + {kind: Kind.Tmpl, fn: insertIncrementalHydrationRuntime}, {kind: Kind.Tmpl, fn: createVariadicPipes}, {kind: Kind.Both, fn: generateArrowFunctions}, {kind: Kind.Both, fn: generatePureLiteralStructures}, @@ -132,6 +135,7 @@ const phases: Phase[] = [ {kind: Kind.Tmpl, fn: generateVariables}, {kind: Kind.Tmpl, fn: saveAndRestoreView}, {kind: Kind.Both, fn: deleteAnyCasts}, + {kind: Kind.Both, fn: removeSafeNavigationMigration}, {kind: Kind.Both, fn: resolveDollarEvent}, {kind: Kind.Tmpl, fn: generateTrackVariables}, {kind: Kind.Tmpl, fn: removeIllegalLetReferences}, @@ -247,7 +251,7 @@ function emitView(view: ViewCompilationUnit): o.FunctionExpr { const createCond = maybeGenerateRfBlock(1, createStatements); const updateCond = maybeGenerateRfBlock(2, updateStatements); return o.fn( - [new o.FnParam(RENDER_FLAGS), new o.FnParam(CONTEXT_NAME)], + [new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, o.DYNAMIC_TYPE)], [...createCond, ...updateCond], /* type */ undefined, /* sourceSpan */ undefined, @@ -307,7 +311,7 @@ export function emitHostBindingFunction(job: HostBindingCompilationJob): o.Funct const createCond = maybeGenerateRfBlock(1, createStatements); const updateCond = maybeGenerateRfBlock(2, updateStatements); return o.fn( - [new o.FnParam(RENDER_FLAGS), new o.FnParam(CONTEXT_NAME)], + [new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, o.DYNAMIC_TYPE)], [...createCond, ...updateCond], /* type */ undefined, /* sourceSpan */ undefined, diff --git a/packages/compiler/src/template/pipeline/src/ingest.ts b/packages/compiler/src/template/pipeline/src/ingest.ts index 0b86eb0dd395..f4cb0d9f1ede 100644 --- a/packages/compiler/src/template/pipeline/src/ingest.ts +++ b/packages/compiler/src/template/pipeline/src/ingest.ts @@ -63,6 +63,7 @@ export function ingestComponent( allDeferrableDepsFn: o.ReadVarExpr | null, relativeTemplatePath: string | null, enableDebugLocations: boolean, + legacyOptionalChaining: boolean, ): ComponentCompilationJob { const job = new ComponentCompilationJob( componentName, @@ -74,6 +75,7 @@ export function ingestComponent( allDeferrableDepsFn, relativeTemplatePath, enableDebugLocations, + legacyOptionalChaining, ); ingestNodes(job.root, template); return job; @@ -85,6 +87,7 @@ export interface HostBindingInput { properties: e.ParsedProperty[] | null; attributes: {[key: string]: o.Expression}; events: e.ParsedEvent[] | null; + legacyOptionalChaining: boolean; } /** @@ -100,6 +103,7 @@ export function ingestHostBinding( input.componentName, constantPool, TemplateCompilationMode.DomOnly, + input.legacyOptionalChaining, ); for (const property of input.properties ?? []) { let bindingKind = ir.BindingKind.Property; @@ -749,7 +753,7 @@ function ingestDeferBlock(unit: ViewCompilationUnit, deferBlock: t.DeferredBlock deferOnOps.push( ir.createDeferOnOp( deferXref, - {kind: ir.DeferTriggerKind.Idle}, + {kind: ir.DeferTriggerKind.Idle, timeout: null}, ir.DeferOpModifierKind.NONE, null!, ), @@ -778,7 +782,7 @@ function ingestDeferTriggers( if (triggers.idle !== undefined) { const deferOnOp = ir.createDeferOnOp( deferXref, - {kind: ir.DeferTriggerKind.Idle}, + {kind: ir.DeferTriggerKind.Idle, timeout: triggers.idle.timeout ?? null}, modifier, triggers.idle.sourceSpan, ); @@ -931,8 +935,19 @@ function ingestForBlock(unit: ViewCompilationUnit, forBlock: t.ForLoopBlock): vo } } - const sourceSpan = convertSourceSpan(forBlock.trackBy.span, forBlock.sourceSpan); - const track = convertAst(forBlock.trackBy, unit.job, sourceSpan); + let track: o.Expression; + + if (forBlock.trackBy === null) { + // `@for` without a `track` is invalid and it produces a parser error. + // Put a placeholder here so we don't need to account for it throughout the pipeline. + track = o.variable('$index'); + } else { + track = convertAst( + forBlock.trackBy, + unit.job, + convertSourceSpan(forBlock.trackBy.span, forBlock.sourceSpan), + ); + } ingestNodes(repeaterView, forBlock.children); @@ -1162,9 +1177,14 @@ function convertAst( return new ir.SafePropertyReadExpr(convertAst(ast.receiver, job, baseSourceSpan), ast.name); } else if (ast instanceof e.SafeCall) { // TODO: source span - return new ir.SafeInvokeFunctionExpr( + return new o.InvokeFunctionExpr( convertAst(ast.receiver, job, baseSourceSpan), ast.args.map((a) => convertAst(a, job, baseSourceSpan)), + null, + convertSourceSpan(ast.span, baseSourceSpan), + false, + [], + true, ); } else if (ast instanceof e.EmptyExpr) { return new ir.EmptyExpr(convertSourceSpan(ast.span, baseSourceSpan)); @@ -1203,7 +1223,7 @@ function convertAst( } else if (ast instanceof e.ArrowFunction) { return updateParameterReferences( o.arrowFn( - ast.parameters.map((arg) => new o.FnParam(arg.name)), + ast.parameters.map((arg) => new o.FnParam(arg.name, o.DYNAMIC_TYPE)), convertAst(ast.body, job, baseSourceSpan), ), ); diff --git a/packages/compiler/src/template/pipeline/src/instruction.ts b/packages/compiler/src/template/pipeline/src/instruction.ts index 0af2ac647d5f..a9e2573fc021 100644 --- a/packages/compiler/src/template/pipeline/src/instruction.ts +++ b/packages/compiler/src/template/pipeline/src/instruction.ts @@ -314,6 +314,10 @@ export function defer( return call(Identifiers.defer, args, sourceSpan); } +export function enableIncrementalHydrationRuntime(sourceSpan: ParseSourceSpan | null): ir.CreateOp { + return call(Identifiers.enableIncrementalHydrationRuntime, [], sourceSpan); +} + const deferTriggerToR3TriggerInstructionsMap = new Map([ [ ir.DeferTriggerKind.Idle, diff --git a/packages/compiler/src/template/pipeline/src/phases/control_directives.ts b/packages/compiler/src/template/pipeline/src/phases/control_directives.ts index d83d3d6a7316..8f568f596648 100644 --- a/packages/compiler/src/template/pipeline/src/phases/control_directives.ts +++ b/packages/compiler/src/template/pipeline/src/phases/control_directives.ts @@ -9,7 +9,12 @@ import * as ir from '../../ir'; import type {ComponentCompilationJob, ViewCompilationUnit} from '../compilation'; -const ELIGIBLE_CONTROL_PROPERTIES = new Set(['formField']); +const ELIGIBLE_CONTROL_PROPERTIES = new Map>([ + ['formField', new Set([ir.OpKind.Property])], + ['formControl', new Set([ir.OpKind.Property])], + ['formControlName', new Set([ir.OpKind.Property, ir.OpKind.Attribute])], + ['ngModel', new Set([ir.OpKind.Attribute, ir.OpKind.Property, ir.OpKind.TwoWayProperty])], +]); export function specializeControlProperties(job: ComponentCompilationJob): void { for (const unit of job.units) { @@ -19,11 +24,17 @@ export function specializeControlProperties(job: ComponentCompilationJob): void function processView(view: ViewCompilationUnit): void { for (const op of view.update) { - if (op.kind !== ir.OpKind.Property) { + // Handle Property ops, TwoWayProperty ops (for [(ngModel)]), and Attribute ops (for static formControlName="name") + if ( + op.kind !== ir.OpKind.Property && + op.kind !== ir.OpKind.TwoWayProperty && + op.kind !== ir.OpKind.Attribute + ) { continue; } - if (ELIGIBLE_CONTROL_PROPERTIES.has(op.name)) { + const eligibleOps = ELIGIBLE_CONTROL_PROPERTIES.get(op.name); + if (eligibleOps !== undefined && eligibleOps.has(op.kind)) { addControlInstruction(view, op); } } @@ -56,10 +67,16 @@ function findCreateInstruction(view: ViewCompilationUnit, target: ir.XrefId): ir return lastFoundOp; } -function addControlInstruction(view: ViewCompilationUnit, propertyOp: ir.PropertyOp): void { +function addControlInstruction( + view: ViewCompilationUnit, + propertyOp: ir.PropertyOp | ir.TwoWayPropertyOp | ir.AttributeOp, +): void { const targetCreateOp = findCreateInstruction(view, propertyOp.target); if (targetCreateOp === null) { - throw new Error(`No create instruction found for control target ${propertyOp.target}`); + // If we didn't find a relevant create instruction, it's possible this property + // was applied to an element that doesn't support control instructions (like a + // structural directive or block). We can safely ignore it. + return; } const controlCreateOp = ir.createControlCreateOp(propertyOp.sourceSpan); diff --git a/packages/compiler/src/template/pipeline/src/phases/expand_safe_reads.ts b/packages/compiler/src/template/pipeline/src/phases/expand_safe_reads.ts index 383576a96cd0..118b03f3d58e 100644 --- a/packages/compiler/src/template/pipeline/src/phases/expand_safe_reads.ts +++ b/packages/compiler/src/template/pipeline/src/phases/expand_safe_reads.ts @@ -15,16 +15,26 @@ interface SafeTransformContext { } /** - * Safe read expressions such as `a?.b` have different semantics in Angular templates as - * compared to JavaScript. In particular, they default to `null` instead of `undefined`. This phase - * finds all unresolved safe read expressions, and converts them into the appropriate output AST - * reads, guarded by null checks. We generate temporaries as needed, to avoid re-evaluating the same - * sub-expression multiple times. + * Safe read expressions such as `a?.b` are evaluated in one of three ways: + * 1. Native optional chaining (default): The expression is converted into a native JS optional chain `a?.b` + * which defaults to `undefined`. + * 2. Global legacy semantics: If the `legacyOptionalChaining` compiler flag is enabled, all safe reads + * will be converted to safe ternaries, defaulting to `null` instead of `undefined`. + * 3. Local legacy semantics: If the expression was wrapped in a `$safeNavigationMigration()` expression, + * which acts as a magic marker to hint the compiler to transform that expression to a safe ternary. + * + * For the legacy semantics, this phase finds all unresolved safe read expressions and converts them into + * the appropriate output AST reads guarded by null checks. We generate temporaries as needed, to avoid + * re-evaluating the same sub-expression multiple times. */ export function expandSafeReads(job: CompilationJob): void { for (const unit of job.units) { for (const op of unit.ops()) { - ir.transformExpressionsInOp(op, (e) => safeTransform(e, {job}), ir.VisitorContextFlag.None); + ir.transformExpressionsInOp( + op, + (e, flags) => safeTransform(e, {job}, flags), + ir.VisitorContextFlag.None, + ); ir.transformExpressionsInOp(op, ternaryTransform, ir.VisitorContextFlag.None); } } @@ -36,7 +46,6 @@ const requiresTemporary = [ o.LiteralArrayExpr, o.LiteralMapExpr, o.ParenthesizedExpr, - ir.SafeInvokeFunctionExpr, ir.PipeBindingExpr, ].map((e) => e.constructor.name); @@ -61,13 +70,14 @@ function needsTemporaryInSafeAccess(e: o.Expression): boolean { return needsTemporaryInSafeAccess(e.receiver) || needsTemporaryInSafeAccess(e.index); } else if (e instanceof o.ParenthesizedExpr) { return needsTemporaryInSafeAccess(e.expr); + } else if (e instanceof ir.SafeNavigationMigrationExpr) { + return needsTemporaryInSafeAccess(e.expr); } // TODO: Switch to a method which is exhaustive of newly added expression subtypes. return ( e instanceof o.InvokeFunctionExpr || e instanceof o.LiteralArrayExpr || e instanceof o.LiteralMapExpr || - e instanceof ir.SafeInvokeFunctionExpr || e instanceof ir.PipeBindingExpr ); } @@ -142,11 +152,11 @@ function safeTernaryWithTemporary( function isSafeAccessExpression( e: o.Expression, -): e is ir.SafePropertyReadExpr | ir.SafeKeyedReadExpr | ir.SafeInvokeFunctionExpr { +): e is ir.SafePropertyReadExpr | ir.SafeKeyedReadExpr | o.InvokeFunctionExpr { return ( e instanceof ir.SafePropertyReadExpr || e instanceof ir.SafeKeyedReadExpr || - e instanceof ir.SafeInvokeFunctionExpr + (e instanceof o.InvokeFunctionExpr && e.isOptional) ); } @@ -154,7 +164,9 @@ function isUnsafeAccessExpression( e: o.Expression, ): e is o.ReadPropExpr | o.ReadKeyExpr | o.InvokeFunctionExpr { return ( - e instanceof o.ReadPropExpr || e instanceof o.ReadKeyExpr || e instanceof o.InvokeFunctionExpr + e instanceof o.ReadPropExpr || + e instanceof o.ReadKeyExpr || + (e instanceof o.InvokeFunctionExpr && !e.isOptional) ); } @@ -165,8 +177,7 @@ function isAccessExpression( | ir.SafePropertyReadExpr | o.ReadKeyExpr | ir.SafeKeyedReadExpr - | o.InvokeFunctionExpr - | ir.SafeInvokeFunctionExpr { + | o.InvokeFunctionExpr { return isSafeAccessExpression(e) || isUnsafeAccessExpression(e); } @@ -183,7 +194,31 @@ function deepestSafeTernary(e: o.Expression): ir.SafeTernaryExpr | null { // TODO: When strict compatibility with TemplateDefinitionBuilder is not required, we can use `&&` // instead to save some code size. -function safeTransform(e: o.Expression, ctx: SafeTransformContext): o.Expression { +function safeTransform( + e: o.Expression, + ctx: SafeTransformContext, + flags: ir.VisitorContextFlag, +): o.Expression { + if (e instanceof ir.SafeNavigationMigrationExpr) { + return e.expr; + } + + const useNullSemantics = + ctx.job.legacyOptionalChaining || + (flags & ir.VisitorContextFlag.InSafeNavigationMigration) !== 0; + if (!useNullSemantics) { + // Convert the intermediate IR safe-navigation nodes to output AST nodes with `isOptional=true`. + // This is the "native optional chaining" path: the output AST nodes carry the `?.` flag and + // are later translated to native JS/TS optional-chain syntax by the code generators. + if (e instanceof ir.SafePropertyReadExpr) { + return new o.ReadPropExpr(e.receiver, e.name, null, e.sourceSpan, [], true); + } + if (e instanceof ir.SafeKeyedReadExpr) { + return new o.ReadKeyExpr(e.receiver, e.index, null, e.sourceSpan, [], true); + } + return e; + } + if (!isAccessExpression(e)) { return e; } @@ -191,7 +226,7 @@ function safeTransform(e: o.Expression, ctx: SafeTransformContext): o.Expression const dst = deepestSafeTernary(e); if (dst) { - if (e instanceof o.InvokeFunctionExpr) { + if (e instanceof o.InvokeFunctionExpr && !e.isOptional) { dst.expr = dst.expr.callFn(e.args); return e.receiver; } @@ -203,7 +238,7 @@ function safeTransform(e: o.Expression, ctx: SafeTransformContext): o.Expression dst.expr = dst.expr.key(e.index); return e.receiver; } - if (e instanceof ir.SafeInvokeFunctionExpr) { + if (e instanceof o.InvokeFunctionExpr && e.isOptional) { dst.expr = safeTernaryWithTemporary(dst.expr, (r: o.Expression) => r.callFn(e.args), ctx); return e.receiver; } @@ -216,7 +251,7 @@ function safeTransform(e: o.Expression, ctx: SafeTransformContext): o.Expression return e.receiver; } } else { - if (e instanceof ir.SafeInvokeFunctionExpr) { + if (e instanceof o.InvokeFunctionExpr && e.isOptional) { return safeTernaryWithTemporary(e.receiver, (r: o.Expression) => r.callFn(e.args), ctx); } if (e instanceof ir.SafePropertyReadExpr) { diff --git a/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts b/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts index 714d26951d69..2d481ee1ece0 100644 --- a/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts +++ b/packages/compiler/src/template/pipeline/src/phases/i18n_const_collection.ts @@ -246,7 +246,7 @@ function collectMessage( // Sort the params for consistency with TemaplateDefinitionBuilder output. messageOp.params = new Map([...messageOp.params.entries()].sort()); - const mainVar = o.variable(job.pool.uniqueName(TRANSLATION_VAR_PREFIX)); + const mainVar = o.variable(job.pool.uniqueName(TRANSLATION_VAR_PREFIX), o.DYNAMIC_TYPE); // Closure Compiler requires const names to start with `MSG_` but disallows any other // const to start with `MSG_`. We define a variable starting with `MSG_` just for the // `goog.getMsg` call diff --git a/packages/compiler/src/template/pipeline/src/phases/insert_incremental_hydration_runtime.ts b/packages/compiler/src/template/pipeline/src/phases/insert_incremental_hydration_runtime.ts new file mode 100644 index 000000000000..768ebf4073ff --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/insert_incremental_hydration_runtime.ts @@ -0,0 +1,37 @@ +/** + * @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 * as ir from '../../ir'; +import type {ComponentCompilationJob} from '../compilation'; + +/** + * For each view that contains at least one `@defer` block with hydrate triggers, + * insert a single `EnableIncrementalHydrationRuntime` op before the first such + * `Defer` op. This results in a top-level `ɵɵenableIncrementalHydrationRuntime()` + * instruction call being emitted once per "create" block, activating the + * incremental hydration runtime regardless of how many hydrating defer blocks + * are present in that view. + */ +export function insertIncrementalHydrationRuntime(job: ComponentCompilationJob): void { + for (const unit of job.units) { + for (const op of unit.create) { + if ( + op.kind === ir.OpKind.Defer && + op.flags !== null && + (op.flags & ir.TDeferDetailsFlags.HasHydrateTriggers) !== 0 + ) { + ir.OpList.insertBefore( + ir.createEnableIncrementalHydrationRuntimeOp(op.sourceSpan), + op, + ); + // Only the first hydrating defer in the view needs the activator. + break; + } + } + } +} diff --git a/packages/compiler/src/template/pipeline/src/phases/pure_function_extraction.ts b/packages/compiler/src/template/pipeline/src/phases/pure_function_extraction.ts index f9e766a5f458..e1d779d3bb15 100644 --- a/packages/compiler/src/template/pipeline/src/phases/pure_function_extraction.ts +++ b/packages/compiler/src/template/pipeline/src/phases/pure_function_extraction.ts @@ -45,7 +45,7 @@ class PureFunctionConstant extends GenericKeyFn implements SharedConstantDefinit toSharedConstantDeclaration(declName: string, keyExpr: o.Expression): o.Statement { const fnParams: o.FnParam[] = []; for (let idx = 0; idx < this.numArgs; idx++) { - fnParams.push(new o.FnParam('a' + idx)); + fnParams.push(new o.FnParam('a' + idx, o.DYNAMIC_TYPE)); } // We will never visit `ir.PureFunctionParameterExpr`s that don't belong to us, because this diff --git a/packages/compiler/src/template/pipeline/src/phases/reify.ts b/packages/compiler/src/template/pipeline/src/phases/reify.ts index ca89a7227274..7a00b45aff59 100644 --- a/packages/compiler/src/template/pipeline/src/phases/reify.ts +++ b/packages/compiler/src/template/pipeline/src/phases/reify.ts @@ -342,7 +342,12 @@ function reifyCreateOperations(unit: CompilationUnit, ops: ir.OpList( op, ir.createStatementOp( - new o.DeclareVarStmt(op.variable.name, op.initializer, undefined, o.StmtModifier.Final), + new o.DeclareVarStmt( + op.variable.name, + op.initializer, + o.DYNAMIC_TYPE, + o.StmtModifier.Final, + ), ), ); break; @@ -383,9 +388,13 @@ function reifyCreateOperations(unit: CompilationUnit, ops: ir.OpList(op, ng.projectionDef(op.def)); break; + case ir.OpKind.EnableIncrementalHydrationRuntime: + ir.OpList.replace(op, ng.enableIncrementalHydrationRuntime(op.sourceSpan)); + break; case ir.OpKind.Projection: if (op.handle.slot === null) { throw new Error('No slot was assigned for project instruction'); @@ -689,7 +701,12 @@ function reifyUpdateOperations(unit: CompilationUnit, ops: ir.OpList( op, ir.createStatementOp( - new o.DeclareVarStmt(op.variable.name, op.initializer, undefined, o.StmtModifier.Final), + new o.DeclareVarStmt( + op.variable.name, + op.initializer, + o.DYNAMIC_TYPE, + o.StmtModifier.Final, + ), ), ); break; @@ -859,7 +876,7 @@ function reifyListenerHandler( const params: o.FnParam[] = []; if (consumesDollarEvent) { // We need the `$event` parameter. - params.push(new o.FnParam('$event')); + params.push(new o.FnParam('$event', o.DYNAMIC_TYPE)); } return o.fn(params, handlerStmts, undefined, undefined, name); @@ -872,7 +889,10 @@ function reifyTrackBy(unit: CompilationUnit, op: ir.RepeaterCreateOp): o.Express return op.trackByFn; } - const params: o.FnParam[] = [new o.FnParam('$index'), new o.FnParam('$item')]; + const params: o.FnParam[] = [ + new o.FnParam('$index', o.NUMBER_TYPE), + new o.FnParam('$item', o.DYNAMIC_TYPE), + ]; let fn: o.FunctionExpr | o.ArrowFunctionExpr; if (op.trackByOps === null) { @@ -932,7 +952,10 @@ function getArrowFunctionFactory( : statements; return o.arrowFn( - [new o.FnParam(expr.contextName), new o.FnParam(expr.currentViewName)], + [ + new o.FnParam(expr.contextName, o.DYNAMIC_TYPE), + new o.FnParam(expr.currentViewName, o.DYNAMIC_TYPE), + ], o.arrowFn(expr.parameters, body), ); } diff --git a/packages/compiler/src/template/pipeline/src/phases/safe_navigation_migration.ts b/packages/compiler/src/template/pipeline/src/phases/safe_navigation_migration.ts new file mode 100644 index 000000000000..25c252bcc7e3 --- /dev/null +++ b/packages/compiler/src/template/pipeline/src/phases/safe_navigation_migration.ts @@ -0,0 +1,43 @@ +/** + * @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 * as o from '../../../../output/output_ast'; +import * as ir from '../../ir'; +import {CompilationJob} from '../compilation'; + +/** + * Find any function calls to `$safeNavigationMigration`, and remove them, while marking the argument + * so that it uses the legacy null-returning safe navigation semantics. + */ +export function removeSafeNavigationMigration(job: CompilationJob): void { + for (const unit of job.units) { + for (const op of unit.ops()) { + ir.transformExpressionsInOp( + op, + (e) => convertSafeNavigationMigrationCall(e), + ir.VisitorContextFlag.None, + ); + } + } +} + +function convertSafeNavigationMigrationCall(e: o.Expression): o.Expression { + if ( + e instanceof o.InvokeFunctionExpr && + e.fn instanceof ir.LexicalReadExpr && + e.fn.name === '$safeNavigationMigration' + ) { + if (e.args.length !== 1) { + throw new Error( + 'The $safeNavigationMigration builtin function expects exactly one argument.', + ); + } + return new ir.SafeNavigationMigrationExpr(e.args[0]); + } + return e; +} diff --git a/packages/compiler/src/template/pipeline/src/phases/track_fn_optimization.ts b/packages/compiler/src/template/pipeline/src/phases/track_fn_optimization.ts index a6bfa552035d..3d8758707ed6 100644 --- a/packages/compiler/src/template/pipeline/src/phases/track_fn_optimization.ts +++ b/packages/compiler/src/template/pipeline/src/phases/track_fn_optimization.ts @@ -8,6 +8,7 @@ import * as o from '../../../../output/output_ast'; import {Identifiers} from '../../../../render3/r3_identifiers'; +import {tsIgnoreComment} from '../../../../render3/util'; import * as ir from '../../ir'; import type {CompilationJob} from '../compilation'; @@ -73,7 +74,13 @@ export function optimizeTrackFns(job: CompilationJob): void { // additional ops when generating the final code (e.g. temporary variables). const trackOpList = new ir.OpList(); trackOpList.push( - ir.createStatementOp(new o.ReturnStatement(op.track, op.track.sourceSpan)), + ir.createStatementOp( + new o.ReturnStatement(op.track, op.track.sourceSpan, [ + // The return statement might have `this` expressions + // that are implicitly typed as `any`. + tsIgnoreComment(), + ]), + ), ); op.trackByOps = trackOpList; } diff --git a/packages/compiler/src/typecheck/api.ts b/packages/compiler/src/typecheck/api.ts new file mode 100644 index 000000000000..3c3e51f0e1cc --- /dev/null +++ b/packages/compiler/src/typecheck/api.ts @@ -0,0 +1,291 @@ +/** + * @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 {SchemaMetadata} from '../core'; +import {AbsoluteSourceSpan} from '../expression_parser/ast'; +import {ClassPropertyMapping, ClassPropertyName, InputOrOutput} from '../property_mapping'; +import {BoundTarget, LegacyAnimationTriggerNames, MatchSource} from '../render3/view/t2_api'; +import {TcbExpr} from './ops/codegen'; + +export interface TypeCtorMetadata { + /** + * The name of the requested type constructor function. + */ + fnName: string; + + /** + * Whether to generate a body for the function or not. + */ + body: boolean; + + /** + * Input, output, and query field names in the type which should be included as constructor input. + */ + fields: {inputs: ClassPropertyMapping}; + + /** + * `Set` of field names which have type coercion enabled. + */ + coercedInputFields: Set; +} + +export interface TcbReferenceMetadata { + /** The name of the class */ + readonly name: string; + /** The module path where the symbol is located, or null if local/ambient */ + readonly moduleName: string | null; + /** True if the symbol successfully emitted locally (no external import required) */ + readonly isLocal: boolean; + /** If the reference could not be externally emitted, this string holds the diagnostic reason why */ + readonly unexportedDiagnostic: string | null; + + /** Key used to uniquely identify the target of this reference. */ + readonly key: TcbReferenceKey; + + /** + * Defines the `AbsoluteSourceSpan` of the target's node name, if available. + */ + readonly nodeNameSpan?: AbsoluteSourceSpan; + + /** + * The absolute path to the file containing the reference node, if available. + */ + readonly nodeFilePath?: string; +} + +export type TcbReferenceKey = string & {__brand: 'TcbReferenceKey'}; + +export interface TcbTypeParameter { + name: string; + representation: string; + representationWithDefault: string; +} + +export type TcbInputMapping = InputOrOutput & { + required: boolean; + + /** + * AST-free string representation of the transform type of the input, if available. + */ + transformType?: string; +}; + +export interface TcbPipeMetadata { + name: string; + ref: TcbReferenceMetadata; + isExplicitlyDeferred: boolean; +} + +/** + * Metadata that describes a template guard for one of the directive's inputs. + */ +export interface TemplateGuardMeta { + /** + * The input name that this guard should be applied to. + */ + inputName: string; + + /** + * Represents the type of the template guard. + * + * - 'invocation' means that a call to the template guard function is emitted so that its return + * type can result in narrowing of the input type. + * - 'binding' means that the input binding expression itself is used as template guard. + */ + type: 'invocation' | 'binding'; +} + +export interface TcbDirectiveMetadata { + ref: TcbReferenceMetadata; + name: string; + selector: string | null; + isComponent: boolean; + isGeneric: boolean; + isStructural: boolean; + isStandalone: boolean; + isExplicitlyDeferred: boolean; + preserveWhitespaces: boolean; + exportAs: string[] | null; + matchSource: MatchSource; + + /** Type parameters of the directive, if available. */ + typeParameters: TcbTypeParameter[] | null; + inputs: ClassPropertyMapping; + outputs: ClassPropertyMapping; + requiresInlineTypeCtor: boolean; + ngTemplateGuards: TemplateGuardMeta[]; + hasNgTemplateContextGuard: boolean; + hasNgFieldDirective: boolean; + coercedInputFields: Set; + restrictedInputFields: Set; + stringLiteralInputFields: Set; + undeclaredInputFields: Set; + publicMethods: Set; + ngContentSelectors: string[] | null; + animationTriggerNames: LegacyAnimationTriggerNames | null; +} + +export interface TcbComponentMetadata { + ref: TcbReferenceMetadata; + typeParameters: TcbTypeParameter[] | null; + typeArguments: string[] | null; +} + +export interface TcbTypeCheckBlockMetadata { + id: TypeCheckId; + boundTarget: BoundTarget; + pipes: Map | null; + schemas: SchemaMetadata[]; + isStandalone: boolean; + preserveWhitespaces: boolean; +} + +export type TypeCheckId = string & {__brand: 'TypeCheckId'}; + +/** + * Interface representing the environment needed for TCB generation. + * This allows us to avoid depending on the full `Environment` class from `compiler-cli` + * which depends on TypeScript APIs. + */ +export interface TcbEnvironment { + config: TypeCheckingConfig; + referenceTcbValue(ref: TcbReferenceMetadata): TcbExpr; + referenceExternalSymbol(moduleName: string, name: string): TcbExpr; + pipeInst(pipeMeta: TcbPipeMetadata): TcbExpr; + typeCtorFor(dir: TcbDirectiveMetadata): TcbExpr; + getPreludeStatements(): TcbExpr[]; +} + +export interface TypeCheckingConfig { + /** + * Whether to check the left-hand side type of binding operations. + */ + checkTypeOfInputBindings: boolean; + + /** + * Whether to honor the access modifiers on input bindings for the component/directive. + */ + honorAccessModifiersForInputBindings: boolean; + + /** + * Whether to use strict null types for input bindings for directives. + */ + strictNullInputBindings: boolean; + + /** + * Whether to check text attributes that happen to be consumed by a directive or component. + */ + checkTypeOfAttributes: boolean; + + /** + * Whether to check the left-hand side type of binding operations to DOM properties. + */ + checkTypeOfDomBindings: boolean; + + /** + * Whether to infer the type of the `$event` variable in event bindings for directive outputs or + * animation events. + */ + checkTypeOfOutputEvents: boolean; + + /** + * Whether to infer the type of the `$event` variable in event bindings for animations. + */ + checkTypeOfAnimationEvents: boolean; + + /** + * Whether to infer the type of the `$event` variable in event bindings to DOM events. + */ + checkTypeOfDomEvents: boolean; + + /** + * Whether to infer the type of local references to DOM elements. + */ + checkTypeOfDomReferences: boolean; + + /** + * Whether to infer the type of local references. + */ + checkTypeOfNonDomReferences: boolean; + + /** + * Whether to adjust the output of the TCB to ensure compatibility with the `TemplateTypeChecker`. + */ + enableTemplateTypeChecker: boolean; + + /** + * Whether to include type information from pipes in the type-checking operation. + */ + checkTypeOfPipes: boolean; + + /** + * Whether to narrow the types of template contexts. + */ + applyTemplateContextGuards: boolean; + + /** + * Whether to use a strict type for null-safe navigation operations. + */ + strictSafeNavigationTypes: boolean; + + /** + * Whether to descend into template bodies and check any bindings there. + */ + checkTemplateBodies: boolean; + + /** + * Whether to always apply DOM schema checks in template bodies, independently of the + * `checkTemplateBodies` setting. + */ + alwaysCheckSchemaInTemplateBodies: boolean; + + /** + * Whether to check resolvable queries. + */ + checkQueries: false; + + /** + * Whether to check if control flow syntax will prevent a node from being projected. + */ + controlFlowPreventingContentProjection: 'error' | 'warning' | 'suppress'; + + /** + * Whether to check if `@Component.imports` contains unused symbols. + */ + unusedStandaloneImports: 'error' | 'warning' | 'suppress'; + + /** + * Whether to use any generic types of the context component. + */ + useContextGenericType: boolean; + + /** + * Whether or not to infer types for object and array literals in the template. + */ + strictLiteralTypes: boolean; + + /** + * Whether to use inline type constructors. + */ + useInlineTypeConstructors: boolean; + + /** + * Whether the type of two-way bindings should be widened to allow `WritableSignal`. + */ + allowSignalsInTwoWayBindings: boolean; + + /** + * Whether the type of DOM events should be asserted with '@angular/core' 'ɵassertType'. + */ + allowDomEventAssertion: boolean; + + /** + * Whether to descend into the bodies of control flow blocks (`@if`, `@switch` and `@for`). + */ + checkControlFlowBodies: boolean; +} diff --git a/packages/compiler/src/typecheck/comments.ts b/packages/compiler/src/typecheck/comments.ts new file mode 100644 index 000000000000..c261ddf0108a --- /dev/null +++ b/packages/compiler/src/typecheck/comments.ts @@ -0,0 +1,22 @@ +/** + * @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 + */ + +/** Used to identify what type the comment is. */ +export enum CommentTriviaType { + DIAGNOSTIC = 'D', + EXPRESSION_TYPE_IDENTIFIER = 'T', +} + +/** Identifies what the TCB expression is for (for example, a directive declaration). */ +export enum ExpressionIdentifier { + DIRECTIVE = 'DIR', + HOST_DIRECTIVE = 'HOSTDIR', + COMPONENT_COMPLETION = 'COMPCOMP', + EVENT_PARAMETER = 'EP', + VARIABLE_AS_EXPRESSION = 'VAE', +} diff --git a/packages/compiler/src/typecheck/expression.ts b/packages/compiler/src/typecheck/expression.ts new file mode 100644 index 000000000000..32bdb7cff2b7 --- /dev/null +++ b/packages/compiler/src/typecheck/expression.ts @@ -0,0 +1,511 @@ +/** + * @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 { + ArrowFunction, + AST, + AstVisitor, + ASTWithSource, + Binary, + BindingPipe, + Call, + Chain, + Conditional, + EmptyExpr, + ImplicitReceiver, + Interpolation, + KeyedRead, + LiteralArray, + LiteralMap, + LiteralPrimitive, + NonNullAssert, + ParenthesizedExpression, + PrefixNot, + PropertyRead, + RegularExpressionLiteral, + SafeCall, + SafeKeyedRead, + SafePropertyRead, + SpreadElement, + TaggedTemplateLiteral, + TemplateLiteral, + ThisReceiver, + TypeofExpression, + Unary, + VoidExpression, +} from '../expression_parser/ast'; +import {TypeCheckingConfig} from './api'; +import {TcbExpr} from './ops/codegen'; + +/** + * Convert an `AST` to a `TcbExpr` directly, without going through an intermediate `Expression` + * AST. + */ +export function astToTcbExpr( + ast: AST, + maybeResolve: (ast: AST) => TcbExpr | null, + config: TypeCheckingConfig, +): TcbExpr { + const translator = new TcbExprTranslator(maybeResolve, config); + return translator.translate(ast); +} + +class TcbExprTranslator implements AstVisitor { + constructor( + private maybeResolve: (ast: AST) => TcbExpr | null, + private config: TypeCheckingConfig, + ) {} + + translate(ast: AST): TcbExpr { + if (ast instanceof ASTWithSource) { + ast = ast.ast; + } + + // First attempt to let any custom resolution logic provide a translation for the given node. + const resolved = this.maybeResolve(ast); + if (resolved !== null) { + return resolved; + } + + return ast.visit(this); + } + + visitUnary(ast: Unary): TcbExpr { + const expr = this.translate(ast.expr); + const node = new TcbExpr(`${ast.operator}${expr.print()}`); + return node.wrapForTypeChecker().addParseSpanInfo(ast.sourceSpan); + } + + visitBinary(ast: Binary): TcbExpr { + const lhs = this.translate(ast.left); + const rhs = this.translate(ast.right); + lhs.wrapForTypeChecker(); + rhs.wrapForTypeChecker(); + const expression = `${lhs.print()} ${ast.operation} ${rhs.print()}`; + const node = new TcbExpr( + ast.operation === '??' || ast.operation === '**' ? `(${expression})` : expression, + ); + node.addParseSpanInfo(ast.sourceSpan); + return node; + } + + visitChain(ast: Chain): TcbExpr { + const elements = ast.expressions.map((expr) => this.translate(expr).print()); + const node = new TcbExpr(elements.join(', ')); + node.wrapForTypeChecker(); + node.addParseSpanInfo(ast.sourceSpan); + return node; + } + + visitConditional(ast: Conditional): TcbExpr { + const condExpr = this.translate(ast.condition); + const trueExpr = this.translate(ast.trueExp); + // Wrap `falseExpr` in parens so that the trailing parse span info is not attributed to the + // whole conditional. + // In the following example, the last source span comment (5,6) could be seen as the + // trailing comment for _either_ the whole conditional expression _or_ just the `falseExpr` that + // is immediately before it: + // `conditional /*1,2*/ ? trueExpr /*3,4*/ : falseExpr /*5,6*/` + // This should be instead be `conditional /*1,2*/ ? trueExpr /*3,4*/ : (falseExpr /*5,6*/)` + const falseExpr = this.translate(ast.falseExp).wrapForTypeChecker(); + const node = new TcbExpr( + `(${condExpr.print()} ? ${trueExpr.print()} : ${falseExpr.print()})`, + ).addParseSpanInfo(ast.sourceSpan); + return node; + } + + visitImplicitReceiver(ast: ImplicitReceiver): never { + throw new Error('Method not implemented.'); + } + + visitThisReceiver(ast: ThisReceiver): never { + throw new Error('Method not implemented.'); + } + + visitRegularExpressionLiteral(ast: RegularExpressionLiteral, context: any): TcbExpr { + const node = new TcbExpr(`/${ast.body}/${ast.flags ?? ''}`); + node.wrapForTypeChecker(); + return node; + } + + visitInterpolation(ast: Interpolation): TcbExpr { + const exprs = ast.expressions.map((e) => { + const node = this.translate(e); + node.wrapForTypeChecker(); + return node.print(); + }); + return new TcbExpr(`"" + ${exprs.join(' + ')}`); + } + + visitKeyedRead(ast: KeyedRead): TcbExpr { + const receiver = this.translate(ast.receiver).wrapForTypeChecker(); + const key = this.translate(ast.key); + return new TcbExpr(`${receiver.print()}[${key.print()}]`).addParseSpanInfo(ast.sourceSpan); + } + + visitLiteralArray(ast: LiteralArray): TcbExpr { + const elements = ast.expressions.map((expr) => this.translate(expr)); + let literal = `[${elements.map((el) => el.print()).join(', ')}]`; + + if (!this.config.strictLiteralTypes) { + literal = `(${literal} as any)`; + } + + return new TcbExpr(literal).addParseSpanInfo(ast.sourceSpan); + } + + visitLiteralMap(ast: LiteralMap): TcbExpr { + const properties = ast.keys.map((key, idx) => { + const value = this.translate(ast.values[idx]); + + if (key.kind === 'property') { + const keyNode = new TcbExpr(TcbExpr.quoteAndEscape(key.key)); + keyNode.addParseSpanInfo(key.sourceSpan); + return `${keyNode.print()}: ${value.print()}`; + } else { + return `...${value.print()}`; + } + }); + + let literal = `{ ${properties.join(', ')} }`; + + if (!this.config.strictLiteralTypes) { + literal = `${literal} as any`; + } + + const expression = new TcbExpr(literal).addParseSpanInfo(ast.sourceSpan); + expression.wrapForTypeChecker(); + + return expression; + } + + visitLiteralPrimitive(ast: LiteralPrimitive): TcbExpr { + let node: TcbExpr; + if (ast.value === undefined) { + node = new TcbExpr('undefined'); + } else if (ast.value === null) { + node = new TcbExpr('null'); + } else if (typeof ast.value === 'string') { + node = new TcbExpr(TcbExpr.quoteAndEscape(ast.value)); + } else if (typeof ast.value === 'number') { + if (Number.isNaN(ast.value)) { + node = new TcbExpr('NaN'); + } else if (!Number.isFinite(ast.value)) { + node = new TcbExpr(ast.value > 0 ? 'Infinity' : '-Infinity'); + } else { + node = new TcbExpr(ast.value.toString()); + } + } else if (typeof ast.value === 'boolean') { + node = new TcbExpr(ast.value + ''); + } else { + throw Error(`Unsupported AST value of type ${typeof ast.value}`); + } + node.addParseSpanInfo(ast.sourceSpan); + return node; + } + + visitNonNullAssert(ast: NonNullAssert): TcbExpr { + const expr = this.translate(ast.expression).wrapForTypeChecker(); + return new TcbExpr(`${expr.print()}!`).addParseSpanInfo(ast.sourceSpan); + } + + visitPipe(ast: BindingPipe): never { + throw new Error('Method not implemented.'); + } + + visitPrefixNot(ast: PrefixNot): TcbExpr { + const expression = this.translate(ast.expression).wrapForTypeChecker(); + return new TcbExpr(`!${expression.print()}`).addParseSpanInfo(ast.sourceSpan); + } + + visitTypeofExpression(ast: TypeofExpression): TcbExpr { + const expression = this.translate(ast.expression).wrapForTypeChecker(); + return new TcbExpr(`typeof ${expression.print()}`).addParseSpanInfo(ast.sourceSpan); + } + + visitVoidExpression(ast: VoidExpression): TcbExpr { + const expression = this.translate(ast.expression).wrapForTypeChecker(); + return new TcbExpr(`void ${expression.print()}`).addParseSpanInfo(ast.sourceSpan); + } + + visitPropertyRead(ast: PropertyRead): TcbExpr { + const receiver = this.translate(ast.receiver).wrapForTypeChecker(); + return new TcbExpr(`${receiver.print()}.${ast.name}`) + .addParseSpanInfo(ast.nameSpan) + .wrapForTypeChecker() + .addParseSpanInfo(ast.sourceSpan); + } + + visitSafePropertyRead(ast: SafePropertyRead): TcbExpr { + let node: TcbExpr; + const receiver = this.translate(ast.receiver).wrapForTypeChecker(); + const name = new TcbExpr(ast.name).addParseSpanInfo(ast.nameSpan); + + if (this.config.strictSafeNavigationTypes) { + node = new TcbExpr(`${receiver.print()}?.${name.print()}`); + } else if (VeSafeLhsInferenceBugDetector.veWillInferAnyFor(ast)) { + node = new TcbExpr(`(${receiver.print()} as any).${name.print()}`); + } else { + node = new TcbExpr(`(${receiver.print()}!.${name.print()} as any)`); + } + return node.addParseSpanInfo(ast.sourceSpan); + } + + visitSafeKeyedRead(ast: SafeKeyedRead): TcbExpr { + const receiver = this.translate(ast.receiver).wrapForTypeChecker(); + const key = this.translate(ast.key); + let node: TcbExpr; + + if (this.config.strictSafeNavigationTypes) { + node = new TcbExpr(`${receiver.print()}?.[${key.print()}]`); + } else if (VeSafeLhsInferenceBugDetector.veWillInferAnyFor(ast)) { + node = new TcbExpr(`(${receiver.print()} as any)[${key.print()}]`); + } else { + const elementAccess = new TcbExpr(`${receiver.print()}![${key.print()}]`).addParseSpanInfo( + ast.sourceSpan, + ); + node = new TcbExpr(`(${elementAccess.print()} as any)`); + } + return node.addParseSpanInfo(ast.sourceSpan); + } + + visitCall(ast: Call): TcbExpr { + const args = ast.args.map((expr) => this.translate(expr)); + const receiver = ast.receiver; + let expr: TcbExpr; + + if (receiver instanceof PropertyRead) { + const resolved = this.maybeResolve(receiver); + if (resolved !== null) { + expr = resolved; + } else { + const propertyReceiver = this.translate(receiver.receiver).wrapForTypeChecker(); + expr = new TcbExpr(`${propertyReceiver.print()}.${receiver.name}`).addParseSpanInfo( + receiver.nameSpan, + ); + } + } else { + expr = this.translate(receiver); + } + + let node: TcbExpr; + + if (ast.receiver instanceof SafePropertyRead || ast.receiver instanceof SafeKeyedRead) { + node = this.convertToSafeCall(ast, expr, args); + } else { + node = new TcbExpr(`${expr.print()}(${args.map((arg) => arg.print()).join(', ')})`); + } + + return node.addParseSpanInfo(ast.sourceSpan); + } + + visitSafeCall(ast: SafeCall): TcbExpr { + const args = ast.args.map((expr) => this.translate(expr)); + const expr = this.translate(ast.receiver).wrapForTypeChecker(); + return this.convertToSafeCall(ast, expr, args).addParseSpanInfo(ast.sourceSpan); + } + + visitTemplateLiteral(ast: TemplateLiteral): TcbExpr { + const length = ast.elements.length; + const head = ast.elements[0]; + let result: string; + + if (length === 1) { + result = `\`${this.escapeTemplateLiteral(head.text)}\``; + } else { + let parts = [`\`${this.escapeTemplateLiteral(head.text)}`]; + const tailIndex = length - 1; + + for (let i = 1; i < tailIndex; i++) { + const expr = this.translate(ast.expressions[i - 1]); + parts.push(`\${${expr.print()}}${this.escapeTemplateLiteral(ast.elements[i].text)}`); + } + const resolvedExpression = this.translate(ast.expressions[tailIndex - 1]); + parts.push( + `\${${resolvedExpression.print()}}${this.escapeTemplateLiteral(ast.elements[tailIndex].text)}\``, + ); + result = parts.join(''); + } + return new TcbExpr(result); + } + + visitTemplateLiteralElement() { + throw new Error('Method not implemented'); + } + + visitTaggedTemplateLiteral(ast: TaggedTemplateLiteral): TcbExpr { + const tag = this.translate(ast.tag); + const template = this.visitTemplateLiteral(ast.template); + return new TcbExpr(`${tag.print()}${template.print()}`); + } + + visitParenthesizedExpression(ast: ParenthesizedExpression): TcbExpr { + const expr = this.translate(ast.expression); + return new TcbExpr(`(${expr.print()})`); + } + + visitSpreadElement(ast: SpreadElement) { + const expression = this.translate(ast.expression); + expression.wrapForTypeChecker(); + const node = new TcbExpr(`...${expression.print()}`); + node.addParseSpanInfo(ast.sourceSpan); + return node; + } + + visitEmptyExpr(ast: EmptyExpr) { + const node = new TcbExpr('undefined'); + node.addParseSpanInfo(ast.sourceSpan); + return node; + } + + visitArrowFunction(ast: ArrowFunction): TcbExpr { + const params = ast.parameters + .map((param) => new TcbExpr(param.name).markIgnoreDiagnostics().print()) + .join(', '); + const body = astToTcbExpr( + ast.body, + (innerAst) => { + if ( + !(innerAst instanceof PropertyRead) || + innerAst.receiver instanceof ThisReceiver || + !(innerAst.receiver instanceof ImplicitReceiver) + ) { + return this.maybeResolve(innerAst); + } + + const correspondingParam = ast.parameters.find((arg) => arg.name === innerAst.name); + + if (correspondingParam) { + const node = new TcbExpr(innerAst.name); + node.addParseSpanInfo(innerAst.sourceSpan); + return node; + } + + return this.maybeResolve(innerAst); + }, + this.config, + ); + + return new TcbExpr( + `${ast.parameters.length === 1 ? params : `(${params})`} => ${body.print()}`, + ); + } + + private convertToSafeCall(ast: Call | SafeCall, exprNode: TcbExpr, argNodes: TcbExpr[]): TcbExpr { + const expr = exprNode.print(); + const args = argNodes.map((node) => node.print()).join(', '); + + if (this.config.strictSafeNavigationTypes) { + return new TcbExpr(`(0 as any ? ${expr}!(${args}) : undefined)`); + } + + if (VeSafeLhsInferenceBugDetector.veWillInferAnyFor(ast)) { + return new TcbExpr(`(${expr} as any)(${args})`); + } + + return new TcbExpr(`(${expr}!(${args}) as any)`); + } + + private escapeTemplateLiteral(value: string) { + return value.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '$\\{'); + } +} + +class VeSafeLhsInferenceBugDetector implements AstVisitor { + private static SINGLETON = new VeSafeLhsInferenceBugDetector(); + + static veWillInferAnyFor(ast: Call | SafeCall | SafePropertyRead | SafeKeyedRead) { + const visitor = VeSafeLhsInferenceBugDetector.SINGLETON; + return ast instanceof Call ? ast.visit(visitor) : ast.receiver.visit(visitor); + } + + visitUnary(ast: Unary): boolean { + return ast.expr.visit(this); + } + visitBinary(ast: Binary): boolean { + return ast.left.visit(this) || ast.right.visit(this); + } + visitChain(): boolean { + return false; + } + visitConditional(ast: Conditional): boolean { + return ast.condition.visit(this) || ast.trueExp.visit(this) || ast.falseExp.visit(this); + } + visitCall(): boolean { + return true; + } + visitSafeCall(): boolean { + return false; + } + visitImplicitReceiver(): boolean { + return false; + } + visitThisReceiver(): boolean { + return false; + } + visitInterpolation(ast: Interpolation): boolean { + return ast.expressions.some((exp) => exp.visit(this)); + } + visitKeyedRead(): boolean { + return false; + } + visitLiteralArray(): boolean { + return true; + } + visitLiteralMap(): boolean { + return true; + } + visitLiteralPrimitive(): boolean { + return false; + } + visitPipe(): boolean { + return true; + } + visitPrefixNot(ast: PrefixNot): boolean { + return ast.expression.visit(this); + } + visitTypeofExpression(ast: TypeofExpression): boolean { + return ast.expression.visit(this); + } + visitVoidExpression(ast: VoidExpression): boolean { + return ast.expression.visit(this); + } + visitNonNullAssert(ast: NonNullAssert): boolean { + return ast.expression.visit(this); + } + visitPropertyRead(): boolean { + return false; + } + visitSafePropertyRead(): boolean { + return false; + } + visitSafeKeyedRead(): boolean { + return false; + } + visitTemplateLiteral() { + return false; + } + visitTemplateLiteralElement() { + return false; + } + visitTaggedTemplateLiteral() { + return false; + } + visitParenthesizedExpression(ast: ParenthesizedExpression) { + return ast.expression.visit(this); + } + visitRegularExpressionLiteral() { + return false; + } + visitSpreadElement(ast: SpreadElement) { + return ast.expression.visit(this); + } + visitArrowFunction(ast: ArrowFunction, context: any) { + return false; + } +} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/host_bindings.ts b/packages/compiler/src/typecheck/host_bindings.ts similarity index 56% rename from packages/compiler-cli/src/ngtsc/typecheck/src/host_bindings.ts rename to packages/compiler/src/typecheck/host_bindings.ts index 17262ba862ab..7ddaae7a24aa 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/host_bindings.ts +++ b/packages/compiler/src/typecheck/host_bindings.ts @@ -6,95 +6,118 @@ * found in the LICENSE file at https://angular.dev/license */ +import {CssSelector} from '../directive_matching'; import { - BindingType, - CssSelector, - makeBindingParser, - ParsedEvent, - ParseSourceSpan, - TmplAstBoundAttribute, - TmplAstBoundEvent, - TmplAstHostElement, - BindingParser, AbsoluteSourceSpan, - ParseSpan, - PropertyRead, - ParsedEventType, + AST, + ASTWithName, + BindingType, Call, - ThisReceiver, + ImplicitReceiver, KeyedRead, LiteralPrimitive, - AST, + ParsedEvent, + ParsedEventType, + ParseSpan, + PropertyRead, RecursiveAstVisitor, - ASTWithName, SafeCall, - ImplicitReceiver, -} from '@angular/compiler'; -import ts from 'typescript'; -import {createSourceSpan} from '../../annotations/common'; -import {ClassDeclaration} from '../../reflection'; + ThisReceiver, +} from '../expression_parser/ast'; +import {ParseSourceSpan} from '../parse_util'; +import {BoundAttribute, BoundEvent, HostElement} from '../render3/r3_ast'; +import {makeBindingParser} from '../render3/view/template'; +import {BindingParser} from '../template_parser/binding_parser'; /** * Comment attached to an AST node that serves as a guard to distinguish nodes * used for type checking host bindings from ones used for templates. */ -const GUARD_COMMENT_TEXT = 'hostBindingsBlockGuard'; +export const HOST_BINDING_GUARD_COMMENT_TEXT = 'hostBindingsBlockGuard'; + +/** Represents information extracted from the source AST. */ +export type SourceNode = + | StaticSourceNode + | { + kind: 'unspecified'; + sourceSpan: ParseSourceSpan; + }; + +/** A `SourceNode` which represents a static expression. */ +export interface StaticSourceNode { + kind: 'string' | 'identifier'; + /** Raw source code of the node (e.g. strings include the quotes). */ + source: string; + /** Actual text of the node (e.g. value inside the quotes in strings). */ + text: string; + /** Location information about the node. */ + sourceSpan: ParseSourceSpan; +} -/** Node that represent a static name of a member. */ -type StaticName = ts.Identifier | ts.StringLiteralLike; +/** A single binding inside the `host` object of a directive. */ +export interface HostObjectLiteralBinding { + /** Node representing the key of the binding. */ + key: SourceNode; + /** Node representing the value of the binding. */ + value: SourceNode; + /** Location information about the entire binding. */ + sourceSpan: ParseSourceSpan; +} -/** Property assignment node with a static name and initializer. */ -type StaticPropertyAssignment = ts.PropertyAssignment & { - name: StaticName; - initializer: ts.StringLiteralLike; -}; +/** A single binding declared by a `@HostListener` decorator on a class member. */ +export interface HostListenerDecorator { + /** Node declaring the name of the event (e.g. first argument of `@HostListener`). */ + eventName: SourceNode | null; + /** Node representing the name of the member that was decorated. */ + memberName: StaticSourceNode; + /** Location information about the member that the decorator is set on. */ + memberSpan: ParseSourceSpan; + /** Arguments passed to the event. */ + arguments: SourceNode[]; + /** Location information about the decorator. */ + decoratorSpan: ParseSourceSpan; +} + +/** A single binding declared by the `@HostBinding` decorator on a class member. */ +export interface HostBindingDecorator { + /** Node representing the name of the member that was decorated. */ + memberName: StaticSourceNode; + /** Location information about the member that the decorator is set on. */ + memberSpan: ParseSourceSpan; + /** Arguments passed into the decorator */ + arguments: SourceNode[]; + /** Location information about the decorator. */ + decoratorSpan: ParseSourceSpan; +} /** * Creates an AST node that represents the host element of a directive. * Can return null if there are no valid bindings to be checked. - * @param type Whether the host element is for a directive or a component. - * @param selector Selector of the directive. - * @param sourceNode Class declaration for the directive. - * @param literal `host` object literal from the decorator. - * @param bindingDecorators `HostBinding` decorators discovered on the node. - * @param listenerDecorators `HostListener` decorators discovered on the node. + * @param meta Metadata used to construct the host element. */ export function createHostElement( type: 'component' | 'directive', selector: string | null, - sourceNode: ClassDeclaration, - literal: ts.ObjectLiteralExpression | null, - bindingDecorators: Iterable, - listenerDecorators: Iterable, -): TmplAstHostElement | null { - const bindings: TmplAstBoundAttribute[] = []; - const listeners: TmplAstBoundEvent[] = []; + nameSpan: ParseSourceSpan, + hostObjectLiteralBindings: HostObjectLiteralBinding[], + hostBindingDecorators: HostBindingDecorator[], + hostListenerDecorators: HostListenerDecorator[], +): HostElement | null { + const bindings: BoundAttribute[] = []; + const listeners: BoundEvent[] = []; let parser: BindingParser | null = null; - if (literal !== null) { - for (const prop of literal.properties) { - // We only support type checking of static bindings. - if ( - ts.isPropertyAssignment(prop) && - ts.isStringLiteralLike(prop.initializer) && - isStaticName(prop.name) - ) { - parser ??= makeBindingParser(); - createNodeFromHostLiteralProperty( - prop as StaticPropertyAssignment, - parser, - bindings, - listeners, - ); - } - } + for (const binding of hostObjectLiteralBindings) { + // We only support type checking of static bindings. + parser ??= makeBindingParser(); + createNodeFromHostLiteralProperty(binding, parser, bindings, listeners); } - for (const decorator of bindingDecorators) { + for (const decorator of hostBindingDecorators) { createNodeFromBindingDecorator(decorator, bindings); } - for (const decorator of listenerDecorators) { + for (const decorator of hostListenerDecorators) { parser ??= makeBindingParser(); createNodeFromListenerDecorator(decorator, parser, listeners); } @@ -122,51 +145,19 @@ export function createHostElement( tagNames.push(`ng-${type}`); } - return new TmplAstHostElement(tagNames, bindings, listeners, createSourceSpan(sourceNode.name)); + return new HostElement(tagNames, bindings, listeners, nameSpan); } /** * Creates an AST node that can be used as a guard in `if` statements to distinguish TypeScript * nodes used for checking host bindings from ones used for checking templates. */ -export function createHostBindingsBlockGuard(): ts.Expression { +export function createHostBindingsBlockGuard(): string { // Note that the comment text is quite generic. This doesn't really matter, because it is // used only inside a TCB and there's no way for users to produce a comment there. - // `true /*hostBindings*/`. - const trueExpr = ts.addSyntheticTrailingComment( - ts.factory.createTrue(), - ts.SyntaxKind.MultiLineCommentTrivia, - GUARD_COMMENT_TEXT, - ); + // `true /*hostBindingsBlockGuard*/`. // Wrap the expression in parentheses to ensure that the comment is attached to the correct node. - return ts.factory.createParenthesizedExpression(trueExpr); -} - -/** - * Determines if a given node is a guard that indicates that descendant nodes are used to check - * host bindings. - */ -export function isHostBindingsBlockGuard(node: ts.Node): boolean { - if (!ts.isIfStatement(node)) { - return false; - } - - // Needs to be kept in sync with `createHostBindingsMarker`. - const expr = node.expression; - if (!ts.isParenthesizedExpression(expr) || expr.expression.kind !== ts.SyntaxKind.TrueKeyword) { - return false; - } - - const text = expr.getSourceFile().text; - return ( - ts.forEachTrailingCommentRange( - text, - expr.expression.getEnd(), - (pos, end, kind) => - kind === ts.SyntaxKind.MultiLineCommentTrivia && - text.substring(pos + 2, end - 2) === GUARD_COMMENT_TEXT, - ) || false - ); + return `(true /*${HOST_BINDING_GUARD_COMMENT_TEXT}*/)`; } /** @@ -178,58 +169,66 @@ export function isHostBindingsBlockGuard(node: ts.Node): boolean { * @param listeners Array tracking the event listeners of the host element. */ function createNodeFromHostLiteralProperty( - property: StaticPropertyAssignment, + binding: HostObjectLiteralBinding, parser: BindingParser, - bindings: TmplAstBoundAttribute[], - listeners: TmplAstBoundEvent[], + bindings: BoundAttribute[], + listeners: BoundEvent[], ): void { // TODO(crisbeto): surface parsing errors here, because currently they just get ignored. // They'll still get reported when the handler tries to parse the bindings, but here we // can highlight the nodes more accurately. - const {name, initializer} = property; + const {key, value, sourceSpan} = binding; - if (name.text.startsWith('[') && name.text.endsWith(']')) { - const {attrName, type} = inferBoundAttribute(name.text.slice(1, -1)); - const valueSpan = createStaticExpressionSpan(initializer); - const ast = parser.parseBinding(initializer.text, true, valueSpan, valueSpan.start.offset); + if (key.kind !== 'string' || value.kind !== 'string') { + return; + } + + if (key.text.startsWith('[') && key.text.endsWith(']')) { + const {attrName, type} = inferBoundAttribute(key.text.slice(1, -1)); + const ast = parser.parseBinding( + value.text, + true, + value.sourceSpan, + value.sourceSpan.start.offset, + ); if (ast.errors.length > 0) { return; // See TODO above. } - fixupSpans(ast, initializer); + fixupSpans(ast, value); bindings.push( - new TmplAstBoundAttribute( + new BoundAttribute( attrName, type, 0, ast, null, - createSourceSpan(property), - createStaticExpressionSpan(name), - valueSpan, + sourceSpan, + key.sourceSpan, + value.sourceSpan, undefined, ), ); - } else if (name.text.startsWith('(') && name.text.endsWith(')')) { + } else if (key.text.startsWith('(') && key.text.endsWith(')')) { const events: ParsedEvent[] = []; parser.parseEvent( - name.text.slice(1, -1), - initializer.text, + key.text.slice(1, -1), + value.text, false, - createSourceSpan(property), - createStaticExpressionSpan(initializer), + sourceSpan, + value.sourceSpan, [], events, - createStaticExpressionSpan(name), + key.sourceSpan, ); if (events.length === 0 || events[0].handler.errors.length > 0) { return; // See TODO above. } - fixupSpans(events[0].handler, initializer); - listeners.push(TmplAstBoundEvent.fromParsedEvent(events[0])); + fixupSpans(events[0].handler, value); + listeners.push(BoundEvent.fromParsedEvent(events[0])); } } @@ -239,34 +238,23 @@ function createNodeFromHostLiteralProperty( * @param bindings Array tracking the bound attributes of the host element. */ function createNodeFromBindingDecorator( - decorator: ts.Decorator, - bindings: TmplAstBoundAttribute[], + decorator: HostBindingDecorator, + bindings: BoundAttribute[], ): void { - // We only support decorators that are being called. - if (!ts.isCallExpression(decorator.expression)) { - return; - } - - const args = decorator.expression.arguments; - const property = decorator.parent; - let nameNode: StaticName | null = null; - let propertyName: StaticName | null = null; - - if (property && ts.isPropertyDeclaration(property) && isStaticName(property.name)) { - propertyName = property.name; - } + const args = decorator.arguments; + let nameNode: SourceNode; // The first parameter is optional. If omitted, the name // of the class member is used as the property. if (args.length === 0) { - nameNode = propertyName; - } else if (ts.isStringLiteralLike(args[0])) { + nameNode = decorator.memberName; + } else if (args[0].kind === 'string') { nameNode = args[0]; } else { return; } - if (nameNode === null || propertyName === null) { + if (nameNode.kind !== 'string' && nameNode.kind !== 'identifier') { return; } @@ -277,29 +265,33 @@ function createNodeFromBindingDecorator( // manually here. Note that we use a dummy span with -1/-1 as offsets, because it isn't // used for type checking and constructing it accurately would take some effort. const span = new ParseSpan(-1, -1); - const propertyStart = property.getStart(); + const propertyStart = decorator.memberSpan.start.offset; const receiver = new ThisReceiver(span, new AbsoluteSourceSpan(propertyStart, propertyStart)); - const nameSpan = new AbsoluteSourceSpan(propertyName.getStart(), propertyName.getEnd()); - const read = ts.isIdentifier(propertyName) - ? new PropertyRead(span, nameSpan, nameSpan, receiver, propertyName.text) - : new KeyedRead( - span, - nameSpan, - receiver, - new LiteralPrimitive(span, nameSpan, propertyName.text), - ); + const nameSpan = new AbsoluteSourceSpan( + nameNode.sourceSpan.start.offset, + nameNode.sourceSpan.end.offset, + ); + const read = + decorator.memberName.kind === 'string' + ? new KeyedRead( + span, + nameSpan, + receiver, + new LiteralPrimitive(span, nameSpan, decorator.memberName.text), + ) + : new PropertyRead(span, nameSpan, nameSpan, receiver, decorator.memberName.text); const {attrName, type} = inferBoundAttribute(nameNode.text); bindings.push( - new TmplAstBoundAttribute( + new BoundAttribute( attrName, type, 0, read, null, - createSourceSpan(decorator), - createStaticExpressionSpan(nameNode), - createSourceSpan(decorator), + decorator.decoratorSpan, + nameNode.sourceSpan, + decorator.decoratorSpan, undefined, ), ); @@ -312,25 +304,11 @@ function createNodeFromBindingDecorator( * @param bindings Array tracking the bound events of the host element. */ function createNodeFromListenerDecorator( - decorator: ts.Decorator, + decorator: HostListenerDecorator, parser: BindingParser, - listeners: TmplAstBoundEvent[], + listeners: BoundEvent[], ): void { - // We only support decorators that are being called with at least one argument. - if (!ts.isCallExpression(decorator.expression) || decorator.expression.arguments.length === 0) { - return; - } - - const args = decorator.expression.arguments; - const method = decorator.parent; - - // Only handle decorators that are statically analyzable. - if ( - !method || - !ts.isMethodDeclaration(method) || - !isStaticName(method.name) || - !ts.isStringLiteralLike(args[0]) - ) { + if (decorator.eventName === null || decorator.eventName.kind !== 'string') { return; } @@ -340,53 +318,61 @@ function createNodeFromListenerDecorator( // something like `(foo)="handleFoo()"` in the AST. Instead we construct the expressions // manually here. Note that we use a dummy span with -1/-1 as offsets, because it isn't // used for type checking and constructing it accurately would take some effort. - const span = new ParseSpan(-1, -1); + const dummySpan = new ParseSpan(-1, -1); const argNodes: AST[] = []; - const methodStart = method.getStart(); - const methodReceiver = new ThisReceiver(span, new AbsoluteSourceSpan(methodStart, methodStart)); - const nameSpan = new AbsoluteSourceSpan(method.name.getStart(), method.name.getEnd()); - const receiver = ts.isIdentifier(method.name) - ? new PropertyRead(span, nameSpan, nameSpan, methodReceiver, method.name.text) - : new KeyedRead( - span, - nameSpan, - methodReceiver, - new LiteralPrimitive(span, nameSpan, method.name.text), + const methodStart = decorator.memberSpan.start.offset; + const methodReceiver = new ThisReceiver( + dummySpan, + new AbsoluteSourceSpan(methodStart, methodStart), + ); + const nameSpan = new AbsoluteSourceSpan( + decorator.memberName.sourceSpan.start.offset, + decorator.memberName.sourceSpan.end.offset, + ); + const receiver = + decorator.memberName.kind === 'string' + ? new KeyedRead( + dummySpan, + nameSpan, + methodReceiver, + new LiteralPrimitive(dummySpan, nameSpan, decorator.memberName.text), + ) + : new PropertyRead(dummySpan, nameSpan, nameSpan, methodReceiver, decorator.memberName.text); + + for (const arg of decorator.arguments) { + // If the parameter is a static string, parse it using the binding parser since it can be any + // expression, otherwise treat it as `any` so the rest of the parameters can be checked. + if (arg.kind === 'string') { + const span = arg.sourceSpan; + const ast = parser.parseBinding(arg.text, true, span, span.start.offset); + fixupSpans(ast, arg); + argNodes.push(ast); + } else { + // Represents `$any(0)`. We need to construct it manually in order to set the right spans. + const expressionSpan = new AbsoluteSourceSpan( + arg.sourceSpan.start.offset, + arg.sourceSpan.end.offset, ); - - if (args.length > 1 && ts.isArrayLiteralExpression(args[1])) { - for (const expr of args[1].elements) { - // If the parameter is a static string, parse it using the binding parser since it can be any - // expression, otherwise treat it as `any` so the rest of the parameters can be checked. - if (ts.isStringLiteralLike(expr)) { - const span = createStaticExpressionSpan(expr); - const ast = parser.parseBinding(expr.text, true, span, span.start.offset); - fixupSpans(ast, expr); - argNodes.push(ast); - } else { - // Represents `$any(0)`. We need to construct it manually in order to set the right spans. - const expressionSpan = new AbsoluteSourceSpan(expr.getStart(), expr.getEnd()); - const anyRead = new PropertyRead( - span, - expressionSpan, - expressionSpan, - new ImplicitReceiver(span, expressionSpan), - '$any', - ); - const anyCall = new Call( - span, - expressionSpan, - anyRead, - [new LiteralPrimitive(span, expressionSpan, 0)], - expressionSpan, - ); - argNodes.push(anyCall); - } + const anyRead = new PropertyRead( + dummySpan, + expressionSpan, + expressionSpan, + new ImplicitReceiver(dummySpan, expressionSpan), + '$any', + ); + const anyCall = new Call( + dummySpan, + expressionSpan, + anyRead, + [new LiteralPrimitive(dummySpan, expressionSpan, 0)], + expressionSpan, + ); + argNodes.push(anyCall); } } - const callNode = new Call(span, nameSpan, receiver, argNodes, span); - const eventNameNode = args[0]; + const callNode = new Call(dummySpan, nameSpan, receiver, argNodes, dummySpan); + const eventNameNode = decorator.eventName; let type: ParsedEventType; let eventName: string; let phase: string | null; @@ -400,22 +386,24 @@ function createNodeFromListenerDecorator( target = null; } else { const parsedName = parser.parseEventListenerName(eventNameNode.text); - type = ParsedEventType.Regular; + type = parsedName.eventName.startsWith('animate.') + ? ParsedEventType.Animation + : ParsedEventType.Regular; eventName = parsedName.eventName; target = parsedName.target; phase = null; } listeners.push( - new TmplAstBoundEvent( + new BoundEvent( eventName, type, callNode, target, phase, - createSourceSpan(decorator), - createSourceSpan(decorator), - createStaticExpressionSpan(eventNameNode), + decorator.decoratorSpan, + decorator.decoratorSpan, + eventNameNode.sourceSpan, ), ); } @@ -457,31 +445,12 @@ function inferBoundAttribute(name: string): {attrName: string; type: BindingType return {attrName, type}; } -/** Checks whether the specified node is a static name node. */ -function isStaticName(node: ts.Node): node is StaticName { - return ts.isIdentifier(node) || ts.isStringLiteralLike(node); -} - -/** Creates a `ParseSourceSpan` pointing to a static expression AST node's source. */ -function createStaticExpressionSpan(node: ts.StringLiteralLike | ts.Identifier): ParseSourceSpan { - const span = createSourceSpan(node); - - // Offset by one on both sides to skip over the quotes. - if (ts.isStringLiteralLike(node)) { - span.fullStart = span.fullStart.moveBy(1); - span.start = span.start.moveBy(1); - span.end = span.end.moveBy(-1); - } - - return span; -} - /** * Adjusts the spans of a parsed AST so that they're appropriate for a host bindings context. * @param ast The parsed AST that may need to be adjusted. * @param initializer TypeScript node from which the source of the AST was extracted. */ -function fixupSpans(ast: AST, initializer: ts.StringLiteralLike): void { +function fixupSpans(ast: AST, node: StaticSourceNode): void { // When parsing the initializer as a property/event binding, we use `.text` which excludes escaped // quotes and is generally what we want, because preserving them would result in a parser error, // however it has the downside in that the spans of the expressions following the escaped @@ -506,11 +475,13 @@ function fixupSpans(ast: AST, initializer: ts.StringLiteralLike): void { // 4. Constructing some sort of string like ``, // passing it through the HTML parser and extracting the first attribute from it - wasn't explored // much, but likely has the same issues as approach #3. - const escapeIndex = initializer.getText().indexOf('\\', 1); + const escapeIndex = node.source.indexOf('\\', 1); if (escapeIndex > -1) { - const newSpan = new ParseSpan(0, initializer.getWidth()); - const newSourceSpan = new AbsoluteSourceSpan(initializer.getStart(), initializer.getEnd()); + const start = node.sourceSpan.start.offset; + const end = node.sourceSpan.end.offset; + const newSpan = new ParseSpan(0, end - start); + const newSourceSpan = new AbsoluteSourceSpan(start, end); ast.visit(new ReplaceSpanVisitor(escapeIndex, newSpan, newSourceSpan)); } } diff --git a/packages/compiler/src/typecheck/oob.ts b/packages/compiler/src/typecheck/oob.ts new file mode 100644 index 000000000000..39e743971f26 --- /dev/null +++ b/packages/compiler/src/typecheck/oob.ts @@ -0,0 +1,236 @@ +/** + * @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 {AST, BindingPipe, PropertyRead} from '../expression_parser/ast'; + +import { + BoundAttribute, + BoundEvent, + Component, + Directive, + Element, + ForLoopBlock, + ForLoopBlockEmpty, + HoverDeferredTrigger, + IfBlockBranch, + InteractionDeferredTrigger, + LetDeclaration, + Reference, + SwitchBlockCase, + Template, + TextAttribute, + Variable, + ViewportDeferredTrigger, +} from '../render3/r3_ast'; + +import {TcbDirectiveMetadata, TypeCheckId} from './api'; + +/** Categories of diagnostics that can be reported by a `OutOfBandDiagnosticRecorder`. */ +export enum OutOfBandDiagnosticCategory { + Error, + Warning, +} + +/** + * Collects diagnostics on problems which occur in the template which aren't directly sourced + * from type check blocks. + * + * During the creation of a type check block, the template is traversed and the + * `OutOfBandDiagnosticRecorder` is called to record cases when a correct interpretation for the + * template cannot be found. These operations create diagnostics which are stored by the + * recorder for later display. + */ +export interface OutOfBandDiagnosticRecorder { + readonly diagnostics: ReadonlyArray; + + /** + * Reports a `#ref="target"` expression in the template for which a target directive could not be + * found. + * + * @param id the type-checking ID of the template which contains the broken reference. + * @param ref the `Reference` which could not be matched to a directive. + */ + missingReferenceTarget(id: TypeCheckId, ref: Reference): void; + + /** + * Reports usage of a `| pipe` expression in the template for which the named pipe could not be + * found. + * + * @param id the type-checking ID of the template which contains the unknown pipe. + * @param ast the `BindingPipe` invocation of the pipe which could not be found. + * @param isStandalone whether the host component is standalone. + */ + missingPipe(id: TypeCheckId, ast: BindingPipe, isStandalone: boolean): void; + + /** + * Reports usage of a pipe imported via `@Component.deferredImports` outside + * of a `@defer` block in a template. + * + * @param id the type-checking ID of the template which contains the unknown pipe. + * @param ast the `BindingPipe` invocation of the pipe which could not be found. + */ + deferredPipeUsedEagerly(id: TypeCheckId, ast: BindingPipe): void; + + /** + * Reports usage of a component/directive imported via `@Component.deferredImports` outside + * of a `@defer` block in a template. + * + * @param id the type-checking ID of the template which contains the unknown pipe. + * @param element the element which hosts a component that was defer-loaded. + */ + deferredComponentUsedEagerly(id: TypeCheckId, element: Element): void; + + /** + * Reports a duplicate declaration of a template variable. + * + * @param id the type-checking ID of the template which contains the duplicate + * declaration. + * @param variable the `Variable` which duplicates a previously declared variable. + * @param firstDecl the first variable declaration which uses the same name as `variable`. + */ + duplicateTemplateVar(id: TypeCheckId, variable: Variable, firstDecl: Variable): void; + + /** + * Report a warning when structural directives support context guards, but the current + * type-checking configuration prohibits their usage. + */ + suboptimalTypeInference(id: TypeCheckId, variables: Variable[]): void; + + /** + * Reports a split two way binding error message. + */ + splitTwoWayBinding( + id: TypeCheckId, + input: BoundAttribute, + output: BoundEvent, + inputConsumer: Pick, + outputConsumer: Pick | Element, + ): void; + + /** Reports required inputs that haven't been bound. */ + missingRequiredInputs( + id: TypeCheckId, + element: Element | Template | Component | Directive, + directiveName: string, + isComponent: boolean, + inputAliases: string[], + ): void; + + /** + * Reports accesses of properties that aren't available in a `for` block's tracking expression. + */ + illegalForLoopTrackAccess(id: TypeCheckId, block: ForLoopBlock, access: PropertyRead): void; + + /** + * Reports deferred triggers that cannot access the element they're referring to. + */ + inaccessibleDeferredTriggerElement( + id: TypeCheckId, + trigger: HoverDeferredTrigger | InteractionDeferredTrigger | ViewportDeferredTrigger, + ): void; + + /** + * Reports cases where control flow nodes prevent content projection. + */ + controlFlowPreventingContentProjection( + id: TypeCheckId, + category: OutOfBandDiagnosticCategory, + projectionNode: Element | Template, + componentName: string, + slotSelector: string, + controlFlowNode: IfBlockBranch | SwitchBlockCase | ForLoopBlock | ForLoopBlockEmpty, + preservesWhitespaces: boolean, + ): void; + + /** Reports cases where users are writing to `@let` declarations. */ + illegalWriteToLetDeclaration(id: TypeCheckId, node: AST, target: LetDeclaration): void; + + /** Reports cases where users are accessing an `@let` before it is defined.. */ + letUsedBeforeDefinition(id: TypeCheckId, node: PropertyRead, target: LetDeclaration): void; + + /** + * Reports a `@let` declaration that conflicts with another symbol in the same scope. + * + * @param id the type-checking ID of the template which contains the declaration. + * @param current the `LetDeclaration` which is invalid. + */ + conflictingDeclaration(id: TypeCheckId, current: LetDeclaration): void; + + /** + * Reports that a named template dependency (e.g. ``) is not available. + * @param id Type checking ID of the template in which the dependency is declared. + * @param node Node that declares the dependency. + */ + missingNamedTemplateDependency(id: TypeCheckId, node: Component | Directive): void; + + /** + * Reports that a templace dependency of the wrong kind has been referenced at a specific position + * (e.g. ``). + * @param id Type checking ID of the template in which the dependency is declared. + * @param node Node that declares the dependency. + */ + incorrectTemplateDependencyType(id: TypeCheckId, node: Component | Directive): void; + + /** + * Reports a binding inside directive syntax that does not match any of the inputs/outputs of + * the directive. + * @param id Type checking ID of the template in which the directive was defined. + * @param directive Directive that contains the binding. + * @param node Node declaring the binding. + */ + unclaimedDirectiveBinding( + id: TypeCheckId, + directive: Directive, + node: BoundAttribute | TextAttribute | BoundEvent, + ): void; + + /** + * Reports that an implicit deferred trigger is set on a block that does not have a placeholder. + */ + deferImplicitTriggerMissingPlaceholder( + id: TypeCheckId, + trigger: HoverDeferredTrigger | InteractionDeferredTrigger | ViewportDeferredTrigger, + ): void; + + /** + * Reports that an implicit deferred trigger is set on a block whose placeholder is not set up + * correctly (e.g. more than one root node). + */ + deferImplicitTriggerInvalidPlaceholder( + id: TypeCheckId, + trigger: HoverDeferredTrigger | InteractionDeferredTrigger | ViewportDeferredTrigger, + ): void; + + /** + * Reports an unsupported binding on a form `FormField` node. + */ + formFieldUnsupportedBinding(id: TypeCheckId, node: BoundAttribute | TextAttribute): void; + + /** + * Reports that multiple components in the compilation scope match a given element. + */ + multipleMatchingComponents(id: TypeCheckId, element: Element, componentNames: string[]): void; + + /** + * Reports that a host directive input/output has been exposed under multiple names. + * @param id Type checking ID of the template in which the host directive was used. + * @param node Node on which the host directive was used. + * @param directiveName Name of the host directive. + * @param kind Type of the conflicting binding. + * @param classPropertyName Name of the class member that declares the input/output. + * @param aliases Aliases under which the binding is exposed. + */ + conflictingHostDirectiveBinding( + id: TypeCheckId, + node: Element | Template | Component | Directive, + directiveName: string, + kind: 'input' | 'output', + classPropertyName: string, + aliases: string[], + ): void; +} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/base.ts b/packages/compiler/src/typecheck/ops/base.ts similarity index 92% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/base.ts rename to packages/compiler/src/typecheck/ops/base.ts index 1a99262d6c4c..cbd0d7b1b4f6 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/base.ts +++ b/packages/compiler/src/typecheck/ops/base.ts @@ -5,7 +5,8 @@ * 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 ts from 'typescript'; + +import {TcbExpr} from './codegen'; /** * A code generation operation that's involved in the construction of a Type Check Block. @@ -32,7 +33,7 @@ export abstract class TcbOp { */ abstract readonly optional: boolean; - abstract execute(): ts.Expression | null; + abstract execute(): TcbExpr | null; /** * Replacement value or operation used while this `TcbOp` is executing (i.e. to resolve circular @@ -42,13 +43,13 @@ export abstract class TcbOp { * `TcbOp` can be returned in cases where additional code generation is necessary to deal with * circular references. */ - circularFallback(): TcbOp | ts.Expression { + circularFallback(): TcbOp | TcbExpr { // Value used to break a circular reference between `TcbOp`s. // // This value is returned whenever `TcbOp`s have a circular dependency. The // expression is a non-null assertion of the null value (in TypeScript, the // expression `null!`). This construction will infer the least narrow type // for whatever it's assigned to. - return ts.factory.createNonNullExpression(ts.factory.createNull()); + return new TcbExpr('null!'); } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/bindings.ts b/packages/compiler/src/typecheck/ops/bindings.ts similarity index 67% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/bindings.ts rename to packages/compiler/src/typecheck/ops/bindings.ts index 85d4c9f57603..741cca6c5ac6 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/bindings.ts +++ b/packages/compiler/src/typecheck/ops/bindings.ts @@ -6,24 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ +import {AST, BindingType, LiteralArray, LiteralMap} from '../../expression_parser/ast'; +import {ClassPropertyName} from '../../property_mapping'; +import {ParseSourceSpan} from '../../parse_util'; import { - AST, - BindingType, - ParseSourceSpan, - TmplAstBoundAttribute, - TmplAstBoundEvent, - TmplAstComponent, - TmplAstDirective, - TmplAstElement, - TmplAstTemplate, - TmplAstTextAttribute, -} from '@angular/compiler'; -import ts from 'typescript'; -import {TypeCheckableDirectiveMeta} from '../../api'; -import {ClassPropertyName} from '../../../metadata'; -import {Reference} from '../../../imports'; + BoundAttribute, + BoundEvent, + Component, + Directive, + Element, + Template, + TextAttribute, +} from '../../render3/r3_ast'; +import {TcbDirectiveMetadata, TcbInputMapping} from '../api'; import {Context} from './context'; -import {tsCastToAny} from '../ts_util'; +import {TcbExpr} from './codegen'; export interface TcbBoundAttribute { value: AST | string; @@ -33,7 +30,7 @@ export interface TcbBoundAttribute { fieldName: ClassPropertyName; required: boolean; isSignal: boolean; - transformType: Reference | null; + transformType?: string; isTwoWayBinding: boolean; }[]; } @@ -50,9 +47,14 @@ export interface TcbDirectiveBoundInput { field: string; /** - * The `ts.Expression` corresponding with the input binding expression. + * The `TcbExpr` corresponding with the input binding expression. */ - expression: ts.Expression; + expression: TcbExpr; + + /** + * The input's original value expression. + */ + originalExpression: AST | string; /** * The source span of the full attribute binding. @@ -80,15 +82,15 @@ export interface TcbDirectiveUnsetInput { export type TcbDirectiveInput = TcbDirectiveBoundInput | TcbDirectiveUnsetInput; export function getBoundAttributes( - directive: TypeCheckableDirectiveMeta, - node: TmplAstTemplate | TmplAstElement | TmplAstComponent | TmplAstDirective, + directive: TcbDirectiveMetadata, + node: Template | Element | Component | Directive, ): TcbBoundAttribute[] { const boundInputs: TcbBoundAttribute[] = []; - const processAttribute = (attr: TmplAstBoundAttribute | TmplAstTextAttribute) => { + const processAttribute = (attr: BoundAttribute | TextAttribute) => { // Skip non-property bindings. if ( - attr instanceof TmplAstBoundAttribute && + attr instanceof BoundAttribute && attr.type !== BindingType.Property && attr.type !== BindingType.TwoWay ) { @@ -103,21 +105,20 @@ export function getBoundAttributes( value: attr.value, sourceSpan: attr.sourceSpan, keySpan: attr.keySpan ?? null, - inputs: inputs.map((input) => { + inputs: inputs.map((input: TcbInputMapping) => { return { fieldName: input.classPropertyName, required: input.required, - transformType: input.transform?.type || null, + transformType: input.transformType, isSignal: input.isSignal, - isTwoWayBinding: - attr instanceof TmplAstBoundAttribute && attr.type === BindingType.TwoWay, + isTwoWayBinding: attr instanceof BoundAttribute && attr.type === BindingType.TwoWay, }; }), }); } }; - if (node instanceof TmplAstTemplate) { + if (node instanceof Template) { if (node.tagName === 'ng-template') { node.inputs.forEach(processAttribute); node.attributes.forEach(processAttribute); @@ -134,8 +135,8 @@ export function getBoundAttributes( export function checkSplitTwoWayBinding( inputName: string, - output: TmplAstBoundEvent, - inputs: TmplAstBoundAttribute[], + output: BoundEvent, + inputs: BoundAttribute[], tcb: Context, ) { const input = inputs.find((input) => input.name === inputName); @@ -143,32 +144,20 @@ export function checkSplitTwoWayBinding( return false; } // Input consumer should be a directive because it's claimed - const inputConsumer = tcb.boundTarget.getConsumerOfBinding(input) as TypeCheckableDirectiveMeta; + const inputConsumer = tcb.boundTarget.getConsumerOfBinding(input) as TcbDirectiveMetadata; const outputConsumer = tcb.boundTarget.getConsumerOfBinding(output); if ( outputConsumer === null || inputConsumer.ref === undefined || - outputConsumer instanceof TmplAstTemplate + outputConsumer instanceof Template ) { return false; } - if (outputConsumer instanceof TmplAstElement) { - tcb.oobRecorder.splitTwoWayBinding( - tcb.id, - input, - output, - inputConsumer.ref.node, - outputConsumer, - ); + if (outputConsumer instanceof Element) { + tcb.oobRecorder.splitTwoWayBinding(tcb.id, input, output, inputConsumer, outputConsumer); return true; } else if (outputConsumer.ref !== inputConsumer.ref) { - tcb.oobRecorder.splitTwoWayBinding( - tcb.id, - input, - output, - inputConsumer.ref.node, - outputConsumer.ref.node, - ); + tcb.oobRecorder.splitTwoWayBinding(tcb.id, input, output, inputConsumer, outputConsumer); return true; } return false; @@ -177,13 +166,13 @@ export function checkSplitTwoWayBinding( /** * Potentially widens the type of `expr` according to the type-checking configuration. */ -export function widenBinding(expr: ts.Expression, tcb: Context): ts.Expression { +export function widenBinding(expr: TcbExpr, tcb: Context, originalValue: string | AST): TcbExpr { if (!tcb.env.config.checkTypeOfInputBindings) { // If checking the type of bindings is disabled, cast the resulting expression to 'any' // before the assignment. - return tsCastToAny(expr); + return new TcbExpr(`((${expr.print()}) as any)`); } else if (!tcb.env.config.strictNullInputBindings) { - if (ts.isObjectLiteralExpression(expr) || ts.isArrayLiteralExpression(expr)) { + if (originalValue instanceof LiteralMap || originalValue instanceof LiteralArray) { // Object literals and array literals should not be wrapped in non-null assertions as that // would cause literals to be prematurely widened, resulting in type errors when assigning // into a literal type. @@ -191,7 +180,7 @@ export function widenBinding(expr: ts.Expression, tcb: Context): ts.Expression { } else { // If strict null checks are disabled, erase `null` and `undefined` from the type by // wrapping the expression in a non-null assertion. - return ts.factory.createNonNullExpression(expr); + return new TcbExpr(`(${expr.print()})!`); } } else { // No widening is requested, use the expression as is. diff --git a/packages/compiler/src/typecheck/ops/codegen.ts b/packages/compiler/src/typecheck/ops/codegen.ts new file mode 100644 index 000000000000..7d134decd14b --- /dev/null +++ b/packages/compiler/src/typecheck/ops/codegen.ts @@ -0,0 +1,137 @@ +/*! + * @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 {AbsoluteSourceSpan} from '../../expression_parser/ast'; +import {ParseSourceSpan} from '../../parse_util'; +import {CommentTriviaType, ExpressionIdentifier} from '../comments'; + +/** Represents an expression generated within a type check block. */ +export class TcbExpr { + /** Text for the content containing the expression's location information. */ + private spanComment: string | null = null; + + /** Text for the content containing the expression's identifier. */ + private identifierComment: string | null = null; + + /** + * Text of the comment instructing the type checker to + * ignore diagnostics coming from this expression. + */ + private ignoreComment: string | null = null; + + constructor(private source: string) {} + + /** Wraps a string value in quotes and escapes relevant characters. */ + static quoteAndEscape(value: string): string { + // Passing the value through `JSON.stringify` automatically + // escapes quotes and allows us to handle line breaks. + return JSON.stringify(value); + } + + /** + * Converts the node's current state to a string. + * @param ignoreComments Whether the comments associated with the expression should be skipped. + */ + print(ignoreComments = false): string { + if (ignoreComments) { + return this.source; + } + + return ( + this.source + + this.formatComment(this.identifierComment) + + this.formatComment(this.ignoreComment) + + this.formatComment(this.spanComment) + ); + } + + /** + * Adds a synthetic comment to the expression that represents the parse span of the provided node. + * This comment can later be retrieved as trivia of a node to recover original source locations. + * @param span Span from the parser containing the location information. + */ + addParseSpanInfo(span: AbsoluteSourceSpan | ParseSourceSpan): this { + let start: number; + let end: number; + + if (span instanceof AbsoluteSourceSpan) { + start = span.start; + end = span.end; + } else { + start = span.start.offset; + end = span.end.offset; + } + + this.spanComment = `${start},${end}`; + return this; + } + + /** Marks the expression to be ignored for diagnostics. */ + markIgnoreDiagnostics(): this { + this.ignoreComment = `${CommentTriviaType.DIAGNOSTIC}:ignore`; + return this; + } + + /** + * Wraps the expression in parenthesis such that inserted + * span comments become attached to the proper node. + */ + wrapForTypeChecker(): this { + this.source = `(${this.print()})`; + this.spanComment = this.identifierComment = this.ignoreComment = null; + return this; + } + + /** + * Tags the expression with an identifier. + * @param identifier Identifier to apply to the expression. + */ + addExpressionIdentifier(identifier: ExpressionIdentifier, id?: number): this { + this.identifierComment = `${CommentTriviaType.EXPRESSION_TYPE_IDENTIFIER}:${identifier}${id !== undefined ? `:${id}` : ''}`; + return this; + } + + /** + * `toString` implementation meant to catch errors like accidentally + * writing `foo ${expr} bar` instead of `foo ${expr.print()} bar`. + */ + toString(): never { + throw new Error( + 'Assertion error: TcbExpr should not be converted to a string through concatenation. ' + + 'Use the `print` method instead.', + ); + } + + /** Format a comment string as a TypeScript comment. */ + private formatComment(content: string | null): string { + return content === null || content.length === 0 ? '' : ` /*${content}*/`; + } +} + +/** + * Declares a variable with a specific type. + * @param identifier Identifier used to refer to the variable. + * @param type Type that the variable should be initialized to. + */ +export function declareVariable(identifier: TcbExpr, type: TcbExpr): TcbExpr { + type.addExpressionIdentifier(ExpressionIdentifier.VARIABLE_AS_EXPRESSION); + return new TcbExpr(`var ${identifier.print()} = null! as ${type.print()}`); +} + +/** + * Formats an array of `TcbExpr` as a block of single statements. + * @param expressions Expressions to format. + * @param singleLine Whether to print them on a single line or across multiple. Defaults to multiple. + */ +export function getStatementsBlock(expressions: TcbExpr[], singleLine = false): string { + let result = ''; + for (const expr of expressions) { + result += `${expr.print()};${singleLine ? ' ' : '\n'}`; + } + return result; +} diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/completions.ts b/packages/compiler/src/typecheck/ops/completions.ts similarity index 66% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/completions.ts rename to packages/compiler/src/typecheck/ops/completions.ts index 37e44a0e242e..cad99aab6ad8 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/completions.ts +++ b/packages/compiler/src/typecheck/ops/completions.ts @@ -6,10 +6,10 @@ * found in the LICENSE file at https://angular.dev/license */ -import ts from 'typescript'; import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import type {Scope} from './scope'; -import {addExpressionIdentifier, ExpressionIdentifier, markIgnoreDiagnostics} from '../comments'; +import {ExpressionIdentifier} from '../comments'; /** * A `TcbOp` which generates a completion point for the component context. @@ -26,11 +26,10 @@ export class TcbComponentContextCompletionOp extends TcbOp { override readonly optional = false; override execute(): null { - const ctx = ts.factory.createThis(); - const ctxDot = ts.factory.createPropertyAccessExpression(ctx, ''); - markIgnoreDiagnostics(ctxDot); - addExpressionIdentifier(ctxDot, ExpressionIdentifier.COMPONENT_COMPLETION); - this.scope.addStatement(ts.factory.createExpressionStatement(ctxDot)); + const ctx = new TcbExpr('this.'); + ctx.markIgnoreDiagnostics(); + ctx.addExpressionIdentifier(ExpressionIdentifier.COMPONENT_COMPLETION); + this.scope.addStatement(ctx); return null; } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/content_projection.ts b/packages/compiler/src/typecheck/ops/content_projection.ts similarity index 79% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/content_projection.ts rename to packages/compiler/src/typecheck/ops/content_projection.ts index 4d681f6462a5..9df54ae5be3d 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/content_projection.ts +++ b/packages/compiler/src/typecheck/ops/content_projection.ts @@ -6,25 +6,24 @@ * found in the LICENSE file at https://angular.dev/license */ +import {CssSelector, SelectorMatcher} from '../../directive_matching'; import { - createCssSelectorFromNode, - CssSelector, - SelectorMatcher, - TmplAstComponent, - TmplAstElement, - TmplAstForLoopBlock, - TmplAstForLoopBlockEmpty, - TmplAstIfBlock, - TmplAstIfBlockBranch, - TmplAstNode, - TmplAstSwitchBlock, - TmplAstSwitchBlockCaseGroup, - TmplAstTemplate, - TmplAstText, -} from '@angular/compiler'; -import ts from 'typescript'; + Component, + Element, + ForLoopBlock, + ForLoopBlockEmpty, + IfBlock, + IfBlockBranch, + Node, + SwitchBlock, + SwitchBlockCaseGroup, + Template, + Text, +} from '../../render3/r3_ast'; +import {createCssSelectorFromNode} from '../../render3/view/util'; import {TcbOp} from './base'; import {Context} from './context'; +import {OutOfBandDiagnosticCategory} from '../oob'; /** * A `TcbOp` that finds and flags control flow nodes that interfere with content projection. @@ -39,11 +38,11 @@ import {Context} from './context'; * flow node didn't exist. */ export class TcbControlFlowContentProjectionOp extends TcbOp { - private readonly category: ts.DiagnosticCategory; + private readonly category: OutOfBandDiagnosticCategory; constructor( private tcb: Context, - private element: TmplAstElement | TmplAstComponent, + private element: Element | Component, private ngContentSelectors: string[], private componentName: string, ) { @@ -53,8 +52,8 @@ export class TcbControlFlowContentProjectionOp extends TcbOp { // this check won't be enabled for `suppress`. this.category = tcb.env.config.controlFlowPreventingContentProjection === 'error' - ? ts.DiagnosticCategory.Error - : ts.DiagnosticCategory.Warning; + ? OutOfBandDiagnosticCategory.Error + : OutOfBandDiagnosticCategory.Warning; } override readonly optional = false; @@ -74,7 +73,7 @@ export class TcbControlFlowContentProjectionOp extends TcbOp { for (const root of controlFlowToCheck) { for (const child of root.children) { - if (child instanceof TmplAstElement || child instanceof TmplAstTemplate) { + if (child instanceof Element || child instanceof Template) { matcher.match(createCssSelectorFromNode(child), (_, originalSelector) => { this.tcb.oobRecorder.controlFlowPreventingContentProjection( this.tcb.id, @@ -95,28 +94,24 @@ export class TcbControlFlowContentProjectionOp extends TcbOp { } private findPotentialControlFlowNodes() { - const result: Array< - | TmplAstIfBlockBranch - | TmplAstSwitchBlockCaseGroup - | TmplAstForLoopBlock - | TmplAstForLoopBlockEmpty - > = []; + const result: Array = + []; for (const child of this.element.children) { - if (child instanceof TmplAstForLoopBlock) { + if (child instanceof ForLoopBlock) { if (this.shouldCheck(child)) { result.push(child); } if (child.empty !== null && this.shouldCheck(child.empty)) { result.push(child.empty); } - } else if (child instanceof TmplAstIfBlock) { + } else if (child instanceof IfBlock) { for (const branch of child.branches) { if (this.shouldCheck(branch)) { result.push(branch); } } - } else if (child instanceof TmplAstSwitchBlock) { + } else if (child instanceof SwitchBlock) { for (const current of child.groups) { if (this.shouldCheck(current)) { result.push(current); @@ -128,7 +123,7 @@ export class TcbControlFlowContentProjectionOp extends TcbOp { return result; } - private shouldCheck(node: TmplAstNode & {children: TmplAstNode[]}): boolean { + private shouldCheck(node: Node & {children: Node[]}): boolean { // Skip nodes with less than two children since it's impossible // for them to run into the issue that we're checking for. if (node.children.length < 2) { @@ -145,7 +140,7 @@ export class TcbControlFlowContentProjectionOp extends TcbOp { // that we have to account for it here since the presence of text nodes affects the // content projection behavior. if ( - !(child instanceof TmplAstText) || + !(child instanceof Text) || this.tcb.hostPreserveWhitespaces || child.value.trim().length > 0 ) { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/context.ts b/packages/compiler/src/typecheck/ops/context.ts similarity index 74% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/context.ts rename to packages/compiler/src/typecheck/ops/context.ts index ea319533c94f..26df5876d70a 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/context.ts +++ b/packages/compiler/src/typecheck/ops/context.ts @@ -6,13 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import {BoundTarget, SchemaMetadata} from '@angular/compiler'; -import ts from 'typescript'; -import {DomSchemaChecker} from '../dom'; +import {SchemaMetadata} from '../../core'; +import {BoundTarget} from '../../render3/view/t2_api'; +import {TypeCheckId, TcbDirectiveMetadata, TcbPipeMetadata, TcbEnvironment} from '../api'; import {OutOfBandDiagnosticRecorder} from '../oob'; -import {TypeCheckableDirectiveMeta, TypeCheckId} from '../../api'; -import {PipeMeta} from '../../../metadata'; -import {Environment} from '../environment'; +import {DomSchemaChecker} from '../schema'; /** * Controls how generics for the component context class will be handled during TCB generation. @@ -53,12 +51,12 @@ export class Context { private nextId = 1; constructor( - readonly env: Environment, - readonly domSchemaChecker: DomSchemaChecker, - readonly oobRecorder: OutOfBandDiagnosticRecorder, + readonly env: TcbEnvironment, + readonly domSchemaChecker: DomSchemaChecker, + readonly oobRecorder: OutOfBandDiagnosticRecorder, readonly id: TypeCheckId, - readonly boundTarget: BoundTarget, - private pipes: Map | null, + readonly boundTarget: BoundTarget, + private pipes: Map | null, readonly schemas: SchemaMetadata[], readonly hostIsStandalone: boolean, readonly hostPreserveWhitespaces: boolean, @@ -70,11 +68,11 @@ export class Context { * Currently this uses a monotonically increasing counter, but in the future the variable name * might change depending on the type of data being stored. */ - allocateId(): ts.Identifier { - return ts.factory.createIdentifier(`_t${this.nextId++}`); + allocateId(): string { + return `_t${this.nextId++}`; } - getPipeByName(name: string): PipeMeta | null { + getPipeByName(name: string): TcbPipeMetadata | null { if (this.pipes === null || !this.pipes.has(name)) { return null; } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/directive_constructor.ts b/packages/compiler/src/typecheck/ops/directive_constructor.ts similarity index 74% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/directive_constructor.ts rename to packages/compiler/src/typecheck/ops/directive_constructor.ts index 6d8ff142f41c..825751f70d77 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/directive_constructor.ts +++ b/packages/compiler/src/typecheck/ops/directive_constructor.ts @@ -6,17 +6,16 @@ * found in the LICENSE file at https://angular.dev/license */ -import {DirectiveOwner, ParseSourceSpan, TmplAstHostElement} from '@angular/compiler'; -import ts from 'typescript'; +import {ParseSourceSpan} from '../../parse_util'; +import {HostElement} from '../../render3/r3_ast'; +import {DirectiveOwner, MatchSource} from '../../render3/view/t2_api'; import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import {Context} from './context'; import type {Scope} from './scope'; -import {TypeCheckableDirectiveMeta} from '../../api'; -import {addExpressionIdentifier, ExpressionIdentifier, markIgnoreDiagnostics} from '../comments'; -import {addParseSpanInfo, wrapForDiagnostics} from '../diagnostics'; -import {tsCreateVariable} from '../ts_util'; +import {TcbDirectiveMetadata} from '../api'; +import {ExpressionIdentifier} from '../comments'; import {unwrapWritableSignal} from './expression'; -import {getAnyExpression} from '../expression'; import {CustomFormControlType, expandBoundAttributesForField} from './signal_forms'; import {getBoundAttributes, TcbBoundAttribute, TcbDirectiveInput, widenBinding} from './bindings'; import {translateInput} from './inputs'; @@ -38,8 +37,9 @@ export class TcbDirectiveCtorOp extends TcbOp { private tcb: Context, private scope: Scope, private node: DirectiveOwner, - private dir: TypeCheckableDirectiveMeta, + private dir: TcbDirectiveMetadata, private customFormControlType: CustomFormControlType | null, + private directiveIndex?: number, ) { super(); } @@ -50,13 +50,13 @@ export class TcbDirectiveCtorOp extends TcbOp { return true; } - override execute(): ts.Identifier { + override execute(): TcbExpr { const genericInputs = new Map(); - const id = this.tcb.allocateId(); + const id = new TcbExpr(this.tcb.allocateId()); let boundAttrs: TcbBoundAttribute[]; let span: ParseSourceSpan; - if (this.node instanceof TmplAstHostElement) { + if (this.node instanceof HostElement) { // Host elements can't bind to their own inputs so we don't resolve any. boundAttrs = []; span = this.node.sourceSpan; @@ -77,8 +77,11 @@ export class TcbDirectiveCtorOp extends TcbOp { } } - addExpressionIdentifier(id, ExpressionIdentifier.DIRECTIVE); - addParseSpanInfo(id, span); + const identifier = + this.dir.matchSource === MatchSource.HostDirective + ? ExpressionIdentifier.HOST_DIRECTIVE + : ExpressionIdentifier.DIRECTIVE; + id.addExpressionIdentifier(identifier, this.directiveIndex).addParseSpanInfo(span); for (const attr of boundAttrs) { // Skip text attributes if configured to do so. @@ -98,6 +101,7 @@ export class TcbDirectiveCtorOp extends TcbOp { type: 'binding', field: fieldName, expression, + originalExpression: attr.value, sourceSpan: attr.sourceSpan, isTwoWayBinding, }); @@ -114,8 +118,8 @@ export class TcbDirectiveCtorOp extends TcbOp { // Call the type constructor of the directive to infer a type, and assign the directive // instance. const typeCtor = tcbCallTypeCtor(this.dir, this.tcb, Array.from(genericInputs.values())); - markIgnoreDiagnostics(typeCtor); - this.scope.addStatement(tsCreateVariable(id, typeCtor)); + typeCtor.markIgnoreDiagnostics(); + this.scope.addStatement(new TcbExpr(`var ${id.print()} = ${typeCtor.print()}`)); return id; } @@ -142,7 +146,7 @@ export class TcbDirectiveCtorCircularFallbackOp extends TcbOp { constructor( private tcb: Context, private scope: Scope, - private dir: TypeCheckableDirectiveMeta, + private dir: TcbDirectiveMetadata, ) { super(); } @@ -151,16 +155,11 @@ export class TcbDirectiveCtorCircularFallbackOp extends TcbOp { return false; } - override execute(): ts.Identifier { + override execute(): TcbExpr { const id = this.tcb.allocateId(); const typeCtor = this.tcb.env.typeCtorFor(this.dir); - const circularPlaceholder = ts.factory.createCallExpression( - typeCtor, - /* typeArguments */ undefined, - [ts.factory.createNonNullExpression(ts.factory.createNull())], - ); - this.scope.addStatement(tsCreateVariable(id, circularPlaceholder)); - return id; + this.scope.addStatement(new TcbExpr(`var ${id} = ${typeCtor.print()}(null!)`)); + return new TcbExpr(id); } } @@ -169,42 +168,42 @@ export class TcbDirectiveCtorCircularFallbackOp extends TcbOp { * the directive instance from any bound inputs. */ function tcbCallTypeCtor( - dir: TypeCheckableDirectiveMeta, + dir: TcbDirectiveMetadata, tcb: Context, inputs: TcbDirectiveInput[], -): ts.Expression { +): TcbExpr { const typeCtor = tcb.env.typeCtorFor(dir); + let literal = '{ '; - // Construct an array of `ts.PropertyAssignment`s for each of the directive's inputs. - const members = inputs.map((input) => { - const propertyName = ts.factory.createStringLiteral(input.field); + // Construct an object literal containing each directive input. + for (let i = 0; i < inputs.length; i++) { + const input = inputs[i]; + const propertyName = TcbExpr.quoteAndEscape(input.field); + const isLast = i === inputs.length - 1; if (input.type === 'binding') { // For bound inputs, the property is assigned the binding expression. - let expr = widenBinding(input.expression, tcb); + let expr = widenBinding(input.expression, tcb, input.originalExpression); if (input.isTwoWayBinding && tcb.env.config.allowSignalsInTwoWayBindings) { expr = unwrapWritableSignal(expr, tcb); } - const assignment = ts.factory.createPropertyAssignment( - propertyName, - wrapForDiagnostics(expr), - ); - addParseSpanInfo(assignment, input.sourceSpan); - return assignment; + const assignment = new TcbExpr(`${propertyName}: ${expr.wrapForTypeChecker().print()}`); + assignment.addParseSpanInfo(input.sourceSpan); + literal += assignment.print(); } else { // A type constructor is required to be called with all input properties, so any unset // inputs are simply assigned a value of type `any` to ignore them. - return ts.factory.createPropertyAssignment(propertyName, getAnyExpression()); + literal += `${propertyName}: 0 as any`; } - }); + + literal += `${isLast ? '' : ','} `; + } + + literal += '}'; // Call the `ngTypeCtor` method on the directive class, with an object literal argument created // from the matched inputs. - return ts.factory.createCallExpression( - /* expression */ typeCtor, - /* typeArguments */ undefined, - /* argumentsArray */ [ts.factory.createObjectLiteralExpression(members)], - ); + return new TcbExpr(`${typeCtor.print()}(${literal})`); } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/directive_type.ts b/packages/compiler/src/typecheck/ops/directive_type.ts similarity index 57% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/directive_type.ts rename to packages/compiler/src/typecheck/ops/directive_type.ts index 16a197069a5a..5b32e15a8a40 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/directive_type.ts +++ b/packages/compiler/src/typecheck/ops/directive_type.ts @@ -6,17 +6,15 @@ * found in the LICENSE file at https://angular.dev/license */ -import {DirectiveOwner, ParseSourceSpan, TmplAstHostElement} from '@angular/compiler'; -import ts from 'typescript'; +import {ParseSourceSpan} from '../../parse_util'; +import {HostElement} from '../../render3/r3_ast'; +import {DirectiveOwner, MatchSource} from '../../render3/view/t2_api'; import type {Context} from './context'; import type {Scope} from './scope'; import {TcbOp} from './base'; -import {TypeCheckableDirectiveMeta} from '../../api'; -import {Reference} from '../../../imports'; -import {ClassDeclaration} from '../../../reflection'; -import {addExpressionIdentifier, ExpressionIdentifier} from '../comments'; -import {addParseSpanInfo} from '../diagnostics'; -import {tsDeclareVariable} from '../ts_util'; +import {declareVariable, TcbExpr} from './codegen'; +import {TcbDirectiveMetadata} from '../api'; +import {ExpressionIdentifier} from '../comments'; /** * A `TcbOp` which constructs an instance of a directive. For generic directives, generic @@ -27,7 +25,8 @@ export abstract class TcbDirectiveTypeOpBase extends TcbOp { protected tcb: Context, protected scope: Scope, protected node: DirectiveOwner, - protected dir: TypeCheckableDirectiveMeta, + protected dir: TcbDirectiveMetadata, + protected directiveIndex?: number, ) { super(); } @@ -39,37 +38,38 @@ export abstract class TcbDirectiveTypeOpBase extends TcbOp { return true; } - override execute(): ts.Identifier { - const dirRef = this.dir.ref as Reference>; + override execute(): TcbExpr { + const rawType = this.tcb.env.referenceTcbValue(this.dir.ref); - const rawType = this.tcb.env.referenceType(this.dir.ref); - - let type: ts.TypeNode; + let type: TcbExpr; let span: ParseSourceSpan; - if (this.dir.isGeneric === false || dirRef.node.typeParameters === undefined) { + if ( + this.dir.isGeneric === false || + this.dir.typeParameters === null || + this.dir.typeParameters.length === 0 + ) { type = rawType; } else { - if (!ts.isTypeReferenceNode(rawType)) { - throw new Error( - `Expected TypeReferenceNode when referencing the type for ${this.dir.ref.debugName}`, - ); - } - const typeArguments = dirRef.node.typeParameters.map(() => - ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - ); - type = ts.factory.createTypeReferenceNode(rawType.typeName, typeArguments); + const typeArguments = Array(this.dir.typeParameters?.length ?? 0) + .fill('any') + .join(', '); + type = new TcbExpr(`${rawType.print()}<${typeArguments}>`); } - if (this.node instanceof TmplAstHostElement) { + if (this.node instanceof HostElement) { span = this.node.sourceSpan; } else { span = this.node.startSourceSpan || this.node.sourceSpan; } - const id = this.tcb.allocateId(); - addExpressionIdentifier(id, ExpressionIdentifier.DIRECTIVE); - addParseSpanInfo(id, span); - this.scope.addStatement(tsDeclareVariable(id, type)); + const identifier = + this.dir.matchSource === MatchSource.HostDirective + ? ExpressionIdentifier.HOST_DIRECTIVE + : ExpressionIdentifier.DIRECTIVE; + const id = new TcbExpr(this.tcb.allocateId()) + .addExpressionIdentifier(identifier, this.directiveIndex) + .addParseSpanInfo(span); + this.scope.addStatement(declareVariable(id, type)); return id; } } @@ -88,10 +88,9 @@ export class TcbNonGenericDirectiveTypeOp extends TcbDirectiveTypeOpBase { * Creates a variable declaration for this op's directive of the argument type. Returns the id of * the newly created variable. */ - override execute(): ts.Identifier { - const dirRef = this.dir.ref as Reference>; + override execute(): TcbExpr { if (this.dir.isGeneric) { - throw new Error(`Assertion Error: expected ${dirRef.debugName} not to be generic.`); + throw new Error(`Assertion Error: expected ${this.dir.ref.name} not to be generic.`); } return super.execute(); } @@ -106,11 +105,10 @@ export class TcbNonGenericDirectiveTypeOp extends TcbDirectiveTypeOpBase { * type parameters set to `any`. */ export class TcbGenericDirectiveTypeWithAnyParamsOp extends TcbDirectiveTypeOpBase { - override execute(): ts.Identifier { - const dirRef = this.dir.ref as Reference>; - if (dirRef.node.typeParameters === undefined) { + override execute(): TcbExpr { + if (this.dir.typeParameters === null || this.dir.typeParameters.length === 0) { throw new Error( - `Assertion Error: expected typeParameters when creating a declaration for ${dirRef.debugName}`, + `Assertion Error: expected typeParameters when creating a declaration for ${this.dir.ref.name}`, ); } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/element.ts b/packages/compiler/src/typecheck/ops/element.ts similarity index 61% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/element.ts rename to packages/compiler/src/typecheck/ops/element.ts index ea13281e4fa5..3b1d9acde10a 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/element.ts +++ b/packages/compiler/src/typecheck/ops/element.ts @@ -6,17 +6,15 @@ * found in the LICENSE file at https://angular.dev/license */ -import {TmplAstElement} from '@angular/compiler'; -import ts from 'typescript'; +import {Element} from '../../render3/r3_ast'; import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import type {Context} from './context'; import type {Scope} from './scope'; -import {tsCreateElement, tsCreateVariable} from '../ts_util'; -import {addParseSpanInfo} from '../diagnostics'; /** * A `TcbOp` which creates an expression for a native DOM element (or web component) from a - * `TmplAstElement`. + * `Element`. * * Executing this operation returns a reference to the element variable. */ @@ -24,7 +22,7 @@ export class TcbElementOp extends TcbOp { constructor( private tcb: Context, private scope: Scope, - private element: TmplAstElement, + private element: Element, ) { super(); } @@ -36,12 +34,17 @@ export class TcbElementOp extends TcbOp { return true; } - override execute(): ts.Identifier { + override execute(): TcbExpr { const id = this.tcb.allocateId(); + const idNode = new TcbExpr(id); + idNode.addParseSpanInfo(this.element.startSourceSpan || this.element.sourceSpan); + // Add the declaration of the element using document.createElement. - const initializer = tsCreateElement(this.element.name); - addParseSpanInfo(initializer, this.element.startSourceSpan || this.element.sourceSpan); - this.scope.addStatement(tsCreateVariable(id, initializer)); - return id; + const initializer = new TcbExpr(`document.createElement("${this.element.name}")`); + initializer.addParseSpanInfo(this.element.startSourceSpan || this.element.sourceSpan); + const stmt = new TcbExpr(`var ${idNode.print()} = ${initializer.print()}`); + stmt.addParseSpanInfo(this.element.startSourceSpan || this.element.sourceSpan); + this.scope.addStatement(stmt); + return idNode; } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/events.ts b/packages/compiler/src/typecheck/ops/events.ts similarity index 63% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/events.ts rename to packages/compiler/src/typecheck/ops/events.ts index 94f763ea8128..ffddcbbac48e 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/events.ts +++ b/packages/compiler/src/typecheck/ops/events.ts @@ -6,26 +6,16 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - AST, - DirectiveOwner, - ImplicitReceiver, - ParsedEventType, - PropertyRead, - ThisReceiver, - TmplAstBoundAttribute, - TmplAstBoundEvent, - TmplAstElement, -} from '@angular/compiler'; -import ts from 'typescript'; +import {AST, ImplicitReceiver, ParsedEventType, PropertyRead} from '../../expression_parser/ast'; +import {BoundAttribute, BoundEvent, Element} from '../../render3/r3_ast'; +import {DirectiveOwner} from '../../render3/view/t2_api'; import {TcbOp} from './base'; +import {getStatementsBlock, TcbExpr} from './codegen'; import type {Context} from './context'; import type {Scope} from './scope'; -import {TypeCheckableDirectiveMeta} from '../../api'; -import {addParseSpanInfo} from '../diagnostics'; +import {TcbDirectiveMetadata} from '../api'; import {TcbExpressionTranslator, unwrapWritableSignal} from './expression'; -import {tsCreateVariable} from '../ts_util'; -import {addExpressionIdentifier, ExpressionIdentifier} from '../comments'; +import {ExpressionIdentifier} from '../comments'; import {checkSplitTwoWayBinding} from './bindings'; import {LocalSymbol} from './references'; @@ -44,7 +34,7 @@ const enum EventParamType { * `ts.Expression`, with special handling of the `$event` variable that can be used within event * bindings. */ -export function tcbEventHandlerExpression(ast: AST, tcb: Context, scope: Scope): ts.Expression { +export function tcbEventHandlerExpression(ast: AST, tcb: Context, scope: Scope): TcbExpr { const translator = new TcbEventHandlerTranslator(tcb, scope); return translator.translate(ast); } @@ -60,9 +50,9 @@ export class TcbDirectiveOutputsOp extends TcbOp { private tcb: Context, private scope: Scope, private node: DirectiveOwner, - private inputs: TmplAstBoundAttribute[] | null, - private outputs: TmplAstBoundEvent[], - private dir: TypeCheckableDirectiveMeta, + private inputs: BoundAttribute[] | null, + private outputs: BoundEvent[], + private dir: TcbDirectiveMetadata, ) { super(); } @@ -72,7 +62,7 @@ export class TcbDirectiveOutputsOp extends TcbOp { } override execute(): null { - let dirId: ts.Expression | null = null; + let dirId: TcbExpr | null = null; const outputs = this.dir.outputs; for (const output of this.outputs) { @@ -97,22 +87,17 @@ export class TcbDirectiveOutputsOp extends TcbOp { if (dirId === null) { dirId = this.scope.resolve(this.node, this.dir); } - const outputField = ts.factory.createElementAccessExpression( - dirId, - ts.factory.createStringLiteral(field), - ); - addParseSpanInfo(outputField, output.keySpan); + const outputField = new TcbExpr(`${dirId.print()}[${TcbExpr.quoteAndEscape(field)}]`); + outputField.addParseSpanInfo(output.keySpan); + if (this.tcb.env.config.checkTypeOfOutputEvents) { // For strict checking of directive events, generate a call to the `subscribe` method // on the directive's output field to let type information flow into the handler function's // `$event` parameter. const handler = tcbCreateEventHandler(output, this.tcb, this.scope, EventParamType.Infer); - const subscribeFn = ts.factory.createPropertyAccessExpression(outputField, 'subscribe'); - const call = ts.factory.createCallExpression(subscribeFn, /* typeArguments */ undefined, [ - handler, - ]); - addParseSpanInfo(call, output.sourceSpan); - this.scope.addStatement(ts.factory.createExpressionStatement(call)); + const call = new TcbExpr(`${outputField.print()}.subscribe(${handler.print()})`); + call.addParseSpanInfo(output.sourceSpan); + this.scope.addStatement(call); } else { // If strict checking of directive events is disabled: // @@ -120,9 +105,9 @@ export class TcbDirectiveOutputsOp extends TcbOp { // of the `TemplateTypeChecker` can still find the node for the class member for the // output. // * Emit a handler function where the `$event` parameter has an explicit `any` type. - this.scope.addStatement(ts.factory.createExpressionStatement(outputField)); + this.scope.addStatement(outputField); const handler = tcbCreateEventHandler(output, this.tcb, this.scope, EventParamType.Any); - this.scope.addStatement(ts.factory.createExpressionStatement(handler)); + this.scope.addStatement(handler); } } @@ -142,8 +127,8 @@ export class TcbUnclaimedOutputsOp extends TcbOp { private tcb: Context, private scope: Scope, private target: LocalSymbol, - private outputs: TmplAstBoundEvent[], - private inputs: TmplAstBoundAttribute[] | null, + private outputs: BoundEvent[], + private inputs: BoundAttribute[] | null, private claimedOutputs: Set | null, ) { super(); @@ -154,7 +139,7 @@ export class TcbUnclaimedOutputsOp extends TcbOp { } override execute(): null { - let elId: ts.Expression | null = null; + let elId: TcbExpr | null = null; // TODO(alxhub): this could be more efficient. for (const output of this.outputs) { @@ -178,31 +163,31 @@ export class TcbUnclaimedOutputsOp extends TcbOp { if (output.type === ParsedEventType.LegacyAnimation) { // Animation output bindings always have an `$event` parameter of type `AnimationEvent`. const eventType = this.tcb.env.config.checkTypeOfAnimationEvents - ? this.tcb.env.referenceExternalType('@angular/animations', 'AnimationEvent') + ? this.tcb.env.referenceExternalSymbol('@angular/animations', 'AnimationEvent').print() : EventParamType.Any; const handler = tcbCreateEventHandler(output, this.tcb, this.scope, eventType); - this.scope.addStatement(ts.factory.createExpressionStatement(handler)); + this.scope.addStatement(handler); } else if (output.type === ParsedEventType.Animation) { - const eventType = this.tcb.env.referenceExternalType( + const eventType = this.tcb.env.referenceExternalSymbol( '@angular/core', 'AnimationCallbackEvent', ); - const handler = tcbCreateEventHandler(output, this.tcb, this.scope, eventType); - this.scope.addStatement(ts.factory.createExpressionStatement(handler)); + const handler = tcbCreateEventHandler(output, this.tcb, this.scope, eventType.print()); + this.scope.addStatement(handler); } else if (this.tcb.env.config.checkTypeOfDomEvents) { // If strict checking of DOM events is enabled, generate a call to `addEventListener` on // the element instance so that TypeScript's type inference for // `HTMLElement.addEventListener` using `HTMLElementEventMap` to infer an accurate type for // `$event` depending on the event name. For unknown event names, TypeScript resorts to the // base `Event` type. - let target: ts.Expression; - let domEventAssertion: ts.Expression | undefined; + let target: TcbExpr; + let domEventAssertion: TcbExpr | undefined; // Only check for `window` and `document` since in theory any target can be passed. if (output.target === 'window' || output.target === 'document') { - target = ts.factory.createIdentifier(output.target); + target = new TcbExpr(output.target); } else if (elId === null) { target = elId = this.scope.resolve(this.target); } else { @@ -226,28 +211,19 @@ export class TcbUnclaimedOutputsOp extends TcbOp { // }); // ``` if ( - this.target instanceof TmplAstElement && + this.target instanceof Element && this.target.isVoid && - ts.isIdentifier(target) && this.tcb.env.config.allowDomEventAssertion ) { - domEventAssertion = ts.factory.createCallExpression( - this.tcb.env.referenceExternalSymbol('@angular/core', 'ɵassertType'), - [ts.factory.createTypeQueryNode(target)], - [ - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(EVENT_PARAMETER), - 'target', - ), - ], + const assertUtil = this.tcb.env.referenceExternalSymbol('@angular/core', 'ɵassertType'); + domEventAssertion = new TcbExpr( + `${assertUtil.print()}(${EVENT_PARAMETER}.target)`, ); } - const propertyAccess = ts.factory.createPropertyAccessExpression( - target, - 'addEventListener', + const propertyAccess = new TcbExpr(`${target.print()}.addEventListener`).addParseSpanInfo( + output.keySpan, ); - addParseSpanInfo(propertyAccess, output.keySpan); const handler = tcbCreateEventHandler( output, this.tcb, @@ -255,18 +231,16 @@ export class TcbUnclaimedOutputsOp extends TcbOp { EventParamType.Infer, domEventAssertion, ); - const call = ts.factory.createCallExpression( - /* expression */ propertyAccess, - /* typeArguments */ undefined, - /* arguments */ [ts.factory.createStringLiteral(output.name), handler], + const call = new TcbExpr( + `${propertyAccess.print()}(${TcbExpr.quoteAndEscape(output.name)}, ${handler.print()})`, ); - addParseSpanInfo(call, output.sourceSpan); - this.scope.addStatement(ts.factory.createExpressionStatement(call)); + call.addParseSpanInfo(output.sourceSpan); + this.scope.addStatement(call); } else { // If strict checking of DOM inputs is disabled, emit a handler function where the `$event` // parameter has an explicit `any` type. const handler = tcbCreateEventHandler(output, this.tcb, this.scope, EventParamType.Any); - this.scope.addStatement(ts.factory.createExpressionStatement(handler)); + this.scope.addStatement(handler); } } @@ -275,7 +249,7 @@ export class TcbUnclaimedOutputsOp extends TcbOp { } class TcbEventHandlerTranslator extends TcbExpressionTranslator { - protected override resolve(ast: AST): ts.Expression | null { + protected override resolve(ast: AST): TcbExpr | null { // Recognize a property read on the implicit receiver corresponding with the event parameter // that is available in event bindings. Since this variable is a parameter of the handler // function that the converted expression becomes a child of, just create a reference to the @@ -285,9 +259,7 @@ class TcbEventHandlerTranslator extends TcbExpressionTranslator { ast.receiver instanceof ImplicitReceiver && ast.name === EVENT_PARAMETER ) { - const event = ts.factory.createIdentifier(EVENT_PARAMETER); - addParseSpanInfo(event, ast.nameSpan); - return event; + return new TcbExpr(EVENT_PARAMETER).addParseSpanInfo(ast.nameSpan); } return super.resolve(ast); @@ -312,48 +284,26 @@ class TcbEventHandlerTranslator extends TcbExpressionTranslator { * bindings. Alternatively, an explicit type can be passed for the `$event` parameter. */ function tcbCreateEventHandler( - event: TmplAstBoundEvent, + event: BoundEvent, tcb: Context, scope: Scope, - eventType: EventParamType | ts.TypeNode, - assertionExpression?: ts.Expression, -): ts.Expression { + eventType: EventParamType | string, + assertionExpression?: TcbExpr, +): TcbExpr { const handler = tcbEventHandlerExpression(event.handler, tcb, scope); - const statements: ts.Statement[] = []; + const statements: TcbExpr[] = []; if (assertionExpression !== undefined) { - statements.push(ts.factory.createExpressionStatement(assertionExpression)); + statements.push(assertionExpression); } - // TODO(crisbeto): remove the `checkTwoWayBoundEvents` check in v20. - if (event.type === ParsedEventType.TwoWay && tcb.env.config.checkTwoWayBoundEvents) { - // If we're dealing with a two-way event, we create a variable initialized to the unwrapped - // signal value of the expression and then we assign `$event` to it. Note that in most cases - // this will already be covered by the corresponding input binding, however it allows us to - // handle the case where the input has a wider type than the output (see #58971). - const target = tcb.allocateId(); - const assignment = ts.factory.createBinaryExpression( - target, - ts.SyntaxKind.EqualsToken, - ts.factory.createIdentifier(EVENT_PARAMETER), - ); - - statements.push( - tsCreateVariable( - target, - tcb.env.config.allowSignalsInTwoWayBindings ? unwrapWritableSignal(handler, tcb) : handler, - ), - ts.factory.createExpressionStatement(assignment), - ); - } else { - statements.push(ts.factory.createExpressionStatement(handler)); - } + statements.push(handler); - let eventParamType: ts.TypeNode | undefined; + let eventParamType: string | undefined; if (eventType === EventParamType.Infer) { eventParamType = undefined; } else if (eventType === EventParamType.Any) { - eventParamType = ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword); + eventParamType = 'any'; } else { eventParamType = eventType; } @@ -361,29 +311,18 @@ function tcbCreateEventHandler( // Obtain all guards that have been applied to the scope and its parents, as they have to be // repeated within the handler function for their narrowing to be in effect within the handler. const guards = scope.guards(); + let body = `{\n${getStatementsBlock(statements)} }`; - let body = ts.factory.createBlock(statements); if (guards !== null) { // Wrap the body in an `if` statement containing all guards that have to be applied. - body = ts.factory.createBlock([ts.factory.createIfStatement(guards, body)]); + body = `{ if (${guards.print()}) ${body} }`; } - const eventParam = ts.factory.createParameterDeclaration( - /* modifiers */ undefined, - /* dotDotDotToken */ undefined, - /* name */ EVENT_PARAMETER, - /* questionToken */ undefined, - /* type */ eventParamType, + const eventParam = new TcbExpr( + `${EVENT_PARAMETER}${eventParamType === undefined ? '' : ': ' + eventParamType}`, ); - addExpressionIdentifier(eventParam, ExpressionIdentifier.EVENT_PARAMETER); + eventParam.addExpressionIdentifier(ExpressionIdentifier.EVENT_PARAMETER); // Return an arrow function instead of a function expression to preserve the `this` context. - return ts.factory.createArrowFunction( - /* modifiers */ undefined, - /* typeParameters */ undefined, - /* parameters */ [eventParam], - /* type */ ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - /* equalsGreaterThanToken */ undefined, - /* body */ body, - ); + return new TcbExpr(`(${eventParam.print()}): any => ${body}`); } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/expression.ts b/packages/compiler/src/typecheck/ops/expression.ts similarity index 66% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/expression.ts rename to packages/compiler/src/typecheck/ops/expression.ts index 13ca9295bc79..9484cb1c45ba 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/expression.ts +++ b/packages/compiler/src/typecheck/ops/expression.ts @@ -13,28 +13,24 @@ import { Call, ImplicitReceiver, PropertyRead, - R3Identifiers, SafeCall, SafePropertyRead, - TemplateEntity, ThisReceiver, - TmplAstLetDeclaration, -} from '@angular/compiler'; -import ts from 'typescript'; +} from '../../expression_parser/ast'; +import {LetDeclaration} from '../../render3/r3_ast'; +import {Identifiers as R3Identifiers} from '../../render3/r3_identifiers'; +import {TemplateEntity} from '../../render3/view/t2_api'; +import {astToTcbExpr} from '../expression'; import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import type {Context} from './context'; import type {Scope} from './scope'; -import {astToTypescript, getAnyExpression} from '../expression'; -import {addParseSpanInfo, wrapForDiagnostics} from '../diagnostics'; -import {markIgnoreDiagnostics} from '../comments'; -import {Reference} from '../../../imports'; -import {ClassDeclaration} from '../../../reflection'; /** * Process an `AST` expression and convert it into a `ts.Expression`, generating references to the * correct identifiers in the current scope. */ -export function tcbExpression(ast: AST, tcb: Context, scope: Scope): ts.Expression { +export function tcbExpression(ast: AST, tcb: Context, scope: Scope): TcbExpr { const translator = new TcbExpressionTranslator(tcb, scope); return translator.translate(ast); } @@ -42,12 +38,12 @@ export function tcbExpression(ast: AST, tcb: Context, scope: Scope): ts.Expressi /** * Wraps an expression in an `unwrapSignal` call which extracts the signal's value. */ -export function unwrapWritableSignal(expression: ts.Expression, tcb: Context): ts.CallExpression { +export function unwrapWritableSignal(expression: TcbExpr, tcb: Context): TcbExpr { const unwrapRef = tcb.env.referenceExternalSymbol( R3Identifiers.unwrapWritableSignal.moduleName, R3Identifiers.unwrapWritableSignal.name, ); - return ts.factory.createCallExpression(unwrapRef, undefined, [expression]); + return new TcbExpr(`${unwrapRef.print()}(${expression.print()})`); } /** @@ -70,7 +66,35 @@ export class TcbExpressionOp extends TcbOp { override execute(): null { const expr = tcbExpression(this.expression, this.tcb, this.scope); - this.scope.addStatement(ts.factory.createExpressionStatement(expr)); + this.scope.addStatement(expr); + return null; + } +} + +/** + * A `TcbOp` which renders an Angular expression inside a conditional context. + * This is used for `@defer` triggers (`when`, `prefetch when`, `hydrate when`) + * to enable TypeScript's TS2774 diagnostic for uninvoked functions/signals. + * + * Executing this operation returns nothing. + */ +export class TcbConditionOp extends TcbOp { + constructor( + private tcb: Context, + private scope: Scope, + private expression: AST, + ) { + super(); + } + + override get optional() { + return false; + } + + override execute(): null { + const expr = tcbExpression(this.expression, this.tcb, this.scope); + // Wrap in an if-statement to enable TS2774 for uninvoked signals/functions. + this.scope.addStatement(new TcbExpr(`if (${expr.print()}) {}`)); return null; } } @@ -81,11 +105,11 @@ export class TcbExpressionTranslator { protected scope: Scope, ) {} - translate(ast: AST): ts.Expression { - // `astToTypescript` actually does the conversion. A special resolver `tcbResolve` is passed + translate(ast: AST): TcbExpr { + // `astToTcbExpr` actually does the conversion. A special resolver `tcbResolve` is passed // which interprets specific expression nodes that interact with the `ImplicitReceiver`. These // nodes actually refer to identifiers within the current scope. - return astToTypescript(ast, (ast) => this.resolve(ast), this.tcb.env.config); + return astToTcbExpr(ast, (ast: AST) => this.resolve(ast), this.tcb.env.config); } /** @@ -94,7 +118,7 @@ export class TcbExpressionTranslator { * Some `AST` expressions refer to top-level concepts (references, variables, the component * context). This method assists in resolving those. */ - protected resolve(ast: AST): ts.Expression | null { + protected resolve(ast: AST): TcbExpr | null { if (ast instanceof PropertyRead && ast.receiver instanceof ImplicitReceiver) { // Try to resolve a bound target for this expression. If no such target is available, then // the expression is referencing the top-level component context. In that case, `null` is @@ -102,19 +126,13 @@ export class TcbExpressionTranslator { // `ImplicitReceiver` is resolved in the branch below. const target = this.tcb.boundTarget.getExpressionTarget(ast); const targetExpression = target === null ? null : this.getTargetNodeExpression(target, ast); - if ( - target instanceof TmplAstLetDeclaration && - !this.isValidLetDeclarationAccess(target, ast) - ) { + if (target instanceof LetDeclaration && !this.isValidLetDeclarationAccess(target, ast)) { this.tcb.oobRecorder.letUsedBeforeDefinition(this.tcb.id, ast, target); // Cast the expression to `any` so we don't produce additional diagnostics. // We don't use `markIgnoreForDiagnostics` here, because it won't prevent duplicate // diagnostics for nested accesses in cases like `@let value = value.foo.bar.baz`. if (targetExpression !== null) { - return ts.factory.createAsExpression( - targetExpression, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - ); + return new TcbExpr(`${targetExpression.print()} as any`); } } return targetExpression; @@ -132,16 +150,14 @@ export class TcbExpressionTranslator { const targetExpression = this.getTargetNodeExpression(target, read); const expr = this.translate(ast.right); - const result = ts.factory.createParenthesizedExpression( - ts.factory.createBinaryExpression(targetExpression, ts.SyntaxKind.EqualsToken, expr), - ); - addParseSpanInfo(result, read.sourceSpan); + const result = new TcbExpr(`(${targetExpression.print()} = ${expr.print()})`); + result.addParseSpanInfo(read.sourceSpan); // Ignore diagnostics from TS produced for writes to `@let` and re-report them using // our own infrastructure. We can't rely on the TS reporting, because it includes // the name of the auto-generated TCB variable name. - if (target instanceof TmplAstLetDeclaration) { - markIgnoreDiagnostics(result); + if (target instanceof LetDeclaration) { + result.markIgnoreDiagnostics(); this.tcb.oobRecorder.illegalWriteToLetDeclaration(this.tcb.id, read, target); } @@ -159,17 +175,17 @@ export class TcbExpressionTranslator { // Therefore if `resolve` is called on an `ImplicitReceiver`, it's because no outer // PropertyRead/Call resolved to a variable or reference, and therefore this is a // property read or method call on the component context itself. - return ts.factory.createThis(); + return new TcbExpr('this'); } else if (ast instanceof BindingPipe) { const expr = this.translate(ast.exp); const pipeMeta = this.tcb.getPipeByName(ast.name); - let pipe: ts.Expression | null; + let pipe: TcbExpr; if (pipeMeta === null) { // No pipe by that name exists in scope. Record this as an error. this.tcb.oobRecorder.missingPipe(this.tcb.id, ast, this.tcb.hostIsStandalone); // Use an 'any' value to at least allow the rest of the expression to be checked. - pipe = getAnyExpression(); + pipe = new TcbExpr('(0 as any)'); } else if ( pipeMeta.isExplicitlyDeferred && this.tcb.boundTarget.getEagerlyUsedPipes().includes(ast.name) @@ -179,37 +195,40 @@ export class TcbExpressionTranslator { this.tcb.oobRecorder.deferredPipeUsedEagerly(this.tcb.id, ast); // Use an 'any' value to at least allow the rest of the expression to be checked. - pipe = getAnyExpression(); + pipe = new TcbExpr('(0 as any)'); } else { // Use a variable declared as the pipe's type. - pipe = this.tcb.env.pipeInst( - pipeMeta.ref as Reference>, - ); + pipe = this.tcb.env.pipeInst(pipeMeta); } - const args = ast.args.map((arg) => this.translate(arg)); - let methodAccess: ts.Expression = ts.factory.createPropertyAccessExpression( - pipe, - 'transform', - ); - addParseSpanInfo(methodAccess, ast.nameSpan); + const args = ast.args.map((arg) => this.translate(arg).print()); + let methodAccess = new TcbExpr(`${pipe.print()}.transform`).addParseSpanInfo(ast.nameSpan); + if (!this.tcb.env.config.checkTypeOfPipes) { - methodAccess = ts.factory.createAsExpression( - methodAccess, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - ); + methodAccess = new TcbExpr(`(${methodAccess.print()} as any)`); } - const result = ts.factory.createCallExpression( - /* expression */ methodAccess, - /* typeArguments */ undefined, - /* argumentsArray */ [expr, ...args], - ); - addParseSpanInfo(result, ast.sourceSpan); - return result; + const result = new TcbExpr(`${methodAccess.print()}(${[expr.print(), ...args].join(', ')})`); + return result.addParseSpanInfo(ast.sourceSpan); } else if ( (ast instanceof Call || ast instanceof SafeCall) && (ast.receiver instanceof PropertyRead || ast.receiver instanceof SafePropertyRead) ) { + // Resolve the special `$safeNavigationMigration(expr)` syntax to evaluate as the wrapped expression. + // `$safeNavigationMigration(expr)` -> `expr` + // In the TCB this magic function will not affect the emitted TS + // as Safe avigation expressions already returned `undefined` instead of null + // see bug https://github.com/angular/angular/issues/37622 + if ( + ast.receiver.receiver instanceof ImplicitReceiver && + ast.receiver.name === '$safeNavigationMigration' && + ast.args.length === 1 + ) { + const expr = this.translate(ast.args[0]); + const result = new TcbExpr(`(${expr.print()})`); + result.addParseSpanInfo(ast.sourceSpan); + return result; + } + // Resolve the special `$any(expr)` syntax to insert a cast of the argument to type `any`. // `$any(expr)` -> `expr as any` if ( @@ -218,12 +237,8 @@ export class TcbExpressionTranslator { ast.args.length === 1 ) { const expr = this.translate(ast.args[0]); - const exprAsAny = ts.factory.createAsExpression( - expr, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - ); - const result = ts.factory.createParenthesizedExpression(exprAsAny); - addParseSpanInfo(result, ast.sourceSpan); + const result = new TcbExpr(`(${expr.print()} as any)`); + result.addParseSpanInfo(ast.sourceSpan); return result; } @@ -236,12 +251,11 @@ export class TcbExpressionTranslator { return null; } - const receiver = this.getTargetNodeExpression(target, ast); - const method = wrapForDiagnostics(receiver); - addParseSpanInfo(method, ast.receiver.nameSpan); - const args = ast.args.map((arg) => this.translate(arg)); - const node = ts.factory.createCallExpression(method, undefined, args); - addParseSpanInfo(node, ast.sourceSpan); + const method = this.getTargetNodeExpression(target, ast); + method.addParseSpanInfo(ast.receiver.nameSpan).wrapForTypeChecker(); + const args = ast.args.map((arg) => this.translate(arg).print()); + const node = new TcbExpr(`${method.print()}(${args.join(', ')})`); + node.addParseSpanInfo(ast.sourceSpan); return node; } else { // This AST isn't special after all. @@ -249,13 +263,13 @@ export class TcbExpressionTranslator { } } - private getTargetNodeExpression(targetNode: TemplateEntity, expressionNode: AST): ts.Expression { + private getTargetNodeExpression(targetNode: TemplateEntity, expressionNode: AST): TcbExpr { const expr = this.scope.resolve(targetNode); - addParseSpanInfo(expr, expressionNode.sourceSpan); + expr.addParseSpanInfo(expressionNode.sourceSpan); return expr; } - protected isValidLetDeclarationAccess(target: TmplAstLetDeclaration, ast: PropertyRead): boolean { + protected isValidLetDeclarationAccess(target: LetDeclaration, ast: PropertyRead): boolean { const targetStart = target.sourceSpan.start.offset; const targetEnd = target.sourceSpan.end.offset; const astStart = ast.sourceSpan.start; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/for_block.ts b/packages/compiler/src/typecheck/ops/for_block.ts similarity index 59% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/for_block.ts rename to packages/compiler/src/typecheck/ops/for_block.ts index 6bd96c9dd535..b5850f103c96 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/for_block.ts +++ b/packages/compiler/src/typecheck/ops/for_block.ts @@ -6,20 +6,13 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - AST, - ImplicitReceiver, - PropertyRead, - ThisReceiver, - TmplAstForLoopBlock, - TmplAstVariable, -} from '@angular/compiler'; -import ts from 'typescript'; +import {AST, ImplicitReceiver, PropertyRead, ThisReceiver} from '../../expression_parser/ast'; +import {ForLoopBlock, Variable} from '../../render3/r3_ast'; import {tcbExpression, TcbExpressionTranslator} from './expression'; import type {Context} from './context'; import type {Scope} from './scope'; import {TcbOp} from './base'; -import {addParseSpanInfo} from '../diagnostics'; +import {getStatementsBlock, TcbExpr} from './codegen'; /** * A `TcbOp` which renders a `for` block as a TypeScript `for...of` loop. @@ -30,7 +23,7 @@ export class TcbForOfOp extends TcbOp { constructor( private tcb: Context, private scope: Scope, - private block: TmplAstForLoopBlock, + private block: ForLoopBlock, ) { super(); } @@ -47,48 +40,41 @@ export class TcbForOfOp extends TcbOp { null, ); const initializerId = loopScope.resolve(this.block.item); - if (!ts.isIdentifier(initializerId)) { - throw new Error( - `Could not resolve for loop variable ${this.block.item.name} to an identifier`, - ); - } - const initializer = ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration(initializerId)], - ts.NodeFlags.Const, - ); - addParseSpanInfo(initializer, this.block.item.keySpan); + const initializer = new TcbExpr(`const ${initializerId.print()}`); + initializer.addParseSpanInfo(this.block.item.keySpan); + // It's common to have a for loop over a nullable value (e.g. produced by the `async` pipe). // Add a non-null expression to allow such values to be assigned. - const expression = ts.factory.createNonNullExpression( - tcbExpression(this.block.expression, this.tcb, this.scope), + const expression = new TcbExpr( + `${tcbExpression(this.block.expression, this.tcb, this.scope).print()}!`, ); - const trackTranslator = new TcbForLoopTrackTranslator(this.tcb, loopScope, this.block); - const trackExpression = trackTranslator.translate(this.block.trackBy); - const statements = [ - ...loopScope.render(), - ts.factory.createExpressionStatement(trackExpression), - ]; + + let statements: TcbExpr[]; + + if (this.block.trackBy === null) { + statements = loopScope.render(); + } else { + const trackTranslator = new TcbForLoopTrackTranslator(this.tcb, loopScope, this.block); + const trackExpression = trackTranslator.translate(this.block.trackBy); + statements = [...loopScope.render(), trackExpression]; + } this.scope.addStatement( - ts.factory.createForOfStatement( - undefined, - initializer, - expression, - ts.factory.createBlock(statements), + new TcbExpr( + `for (${initializer.print()} of ${expression.print()}) {\n${getStatementsBlock(statements)} }`, ), ); - return null; } } export class TcbForLoopTrackTranslator extends TcbExpressionTranslator { - private allowedVariables: Set; + private allowedVariables: Set; constructor( tcb: Context, scope: Scope, - private block: TmplAstForLoopBlock, + private block: ForLoopBlock, ) { super(tcb, scope); @@ -102,7 +88,7 @@ export class TcbForLoopTrackTranslator extends TcbExpressionTranslator { } } - protected override resolve(ast: AST): ts.Expression | null { + protected override resolve(ast: AST): TcbExpr | null { if ( ast instanceof PropertyRead && (ast.receiver instanceof ImplicitReceiver || ast.receiver instanceof ThisReceiver) @@ -111,7 +97,7 @@ export class TcbForLoopTrackTranslator extends TcbExpressionTranslator { if ( target !== null && - (!(target instanceof TmplAstVariable) || !this.allowedVariables.has(target)) + (!(target instanceof Variable) || !this.allowedVariables.has(target)) ) { this.tcb.oobRecorder.illegalForLoopTrackAccess(this.tcb.id, this.block, ast); } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/host.ts b/packages/compiler/src/typecheck/ops/host.ts similarity index 52% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/host.ts rename to packages/compiler/src/typecheck/ops/host.ts index 55857954c5ae..199fd5493862 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/host.ts +++ b/packages/compiler/src/typecheck/ops/host.ts @@ -6,13 +6,11 @@ * found in the LICENSE file at https://angular.dev/license */ -import {TmplAstHostElement} from '@angular/compiler'; -import ts from 'typescript'; +import {HostElement} from '../../render3/r3_ast'; import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import type {Context} from './context'; import type {Scope} from './scope'; -import {tsCreateElement, tsCreateVariable} from '../ts_util'; -import {addParseSpanInfo} from '../diagnostics'; /** * A `TcbOp` which creates an expression for a the host element of a directive. @@ -25,16 +23,24 @@ export class TcbHostElementOp extends TcbOp { constructor( private tcb: Context, private scope: Scope, - private element: TmplAstHostElement, + private element: HostElement, ) { super(); } - override execute(): ts.Identifier { + override execute(): TcbExpr { const id = this.tcb.allocateId(); - const initializer = tsCreateElement(...this.element.tagNames); - addParseSpanInfo(initializer, this.element.sourceSpan); - this.scope.addStatement(tsCreateVariable(id, initializer)); - return id; + let tagNames: string; + + if (this.element.tagNames.length === 1) { + tagNames = `"${this.element.tagNames[0]}"`; + } else { + tagNames = `null! as ${this.element.tagNames.map((t) => `"${t}"`).join(' | ')}`; + } + + const initializer = new TcbExpr(`document.createElement(${tagNames})`); + initializer.addParseSpanInfo(this.element.sourceSpan); + this.scope.addStatement(new TcbExpr(`var ${id} = ${initializer.print()}`)); + return new TcbExpr(id); } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/if_block.ts b/packages/compiler/src/typecheck/ops/if_block.ts similarity index 69% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/if_block.ts rename to packages/compiler/src/typecheck/ops/if_block.ts index da5ae476d38f..bb4e0d1080e2 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/if_block.ts +++ b/packages/compiler/src/typecheck/ops/if_block.ts @@ -6,26 +6,25 @@ * found in the LICENSE file at https://angular.dev/license */ -import {TmplAstIfBlock, TmplAstIfBlockBranch} from '@angular/compiler'; -import ts from 'typescript'; +import {IfBlock, IfBlockBranch} from '../../render3/r3_ast'; import {TcbOp} from './base'; +import {getStatementsBlock, TcbExpr} from './codegen'; import type {Scope} from './scope'; import type {Context} from './context'; import {tcbExpression} from './expression'; -import {markIgnoreDiagnostics} from '../comments'; /** * A `TcbOp` which renders an `if` template block as a TypeScript `if` statement. * * Executing this operation returns nothing. */ -export class TcbIfOp extends TcbOp { - private expressionScopes = new Map(); +export class TcbIfBlockOp extends TcbOp { + private expressionScopes = new Map(); constructor( private tcb: Context, private scope: Scope, - private block: TmplAstIfBlock, + private block: IfBlock, ) { super(); } @@ -40,7 +39,7 @@ export class TcbIfOp extends TcbOp { return null; } - private generateBranch(index: number): ts.Statement | undefined { + private generateBranch(index: number): TcbExpr | undefined { const branch = this.block.branches[index]; if (!branch) { @@ -50,7 +49,7 @@ export class TcbIfOp extends TcbOp { // If the expression is null, it means that it's an `else` statement. if (branch.expression === null) { const branchScope = this.getBranchScope(this.scope, branch, index); - return ts.factory.createBlock(branchScope.render()); + return new TcbExpr(`{\n${getStatementsBlock(branchScope.render())}}`); } // We process the expression first in the parent scope, but create a scope around the block @@ -63,22 +62,18 @@ export class TcbIfOp extends TcbOp { let expression = tcbExpression(branch.expression, this.tcb, this.scope); if (branch.expressionAlias !== null) { - expression = ts.factory.createBinaryExpression( - ts.factory.createParenthesizedExpression(expression), - ts.SyntaxKind.AmpersandAmpersandToken, - outerScope.resolve(branch.expressionAlias), + expression = new TcbExpr( + `(${expression.print()}) && ${outerScope.resolve(branch.expressionAlias).print()}`, ); } const bodyScope = this.getBranchScope(outerScope, branch, index); + const ifStatement = `if (${expression.print()}) {\n${getStatementsBlock(bodyScope.render())}}`; + const elseBranch = this.generateBranch(index + 1); - return ts.factory.createIfStatement( - expression, - ts.factory.createBlock(bodyScope.render()), - this.generateBranch(index + 1), - ); + return new TcbExpr(ifStatement + (elseBranch ? ' else ' + elseBranch.print() : '')); } - private getBranchScope(parentScope: Scope, branch: TmplAstIfBlockBranch, index: number): Scope { + private getBranchScope(parentScope: Scope, branch: IfBlockBranch, index: number): Scope { const checkBody = this.tcb.env.config.checkControlFlowBodies; return this.scope.createChildScope( parentScope, @@ -88,8 +83,8 @@ export class TcbIfOp extends TcbOp { ); } - private generateBranchGuard(index: number): ts.Expression | null { - let guard: ts.Expression | null = null; + private generateBranchGuard(index: number): TcbExpr | null { + let guard: TcbExpr | null = null; // Since event listeners are inside callbacks, type narrowing doesn't apply to them anymore. // To recreate the behavior, we generate an expression that negates all the values of the @@ -111,41 +106,30 @@ export class TcbIfOp extends TcbOp { } const expressionScope = this.expressionScopes.get(branch)!; - let expression: ts.Expression; + let expression: TcbExpr; // We need to recreate the expression and mark it to be ignored for diagnostics, // because it was already checked as a part of the block's condition and we don't // want it to produce a duplicate diagnostic. expression = tcbExpression(branch.expression, this.tcb, expressionScope); if (branch.expressionAlias !== null) { - expression = ts.factory.createBinaryExpression( - ts.factory.createParenthesizedExpression(expression), - ts.SyntaxKind.AmpersandAmpersandToken, - expressionScope.resolve(branch.expressionAlias), + expression = new TcbExpr( + `(${expression.print()}) && ${expressionScope.resolve(branch.expressionAlias).print()}`, ); } - markIgnoreDiagnostics(expression); + expression.markIgnoreDiagnostics(); // The expressions of the preceding branches have to be negated // (e.g. `expr` becomes `!(expr)`) when comparing in the guard, except // for the branch's own expression which is preserved as is. const comparisonExpression = - i === index - ? expression - : ts.factory.createPrefixUnaryExpression( - ts.SyntaxKind.ExclamationToken, - ts.factory.createParenthesizedExpression(expression), - ); + i === index ? expression : new TcbExpr(`!(${expression.print()})`); // Finally add the expression to the guard with an && operator. guard = guard === null ? comparisonExpression - : ts.factory.createBinaryExpression( - guard, - ts.SyntaxKind.AmpersandAmpersandToken, - comparisonExpression, - ); + : new TcbExpr(`(${guard.print()}) && (${comparisonExpression.print()})`); } return guard; diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/inputs.ts b/packages/compiler/src/typecheck/ops/inputs.ts similarity index 66% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/inputs.ts rename to packages/compiler/src/typecheck/ops/inputs.ts index 6b3f6a268bc0..f476b5858ccc 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/inputs.ts +++ b/packages/compiler/src/typecheck/ops/inputs.ts @@ -6,28 +6,18 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - AST, - BindingType, - R3Identifiers, - TmplAstBoundAttribute, - TmplAstComponent, - TmplAstDirective, - TmplAstElement, - TmplAstTemplate, - TransplantedType, -} from '@angular/compiler'; -import ts from 'typescript'; +import {AST, BindingType} from '../../expression_parser/ast'; +import {BindingPropertyName, ClassPropertyName} from '../../property_mapping'; +import {Identifiers as R3Identifiers} from '../../render3/r3_identifiers'; +import {BoundAttribute, Component, Directive, Element, Template} from '../../render3/r3_ast'; import type {Context} from './context'; import type {Scope} from './scope'; -import {TypeCheckableDirectiveMeta} from '../../api'; +import {TcbDirectiveMetadata} from '../api'; import {TcbOp} from './base'; -import {BindingPropertyName, ClassPropertyName} from '../../../metadata'; -import {addParseSpanInfo, wrapForDiagnostics} from '../diagnostics'; -import {markIgnoreDiagnostics} from '../comments'; -import {REGISTRY} from '../dom'; +import {declareVariable, TcbExpr} from './codegen'; +import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry'; +const REGISTRY = new DomElementSchemaRegistry(); import {tcbExpression, unwrapWritableSignal} from './expression'; -import {tsCreateTypeQueryForCoercedInput, tsDeclareVariable} from '../ts_util'; import { checkUnsupportedFieldBindings, CustomFormControlType, @@ -36,14 +26,15 @@ import { } from './signal_forms'; import {getBoundAttributes, widenBinding} from './bindings'; import {LocalSymbol} from './references'; +import {isUnsafeObjectKey} from '../../render3/util'; /** * Translates the given attribute binding to a `ts.Expression`. */ -export function translateInput(value: AST | string, tcb: Context, scope: Scope): ts.Expression { +export function translateInput(value: AST | string, tcb: Context, scope: Scope): TcbExpr { if (typeof value === 'string') { // For regular attributes with a static string value, use the represented string literal. - return ts.factory.createStringLiteral(value); + return new TcbExpr(TcbExpr.quoteAndEscape(value)); } else { // Produce an expression representing the value of the binding. return tcbExpression(value, tcb, scope); @@ -60,8 +51,8 @@ export class TcbDirectiveInputsOp extends TcbOp { constructor( private tcb: Context, private scope: Scope, - private node: TmplAstTemplate | TmplAstElement | TmplAstComponent | TmplAstDirective, - private dir: TypeCheckableDirectiveMeta, + private node: Template | Element | Component | Directive, + private dir: TcbDirectiveMetadata, private isFormControl: boolean = false, private customFormControlType: CustomFormControlType | null, ) { @@ -73,7 +64,7 @@ export class TcbDirectiveInputsOp extends TcbOp { } override execute(): null { - let dirId: ts.Expression | null = null; + let dirId: TcbExpr | null = null; // TODO(joost): report duplicate properties const seenRequiredInputs = new Set(); @@ -97,12 +88,15 @@ export class TcbDirectiveInputsOp extends TcbOp { for (const attr of boundAttrs) { // For bound inputs, the property is assigned the binding expression. - const expr = widenBinding(translateInput(attr.value, this.tcb, this.scope), this.tcb); - - let assignment: ts.Expression = wrapForDiagnostics(expr); + let assignment = widenBinding( + translateInput(attr.value, this.tcb, this.scope), + this.tcb, + attr.value, + ); + assignment.wrapForTypeChecker(); for (const {fieldName, required, transformType, isSignal, isTwoWayBinding} of attr.inputs) { - let target: ts.LeftHandSideExpression; + let target: TcbExpr; if (required) { seenRequiredInputs.add(fieldName); @@ -115,29 +109,25 @@ export class TcbDirectiveInputsOp extends TcbOp { // setting the `WriteT` of such `InputSignalWithTransform<_, WriteT>`. if (this.dir.coercedInputFields.has(fieldName)) { - let type: ts.TypeNode; + let type: TcbExpr; - if (transformType !== null) { - type = this.tcb.env.referenceTransplantedType(new TransplantedType(transformType)); + if (transformType !== undefined) { + type = new TcbExpr(transformType); } else { // The input has a coercion declaration which should be used instead of assigning the // expression into the input field directly. To achieve this, a variable is declared // with a type of `typeof Directive.ngAcceptInputType_fieldName` which is then used as // target of the assignment. - const dirTypeRef: ts.TypeNode = this.tcb.env.referenceType(this.dir.ref); - - if (!ts.isTypeReferenceNode(dirTypeRef)) { - throw new Error( - `Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`, - ); - } - - type = tsCreateTypeQueryForCoercedInput(dirTypeRef.typeName, fieldName); + const dirTypeRef = this.tcb.env.referenceTcbValue(this.dir.ref); + const propName = `ngAcceptInputType_${fieldName}`; + const access = isUnsafeObjectKey(fieldName) + ? `[${TcbExpr.quoteAndEscape(propName)}]` + : `.${propName}`; + type = new TcbExpr(`typeof ${dirTypeRef.print()}${access}`); } - const id = this.tcb.allocateId(); - this.scope.addStatement(tsDeclareVariable(id, type)); - + const id = new TcbExpr(this.tcb.allocateId()); + this.scope.addStatement(declareVariable(id, type)); target = id; } else if (this.dir.undeclaredInputFields.has(fieldName)) { // If no coercion declaration is present nor is the field declared (i.e. the input is @@ -156,18 +146,11 @@ export class TcbDirectiveInputsOp extends TcbOp { dirId = this.scope.resolve(this.node, this.dir); } - const id = this.tcb.allocateId(); - const dirTypeRef = this.tcb.env.referenceType(this.dir.ref); - if (!ts.isTypeReferenceNode(dirTypeRef)) { - throw new Error( - `Expected TypeReferenceNode from reference to ${this.dir.ref.debugName}`, - ); - } - const type = ts.factory.createIndexedAccessTypeNode( - ts.factory.createTypeQueryNode(dirId as ts.Identifier), - ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(fieldName)), + const id = new TcbExpr(this.tcb.allocateId()); + const type = new TcbExpr( + `(typeof ${dirId.print()})[${TcbExpr.quoteAndEscape(fieldName)}]`, ); - const temp = tsDeclareVariable(id, type); + const temp = declareVariable(id, type); this.scope.addStatement(temp); target = id; } else { @@ -179,14 +162,8 @@ export class TcbDirectiveInputsOp extends TcbOp { // when possible. String literal fields may not be valid JS identifiers so we use // literal element access instead for those cases. target = this.dir.stringLiteralInputFields.has(fieldName) - ? ts.factory.createElementAccessExpression( - dirId, - ts.factory.createStringLiteral(fieldName), - ) - : ts.factory.createPropertyAccessExpression( - dirId, - ts.factory.createIdentifier(fieldName), - ); + ? new TcbExpr(`${dirId.print()}[${TcbExpr.quoteAndEscape(fieldName)}]`) + : new TcbExpr(`${dirId.print()}.${fieldName}`); } // For signal inputs, we unwrap the target `InputSignal`. Note that @@ -199,20 +176,12 @@ export class TcbDirectiveInputsOp extends TcbOp { R3Identifiers.InputSignalBrandWriteType.moduleName, R3Identifiers.InputSignalBrandWriteType.name, ); - if ( - !ts.isIdentifier(inputSignalBrandWriteSymbol) && - !ts.isPropertyAccessExpression(inputSignalBrandWriteSymbol) - ) { - throw new Error( - `Expected identifier or property access for reference to ${R3Identifiers.InputSignalBrandWriteType.name}`, - ); - } - target = ts.factory.createElementAccessExpression(target, inputSignalBrandWriteSymbol); + target = new TcbExpr(`${target.print()}[${inputSignalBrandWriteSymbol.print()}]`); } if (attr.keySpan !== null) { - addParseSpanInfo(target, attr.keySpan); + target.addParseSpanInfo(attr.keySpan); } // Two-way bindings accept `T | WritableSignal` so we have to unwrap the value. @@ -221,20 +190,17 @@ export class TcbDirectiveInputsOp extends TcbOp { } // Finally the assignment is extended by assigning it into the target expression. - assignment = ts.factory.createBinaryExpression( - target, - ts.SyntaxKind.EqualsToken, - assignment, - ); + assignment = new TcbExpr(`${target.print()} = ${assignment.print()}`); } - addParseSpanInfo(assignment, attr.sourceSpan); + assignment.addParseSpanInfo(attr.sourceSpan); + // Ignore diagnostics for text attributes if configured to do so. if (!this.tcb.env.config.checkTypeOfAttributes && typeof attr.value === 'string') { - markIgnoreDiagnostics(assignment); + assignment.markIgnoreDiagnostics(); } - this.scope.addStatement(ts.factory.createExpressionStatement(assignment)); + this.scope.addStatement(assignment); } this.checkRequiredInputs(seenRequiredInputs); @@ -277,7 +243,7 @@ export class TcbUnclaimedInputsOp extends TcbOp { constructor( private tcb: Context, private scope: Scope, - private inputs: TmplAstBoundAttribute[], + private inputs: BoundAttribute[], private target: LocalSymbol, private claimedInputs: Set | null, ) { @@ -291,7 +257,7 @@ export class TcbUnclaimedInputsOp extends TcbOp { override execute(): null { // `this.inputs` contains only those bindings not matched by any directive. These bindings go to // the element itself. - let elId: ts.Expression | null = null; + let elId: TcbExpr | null = null; // TODO(alxhub): this could be more efficient. for (const binding of this.inputs) { @@ -303,7 +269,11 @@ export class TcbUnclaimedInputsOp extends TcbOp { continue; } - const expr = widenBinding(tcbExpression(binding.value, this.tcb, this.scope), this.tcb); + const expr = widenBinding( + tcbExpression(binding.value, this.tcb, this.scope), + this.tcb, + binding.value, + ); if (this.tcb.env.config.checkTypeOfDomBindings && isPropertyBinding) { if (binding.name !== 'style' && binding.name !== 'class') { @@ -312,25 +282,18 @@ export class TcbUnclaimedInputsOp extends TcbOp { } // A direct binding to a property. const propertyName = REGISTRY.getMappedPropName(binding.name); - const prop = ts.factory.createElementAccessExpression( - elId, - ts.factory.createStringLiteral(propertyName), - ); - const stmt = ts.factory.createBinaryExpression( - prop, - ts.SyntaxKind.EqualsToken, - wrapForDiagnostics(expr), - ); - addParseSpanInfo(stmt, binding.sourceSpan); - this.scope.addStatement(ts.factory.createExpressionStatement(stmt)); + const stmt = new TcbExpr( + `${elId.print()}[${TcbExpr.quoteAndEscape(propertyName)}] = ${expr.wrapForTypeChecker().print()}`, + ).addParseSpanInfo(binding.sourceSpan); + this.scope.addStatement(stmt); } else { - this.scope.addStatement(ts.factory.createExpressionStatement(expr)); + this.scope.addStatement(expr); } } else { // A binding to an animation, attribute, class or style. For now, only validate the right- // hand side of the expression. // TODO: properly check class and style bindings. - this.scope.addStatement(ts.factory.createExpressionStatement(expr)); + this.scope.addStatement(expr); } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/intersection_observer.ts b/packages/compiler/src/typecheck/ops/intersection_observer.ts similarity index 66% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/intersection_observer.ts rename to packages/compiler/src/typecheck/ops/intersection_observer.ts index 658c56501185..c86c980b4627 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/intersection_observer.ts +++ b/packages/compiler/src/typecheck/ops/intersection_observer.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.dev/license */ -import {AST} from '@angular/compiler'; -import ts from 'typescript'; +import {AST} from '../../expression_parser/ast'; import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import {Context} from './context'; import type {Scope} from './scope'; import {tcbExpression} from './expression'; @@ -29,14 +29,7 @@ export class TcbIntersectionObserverOp extends TcbOp { override execute(): null { const options = tcbExpression(this.options, this.tcb, this.scope); - const callback = ts.factory.createNonNullExpression(ts.factory.createNull()); - const expression = ts.factory.createNewExpression( - ts.factory.createIdentifier('IntersectionObserver'), - undefined, - [callback, options], - ); - - this.scope.addStatement(ts.factory.createExpressionStatement(expression)); + this.scope.addStatement(new TcbExpr(`new IntersectionObserver(null!, ${options.print()})`)); return null; } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/let.ts b/packages/compiler/src/typecheck/ops/let.ts similarity index 57% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/let.ts rename to packages/compiler/src/typecheck/ops/let.ts index 7cc9ef8b2ca0..078fcf4af1fb 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/let.ts +++ b/packages/compiler/src/typecheck/ops/let.ts @@ -6,25 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import {TmplAstLetDeclaration} from '@angular/compiler'; -import ts from 'typescript'; +import {LetDeclaration} from '../../render3/r3_ast'; import {Context} from './context'; import type {Scope} from './scope'; import {TcbOp} from './base'; -import {addParseSpanInfo, wrapForTypeChecker} from '../diagnostics'; -import {tsCreateVariable} from '../ts_util'; +import {TcbExpr} from './codegen'; import {tcbExpression} from './expression'; /** - * A `TcbOp` which generates a constant for a `TmplAstLetDeclaration`. - * - * Executing this operation returns a reference to the `@let` declaration. + * A `TcbOp` which generates a constant for a `LetDeclaration`. */ export class TcbLetDeclarationOp extends TcbOp { constructor( private tcb: Context, private scope: Scope, - private node: TmplAstLetDeclaration, + private node: LetDeclaration, ) { super(); } @@ -35,14 +31,13 @@ export class TcbLetDeclarationOp extends TcbOp { */ override readonly optional = false; - override execute(): ts.Identifier { - const id = this.tcb.allocateId(); - addParseSpanInfo(id, this.node.nameSpan); - const value = tcbExpression(this.node.value, this.tcb, this.scope); + override execute(): TcbExpr { + const id = new TcbExpr(this.tcb.allocateId()).addParseSpanInfo(this.node.nameSpan); + const value = tcbExpression(this.node.value, this.tcb, this.scope).wrapForTypeChecker(); // Value needs to be wrapped, because spans for the expressions inside of it can // be picked up incorrectly as belonging to the full variable declaration. - const varStatement = tsCreateVariable(id, wrapForTypeChecker(value), ts.NodeFlags.Const); - addParseSpanInfo(varStatement.declarationList.declarations[0], this.node.sourceSpan); + const varStatement = new TcbExpr(`const ${id.print()} = ${value.print()}`); + varStatement.addParseSpanInfo(this.node.sourceSpan); this.scope.addStatement(varStatement); return id; } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/references.ts b/packages/compiler/src/typecheck/ops/references.ts similarity index 60% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/references.ts rename to packages/compiler/src/typecheck/ops/references.ts index 63a7c699f4cc..d4eac0152803 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/references.ts +++ b/packages/compiler/src/typecheck/ops/references.ts @@ -7,35 +7,31 @@ */ import { - DYNAMIC_TYPE, - TmplAstComponent, - TmplAstDirective, - TmplAstElement, - TmplAstHostElement, - TmplAstLetDeclaration, - TmplAstReference, - TmplAstTemplate, - TmplAstVariable, -} from '@angular/compiler'; -import ts from 'typescript'; + Component, + Directive, + Element, + HostElement, + LetDeclaration, + Reference, + Template, + Variable, +} from '../../render3/r3_ast'; import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import type {Context} from './context'; import type {Scope} from './scope'; -import {TypeCheckableDirectiveMeta} from '../../api'; -import {addParseSpanInfo} from '../diagnostics'; -import {tsCreateVariable} from '../ts_util'; -import {getAnyExpression} from '../expression'; +import {TcbDirectiveMetadata} from '../api'; /** Types that can referenced locally in a template. */ export type LocalSymbol = - | TmplAstElement - | TmplAstTemplate - | TmplAstVariable - | TmplAstLetDeclaration - | TmplAstReference - | TmplAstHostElement - | TmplAstComponent - | TmplAstDirective; + | Element + | Template + | Variable + | LetDeclaration + | Reference + | HostElement + | Component + | Directive; /** * A `TcbOp` which creates a variable for a local ref in a template. @@ -61,9 +57,9 @@ export class TcbReferenceOp extends TcbOp { constructor( private readonly tcb: Context, private readonly scope: Scope, - private readonly node: TmplAstReference, - private readonly host: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective, - private readonly target: TypeCheckableDirectiveMeta | TmplAstTemplate | TmplAstElement, + private readonly node: Reference, + private readonly host: Element | Template | Component | Directive, + private readonly target: TcbDirectiveMetadata | Template | Element, ) { super(); } @@ -72,44 +68,34 @@ export class TcbReferenceOp extends TcbOp { // so it can map a reference variable in the template directly to a node in the TCB. override readonly optional = true; - override execute(): ts.Identifier { - const id = this.tcb.allocateId(); - let initializer: ts.Expression = - this.target instanceof TmplAstTemplate || this.target instanceof TmplAstElement + override execute(): TcbExpr { + const id = new TcbExpr(this.tcb.allocateId()); + let initializer: TcbExpr = + this.target instanceof Template || this.target instanceof Element ? this.scope.resolve(this.target) : this.scope.resolve(this.host, this.target); // The reference is either to an element, an node, or to a directive on an // element or template. if ( - (this.target instanceof TmplAstElement && !this.tcb.env.config.checkTypeOfDomReferences) || + (this.target instanceof Element && !this.tcb.env.config.checkTypeOfDomReferences) || !this.tcb.env.config.checkTypeOfNonDomReferences ) { // References to DOM nodes are pinned to 'any' when `checkTypeOfDomReferences` is `false`. // References to `TemplateRef`s and directives are pinned to 'any' when // `checkTypeOfNonDomReferences` is `false`. - initializer = ts.factory.createAsExpression( - initializer, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - ); - } else if (this.target instanceof TmplAstTemplate) { + initializer = new TcbExpr(`${initializer.print()} as any`); + } else if (this.target instanceof Template) { // Direct references to an node simply require a value of type // `TemplateRef`. To get this, an expression of the form // `(_t1 as any as TemplateRef)` is constructed. - initializer = ts.factory.createAsExpression( - initializer, - ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), - ); - initializer = ts.factory.createAsExpression( - initializer, - this.tcb.env.referenceExternalType('@angular/core', 'TemplateRef', [DYNAMIC_TYPE]), - ); - initializer = ts.factory.createParenthesizedExpression(initializer); + const templateRef = this.tcb.env.referenceExternalSymbol('@angular/core', 'TemplateRef'); + initializer = new TcbExpr(`(${initializer.print()} as any as ${templateRef.print()})`); } - addParseSpanInfo(initializer, this.node.sourceSpan); - addParseSpanInfo(id, this.node.keySpan); + initializer.addParseSpanInfo(this.node.sourceSpan); + id.addParseSpanInfo(this.node.keySpan); - this.scope.addStatement(tsCreateVariable(id, initializer)); + this.scope.addStatement(new TcbExpr(`var ${id.print()} = ${initializer.print()}`)); return id; } } @@ -130,9 +116,9 @@ export class TcbInvalidReferenceOp extends TcbOp { // The declaration of a missing reference is only needed when the reference is resolved. override readonly optional = true; - override execute(): ts.Identifier { - const id = this.tcb.allocateId(); - this.scope.addStatement(tsCreateVariable(id, getAnyExpression())); + override execute(): TcbExpr { + const id = new TcbExpr(this.tcb.allocateId()); + this.scope.addStatement(new TcbExpr(`var ${id.print()} = any`)); return id; } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/schema.ts b/packages/compiler/src/typecheck/ops/schema.ts similarity index 80% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/schema.ts rename to packages/compiler/src/typecheck/ops/schema.ts index 9ba2a326dc3c..11aa051befe2 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/schema.ts +++ b/packages/compiler/src/typecheck/ops/schema.ts @@ -6,10 +6,12 @@ * found in the LICENSE file at https://angular.dev/license */ -import {BindingType, TmplAstComponent, TmplAstElement, TmplAstHostElement} from '@angular/compiler'; -import ts from 'typescript'; -import {REGISTRY} from '../dom'; +import {BindingType} from '../../expression_parser/ast'; +import {Component, Element, HostElement} from '../../render3/r3_ast'; +import {DomElementSchemaRegistry} from '../../schema/dom_element_schema_registry'; +const REGISTRY = new DomElementSchemaRegistry(); import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import {Context} from './context'; import {getComponentTagName} from './selectorless'; @@ -26,7 +28,7 @@ import {getComponentTagName} from './selectorless'; export class TcbDomSchemaCheckerOp extends TcbOp { constructor( private tcb: Context, - private element: TmplAstElement | TmplAstComponent | TmplAstHostElement, + private element: Element | Component | HostElement, private checkElement: boolean, private claimedInputs: Set | null, ) { @@ -37,10 +39,9 @@ export class TcbDomSchemaCheckerOp extends TcbOp { return false; } - override execute(): ts.Expression | null { + override execute(): TcbExpr | null { const element = this.element; - const isTemplateElement = - element instanceof TmplAstElement || element instanceof TmplAstComponent; + const isTemplateElement = element instanceof Element || element instanceof Component; const bindings = isTemplateElement ? element.inputs : element.bindings; if (this.checkElement && isTemplateElement) { @@ -90,7 +91,7 @@ export class TcbDomSchemaCheckerOp extends TcbOp { return null; } - private getTagName(node: TmplAstElement | TmplAstComponent): string { - return node instanceof TmplAstElement ? node.name : getComponentTagName(node); + private getTagName(node: Element | Component): string { + return node instanceof Element ? node.name : getComponentTagName(node); } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/scope.ts b/packages/compiler/src/typecheck/ops/scope.ts similarity index 72% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/scope.ts rename to packages/compiler/src/typecheck/ops/scope.ts index 96d3eb7b4d50..210978b20c23 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/scope.ts +++ b/packages/compiler/src/typecheck/ops/scope.ts @@ -6,45 +6,44 @@ * found in the LICENSE file at https://angular.dev/license */ +import {DirectiveOwner} from '../../render3/view/t2_api'; import { - DirectiveOwner, - TmplAstBoundAttribute, - TmplAstBoundEvent, - TmplAstBoundText, - TmplAstComponent, - TmplAstContent, - TmplAstDeferredBlock, - TmplAstDeferredBlockTriggers, - TmplAstDirective, - TmplAstElement, - TmplAstForLoopBlock, - TmplAstHostElement, - TmplAstHoverDeferredTrigger, - TmplAstIcu, - TmplAstIfBlock, - TmplAstIfBlockBranch, - TmplAstInteractionDeferredTrigger, - TmplAstLetDeclaration, - TmplAstNode, - TmplAstReference, - TmplAstSwitchBlock, - TmplAstTemplate, - TmplAstText, - TmplAstVariable, - TmplAstViewportDeferredTrigger, -} from '@angular/compiler'; -import ts from 'typescript'; + BoundAttribute, + BoundEvent, + BoundText, + Component, + Content, + DeferredBlock, + DeferredBlockTriggers, + Directive, + Element, + ForLoopBlock, + HostElement, + HoverDeferredTrigger, + Icu, + IfBlock, + IfBlockBranch, + InteractionDeferredTrigger, + LetDeclaration, + Node, + Reference, + SwitchBlock, + Template, + Text, + Variable, + ViewportDeferredTrigger, +} from '../../render3/r3_ast'; import {TcbOp} from './base'; -import {TypeCheckableDirectiveMeta} from '../../api'; +import {TcbExpr} from './codegen'; +import {TcbDirectiveMetadata} from '../api'; import {Context} from './context'; import {TcbTemplateBodyOp, TcbTemplateContextOp} from './template'; import {TcbElementOp} from './element'; -import {addParseSpanInfo} from '../diagnostics'; -import {tcbExpression, TcbExpressionOp} from './expression'; +import {tcbExpression, TcbConditionOp, TcbExpressionOp} from './expression'; import {TcbBlockImplicitVariableOp, TcbBlockVariableOp, TcbTemplateVariableOp} from './variables'; import {TcbComponentContextCompletionOp} from './completions'; import {LocalSymbol, TcbInvalidReferenceOp, TcbReferenceOp} from './references'; -import {TcbIfOp} from './if_block'; +import {TcbIfBlockOp} from './if_block'; import {TcbSwitchOp} from './switch_block'; import {TcbForOfOp} from './for_block'; import {TcbLetDeclarationOp} from './let'; @@ -59,13 +58,10 @@ import { TcbNativeFieldOp, TcbNativeRadioButtonFieldOp, } from './signal_forms'; -import {Reference} from '../../../imports'; -import {ClassDeclaration} from '../../../reflection'; import { TcbGenericDirectiveTypeWithAnyParamsOp, TcbNonGenericDirectiveTypeOp, } from './directive_type'; -import {requiresInlineTypeCtor} from '../type_constructor'; import {TcbDirectiveCtorOp} from './directive_constructor'; import {TcbControlFlowContentProjectionOp} from './content_projection'; import {TcbComponentNodeOp} from './selectorless'; @@ -89,7 +85,7 @@ export class Scope { /** * A queue of operations which need to be performed to generate the TCB code for this scope. * - * This array can contain either a `TcbOp` which has yet to be executed, or a `ts.Expression|null` + * This array can contain either a `TcbOp` which has yet to be executed, or a `TcbExpr|null` * representing the memoized result of executing the operation. As operations are executed, their * results are written into the `opQueue`, overwriting the original operation. * @@ -99,103 +95,98 @@ export class Scope { * that fits instead. This has the same semantics as TypeScript itself when types are referenced * circularly. */ - private opQueue: (TcbOp | ts.Expression | null)[] = []; + private opQueue: (TcbOp | TcbExpr | null)[] = []; /** - * A map of `TmplAstElement`s to the index of their `TcbElementOp` in the `opQueue` + * A map of `Element`s to the index of their `TcbElementOp` in the `opQueue` */ - private elementOpMap = new Map(); + private elementOpMap = new Map(); /** - * A map of `TmplAstHostElement`s to the index of their `TcbHostElementOp` in the `opQueue` + * A map of `HostElement`s to the index of their `TcbHostElementOp` in the `opQueue` */ - private hostElementOpMap = new Map(); + private hostElementOpMap = new Map(); /** - * A map of `TmplAstComponent`s to the index of their `TcbComponentNodeOp` in the `opQueue` + * A map of `Component`s to the index of their `TcbComponentNodeOp` in the `opQueue` */ - private componentNodeOpMap = new Map(); + private componentNodeOpMap = new Map(); /** * A map of maps which tracks the index of `TcbDirectiveCtorOp`s in the `opQueue` for each - * directive on a `TmplAstElement` or `TmplAstTemplate` node. + * directive on a `Element` or `Template` node. */ - private directiveOpMap = new Map>(); + private directiveOpMap = new Map>(); /** - * A map of `TmplAstReference`s to the index of their `TcbReferenceOp` in the `opQueue` + * A map of `Reference`s to the index of their `TcbReferenceOp` in the `opQueue` */ - private referenceOpMap = new Map(); + private referenceOpMap = new Map(); /** - * Map of immediately nested s (within this `Scope`) represented by `TmplAstTemplate` + * Map of immediately nested s (within this `Scope`) represented by `Template` * nodes to the index of their `TcbTemplateContextOp`s in the `opQueue`. */ - private templateCtxOpMap = new Map(); + private templateCtxOpMap = new Map(); /** * Map of variables declared on the template that created this `Scope` (represented by - * `TmplAstVariable` nodes) to the index of their `TcbVariableOp`s in the `opQueue`, or to + * `Variable` nodes) to the index of their `TcbVariableOp`s in the `opQueue`, or to * pre-resolved variable identifiers. */ - private varMap = new Map(); + private varMap = new Map(); /** - * A map of the names of `TmplAstLetDeclaration`s to the index of their op in the `opQueue`. + * A map of the names of `LetDeclaration`s to the index of their op in the `opQueue`. * * Assumes that there won't be duplicated `@let` declarations within the same scope. */ - private letDeclOpMap = new Map(); + private letDeclOpMap = new Map(); /** * Statements for this template. * * Executing the `TcbOp`s in the `opQueue` populates this array. */ - private statements: ts.Statement[] = []; + private statements: TcbExpr[] = []; /** * Gets names of the for loop context variables and their types. */ private static getForLoopContextVariableTypes() { - return new Map([ - ['$first', ts.SyntaxKind.BooleanKeyword], - ['$last', ts.SyntaxKind.BooleanKeyword], - ['$even', ts.SyntaxKind.BooleanKeyword], - ['$odd', ts.SyntaxKind.BooleanKeyword], - ['$index', ts.SyntaxKind.NumberKeyword], - ['$count', ts.SyntaxKind.NumberKeyword], + return new Map([ + ['$first', 'boolean'], + ['$last', 'boolean'], + ['$even', 'boolean'], + ['$odd', 'boolean'], + ['$index', 'number'], + ['$count', 'number'], ]); } private constructor( private tcb: Context, private parent: Scope | null = null, - private guard: ts.Expression | null = null, + private guard: TcbExpr | null = null, ) {} /** - * Constructs a `Scope` given either a `TmplAstTemplate` or a list of `TmplAstNode`s. + * Constructs a `Scope` given either a `Template` or a list of `Node`s. * * @param tcb the overall context of TCB generation. * @param parentScope the `Scope` of the parent template (if any) or `null` if this is the root * `Scope`. * @param scopedNode Node that provides the scope around the child nodes (e.g. a - * `TmplAstTemplate` node exposing variables to its children). + * `Template` node exposing variables to its children). * @param children Child nodes that should be appended to the TCB. * @param guard an expression that is applied to this scope for type narrowing purposes. */ static forNodes( tcb: Context, parentScope: Scope | null, - scopedNode: - | TmplAstTemplate - | TmplAstIfBlockBranch - | TmplAstForLoopBlock - | TmplAstHostElement - | null, - children: TmplAstNode[] | null, - guard: ts.Expression | null, + scopedNode: Template | IfBlockBranch | ForLoopBlock | HostElement | null, + children: Node[] | null, + guard: TcbExpr | null, ): Scope { const scope = new Scope(tcb, parentScope, guard); @@ -204,14 +195,14 @@ export class Scope { scope.opQueue.push(new TcbComponentContextCompletionOp(scope)); } - // If given an actual `TmplAstTemplate` instance, then process any additional information it + // If given an actual `Template` instance, then process any additional information it // has. - if (scopedNode instanceof TmplAstTemplate) { + if (scopedNode instanceof Template) { // The template's variable declarations need to be added as `TcbVariableOp`s. - const varMap = new Map(); + const varMap = new Map(); for (const v of scopedNode.variables) { - // Validate that variables on the `TmplAstTemplate` are only declared once. + // Validate that variables on the `Template` are only declared once. if (!varMap.has(v.name)) { varMap.set(v.name, v); } else { @@ -220,7 +211,7 @@ export class Scope { } Scope.registerVariable(scope, v, new TcbTemplateVariableOp(tcb, scope, scopedNode, v)); } - } else if (scopedNode instanceof TmplAstIfBlockBranch) { + } else if (scopedNode instanceof IfBlockBranch) { const {expression, expressionAlias} = scopedNode; if (expression !== null && expressionAlias !== null) { Scope.registerVariable( @@ -234,11 +225,11 @@ export class Scope { ), ); } - } else if (scopedNode instanceof TmplAstForLoopBlock) { + } else if (scopedNode instanceof ForLoopBlock) { // Register the variable for the loop so it can be resolved by // children. It'll be declared once the loop is created. - const loopInitializer = tcb.allocateId(); - addParseSpanInfo(loopInitializer, scopedNode.item.sourceSpan); + const loopInitializer = new TcbExpr(tcb.allocateId()); + loopInitializer.addParseSpanInfo(scopedNode.item.sourceSpan); scope.varMap.set(scopedNode.item, loopInitializer); const forLoopContextVariableTypes = Scope.getForLoopContextVariableTypes(); @@ -248,16 +239,14 @@ export class Scope { throw new Error(`Unrecognized for loop context variable ${variable.name}`); } - const type = ts.factory.createKeywordTypeNode( - forLoopContextVariableTypes.get(variable.value)!, - ); + const type = new TcbExpr(forLoopContextVariableTypes.get(variable.value)!); Scope.registerVariable( scope, variable, new TcbBlockImplicitVariableOp(tcb, scope, type, variable), ); } - } else if (scopedNode instanceof TmplAstHostElement) { + } else if (scopedNode instanceof HostElement) { scope.appendNode(scopedNode); } if (children !== null) { @@ -277,7 +266,7 @@ export class Scope { } /** Registers a local variable with a scope. */ - private static registerVariable(scope: Scope, variable: TmplAstVariable, op: TcbOp): void { + private static registerVariable(scope: Scope, variable: Variable, op: TcbOp): void { const opIndex = scope.opQueue.push(op) - 1; scope.varMap.set(variable, opIndex); } @@ -287,48 +276,25 @@ export class Scope { * including any parent scope(s). This method always returns a mutable clone of the * `ts.Expression` with the comments cleared. * - * @param node a `TmplAstNode` of the operation in question. The lookup performed will depend on + * @param node a `Node` of the operation in question. The lookup performed will depend on * the type of this node: * * Assuming `directive` is not present, then `resolve` will return: * - * * `TmplAstElement` - retrieve the expression for the element DOM node - * * `TmplAstTemplate` - retrieve the template context variable - * * `TmplAstVariable` - retrieve a template let- variable - * * `TmplAstLetDeclaration` - retrieve a template `@let` declaration - * * `TmplAstReference` - retrieve variable created for the local ref + * * `Element` - retrieve the expression for the element DOM node + * * `Template` - retrieve the template context variable + * * `Variable` - retrieve a template let- variable + * * `LetDeclaration` - retrieve a template `@let` declaration + * * `Reference` - retrieve variable created for the local ref * - * @param directive if present, a directive type on a `TmplAstElement` or `TmplAstTemplate` to + * @param directive if present, a directive type on a `Element` or `Template` to * look up instead of the default for an element or template node. */ - resolve( - node: LocalSymbol, - directive?: TypeCheckableDirectiveMeta, - ): ts.Identifier | ts.NonNullExpression { + resolve(node: LocalSymbol, directive?: TcbDirectiveMetadata): TcbExpr { // Attempt to resolve the operation locally. const res = this.resolveLocal(node, directive); if (res !== null) { - // We want to get a clone of the resolved expression and clear the trailing comments - // so they don't continue to appear in every place the expression is used. - // As an example, this would otherwise produce: - // var _t1 /**T:DIR*/ /*1,2*/ = _ctor1(); - // _t1 /**T:DIR*/ /*1,2*/.input = 'value'; - // - // In addition, returning a clone prevents the consumer of `Scope#resolve` from - // attaching comments at the declaration site. - let clone: ts.Identifier | ts.NonNullExpression; - - if (ts.isIdentifier(res)) { - clone = ts.factory.createIdentifier(res.text); - } else if (ts.isNonNullExpression(res)) { - clone = ts.factory.createNonNullExpression(res.expression); - } else { - throw new Error(`Could not resolve ${node} to an Identifier or a NonNullExpression`); - } - - ts.setOriginalNode(clone, res); - (clone as any).parent = clone.parent; - return ts.setSyntheticTrailingComments(clone, []); + return res; } else if (this.parent !== null) { // Check with the parent. return this.parent.resolve(node, directive); @@ -340,14 +306,14 @@ export class Scope { /** * Add a statement to this scope. */ - addStatement(stmt: ts.Statement): void { + addStatement(stmt: TcbExpr): void { this.statements.push(stmt); } /** * Get the statements. */ - render(): ts.Statement[] { + render(): TcbExpr[] { for (let i = 0; i < this.opQueue.length; i++) { // Optional statements cannot be skipped when we are generating the TCB for use // by the TemplateTypeChecker. @@ -361,8 +327,8 @@ export class Scope { * Returns an expression of all template guards that apply to this scope, including those of * parent scopes. If no guards have been applied, null is returned. */ - guards(): ts.Expression | null { - let parentGuards: ts.Expression | null = null; + guards(): TcbExpr | null { + let parentGuards: TcbExpr | null = null; if (this.parent !== null) { // Start with the guards from the parent scope, if present. parentGuards = this.parent.guards(); @@ -374,70 +340,61 @@ export class Scope { } else if (parentGuards === null) { // There's no guards from the parent scope, so this scope's guard represents all available // guards. - return this.guard; + return typeof this.guard === 'string' ? new TcbExpr(this.guard) : this.guard; } else { // Both the parent scope and this scope provide a guard, so create a combination of the two. // It is important that the parent guard is used as left operand, given that it may provide // narrowing that is required for this scope's guard to be valid. - return ts.factory.createBinaryExpression( - parentGuards, - ts.SyntaxKind.AmpersandAmpersandToken, - this.guard, - ); + const guard = typeof this.guard === 'string' ? this.guard : this.guard.print(); + return new TcbExpr(`(${parentGuards.print()}) && (${guard})`); } } /** Returns whether a template symbol is defined locally within the current scope. */ - isLocal(node: TmplAstVariable | TmplAstLetDeclaration | TmplAstReference): boolean { - if (node instanceof TmplAstVariable) { + isLocal(node: Variable | LetDeclaration | Reference): boolean { + if (node instanceof Variable) { return this.varMap.has(node); } - if (node instanceof TmplAstLetDeclaration) { + if (node instanceof LetDeclaration) { return this.letDeclOpMap.has(node.name); } return this.referenceOpMap.has(node); } /** - * Constructs a `Scope` given either a `TmplAstTemplate` or a list of `TmplAstNode`s. + * Constructs a `Scope` given either a `Template` or a list of `Node`s. * This is identical to `Scope.forNodes` which we can't reference in some ops due to * circular dependencies. *. * @param parentScope the `Scope` of the parent template. * @param scopedNode Node that provides the scope around the child nodes (e.g. a - * `TmplAstTemplate` node exposing variables to its children). + * `Template` node exposing variables to its children). * @param children Child nodes that should be appended to the TCB. * @param guard an expression that is applied to this scope for type narrowing purposes. */ createChildScope( parentScope: Scope, - scopedNode: - | TmplAstTemplate - | TmplAstIfBlockBranch - | TmplAstForLoopBlock - | TmplAstHostElement - | null, - children: TmplAstNode[] | null, - guard: ts.Expression | null, + scopedNode: Template | IfBlockBranch | ForLoopBlock | HostElement | null, + children: Node[] | null, + guard: TcbExpr | null, ): Scope { return Scope.forNodes(this.tcb, parentScope, scopedNode, children, guard); } - private resolveLocal( - ref: LocalSymbol, - directive?: TypeCheckableDirectiveMeta, - ): ts.Expression | null { - if (ref instanceof TmplAstReference && this.referenceOpMap.has(ref)) { + private resolveLocal(ref: LocalSymbol, directive?: TcbDirectiveMetadata): TcbExpr | null { + if (ref instanceof Reference && this.referenceOpMap.has(ref)) { return this.resolveOp(this.referenceOpMap.get(ref)!); - } else if (ref instanceof TmplAstLetDeclaration && this.letDeclOpMap.has(ref.name)) { + } else if (ref instanceof LetDeclaration && this.letDeclOpMap.has(ref.name)) { return this.resolveOp(this.letDeclOpMap.get(ref.name)!.opIndex); - } else if (ref instanceof TmplAstVariable && this.varMap.has(ref)) { + } else if (ref instanceof Variable && this.varMap.has(ref)) { // Resolving a context variable for this template. - // Execute the `TcbVariableOp` associated with the `TmplAstVariable`. + // Execute the `TcbVariableOp` associated with the `Variable`. const opIndexOrNode = this.varMap.get(ref)!; - return typeof opIndexOrNode === 'number' ? this.resolveOp(opIndexOrNode) : opIndexOrNode; + return typeof opIndexOrNode === 'number' + ? this.resolveOp(opIndexOrNode) + : new TcbExpr(opIndexOrNode.print(true /* ignoreComments */)); } else if ( - ref instanceof TmplAstTemplate && + ref instanceof Template && directive === undefined && this.templateCtxOpMap.has(ref) ) { @@ -445,23 +402,23 @@ export class Scope { // Execute the `TcbTemplateContextOp` for the template. return this.resolveOp(this.templateCtxOpMap.get(ref)!); } else if ( - (ref instanceof TmplAstElement || - ref instanceof TmplAstTemplate || - ref instanceof TmplAstComponent || - ref instanceof TmplAstDirective || - ref instanceof TmplAstHostElement) && + (ref instanceof Element || + ref instanceof Template || + ref instanceof Component || + ref instanceof Directive || + ref instanceof HostElement) && directive !== undefined && this.directiveOpMap.has(ref) ) { // Resolving a directive on an element or sub-template. const dirMap = this.directiveOpMap.get(ref)!; return dirMap.has(directive) ? this.resolveOp(dirMap.get(directive)!) : null; - } else if (ref instanceof TmplAstElement && this.elementOpMap.has(ref)) { + } else if (ref instanceof Element && this.elementOpMap.has(ref)) { // Resolving the DOM node of an element in this template. return this.resolveOp(this.elementOpMap.get(ref)!); - } else if (ref instanceof TmplAstComponent && this.componentNodeOpMap.has(ref)) { + } else if (ref instanceof Component && this.componentNodeOpMap.has(ref)) { return this.resolveOp(this.componentNodeOpMap.get(ref)!); - } else if (ref instanceof TmplAstHostElement && this.hostElementOpMap.has(ref)) { + } else if (ref instanceof HostElement && this.hostElementOpMap.has(ref)) { return this.resolveOp(this.hostElementOpMap.get(ref)!); } else { return null; @@ -469,9 +426,9 @@ export class Scope { } /** - * Like `executeOp`, but assert that the operation actually returned `ts.Expression`. + * Like `executeOp`, but assert that the operation actually returned `TcbExpr`. */ - private resolveOp(opIndex: number): ts.Expression { + private resolveOp(opIndex: number): TcbExpr { const res = this.executeOp(opIndex, /* skipOptional */ false); if (res === null) { throw new Error(`Error resolving operation, got null`); @@ -486,10 +443,10 @@ export class Scope { * and also protects against a circular dependency from the operation to itself by temporarily * setting the operation's result to a special expression. */ - private executeOp(opIndex: number, skipOptional: boolean): ts.Expression | null { + private executeOp(opIndex: number, skipOptional: boolean): TcbExpr | null { const op = this.opQueue[opIndex]; if (!(op instanceof TcbOp)) { - return op; + return op === null ? null : new TcbExpr(op.print(true /* ignoreComments */)); } if (skipOptional && op.optional) { @@ -500,14 +457,17 @@ export class Scope { // operation results in a circular dependency, this will prevent an infinite loop and allow for // the resolution of such cycles. this.opQueue[opIndex] = op.circularFallback(); - const res = op.execute(); + let res = op.execute(); + if (res !== null) { + res = new TcbExpr(res.print(true /* ignoreComments */)); + } // Once the operation has finished executing, it's safe to cache the real result. this.opQueue[opIndex] = res; return res; } - private appendNode(node: TmplAstNode): void { - if (node instanceof TmplAstElement) { + private appendNode(node: Node): void { + if (node instanceof Element) { const opIndex = this.opQueue.push(new TcbElementOp(this.tcb, this, node)) - 1; this.elementOpMap.set(node, opIndex); if (this.tcb.env.config.controlFlowPreventingContentProjection !== 'suppress') { @@ -518,7 +478,7 @@ export class Scope { this.appendSelectorlessDirectives(node); this.appendChildren(node); this.checkAndAppendReferencesOfNode(node); - } else if (node instanceof TmplAstTemplate) { + } else if (node instanceof Template) { // Template children are rendered in a child scope. this.appendDirectivesAndInputsOfElementLikeNode(node); this.appendOutputsOfElementLikeNode(node, node.inputs, node.outputs); @@ -531,44 +491,42 @@ export class Scope { this.appendDeepSchemaChecks(node.children); } this.checkAndAppendReferencesOfNode(node); - } else if (node instanceof TmplAstComponent) { + } else if (node instanceof Component) { this.appendComponentNode(node); - } else if (node instanceof TmplAstDeferredBlock) { + } else if (node instanceof DeferredBlock) { this.appendDeferredBlock(node); - } else if (node instanceof TmplAstIfBlock) { - this.opQueue.push(new TcbIfOp(this.tcb, this, node)); - } else if (node instanceof TmplAstSwitchBlock) { + } else if (node instanceof IfBlock) { + this.opQueue.push(new TcbIfBlockOp(this.tcb, this, node)); + } else if (node instanceof SwitchBlock) { this.opQueue.push(new TcbSwitchOp(this.tcb, this, node)); - } else if (node instanceof TmplAstForLoopBlock) { + } else if (node instanceof ForLoopBlock) { this.opQueue.push(new TcbForOfOp(this.tcb, this, node)); node.empty && this.tcb.env.config.checkControlFlowBodies && this.appendChildren(node.empty); - } else if (node instanceof TmplAstBoundText) { + } else if (node instanceof BoundText) { this.opQueue.push(new TcbExpressionOp(this.tcb, this, node.value)); - } else if (node instanceof TmplAstIcu) { + } else if (node instanceof Icu) { this.appendIcuExpressions(node); - } else if (node instanceof TmplAstContent) { + } else if (node instanceof Content) { this.appendChildren(node); - } else if (node instanceof TmplAstLetDeclaration) { + } else if (node instanceof LetDeclaration) { const opIndex = this.opQueue.push(new TcbLetDeclarationOp(this.tcb, this, node)) - 1; if (this.isLocal(node)) { this.tcb.oobRecorder.conflictingDeclaration(this.tcb.id, node); } else { this.letDeclOpMap.set(node.name, {opIndex, node}); } - } else if (node instanceof TmplAstHostElement) { + } else if (node instanceof HostElement) { this.appendHostElement(node); } } - private appendChildren(node: TmplAstNode & {children: TmplAstNode[]}) { + private appendChildren(node: Node & {children: Node[]}) { for (const child of node.children) { this.appendNode(child); } } - private checkAndAppendReferencesOfNode( - node: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective, - ): void { + private checkAndAppendReferencesOfNode(node: Element | Template | Component | Directive): void { for (const ref of node.references) { const target = this.tcb.boundTarget.getReferenceTarget(ref); @@ -579,7 +537,7 @@ export class Scope { // Any usages of the invalid reference will be resolved to a variable of type any. ctxIndex = this.opQueue.push(new TcbInvalidReferenceOp(this.tcb, this)) - 1; - } else if (target instanceof TmplAstTemplate || target instanceof TmplAstElement) { + } else if (target instanceof Template || target instanceof Element) { ctxIndex = this.opQueue.push(new TcbReferenceOp(this.tcb, this, ref, node, target)) - 1; } else { ctxIndex = @@ -589,7 +547,7 @@ export class Scope { } } - private appendDirectivesAndInputsOfElementLikeNode(node: TmplAstElement | TmplAstTemplate): void { + private appendDirectivesAndInputsOfElementLikeNode(node: Element | Template): void { // Collect all the inputs on the element. const claimedInputs = new Set(); @@ -600,7 +558,7 @@ export class Scope { if (directives === null || directives.length === 0) { // If there are no directives, then all inputs are unclaimed inputs, so queue an operation // to add them if needed. - if (node instanceof TmplAstElement) { + if (node instanceof Element) { this.opQueue.push( new TcbUnclaimedInputsOp(this.tcb, this, node.inputs, node, claimedInputs), new TcbDomSchemaCheckerOp(this.tcb, node, /* checkElement */ true, claimedInputs), @@ -609,7 +567,9 @@ export class Scope { return; } - if (node instanceof TmplAstElement) { + this.reportConflictingBindings(node); + + if (node instanceof Element) { const isDeferred = this.tcb.boundTarget.isDeferred(node); if (!isDeferred && directives.some((dirMeta) => dirMeta.isExplicitlyDeferred)) { // This node has directives/components that were defer-loaded (included into @@ -619,15 +579,27 @@ export class Scope { } } - const dirMap = new Map(); - for (const dir of directives) { - this.appendDirectiveInputs(dir, node, dirMap, directives); + if (node instanceof Element) { + const matchedComponents = directives.filter((dir) => dir.isComponent); + if (matchedComponents.length > 1) { + this.tcb.oobRecorder.multipleMatchingComponents( + this.tcb.id, + node, + matchedComponents.map((dir) => dir.name), + ); + } + } + + const dirMap = new Map(); + for (let i = 0; i < directives.length; i++) { + const dir = directives[i]; + this.appendDirectiveInputs(dir, node, dirMap, directives, i); } this.directiveOpMap.set(node, dirMap); // After expanding the directives, we might need to queue an operation to check any unclaimed // inputs. - if (node instanceof TmplAstElement) { + if (node instanceof Element) { // Go through the directives and remove any inputs that it claims from `elementInputs`. for (const dir of directives) { for (const propertyName of dir.inputs.propertyNames) { @@ -646,9 +618,9 @@ export class Scope { } private appendOutputsOfElementLikeNode( - node: TmplAstElement | TmplAstTemplate | TmplAstHostElement, - bindings: TmplAstBoundAttribute[] | null, - events: TmplAstBoundEvent[], + node: Element | Template | HostElement, + bindings: BoundAttribute[] | null, + events: BoundEvent[], ): void { // Collect all the outputs on the element. const claimedOutputs = new Set(); @@ -661,7 +633,7 @@ export class Scope { if (directives === null || directives.length === 0) { // If there are no directives, then all outputs are unclaimed outputs, so queue an operation // to add them if needed. - if (node instanceof TmplAstElement) { + if (node instanceof Element) { this.opQueue.push( new TcbUnclaimedOutputsOp(this.tcb, this, node, events, bindings, claimedOutputs), ); @@ -676,7 +648,7 @@ export class Scope { // After expanding the directives, we might need to queue an operation to check any unclaimed // outputs. - if (node instanceof TmplAstElement || node instanceof TmplAstHostElement) { + if (node instanceof Element || node instanceof HostElement) { // Go through the directives and register any outputs that it claims in `claimedOutputs`. for (const dir of directives) { for (const outputProperty of dir.outputs.propertyNames) { @@ -690,15 +662,16 @@ export class Scope { } } - private appendInputsOfSelectorlessNode(node: TmplAstComponent | TmplAstDirective): void { + private appendInputsOfSelectorlessNode(node: Component | Directive): void { // Only resolve the directives that were brought in by this specific directive. const directives = this.tcb.boundTarget.getDirectivesOfNode(node); const claimedInputs = new Set(); if (directives !== null && directives.length > 0) { - const dirMap = new Map(); - for (const dir of directives) { - this.appendDirectiveInputs(dir, node, dirMap, directives); + const dirMap = new Map(); + for (let i = 0; i < directives.length; i++) { + const dir = directives[i]; + this.appendDirectiveInputs(dir, node, dirMap, directives, i); for (const propertyName of dir.inputs.propertyNames) { claimedInputs.add(propertyName); @@ -707,8 +680,10 @@ export class Scope { this.directiveOpMap.set(node, dirMap); } + this.reportConflictingBindings(node); + // In selectorless all directive inputs have to be claimed. - if (node instanceof TmplAstDirective) { + if (node instanceof Directive) { for (const input of node.inputs) { if (!claimedInputs.has(input.name)) { this.tcb.oobRecorder.unclaimedDirectiveBinding(this.tcb.id, node, input); @@ -729,7 +704,7 @@ export class Scope { } } - private appendOutputsOfSelectorlessNode(node: TmplAstComponent | TmplAstDirective): void { + private appendOutputsOfSelectorlessNode(node: Component | Directive): void { // Only resolve the directives that were brought in by this specific directive. const directives = this.tcb.boundTarget.getDirectivesOfNode(node); const claimedOutputs = new Set(); @@ -747,7 +722,7 @@ export class Scope { } // In selectorless all directive outputs have to be claimed. - if (node instanceof TmplAstDirective) { + if (node instanceof Directive) { for (const output of node.outputs) { if (!claimedOutputs.has(output.name)) { this.tcb.oobRecorder.unclaimedDirectiveBinding(this.tcb.id, node, output); @@ -761,15 +736,16 @@ export class Scope { } private appendDirectiveInputs( - dir: TypeCheckableDirectiveMeta, - node: TmplAstElement | TmplAstTemplate | TmplAstComponent | TmplAstDirective, - dirMap: Map, - allDirectiveMatches: TypeCheckableDirectiveMeta[], + dir: TcbDirectiveMetadata, + node: Element | Template | Component | Directive, + dirMap: Map, + allDirectiveMatches: TcbDirectiveMetadata[], + directiveIndex?: number, ): void { const nodeIsFormControl = isFormControl(allDirectiveMatches); const customFormControlType = nodeIsFormControl ? getCustomFieldDirectiveType(dir) : null; - const directiveOp = this.getDirectiveOp(dir, node, customFormControlType); + const directiveOp = this.getDirectiveOp(dir, node, customFormControlType, directiveIndex); const dirIndex = this.opQueue.push(directiveOp) - 1; dirMap.set(dir, dirIndex); @@ -791,34 +767,28 @@ export class Scope { } private getDirectiveOp( - dir: TypeCheckableDirectiveMeta, + dir: TcbDirectiveMetadata, node: DirectiveOwner, customFieldType: CustomFormControlType | null, + directiveIndex?: number, ): TcbOp { - const dirRef = dir.ref as Reference>; - if (!dir.isGeneric) { // The most common case is that when a directive is not generic, we use the normal // `TcbNonDirectiveTypeOp`. - return new TcbNonGenericDirectiveTypeOp(this.tcb, this, node, dir); - } else if ( - !requiresInlineTypeCtor(dirRef.node, this.tcb.env.reflector, this.tcb.env) || - this.tcb.env.config.useInlineTypeConstructors - ) { + return new TcbNonGenericDirectiveTypeOp(this.tcb, this, node, dir, directiveIndex); + } else if (!dir.requiresInlineTypeCtor || this.tcb.env.config.useInlineTypeConstructors) { // For generic directives, we use a type constructor to infer types. If a directive requires // an inline type constructor, then inlining must be available to use the // `TcbDirectiveCtorOp`. If not we, we fallback to using `any` – see below. - return new TcbDirectiveCtorOp(this.tcb, this, node, dir, customFieldType); + return new TcbDirectiveCtorOp(this.tcb, this, node, dir, customFieldType, directiveIndex); } // If inlining is not available, then we give up on inferring the generic params, and use // `any` type for the directive's generic parameters. - return new TcbGenericDirectiveTypeWithAnyParamsOp(this.tcb, this, node, dir); + return new TcbGenericDirectiveTypeWithAnyParamsOp(this.tcb, this, node, dir, directiveIndex); } - private appendSelectorlessDirectives( - node: TmplAstElement | TmplAstTemplate | TmplAstComponent, - ): void { + private appendSelectorlessDirectives(node: Element | Template | Component): void { for (const directive of node.directives) { // Check that the directive exists. if (!this.tcb.boundTarget.referencedDirectiveExists(directive.name)) { @@ -843,13 +813,13 @@ export class Scope { } } - private appendDeepSchemaChecks(nodes: TmplAstNode[]): void { + private appendDeepSchemaChecks(nodes: Node[]): void { for (const node of nodes) { - if (!(node instanceof TmplAstElement || node instanceof TmplAstTemplate)) { + if (!(node instanceof Element || node instanceof Template)) { continue; } - if (node instanceof TmplAstElement) { + if (node instanceof Element) { const claimedInputs = new Set(); let directives = this.tcb.boundTarget.getDirectivesOfNode(node); @@ -880,18 +850,18 @@ export class Scope { } } - private appendIcuExpressions(node: TmplAstIcu): void { + private appendIcuExpressions(node: Icu): void { for (const variable of Object.values(node.vars)) { this.opQueue.push(new TcbExpressionOp(this.tcb, this, variable.value)); } for (const placeholder of Object.values(node.placeholders)) { - if (placeholder instanceof TmplAstBoundText) { + if (placeholder instanceof BoundText) { this.opQueue.push(new TcbExpressionOp(this.tcb, this, placeholder.value)); } } } - private appendContentProjectionCheckOp(root: TmplAstElement | TmplAstComponent): void { + private appendContentProjectionCheckOp(root: Element | Component): void { const meta = this.tcb.boundTarget.getDirectivesOfNode(root)?.find((meta) => meta.isComponent) || null; @@ -908,7 +878,7 @@ export class Scope { } } - private appendComponentNode(node: TmplAstComponent): void { + private appendComponentNode(node: Component): void { // TODO(crisbeto): should we still append the children if the component is invalid? // Check that the referenced class exists. if (!this.tcb.boundTarget.referencedDirectiveExists(node.componentName)) { @@ -939,13 +909,13 @@ export class Scope { this.checkAndAppendReferencesOfNode(node); } - private appendDeferredBlock(block: TmplAstDeferredBlock): void { + private appendDeferredBlock(block: DeferredBlock): void { this.appendDeferredTriggers(block, block.triggers); this.appendDeferredTriggers(block, block.prefetchTriggers); // Only the `when` hydration trigger needs to be checked. if (block.hydrateTriggers.when) { - this.opQueue.push(new TcbExpressionOp(this.tcb, this, block.hydrateTriggers.when.value)); + this.opQueue.push(new TcbConditionOp(this.tcb, this, block.hydrateTriggers.when.value)); } this.appendChildren(block); @@ -963,12 +933,9 @@ export class Scope { } } - private appendDeferredTriggers( - block: TmplAstDeferredBlock, - triggers: TmplAstDeferredBlockTriggers, - ): void { + private appendDeferredTriggers(block: DeferredBlock, triggers: DeferredBlockTriggers): void { if (triggers.when !== undefined) { - this.opQueue.push(new TcbExpressionOp(this.tcb, this, triggers.when.value)); + this.opQueue.push(new TcbConditionOp(this.tcb, this, triggers.when.value)); } if (triggers.viewport !== undefined && triggers.viewport.options !== null) { @@ -988,12 +955,12 @@ export class Scope { } } - private appendHostElement(node: TmplAstHostElement): void { + private appendHostElement(node: HostElement): void { const opIndex = this.opQueue.push(new TcbHostElementOp(this.tcb, this, node)) - 1; const directives = this.tcb.boundTarget.getDirectivesOfNode(node); if (directives !== null && directives.length > 0) { - const directiveOpMap = new Map(); + const directiveOpMap = new Map(); for (const directive of directives) { const directiveOp = this.getDirectiveOp(directive, node, null); @@ -1012,11 +979,8 @@ export class Scope { } private validateReferenceBasedDeferredTrigger( - block: TmplAstDeferredBlock, - trigger: - | TmplAstHoverDeferredTrigger - | TmplAstInteractionDeferredTrigger - | TmplAstViewportDeferredTrigger, + block: DeferredBlock, + trigger: HoverDeferredTrigger | InteractionDeferredTrigger | ViewportDeferredTrigger, ): void { if (trigger.reference === null) { if (block.placeholder === null) { @@ -1024,13 +988,13 @@ export class Scope { return; } - let rootNode: TmplAstNode | null = null; + let rootNode: Node | null = null; for (const child of block.placeholder.children) { // Skip over empty text nodes if the host doesn't preserve whitespaces. if ( !this.tcb.hostPreserveWhitespaces && - child instanceof TmplAstText && + child instanceof Text && child.value.trim().length === 0 ) { continue; @@ -1047,7 +1011,7 @@ export class Scope { } } - if (rootNode === null || !(rootNode instanceof TmplAstElement)) { + if (rootNode === null || !(rootNode instanceof Element)) { this.tcb.oobRecorder.deferImplicitTriggerInvalidPlaceholder(this.tcb.id, trigger); } return; @@ -1059,7 +1023,7 @@ export class Scope { } /** Reports a diagnostic if there are any `@let` declarations that conflict with a node. */ - private static checkConflictingLet(scope: Scope, node: TmplAstVariable | TmplAstReference): void { + private static checkConflictingLet(scope: Scope, node: Variable | Reference): void { if (scope.letDeclOpMap.has(node.name)) { scope.tcb.oobRecorder.conflictingDeclaration( scope.tcb.id, @@ -1067,4 +1031,21 @@ export class Scope { ); } } + + private reportConflictingBindings(node: Element | Template | Component | Directive): void { + const conflictingBindings = this.tcb.boundTarget.getConflictingHostDirectiveBindings(node); + + if (conflictingBindings !== null) { + for (const binding of conflictingBindings) { + this.tcb.oobRecorder.conflictingHostDirectiveBinding( + this.tcb.id, + node, + binding.directive.name, + binding.kind, + binding.classPropertyName, + Array.from(binding.conflictingAliases), + ); + } + } + } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/selectorless.ts b/packages/compiler/src/typecheck/ops/selectorless.ts similarity index 60% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/selectorless.ts rename to packages/compiler/src/typecheck/ops/selectorless.ts index 3e152cd52881..fb7394fff3b1 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/selectorless.ts +++ b/packages/compiler/src/typecheck/ops/selectorless.ts @@ -6,23 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ -import {TmplAstComponent} from '@angular/compiler'; -import ts from 'typescript'; -import {addParseSpanInfo} from '../diagnostics'; -import {tsCreateElement, tsCreateVariable} from '../ts_util'; +import {Component} from '../../render3/r3_ast'; import {TcbOp} from './base'; +import {TcbExpr} from './codegen'; import {Context} from './context'; import type {Scope} from './scope'; // TODO(crisbeto): the logic for determining the fallback tag name of a Component node is // still being designed. For now fall back to `ng-component`, but this will have to be // revisited once the design is finalized. -export function getComponentTagName(node: TmplAstComponent): string { +export function getComponentTagName(node: Component): string { return node.tagName || 'ng-component'; } /** - * A `TcbOp` which creates an expression for a native DOM element from a `TmplAstComponent`. + * A `TcbOp` which creates an expression for a native DOM element from a `Component`. * * Executing this operation returns a reference to the element variable. */ @@ -32,16 +30,18 @@ export class TcbComponentNodeOp extends TcbOp { constructor( private tcb: Context, private scope: Scope, - private component: TmplAstComponent, + private component: Component, ) { super(); } - override execute(): ts.Identifier { + override execute(): TcbExpr { const id = this.tcb.allocateId(); - const initializer = tsCreateElement(getComponentTagName(this.component)); - addParseSpanInfo(initializer, this.component.startSourceSpan || this.component.sourceSpan); - this.scope.addStatement(tsCreateVariable(id, initializer)); - return id; + const initializer = new TcbExpr( + `document.createElement("${getComponentTagName(this.component)}")`, + ); + initializer.addParseSpanInfo(this.component.startSourceSpan || this.component.sourceSpan); + this.scope.addStatement(new TcbExpr(`var ${id} = ${initializer.print()}`)); + return new TcbExpr(id); } } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts b/packages/compiler/src/typecheck/ops/signal_forms.ts similarity index 64% rename from packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts rename to packages/compiler/src/typecheck/ops/signal_forms.ts index 0fabdb6ef77f..94f0db1bd738 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/ops/signal_forms.ts +++ b/packages/compiler/src/typecheck/ops/signal_forms.ts @@ -6,27 +6,21 @@ * found in the LICENSE file at https://angular.dev/license */ +import {AST, BindingType, Call, PropertyRead, SafeCall} from '../../expression_parser/ast'; +import {DirectiveOwner} from '../../render3/view/t2_api'; import { - AST, - BindingType, - Call, - DirectiveOwner, - PropertyRead, - SafeCall, - TmplAstBoundAttribute, - TmplAstComponent, - TmplAstDirective, - TmplAstElement, - TmplAstHostElement, - TmplAstNode, - TmplAstTemplate, -} from '@angular/compiler'; -import ts from 'typescript'; -import {TypeCheckableDirectiveMeta} from '../../api'; -import {markIgnoreDiagnostics} from '../comments'; -import {addParseSpanInfo} from '../diagnostics'; -import {tsDeclareVariable} from '../ts_util'; + BoundAttribute, + Component, + Directive, + Element, + HostElement, + Node, + Template, +} from '../../render3/r3_ast'; + +import {TcbDirectiveMetadata, TcbInputMapping} from '../api'; import {TcbOp} from './base'; +import {declareVariable, TcbExpr} from './codegen'; import {TcbBoundAttribute} from './bindings'; import type {Context} from './context'; import {tcbExpression} from './expression'; @@ -87,6 +81,12 @@ export class TcbNativeFieldOp extends TcbOp { 'minlength', ]); + /** + * Whether the host element has a dynamic `type` binding, meaning we cannot + * statically determine the input type. + */ + private readonly hasDynamicType: boolean; + override get optional() { return false; } @@ -94,14 +94,35 @@ export class TcbNativeFieldOp extends TcbOp { constructor( protected tcb: Context, protected scope: Scope, - protected node: TmplAstElement, + protected node: Element, private inputType: string | null, ) { super(); + + this.hasDynamicType = + this.inputType === null && + this.node.inputs.some( + (input) => + (input.type === BindingType.Property || input.type === BindingType.Attribute) && + input.name === 'type', + ); + + const isPossiblyDateOrTime = + this.hasDynamicType || + this.inputType === 'date' || + this.inputType === 'time' || + this.inputType === 'month' || + this.inputType === 'week' || + this.inputType === 'datetime-local'; + + if (isPossiblyDateOrTime) { + this.unsupportedBindingFields.delete('min'); + this.unsupportedBindingFields.delete('max'); + } } override execute(): null { - const inputs = this.node instanceof TmplAstHostElement ? this.node.bindings : this.node.inputs; + const inputs = this.node instanceof HostElement ? this.node.bindings : this.node.inputs; const fieldBinding = inputs.find((input) => input.type === BindingType.Property && input.name === 'formField') ?? null; @@ -114,23 +135,43 @@ export class TcbNativeFieldOp extends TcbOp { checkUnsupportedFieldBindings(this.node, this.unsupportedBindingFields, this.tcb); - const expectedType = this.getExpectedTypeFromDomNode(this.node); - const value = extractFieldValue(fieldBinding.value, this.tcb, this.scope); + const rawExpectedType = this.getExpectedTypeFromDomNode(this.node); + + if (rawExpectedType === null) { + // For text-like elements, use an invariant check on the value signal. + // WritableSignal is invariant in T, so assigning it to a union of structural types + // gives us exact type matching: only Field or Field are accepted. + const signal = extractFieldValueSignal(fieldBinding.value, this.tcb, this.scope); + const id = new TcbExpr(this.tcb.allocateId()); + const unionType = new TcbExpr( + '{ (): string; set: (v: string) => void; } | { (): number | null; set: (v: number | null) => void; }', + ); + const assignment = new TcbExpr(`${id.print()} = ${signal.print()}`); + assignment.addParseSpanInfo(fieldBinding.valueSpan ?? fieldBinding.sourceSpan); + + this.scope.addStatement(declareVariable(id, unionType)); + this.scope.addStatement(assignment); + } else { + const expectedType = new TcbExpr(rawExpectedType); + const value = extractFieldValue(fieldBinding.value, this.tcb, this.scope); + + // Create a variable with the expected type and check that the field value is assignable, e.g. + // var t1 = null! as string | number; t1 = f().value()`. + const id = new TcbExpr(this.tcb.allocateId()); + const assignment = new TcbExpr(`${id.print()} = ${value.print()}`); + assignment.addParseSpanInfo(fieldBinding.valueSpan ?? fieldBinding.sourceSpan); + + this.scope.addStatement(declareVariable(id, expectedType)); + this.scope.addStatement(assignment); + } - // Create a variable with the expected type and check that the field value is assignable, e.g. - // var t1 = null! as string | number; t1 = f().value()`. - const id = this.tcb.allocateId(); - const assignment = ts.factory.createBinaryExpression(id, ts.SyntaxKind.EqualsToken, value); - addParseSpanInfo(assignment, fieldBinding.valueSpan ?? fieldBinding.sourceSpan); - this.scope.addStatement(tsDeclareVariable(id, expectedType)); - this.scope.addStatement(ts.factory.createExpressionStatement(assignment)); return null; } - private getExpectedTypeFromDomNode(node: TmplAstElement): ts.TypeNode { + private getExpectedTypeFromDomNode(node: Element): string | null { if (node.name === 'textarea' || node.name === 'select') { // `