mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 147ea13d59 | |||
| 79273c18c0 | |||
| d710ebee6d | |||
| b0b341f440 | |||
| a0361b3ae2 | |||
| 79e6dfdef3 | |||
| 16a23a5fe8 | |||
| de9277b174 | |||
| 492b10686b | |||
| 99b10e45d2 | |||
| 01e5babdf6 | |||
| 8f4cc8488c | |||
| 33de839fe8 | |||
| 30742c59ef | |||
| 6e8f1421a6 | |||
| d5caac6bba | |||
| 82d556a8fb | |||
| 5cbb66fdf2 | |||
| 6a9d4d5458 | |||
| 22d27ed792 | |||
| b395a252cc | |||
| bb34ccb14d | |||
| dc9f59b9be | |||
| 5f935eee58 | |||
| 79023440fb | |||
| 007d790018 | |||
| a1dea3e736 | |||
| e72e136096 | |||
| 74e99fa389 | |||
| 08d0ac73b1 | |||
| 8655913dcc | |||
| 77226dd585 | |||
| 49e18e5582 | |||
| dfd8c5220e | |||
| 5b0e705eb4 | |||
| 89a51c8415 | |||
| 1efd5fa33f | |||
| 615a77b685 | |||
| 450b4c1e90 | |||
| 0055f3cc79 | |||
| e4bfa5c9e7 | |||
| 5bc072ea3e | |||
| 018edd5566 | |||
| a6efb7752d | |||
| a63f6d0978 | |||
| 5b0c2b3272 | |||
| 24fa4a2460 | |||
| 3033568bb4 | |||
| 418326839a | |||
| 9b97a1dfa3 | |||
| 491a1569ad | |||
| baee12d944 | |||
| 696adbca5f | |||
| a0904c8a5a | |||
| 5239e471a1 | |||
| 8ff277612e | |||
| 0dead0fbac | |||
| 2b830917d8 | |||
| e0a8a4d4f9 | |||
| 32ff1a0d12 | |||
| 8aab227960 | |||
| 1488c2e30b | |||
| 37dcc26727 | |||
| f5bad8e417 | |||
| ecc5c27b17 | |||
| a6a2621bf9 | |||
| 8fd585cc0b | |||
| ff531752b0 | |||
| 5e9e09aee0 | |||
| fbb37d8388 | |||
| c3adc5a707 | |||
| 8eb0cd51de | |||
| 0b26947802 | |||
| 35fe58f7da | |||
| 8aa77557f1 | |||
| 41cd4a6af8 | |||
| ae1dc16bb0 | |||
| 63b1cdcf70 | |||
| c3f3ebec8f | |||
| 61370dc8c8 | |||
| 7c74674eb0 | |||
| 7738eb94cb | |||
| c1e9627f6d | |||
| d699eef2ca | |||
| 0ff2d5af2d | |||
| 4161161320 | |||
| e3fba182f9 | |||
| defc9246a5 | |||
| af1d4acb9d | |||
| 0e8b9d8cde | |||
| ff37a13913 | |||
| a4ba820d36 | |||
| 92c162e1b9 | |||
| 8e808740c9 | |||
| f9c29572d2 | |||
| 45bfd824bf | |||
| 9d33cf3793 | |||
| 4917befe59 | |||
| a119427834 | |||
| b16c2f59ee | |||
| e21076d068 | |||
| 8f26a52aa8 | |||
| 1748c8984c | |||
| 834bae1331 | |||
| 9494b56cc3 | |||
| cc067c2a0d | |||
| 6cfbc736b4 | |||
| 7e9dc56186 | |||
| c1f22a3fa4 | |||
| e65c3cabe5 | |||
| 06109f5ad3 | |||
| 5e6a9ba2af | |||
| b2d6d36458 | |||
| 73a3fd5bf7 | |||
| 4227a4915e | |||
| aac05ff1d6 | |||
| d75cd8ed25 | |||
| 6c911adf05 | |||
| c98549d6de | |||
| 22399296f0 | |||
| 958d7f7909 | |||
| 6ee4834d35 | |||
| cf57c01ca6 | |||
| b0a4ecc50e | |||
| 65a2a0da8f | |||
| caeea1ced9 | |||
| 70a4739113 | |||
| d669763a31 | |||
| 71a4373fbb | |||
| f8b6a3fed1 | |||
| d370c4ba44 | |||
| 5725509e88 | |||
| b4fc0dace5 | |||
| efaf84a168 | |||
| c0cdec5cee | |||
| cd405685af | |||
| 263e7290bd | |||
| 09ba268fc5 | |||
| 6bd22df987 | |||
| 86374b2c0e | |||
| a45141383f | |||
| 9babb85fed | |||
| 4789653db0 | |||
| 7cc2a7669f | |||
| 523dbaf1c3 | |||
| 778460fcca | |||
| 4d9c4567ed | |||
| aa3b028e25 | |||
| 514abbfc82 | |||
| 3c3ae0cb64 | |||
| b37f46b652 | |||
| 31a2ac2a22 | |||
| 71998069bd | |||
| f173acab69 | |||
| 349b54351a | |||
| b9d7455d9c | |||
| d4799c1e82 | |||
| 901d66dbb7 | |||
| 0aa5c7b815 | |||
| 269799979f | |||
| 85621f11e1 | |||
| 6d167bd64c | |||
| ec110f170b | |||
| 79d16919a5 | |||
| 8c83266816 | |||
| 561772b152 | |||
| 929306e658 | |||
| f0fb1d8581 | |||
| 9748b0d5da | |||
| ab86c90c20 | |||
| ce7b1ab3b6 | |||
| 069974a519 | |||
| 59b3790031 | |||
| 96dccc4e6f | |||
| 0edbee4550 | |||
| a8127273a6 | |||
| 441d158302 | |||
| c8d14d75ba | |||
| ae0d707d44 | |||
| eaa68c4cd7 |
@@ -1,23 +1,16 @@
|
||||
# Image metadata and config.
|
||||
# Ideally, the Node.js version should match what we use on CI.
|
||||
FROM cimg/node:18.13.0@sha256:45d0ee279f522c4562a464df71108104cc9b2187205333ffd0707d4f05589731
|
||||
|
||||
FROM cimg/node:22.21.1-browsers@sha256:eee8ed828d5a54196456a86ac6872c43325acad54e59632261e23b486c458992
|
||||
|
||||
LABEL name="Angular dev environment" \
|
||||
description="This image can be used to create a dev environment for building Angular." \
|
||||
vendor="angular" \
|
||||
version="1.0"
|
||||
|
||||
EXPOSE 4000 4200 4433 5000 8080 9876
|
||||
description="This image can be used to create a dev environment for building Angular." \
|
||||
vendor="angular" \
|
||||
version="1.0"
|
||||
|
||||
EXPOSE 4200 4201 4205 4206 9876
|
||||
|
||||
# Switch to `root`.
|
||||
USER root
|
||||
|
||||
|
||||
# Configure `Node.js`/`npm`.
|
||||
RUN npm config --global set user root
|
||||
|
||||
|
||||
# Go! (And keep going.)
|
||||
CMD ["tail", "--follow", "/dev/null"]
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
{
|
||||
"name": "Angular dev container",
|
||||
"dockerFile": "Dockerfile",
|
||||
"appPort": [4000, 4200, 4433, 5000, 8080, 9876],
|
||||
"forwardPorts": [4200, 4201, 4205, 4206, 9876],
|
||||
"postCreateCommand": "pnpm install",
|
||||
"extensions": [
|
||||
"devondcarew.bazel-code",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin"
|
||||
],
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": ["BazelBuild.vscode-bazel", "ms-vscode.vscode-typescript-tslint-plugin"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,53 @@
|
||||
import {Deployment} from './deployments.mjs';
|
||||
import {join} from 'path';
|
||||
import {readFileSync, writeFileSync} from 'fs';
|
||||
|
||||
export async function generateSitemap(deployment: Deployment, distDir: string) {
|
||||
const servingUrlWithoutEndingSlash = deployment.servingUrl.endsWith('/')
|
||||
? deployment.servingUrl.slice(0, -1)
|
||||
: deployment.servingUrl;
|
||||
import {join} from 'node:path';
|
||||
import {readFile, writeFile} from 'node:fs/promises';
|
||||
|
||||
export async function generateSitemap(deployment: Deployment, distDir: string): Promise<void> {
|
||||
/** Timestamp string used to of the last file update. */
|
||||
const lastModifiedTimestamp = new Date().toISOString();
|
||||
/** An object containing all of the routes available within the application. */
|
||||
const routes = JSON.parse(readFileSync(join(distDir, 'prerendered-routes.json'), 'utf-8'));
|
||||
const {routes} = JSON.parse(await readFile(join(distDir, 'prerendered-routes.json'), 'utf-8'));
|
||||
const routePaths = Object.keys(routes);
|
||||
|
||||
/** The generated sitemap string. */
|
||||
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${Object.keys(routes.routes)
|
||||
.map((route) => {
|
||||
const routeWithoutLeadingSlash = route.startsWith('/') ? route.slice(1) : route;
|
||||
return `
|
||||
<url>
|
||||
<loc>${servingUrlWithoutEndingSlash}/${routeWithoutLeadingSlash}</loc>
|
||||
${routePaths
|
||||
.map(
|
||||
(route) => `<url>
|
||||
<loc>${joinUrlParts(deployment.servingUrl, route)}</loc>
|
||||
<lastmod>${lastModifiedTimestamp}</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
`;
|
||||
})
|
||||
</url>`,
|
||||
)
|
||||
.join('')}
|
||||
</urlset>`;
|
||||
writeFileSync(join(distDir, 'browser', 'sitemap.xml'), sitemap, 'utf-8');
|
||||
console.log(`Generated sitemap with ${Object.keys(routes.routes).length} entries.`);
|
||||
|
||||
await writeFile(join(distDir, 'browser', 'sitemap.xml'), sitemap, 'utf-8');
|
||||
|
||||
console.log(`Generated sitemap with ${routePaths.length} entries.`);
|
||||
}
|
||||
|
||||
function joinUrlParts(...parts: string[]): string {
|
||||
const normalizeParts: string[] = [];
|
||||
for (const part of parts) {
|
||||
if (part === '') {
|
||||
// Skip any empty parts
|
||||
continue;
|
||||
}
|
||||
|
||||
let normalizedPart = part;
|
||||
if (part[0] === '/') {
|
||||
normalizedPart = normalizedPart.slice(1);
|
||||
}
|
||||
if (part.at(-1) === '/') {
|
||||
normalizedPart = normalizedPart.slice(0, -1);
|
||||
}
|
||||
if (normalizedPart !== '') {
|
||||
normalizeParts.push(normalizedPart);
|
||||
}
|
||||
}
|
||||
|
||||
return normalizeParts.join('/');
|
||||
}
|
||||
|
||||
+10528
-10949
File diff suppressed because one or more lines are too long
@@ -5,9 +5,9 @@ runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/saucelabs@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Starting Saucelabs tunnel service
|
||||
shell: bash
|
||||
run: ./tools/saucelabs/sauce-service.sh run &
|
||||
|
||||
@@ -21,16 +21,16 @@ 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@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build adev
|
||||
run: pnpm bazel build //adev:build.production --config=snapshot-build
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
pull-number: '${{github.event.pull_request.number}}'
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
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}}
|
||||
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
github-token: '${{secrets.GITHUB_TOKEN}}'
|
||||
workflow-artifact-name: 'adev-preview'
|
||||
|
||||
@@ -17,6 +17,6 @@ jobs:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
- uses: angular/dev-infra/github-actions/branch-manager@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
- uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
bazelrc: ./.bazelrc.user
|
||||
|
||||
|
||||
+25
-25
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Check code lint
|
||||
@@ -39,13 +39,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
disable-package-manager-cache: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -67,11 +67,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -83,11 +83,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -100,11 +100,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -119,11 +119,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -136,11 +136,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
|
||||
@@ -152,11 +152,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
|
||||
- name: Install node modules
|
||||
@@ -206,11 +206,11 @@ jobs:
|
||||
runs-on: ubuntu-latest-8core
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Build adev
|
||||
|
||||
@@ -13,13 +13,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: angular/dev-infra/github-actions/pull-request-labeling@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
- uses: angular/dev-infra/github-actions/pull-request-labeling@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
post_approval_changes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
- uses: angular/dev-infra/github-actions/post-approval-changes@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
- uses: angular/dev-infra/github-actions/google-internal-tests@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
run-tests-guide-url: http://go/angular-g3sync-start
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -13,15 +13,15 @@ jobs:
|
||||
JOBS: 2
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Saucelabs Variables
|
||||
uses: angular/dev-infra/github-actions/saucelabs@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/saucelabs@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Set up Sauce Tunnel Daemon
|
||||
run: pnpm bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
|
||||
env:
|
||||
|
||||
@@ -9,6 +9,6 @@ jobs:
|
||||
status:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
- uses: angular/dev-infra/github-actions/unified-status-check@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
|
||||
|
||||
@@ -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@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- 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@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- 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
|
||||
|
||||
+20
-20
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Check code lint
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Check code format
|
||||
run: pnpm ng-dev format changed --check ${{ github.event.pull_request.base.sha }}
|
||||
- name: Check Package Licenses
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/linting/licenses@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
|
||||
|
||||
@@ -45,13 +45,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
with:
|
||||
disable-package-manager-cache: true
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run unit tests
|
||||
@@ -71,11 +71,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run CI tests for framework
|
||||
@@ -95,11 +95,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel Remote Caching
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run integration CI tests for framework
|
||||
@@ -110,11 +110,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run tests
|
||||
@@ -127,11 +127,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- name: Run tests
|
||||
@@ -142,11 +142,11 @@ jobs:
|
||||
labels: ubuntu-latest
|
||||
steps:
|
||||
- name: Initialize environment
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/setup@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Setup Bazel RBE
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d
|
||||
uses: angular/dev-infra/github-actions/bazel/configure-remote@2ff164a0ca60e692f3d7e1a982bde9bf3379365e
|
||||
- name: Install node modules
|
||||
run: pnpm install --frozen-lockfile
|
||||
- run: |
|
||||
|
||||
@@ -54,3 +54,6 @@ baseline.json
|
||||
|
||||
# Ignore cache created with the Angular CLI.
|
||||
.angular/
|
||||
|
||||
# pnpm store
|
||||
.pnpm-store
|
||||
|
||||
@@ -9,4 +9,5 @@ export const github = {
|
||||
name: 'angular',
|
||||
mainBranchName: 'main',
|
||||
mergeMode: 'caretaker-only',
|
||||
requireReleaseModeForRelease: true,
|
||||
};
|
||||
|
||||
@@ -270,6 +270,8 @@ groups:
|
||||
- ~amishne
|
||||
- ~leonsenft
|
||||
- ~mattrbeck
|
||||
- ~clydin
|
||||
- ~dgp1130
|
||||
|
||||
# =========================================================
|
||||
# Angular DevTools
|
||||
|
||||
+64
-1
@@ -1,6 +1,69 @@
|
||||
<a name="21.0.9"></a>
|
||||
# 21.0.9 (2026-01-14)
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [82d556a8fb](https://github.com/angular/angular/commit/82d556a8fb38cd2024e3d098c55254305ba12b6b) | fix | Ensure the control instruction comes after the other bindings |
|
||||
| [0055f3cc79](https://github.com/angular/angular/commit/0055f3cc79f387b8dec6ce5e1a33fad5486f9341) | fix | Rename signal form [field] to [formField] |
|
||||
### migrations
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e4bfa5c9e7](https://github.com/angular/angular/commit/e4bfa5c9e7feec48d3c4e9425a21a2ccf6532bdb) | fix | prevent duplicate imports in common-to-standalone migration |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.0.8"></a>
|
||||
# 21.0.8 (2026-01-08)
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [a6a2621bf9](https://github.com/angular/angular/commit/a6a2621bf9df02584e4079f4a804278fc2060a9c) | fix | fix memory leak with event replay |
|
||||
| [5239e471a1](https://github.com/angular/angular/commit/5239e471a1f887574c6703c0497e5854304cce4e) | fix | handle cancelled traversals in fake navigation |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.0.7"></a>
|
||||
# 21.0.7 (2026-01-07)
|
||||
### compiler
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [8e808740c9](https://github.com/angular/angular/commit/8e808740c9311daa0f1c9bab8596ed5e54bdcc6a) | fix | better types for a few expression AST nodes |
|
||||
| [63b1cdcf70](https://github.com/angular/angular/commit/63b1cdcf70e6de448e8fa4ba1732d7bd7b5400d1) | fix | produce accurate span for typeof and void expressions |
|
||||
| [3c3ae0cb64](https://github.com/angular/angular/commit/3c3ae0cb64bb112d7167fd9b0bf7739f0c9e6a39) | fix | provide location information for literal map keys |
|
||||
| [523dbaf1c3](https://github.com/angular/angular/commit/523dbaf1c3646ce27f1cf2e4cfc84c730fea8da9) | fix | stop ThisReceiver inheritance from ImplicitReceiver |
|
||||
### compiler-cli
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [4d9c4567ed](https://github.com/angular/angular/commit/4d9c4567edfb8dd424a3336ef54ffdfc6ca7c15f) | fix | ensure component import diagnostics are reported within the `imports` expression |
|
||||
| [cd405685af](https://github.com/angular/angular/commit/cd405685afbfad530de7fb841ad352d2b702a9a4) | fix | fix up spelling of diagnostic |
|
||||
| [778460fcca](https://github.com/angular/angular/commit/778460fccac13d8667bb53fa24ba977a930c0253) | fix | support qualified names in `typeof` type references |
|
||||
### core
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [7c74674eb0](https://github.com/angular/angular/commit/7c74674eb07491f808f79976e3e21787a841aefb) | fix | avoid leaking view data in animations |
|
||||
| [0edbee4550](https://github.com/angular/angular/commit/0edbee4550e85b933e9bd2ba3c5511ef6fbf7304) | fix | explicitly cast signal node value to String |
|
||||
| [f9c29572d2](https://github.com/angular/angular/commit/f9c29572d28feef878c73edad562b3a6451825a6) | fix | sanitize sensitive attributes on SVG script elements |
|
||||
### forms
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [e3fba182f9](https://github.com/angular/angular/commit/e3fba182f90a2673040cf267a970c54c07d4840f) | feat | add `[formField]` directive |
|
||||
| [561772b152](https://github.com/angular/angular/commit/561772b152458e1d91d4bf3ef45d9645a731f2b1) | fix | allow custom controls to require `dirty` input |
|
||||
| [f0fb1d8581](https://github.com/angular/angular/commit/f0fb1d8581671ca499bcb4790b0549825eb36a91) | fix | allow custom controls to require `hidden` input |
|
||||
| [ec110f170b](https://github.com/angular/angular/commit/ec110f170bbba95f023c8ae0e4429c35bfedc572) | fix | allow custom controls to require `pending` input |
|
||||
| [ae1dc16bb0](https://github.com/angular/angular/commit/ae1dc16bb0d30b6e87b0f98b7989e6685d856e31) | fix | clean up abort listener after timeout |
|
||||
| [9748b0d5da](https://github.com/angular/angular/commit/9748b0d5da6ffb1fd2498b23cc452240f46e0549) | fix | support custom controls with non signal-based models |
|
||||
| [6bd22df987](https://github.com/angular/angular/commit/6bd22df987e433a9e3cb759e35eb6403991cf4b7) | fix | Support readonly arrays in signal forms |
|
||||
### router
|
||||
| Commit | Type | Description |
|
||||
| -- | -- | -- |
|
||||
| [41cd4a6af8](https://github.com/angular/angular/commit/41cd4a6af800cf7807c46862c99ae036457d8fa7) | fix | Fix RouterLink href not updating with `queryParamsHandling` |
|
||||
| [5e9e09aee0](https://github.com/angular/angular/commit/5e9e09aee0c08901d2a4d48b60bd13692c73e76e) | fix | handle errors from view transition `updateCallbackDone` promise |
|
||||
|
||||
<!-- CHANGELOG SPLIT MARKER -->
|
||||
|
||||
<a name="21.0.6"></a>
|
||||
# 21.0.6 (2025-12-17)
|
||||
## Breaking Changes
|
||||
## Breaking Changes (affecting only experimental features)
|
||||
### forms
|
||||
- The shape of `SignalFormsConfig.classes` has changed
|
||||
|
||||
|
||||
+6
-6
@@ -4,7 +4,7 @@ module(
|
||||
name = "angular",
|
||||
)
|
||||
|
||||
bazel_dep(name = "rules_pkg", version = "1.1.0")
|
||||
bazel_dep(name = "rules_pkg", version = "1.2.0")
|
||||
bazel_dep(name = "rules_nodejs", version = "6.6.2")
|
||||
bazel_dep(name = "aspect_rules_ts", version = "3.8.1")
|
||||
bazel_dep(name = "aspect_rules_js", version = "2.8.3")
|
||||
@@ -12,9 +12,9 @@ bazel_dep(name = "aspect_rules_esbuild", version = "0.25.0")
|
||||
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.2")
|
||||
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.0")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.22.2")
|
||||
bazel_dep(name = "tar.bzl", version = "0.7.0")
|
||||
bazel_dep(name = "yq.bzl", version = "0.3.2")
|
||||
bazel_dep(name = "yq.bzl", version = "0.3.4")
|
||||
bazel_dep(name = "rules_angular")
|
||||
git_override(
|
||||
module_name = "rules_angular",
|
||||
@@ -25,7 +25,7 @@ git_override(
|
||||
bazel_dep(name = "devinfra")
|
||||
git_override(
|
||||
module_name = "devinfra",
|
||||
commit = "091b9a65ff074a3eb84c1fc1d0e17cc9f7e8868d",
|
||||
commit = "2ff164a0ca60e692f3d7e1a982bde9bf3379365e",
|
||||
remote = "https://github.com/angular/dev-infra.git",
|
||||
)
|
||||
|
||||
@@ -39,7 +39,7 @@ git_override(
|
||||
bazel_dep(name = "rules_browsers")
|
||||
git_override(
|
||||
module_name = "rules_browsers",
|
||||
commit = "8ef3e996d5fc040a35770f860987d8b9cad8ef3d",
|
||||
commit = "5f0e50d17d15c70e6ab5546f1659338ae94c4072",
|
||||
remote = "https://github.com/devversion/rules_browsers.git",
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ yq = use_extension("@yq.bzl//yq:extensions.bzl", "yq")
|
||||
use_repo(yq, "yq_toolchains")
|
||||
|
||||
node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
|
||||
node.toolchain(node_version = "22.21.1")
|
||||
node.toolchain(node_version_from_nvmrc = "//:.nvmrc")
|
||||
use_repo(node, "nodejs_toolchains")
|
||||
use_repo(node, "nodejs_darwin_amd64")
|
||||
use_repo(node, "nodejs_darwin_arm64")
|
||||
|
||||
Generated
+77
-53
@@ -15,12 +15,11 @@
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.17.1/MODULE.bazel": "9b027af55f619c7c444cead71061578fab6587e5e1303fa4ed61d49d2b1a7262",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/MODULE.bazel": "7fe0191f047d4fe4a4a46c1107e2350cbb58a8fc2e10913aa4322d3190dec0bf",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.0/source.json": "369df5b7f2eae82f200fff95cf1425f90dee90a0d0948122060b48150ff0e224",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.2/MODULE.bazel": "6b735f3fdd64978e217c9725f4ff0d84bf606554c8e77d20e90977841d7ff2ed",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.2/source.json": "58fffa2d722cff47cb8d921c8bbed7701c53f233009d9ca82beb4a0fb8fb9418",
|
||||
"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.24.0/MODULE.bazel": "15d19e46ec74e9e49ddf3c335e7a91b0657571654b0960bdcd10b771eeb4f7cb",
|
||||
"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",
|
||||
@@ -34,7 +33,6 @@
|
||||
"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.0/MODULE.bazel": "b1ab5a46cc68c11192613035703606482b115516969788b8c313ad583395366d",
|
||||
"https://bcr.bazel.build/modules/aspect_rules_ts/3.8.1/MODULE.bazel": "796622c65ae3008374fc2d77c32ddb4ef6da9fe891826ce648f70033a48b3667",
|
||||
"https://bcr.bazel.build/modules/aspect_rules_ts/3.8.1/source.json": "a7c4f332f5c21f4e63d073f8dda40bf278d5307499fb307b35058dba558f417a",
|
||||
"https://bcr.bazel.build/modules/aspect_tools_telemetry/0.2.3/MODULE.bazel": "20f53b145f40957a51077ae90b37b7ce83582a1daf9350349f0f86179e19dd0d",
|
||||
@@ -165,8 +163,8 @@
|
||||
"https://bcr.bazel.build/modules/rules_nodejs/6.6.2/source.json": "6e8c1ecc64ff8da147c1620f862ad77d7b19c5d1b52b3aa5e847d5b3d0de4cc3",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.1.0/MODULE.bazel": "9db8031e71b6ef32d1846106e10dd0ee2deac042bd9a2de22b4761b0c3036453",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.1.0/source.json": "fef768df13a92ce6067e1cd0cdc47560dace01354f1d921cfb1d632511f7d608",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.2.0/source.json": "9062e00845bf91a4247465d371baa837adf9b6ff44c542f73ba084f07667e1dc",
|
||||
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
|
||||
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
|
||||
"https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f",
|
||||
@@ -202,7 +200,8 @@
|
||||
"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.2/source.json": "c4ec3e192477e154f08769e29d69e8fd36e8a4f0f623997f3e1f6f7d328f7d7d",
|
||||
"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/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
||||
"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",
|
||||
@@ -212,7 +211,7 @@
|
||||
"moduleExtensions": {
|
||||
"@@aspect_rules_esbuild+//esbuild:extensions.bzl%esbuild": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "0aod3RK04ALA/OKTExzd7QqyeqcC4O2GWuDwUxhQHp4=",
|
||||
"bzlTransitiveDigest": "9odiC0alKLq5PUYv/CZiw2yiMHsGpxvEhPsqt//fRRk=",
|
||||
"usagesDigest": "ToTaCONCN/E05krnHXLM1kpV1zrHNxHrGpUip973II4=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
@@ -318,6 +317,11 @@
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"aspect_bazel_lib+",
|
||||
"bazel_lib",
|
||||
"bazel_lib+"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib+",
|
||||
"bazel_skylib",
|
||||
@@ -408,7 +412,7 @@
|
||||
},
|
||||
"@@aspect_rules_js+//npm:extensions.bzl%pnpm": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "tQ+7EwLfQwqi/T4v5/N3NNHTmP6Wu/FqXxRDndEB2OU=",
|
||||
"bzlTransitiveDigest": "zVV86HvMwDBJ8IsFt27k/Sjq0vCMPr8b8X9OAuprQ6w=",
|
||||
"usagesDigest": "3ECQVM1bdy/C4w7vxglaG1gt0S98v0T14gP5wNiDgwY=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
@@ -467,6 +471,11 @@
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"aspect_bazel_lib+",
|
||||
"bazel_lib",
|
||||
"bazel_lib+"
|
||||
],
|
||||
[
|
||||
"aspect_bazel_lib+",
|
||||
"bazel_skylib",
|
||||
@@ -718,7 +727,7 @@
|
||||
},
|
||||
"@@rules_browsers+//browsers:extensions.bzl%browsers": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "ljZlVgWkQJnI6EvlHVfYit2EttUE52gDTbvmota5YO8=",
|
||||
"bzlTransitiveDigest": "agkaLQ8wE1r/5IX6pkERzFxI/z0M42Em+ICNO6TXsVo=",
|
||||
"usagesDigest": "FS7q5WaIwg3KirS3njhuPFkTIBYvDaTInVGrlzu0XL8=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
@@ -727,9 +736,9 @@
|
||||
"rules_browsers_chrome_linux": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "1419fa328bd7ea2697f26412ec693867516e4ef23c32eb13143a0b0b179b604b",
|
||||
"sha256": "0a2ff0fc9eb5958b7b420f20e3968f424be7423fef89739e71565a48aa073a57",
|
||||
"urls": [
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/linux64/chrome-headless-shell-linux64.zip"
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/linux64/chrome-headless-shell-linux64.zip"
|
||||
],
|
||||
"named_files": {
|
||||
"CHROME-HEADLESS-SHELL": "chrome-headless-shell-linux64/chrome-headless-shell"
|
||||
@@ -745,9 +754,9 @@
|
||||
"rules_browsers_chrome_mac": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "792cbf9b77219b4476e41c49647bcd15e55f0988002fa1e4e6a720eb430c7eda",
|
||||
"sha256": "e6076b1201d86f74c5eab982a239d5af83e66b1aa4d780bcb792698790e01d87",
|
||||
"urls": [
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-x64/chrome-headless-shell-mac-x64.zip"
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/mac-x64/chrome-headless-shell-mac-x64.zip"
|
||||
],
|
||||
"named_files": {
|
||||
"CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-x64/chrome-headless-shell"
|
||||
@@ -763,9 +772,9 @@
|
||||
"rules_browsers_chrome_mac_arm": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "f0c1917769775e826dfa69936381d0d95b06fe67cf631ecd842380d5de0e4c7f",
|
||||
"sha256": "b74dbcf5543d916b02d0a133e2e7c6a4de251f06733f72c2c15ea8c42213f63b",
|
||||
"urls": [
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-arm64/chrome-headless-shell-mac-arm64.zip"
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/mac-arm64/chrome-headless-shell-mac-arm64.zip"
|
||||
],
|
||||
"named_files": {
|
||||
"CHROME-HEADLESS-SHELL": "chrome-headless-shell-mac-arm64/chrome-headless-shell"
|
||||
@@ -781,9 +790,9 @@
|
||||
"rules_browsers_chrome_win64": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "6ce0f20dd743a804890f45f5349370e1aa7cd3ac3482c04686fcff5fafd01bb3",
|
||||
"sha256": "df1e612dc3b1615e182a1f11821052995913c39df37caa52699de21a68d030d2",
|
||||
"urls": [
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/win64/chrome-headless-shell-win64.zip"
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/win64/chrome-headless-shell-win64.zip"
|
||||
],
|
||||
"named_files": {
|
||||
"CHROME-HEADLESS-SHELL": "chrome-headless-shell-win64/chrome-headless-shell.exe"
|
||||
@@ -799,9 +808,9 @@
|
||||
"rules_browsers_chromedriver_linux": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "baf4bf9d22881265487732f17d35a49e9aadd0837aa5c1c1eea520c8aa24a97f",
|
||||
"sha256": "69c504306399d979a2766fea603c3fb9d3d87d46c75bddc9f2a049b4f636d57c",
|
||||
"urls": [
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/linux64/chromedriver-linux64.zip"
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/linux64/chromedriver-linux64.zip"
|
||||
],
|
||||
"named_files": {
|
||||
"CHROMEDRIVER": "chromedriver-linux64/chromedriver"
|
||||
@@ -815,9 +824,9 @@
|
||||
"rules_browsers_chromedriver_mac": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "87560768d5aa203b37c0a1b8459a35b05e4ece54afee2df530f3bc33de4f63c5",
|
||||
"sha256": "5fc9d6f594fc5f2568a15145f25116dd8e9c9a60baa8da4bb21a17650fb00e7e",
|
||||
"urls": [
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-x64/chromedriver-mac-x64.zip"
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/mac-x64/chromedriver-mac-x64.zip"
|
||||
],
|
||||
"named_files": {
|
||||
"CHROMEDRIVER": "chromedriver-mac-x64/chromedriver"
|
||||
@@ -831,9 +840,9 @@
|
||||
"rules_browsers_chromedriver_mac_arm": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "99821795fa7c87eb92fb15248e23b237c83f397486d22ad9a10771622c36a5a0",
|
||||
"sha256": "14e92294c2c3639ca4e7d27e850588b619d698e2f8905cee368f07db2e1bf1e9",
|
||||
"urls": [
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/mac-arm64/chromedriver-mac-arm64.zip"
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/mac-arm64/chromedriver-mac-arm64.zip"
|
||||
],
|
||||
"named_files": {
|
||||
"CHROMEDRIVER": "chromedriver-mac-arm64/chromedriver"
|
||||
@@ -847,9 +856,9 @@
|
||||
"rules_browsers_chromedriver_win64": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "6e180e234a710c3cbf69566f64a662ed85473db6ae82275fd359f80ab288df99",
|
||||
"sha256": "cf641d2e176db95bcc158cd90eafd347ad4928fa0458a5f3bfd56c6d983e70db",
|
||||
"urls": [
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/144.0.7531.0/win64/chromedriver-win64.zip"
|
||||
"https://storage.googleapis.com/chrome-for-testing-public/145.0.7586.0/win64/chromedriver-win64.zip"
|
||||
],
|
||||
"named_files": {
|
||||
"CHROMEDRIVER": "chromedriver-win64/chromedriver.exe"
|
||||
@@ -863,9 +872,9 @@
|
||||
"rules_browsers_firefox_linux": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "00fb922cda6bab971e02bcbfb77923b0a234388ed7d77c23506ca0a1a61d4a86",
|
||||
"sha256": "8d56f479cc398a537a60a3fa20dca92d8a41925113d3a67f534881a4e4d7e344",
|
||||
"urls": [
|
||||
"https://archive.mozilla.org/pub/firefox/releases/145.0/linux-x86_64/en-US/firefox-145.0.tar.xz"
|
||||
"https://archive.mozilla.org/pub/firefox/releases/146.0/linux-x86_64/en-US/firefox-146.0.tar.xz"
|
||||
],
|
||||
"named_files": {
|
||||
"FIREFOX": "firefox/firefox"
|
||||
@@ -879,9 +888,9 @@
|
||||
"rules_browsers_firefox_mac": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "1c4556480deac8424049f3081a6de1e2c6de619bab3e8ce53e5a497b8d6d919e",
|
||||
"sha256": "4b1645313887972d466cd82166ea571485c2c40a167f84624e3f3ca739993cc9",
|
||||
"urls": [
|
||||
"https://archive.mozilla.org/pub/firefox/releases/145.0/mac/en-US/Firefox%20145.0.dmg"
|
||||
"https://archive.mozilla.org/pub/firefox/releases/146.0/mac/en-US/Firefox%20146.0.dmg"
|
||||
],
|
||||
"named_files": {
|
||||
"FIREFOX": "Firefox.app/Contents/MacOS/firefox"
|
||||
@@ -895,9 +904,9 @@
|
||||
"rules_browsers_firefox_mac_arm": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "1c4556480deac8424049f3081a6de1e2c6de619bab3e8ce53e5a497b8d6d919e",
|
||||
"sha256": "4b1645313887972d466cd82166ea571485c2c40a167f84624e3f3ca739993cc9",
|
||||
"urls": [
|
||||
"https://archive.mozilla.org/pub/firefox/releases/145.0/mac/en-US/Firefox%20145.0.dmg"
|
||||
"https://archive.mozilla.org/pub/firefox/releases/146.0/mac/en-US/Firefox%20146.0.dmg"
|
||||
],
|
||||
"named_files": {
|
||||
"FIREFOX": "Firefox.app/Contents/MacOS/firefox"
|
||||
@@ -911,9 +920,9 @@
|
||||
"rules_browsers_firefox_win64": {
|
||||
"repoRuleId": "@@rules_browsers+//browsers/private:browser_repo.bzl%browser_repo",
|
||||
"attributes": {
|
||||
"sha256": "4b0345c113242653d923b369fcbd48e3089c57658f8c1542f887c8a375d50306",
|
||||
"sha256": "216870c89648f32450cfefb5cec417fcd66d480d5dc83f894bf99f5fd7f38dbb",
|
||||
"urls": [
|
||||
"https://archive.mozilla.org/pub/firefox/releases/145.0/win64/en-US/Firefox%20Setup%20145.0.exe"
|
||||
"https://archive.mozilla.org/pub/firefox/releases/146.0/win64/en-US/Firefox%20Setup%20146.0.exe"
|
||||
],
|
||||
"named_files": {
|
||||
"FIREFOX": "core/firefox.exe"
|
||||
@@ -1078,7 +1087,7 @@
|
||||
"@@rules_nodejs+//nodejs:extensions.bzl%node": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "NwcLXHrbh2hoorA/Ybmcpjxsn/6avQmewDglodkDrgo=",
|
||||
"usagesDigest": "eycVhpa7dyjfsYiyP69uXfJ7I60zQlL3Bd0gcVPq8+8=",
|
||||
"usagesDigest": "QbHTNODxV2P0w/Zs/oELuqv6ovXgi81f6SUYmX7dNhc=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
@@ -1091,7 +1100,8 @@
|
||||
"node_urls": [
|
||||
"https://nodejs.org/dist/v{version}/{filename}"
|
||||
],
|
||||
"node_version": "22.21.1",
|
||||
"node_version": "20.19.5",
|
||||
"node_version_from_nvmrc": "@@//:.nvmrc",
|
||||
"include_headers": false,
|
||||
"platform": "linux_amd64"
|
||||
}
|
||||
@@ -1104,7 +1114,8 @@
|
||||
"node_urls": [
|
||||
"https://nodejs.org/dist/v{version}/{filename}"
|
||||
],
|
||||
"node_version": "22.21.1",
|
||||
"node_version": "20.19.5",
|
||||
"node_version_from_nvmrc": "@@//:.nvmrc",
|
||||
"include_headers": false,
|
||||
"platform": "linux_arm64"
|
||||
}
|
||||
@@ -1117,7 +1128,8 @@
|
||||
"node_urls": [
|
||||
"https://nodejs.org/dist/v{version}/{filename}"
|
||||
],
|
||||
"node_version": "22.21.1",
|
||||
"node_version": "20.19.5",
|
||||
"node_version_from_nvmrc": "@@//:.nvmrc",
|
||||
"include_headers": false,
|
||||
"platform": "linux_s390x"
|
||||
}
|
||||
@@ -1130,7 +1142,8 @@
|
||||
"node_urls": [
|
||||
"https://nodejs.org/dist/v{version}/{filename}"
|
||||
],
|
||||
"node_version": "22.21.1",
|
||||
"node_version": "20.19.5",
|
||||
"node_version_from_nvmrc": "@@//:.nvmrc",
|
||||
"include_headers": false,
|
||||
"platform": "linux_ppc64le"
|
||||
}
|
||||
@@ -1143,7 +1156,8 @@
|
||||
"node_urls": [
|
||||
"https://nodejs.org/dist/v{version}/{filename}"
|
||||
],
|
||||
"node_version": "22.21.1",
|
||||
"node_version": "20.19.5",
|
||||
"node_version_from_nvmrc": "@@//:.nvmrc",
|
||||
"include_headers": false,
|
||||
"platform": "darwin_amd64"
|
||||
}
|
||||
@@ -1156,7 +1170,8 @@
|
||||
"node_urls": [
|
||||
"https://nodejs.org/dist/v{version}/{filename}"
|
||||
],
|
||||
"node_version": "22.21.1",
|
||||
"node_version": "20.19.5",
|
||||
"node_version_from_nvmrc": "@@//:.nvmrc",
|
||||
"include_headers": false,
|
||||
"platform": "darwin_arm64"
|
||||
}
|
||||
@@ -1169,7 +1184,8 @@
|
||||
"node_urls": [
|
||||
"https://nodejs.org/dist/v{version}/{filename}"
|
||||
],
|
||||
"node_version": "22.21.1",
|
||||
"node_version": "20.19.5",
|
||||
"node_version_from_nvmrc": "@@//:.nvmrc",
|
||||
"include_headers": false,
|
||||
"platform": "windows_amd64"
|
||||
}
|
||||
@@ -1182,7 +1198,8 @@
|
||||
"node_urls": [
|
||||
"https://nodejs.org/dist/v{version}/{filename}"
|
||||
],
|
||||
"node_version": "22.21.1",
|
||||
"node_version": "20.19.5",
|
||||
"node_version_from_nvmrc": "@@//:.nvmrc",
|
||||
"include_headers": false,
|
||||
"platform": "windows_arm64"
|
||||
}
|
||||
@@ -4005,8 +4022,8 @@
|
||||
},
|
||||
"@@yq.bzl+//yq:extensions.bzl%yq": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "61Uz+o5PnlY0jJfPZEUNqsKxnM/UCLeWsn5VVCc8u5Y=",
|
||||
"usagesDigest": "RMl/keg0xfMeM4V6asCKGMUDCuo+wyQ3styJIK7uDok=",
|
||||
"bzlTransitiveDigest": "tDqk+ntWTdxNAWPDjRY1uITgHbti2jcXR5ZdinltBs0=",
|
||||
"usagesDigest": "fKeGl7m4X6ZwRQS4Up/5Bzx+9laXzZyKEn6s9ODJQTc=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
@@ -4015,56 +4032,63 @@
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "darwin_amd64",
|
||||
"version": "4.45.1"
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_darwin_arm64": {
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "darwin_arm64",
|
||||
"version": "4.45.1"
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_linux_amd64": {
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "linux_amd64",
|
||||
"version": "4.45.1"
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_linux_arm64": {
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "linux_arm64",
|
||||
"version": "4.45.1"
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_linux_s390x": {
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "linux_s390x",
|
||||
"version": "4.45.1"
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_linux_riscv64": {
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "linux_riscv64",
|
||||
"version": "4.45.1"
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_linux_ppc64le": {
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "linux_ppc64le",
|
||||
"version": "4.45.1"
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_windows_amd64": {
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "windows_amd64",
|
||||
"version": "4.45.1"
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_windows_arm64": {
|
||||
"repoRuleId": "@@yq.bzl+//yq/toolchain:platforms.bzl%yq_platform_repo",
|
||||
"attributes": {
|
||||
"platform": "windows_arm64",
|
||||
"version": "4.45.2"
|
||||
}
|
||||
},
|
||||
"yq_toolchains": {
|
||||
|
||||
+6
-6
@@ -5,21 +5,21 @@
|
||||
"@algolia/requester-browser-xhr": "5.41.0",
|
||||
"@algolia/requester-node-http": "5.41.0",
|
||||
"@angular/animations": "workspace:*",
|
||||
"@angular/aria": "21.0.3",
|
||||
"@angular/build": "21.0.3",
|
||||
"@angular/cdk": "21.0.3",
|
||||
"@angular/cli": "21.0.3",
|
||||
"@angular/aria": "21.0.5",
|
||||
"@angular/build": "21.0.5",
|
||||
"@angular/cdk": "21.0.5",
|
||||
"@angular/cli": "21.0.5",
|
||||
"@angular/common": "workspace:*",
|
||||
"@angular/compiler": "workspace:*",
|
||||
"@angular/compiler-cli": "workspace:*",
|
||||
"@angular/core": "workspace:*",
|
||||
"@angular/docs": "workspace:*",
|
||||
"@angular/forms": "workspace:*",
|
||||
"@angular/material": "21.0.3",
|
||||
"@angular/material": "21.0.5",
|
||||
"@angular/platform-browser": "workspace:*",
|
||||
"@angular/platform-server": "workspace:*",
|
||||
"@angular/router": "workspace:*",
|
||||
"@angular/ssr": "21.0.3",
|
||||
"@angular/ssr": "21.0.5",
|
||||
"@codemirror/autocomplete": "6.19.1",
|
||||
"@codemirror/commands": "6.10.0",
|
||||
"@codemirror/lang-angular": "0.1.4",
|
||||
|
||||
@@ -13,10 +13,8 @@ import {NavigationState} from '../../services';
|
||||
import {NavigationItem} from '../../interfaces';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {provideZonelessChangeDetection} from '@angular/core';
|
||||
|
||||
describe('Breadcrumb', () => {
|
||||
let fixture: ComponentFixture<Breadcrumb>;
|
||||
let navigationStateSpy: jasmine.SpyObj<NavigationState>;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -26,20 +24,19 @@ describe('Breadcrumb', () => {
|
||||
imports: [Breadcrumb],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideZonelessChangeDetection(),
|
||||
{
|
||||
provide: NavigationState,
|
||||
useValue: navigationStateSpy,
|
||||
},
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(Breadcrumb);
|
||||
});
|
||||
|
||||
it('should display proper breadcrumb structure based on navigation state', () => {
|
||||
it('should display proper breadcrumb structure based on navigation state', async () => {
|
||||
navigationStateSpy.activeNavigationItem.and.returnValue(item);
|
||||
const fixture = TestBed.createComponent(Breadcrumb);
|
||||
await fixture.whenStable();
|
||||
|
||||
fixture.detectChanges();
|
||||
const breadcrumbs = fixture.debugElement.queryAll(By.css('.docs-breadcrumb span'));
|
||||
|
||||
expect(breadcrumbs.length).toBe(2);
|
||||
@@ -47,10 +44,11 @@ describe('Breadcrumb', () => {
|
||||
expect(breadcrumbs[1].nativeElement.innerText).toEqual('Parent');
|
||||
});
|
||||
|
||||
it('should display breadcrumb links when navigation item has got path', () => {
|
||||
it('should display breadcrumb links when navigation item has got path', async () => {
|
||||
navigationStateSpy.activeNavigationItem.and.returnValue(exampleItemWithPath);
|
||||
|
||||
fixture.detectChanges();
|
||||
const fixture = TestBed.createComponent(Breadcrumb);
|
||||
await fixture.whenStable();
|
||||
const breadcrumbs = fixture.debugElement.queryAll(By.css('.docs-breadcrumb a'));
|
||||
|
||||
expect(breadcrumbs.length).toBe(1);
|
||||
|
||||
@@ -11,7 +11,6 @@ import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {CookiePopup, STORAGE_KEY} from './cookie-popup.component';
|
||||
import {LOCAL_STORAGE} from '../../providers/index';
|
||||
import {MockLocalStorage} from '../../testing/index';
|
||||
import {provideZonelessChangeDetection} from '@angular/core';
|
||||
|
||||
describe('CookiePopup', () => {
|
||||
let fixture: ComponentFixture<CookiePopup>;
|
||||
@@ -21,7 +20,6 @@ describe('CookiePopup', () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CookiePopup],
|
||||
providers: [
|
||||
provideZonelessChangeDetection(),
|
||||
{
|
||||
provide: LOCAL_STORAGE,
|
||||
useValue: mockLocalStorage,
|
||||
@@ -30,34 +28,34 @@ describe('CookiePopup', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should make the popup visible by default', () => {
|
||||
initComponent(false);
|
||||
it('should make the popup visible by default', async () => {
|
||||
await initComponent(false);
|
||||
|
||||
expect(getCookiesPopup()).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should hide the cookies popup if the user has already accepted cookies', () => {
|
||||
initComponent(true);
|
||||
it('should hide the cookies popup if the user has already accepted cookies', async () => {
|
||||
await initComponent(true);
|
||||
|
||||
expect(getCookiesPopup()).toBeNull();
|
||||
});
|
||||
|
||||
it('should hide the cookies popup', () => {
|
||||
initComponent(false);
|
||||
it('should hide the cookies popup', async () => {
|
||||
await initComponent(false);
|
||||
|
||||
accept();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getCookiesPopup()).toBeNull();
|
||||
});
|
||||
|
||||
it('should store the user confirmation', () => {
|
||||
initComponent(false);
|
||||
it('should store the user confirmation', async () => {
|
||||
await initComponent(false);
|
||||
|
||||
expect(mockLocalStorage.getItem(STORAGE_KEY)).toBeNull();
|
||||
|
||||
accept();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(mockLocalStorage.getItem(STORAGE_KEY)).toBe('true');
|
||||
});
|
||||
@@ -73,10 +71,10 @@ describe('CookiePopup', () => {
|
||||
?.click();
|
||||
}
|
||||
|
||||
function initComponent(cookiesAccepted: boolean) {
|
||||
async function initComponent(cookiesAccepted: boolean) {
|
||||
mockLocalStorage.setItem(STORAGE_KEY, cookiesAccepted ? 'true' : null);
|
||||
fixture = TestBed.createComponent(CookiePopup);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
}
|
||||
});
|
||||
|
||||
+8
-14
@@ -12,12 +12,7 @@ import {
|
||||
CONFIRMATION_DISPLAY_TIME_MS,
|
||||
CopySourceCodeButton,
|
||||
} from './copy-source-code-button.component';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
provideZonelessChangeDetection,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, signal} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
|
||||
@@ -32,7 +27,6 @@ describe('CopySourceCodeButton', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CodeSnippetWrapper],
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
});
|
||||
fixture = TestBed.createComponent(CodeSnippetWrapper);
|
||||
component = fixture.componentInstance;
|
||||
@@ -56,20 +50,20 @@ describe('CopySourceCodeButton', () => {
|
||||
expect(copySpy.calls.argsFor(0)[0].trim()).toBe(expectedCodeToBeCopied);
|
||||
});
|
||||
|
||||
it(`should set ${SUCCESSFULLY_COPY_CLASS_NAME} for ${CONFIRMATION_DISPLAY_TIME_MS} ms when copy was executed properly`, () => {
|
||||
it(`should set ${SUCCESSFULLY_COPY_CLASS_NAME} for ${CONFIRMATION_DISPLAY_TIME_MS} ms when copy was executed properly`, async () => {
|
||||
const clock = jasmine.clock().install();
|
||||
component.code.set('example');
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement;
|
||||
button.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(button).toHaveClass(SUCCESSFULLY_COPY_CLASS_NAME);
|
||||
|
||||
clock.tick(CONFIRMATION_DISPLAY_TIME_MS);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(button).not.toHaveClass(SUCCESSFULLY_COPY_CLASS_NAME);
|
||||
clock.uninstall();
|
||||
@@ -80,17 +74,17 @@ describe('CopySourceCodeButton', () => {
|
||||
component.code.set('example');
|
||||
copySpy.and.throwError('Fake copy error');
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const button = fixture.debugElement.query(By.directive(CopySourceCodeButton)).nativeElement;
|
||||
button.click();
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(button).toHaveClass(FAILED_COPY_CLASS_NAME);
|
||||
|
||||
clock.tick(CONFIRMATION_DISPLAY_TIME_MS);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(button).not.toHaveClass(FAILED_COPY_CLASS_NAME);
|
||||
clock.uninstall();
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
<ng-template #navigationList let-navigationItems>
|
||||
<ng-template
|
||||
#navigationList
|
||||
let-navigationItems
|
||||
let-preserveOtherCategoryOrder="preserveOtherCategoryOrder"
|
||||
>
|
||||
<ul
|
||||
class="docs-navigation-list docs-faceted-list"
|
||||
[class.docs-navigation-list-dropdown]="isDropdownView()"
|
||||
>
|
||||
@for (itemGroup of groupItems(navigationItems); track $index) {
|
||||
@for (itemGroup of groupItems(navigationItems, preserveOtherCategoryOrder); track $index) {
|
||||
@let groupLabel = itemGroup[0];
|
||||
@let items = itemGroup[1];
|
||||
@if (groupLabel) {
|
||||
<li class="docs-navigation-group">{{ groupLabel | titlecase }}</li>
|
||||
@let firstItem = items[0];
|
||||
|
||||
@if (groupLabel && collapsableLevel() !== firstItem.level && items.length > 1) {
|
||||
<li class="docs-navigation-group">{{ groupLabel }}</li>
|
||||
}
|
||||
@for (item of items; track $index) {
|
||||
<li class="docs-faceted-list-item">
|
||||
<li
|
||||
class="docs-faceted-list-item"
|
||||
[class.single-entry]="items.length === 1 && item.level !== 1"
|
||||
>
|
||||
@let itemLabel = item.label!;
|
||||
@if (item.path) {
|
||||
@if (item.isExternal) {
|
||||
@@ -28,7 +37,6 @@
|
||||
class="docs-faceted-list-item-text"
|
||||
>
|
||||
{{ item.label }}
|
||||
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
|
||||
</span>
|
||||
|
||||
@if (item.children && item.level! > 1 && !item.isExpanded) {
|
||||
@@ -122,7 +130,11 @@
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
navigationList;
|
||||
context: {$implicit: item.children, level: item.level}
|
||||
context: {
|
||||
$implicit: item.children,
|
||||
level: item.level,
|
||||
preserveOtherCategoryOrder: item.preserveOtherCategoryOrder,
|
||||
}
|
||||
"
|
||||
/>
|
||||
}
|
||||
@@ -132,7 +144,15 @@
|
||||
</ul>
|
||||
</ng-template>
|
||||
|
||||
<ng-container *ngTemplateOutlet="navigationList; context: {$implicit: navigationItems()}" />
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
navigationList;
|
||||
context: {
|
||||
$implicit: navigationItems(),
|
||||
preserveOtherCategoryOrder: preserveOtherCategoryOrder(),
|
||||
}
|
||||
"
|
||||
/>
|
||||
|
||||
<ng-template let-item #itemStatus>
|
||||
@if (item.status === 'new') {
|
||||
|
||||
@@ -194,3 +194,7 @@ a,
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.single-entry:not(:first-child) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {NavigationList} from './navigation-list.component';
|
||||
import {signal} from '@angular/core';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {NavigationItem} from '../../interfaces';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {provideZonelessChangeDetection, signal} from '@angular/core';
|
||||
import {NavigationItem} from '../../interfaces';
|
||||
import {NavigationState} from '../../services';
|
||||
import {NavigationList} from './navigation-list.component';
|
||||
|
||||
const navigationItems: NavigationItem[] = [
|
||||
{
|
||||
@@ -38,21 +38,18 @@ describe('NavigationList', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NavigationList],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{provide: NavigationState, useClass: FakeNavigationListState},
|
||||
provideZonelessChangeDetection(),
|
||||
],
|
||||
providers: [provideRouter([]), {provide: NavigationState, useClass: FakeNavigationListState}],
|
||||
});
|
||||
fixture = TestBed.createComponent(NavigationList);
|
||||
fixture.componentRef.setInput('navigationItems', []);
|
||||
fixture.componentRef.setInput('preserveOtherCategoryOrder', false);
|
||||
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should display provided navigation structure', () => {
|
||||
it('should display provided navigation structure', async () => {
|
||||
fixture.componentRef.setInput('navigationItems', [...navigationItems]);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const links = fixture.debugElement.queryAll(By.css('a'));
|
||||
const nonClickableItem = fixture.debugElement.queryAll(By.css('.docs-secondary-nav-header'));
|
||||
@@ -61,28 +58,28 @@ describe('NavigationList', () => {
|
||||
expect(nonClickableItem.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should append `docs-navigation-list-dropdown` when isDropdownView is true', () => {
|
||||
it('should append `docs-navigation-list-dropdown` when isDropdownView is true', async () => {
|
||||
fixture.componentRef.setInput('isDropdownView', true);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const ulElement = fixture.debugElement.query(By.css('ul.docs-navigation-list-dropdown'));
|
||||
|
||||
expect(ulElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not append `docs-navigation-list-dropdown` when isDropdownView is false', () => {
|
||||
it('should not append `docs-navigation-list-dropdown` when isDropdownView is false', async () => {
|
||||
fixture.componentRef.setInput('isDropdownView', false);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const ulElement = fixture.debugElement.query(By.css('ul.docs-navigation-list-dropdown'));
|
||||
|
||||
expect(ulElement).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should emit linkClicked when user clicked on link', () => {
|
||||
it('should emit linkClicked when user clicked on link', async () => {
|
||||
const emitClickOnLinkSpy = spyOn(component, 'emitClickOnLink');
|
||||
fixture.componentRef.setInput('navigationItems', [...navigationItems]);
|
||||
fixture.detectChanges(true);
|
||||
await fixture.whenStable();
|
||||
|
||||
const guideLink = fixture.debugElement.query(By.css('a[href="/guide"]'));
|
||||
guideLink.nativeElement.click();
|
||||
@@ -122,10 +119,10 @@ describe('NavigationList', () => {
|
||||
expect(toggleItemSpy).toHaveBeenCalledOnceWith(itemToToggle);
|
||||
});
|
||||
|
||||
it('should display only items to provided level (Level 1)', () => {
|
||||
it('should display only items to provided level (Level 1)', async () => {
|
||||
fixture.componentRef.setInput('navigationItems', [...navigationItems]);
|
||||
fixture.componentRef.setInput('displayItemsToLevel', 1);
|
||||
fixture.detectChanges(true);
|
||||
await fixture.whenStable();
|
||||
|
||||
const items = fixture.debugElement.queryAll(By.css('li.docs-faceted-list-item'));
|
||||
|
||||
@@ -134,10 +131,10 @@ describe('NavigationList', () => {
|
||||
expect(items[1].nativeElement.innerText).toBe(navigationItems[1].label);
|
||||
});
|
||||
|
||||
it('should display all items (Level 2)', () => {
|
||||
it('should display all items (Level 2)', async () => {
|
||||
fixture.componentRef.setInput('navigationItems', [...navigationItems]);
|
||||
fixture.componentRef.setInput('displayItemsToLevel', 2);
|
||||
fixture.detectChanges(true);
|
||||
await fixture.whenStable();
|
||||
|
||||
const items = fixture.debugElement.queryAll(By.css('li.docs-faceted-list-item'));
|
||||
|
||||
|
||||
@@ -6,15 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
import {ChangeDetectionStrategy, Component, inject, input, output} from '@angular/core';
|
||||
import {NavigationItem} from '../../interfaces/index';
|
||||
import {NavigationState} from '../../services/index';
|
||||
import {RouterLink, RouterLinkActive} from '@angular/router';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
import {IsActiveNavigationItem} from '../../pipes';
|
||||
import {NgTemplateOutlet, TitleCasePipe} from '@angular/common';
|
||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
||||
import {ReactiveFormsModule} from '@angular/forms';
|
||||
import {RouterLink, RouterLinkActive} from '@angular/router';
|
||||
import {NavigationItem} from '../../interfaces/index';
|
||||
import {IsActiveNavigationItem} from '../../pipes';
|
||||
import {NavigationState} from '../../services/index';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-navigation-list',
|
||||
@@ -25,8 +24,6 @@ import {ReactiveFormsModule} from '@angular/forms';
|
||||
IsActiveNavigationItem,
|
||||
NgTemplateOutlet,
|
||||
MatTooltipModule,
|
||||
ReactiveFormsModule,
|
||||
TitleCasePipe,
|
||||
],
|
||||
templateUrl: './navigation-list.component.html',
|
||||
styleUrls: ['./navigation-list.component.scss'],
|
||||
@@ -34,6 +31,7 @@ import {ReactiveFormsModule} from '@angular/forms';
|
||||
})
|
||||
export class NavigationList {
|
||||
readonly navigationItems = input.required<NavigationItem[]>();
|
||||
readonly preserveOtherCategoryOrder = input.required<boolean>();
|
||||
readonly displayItemsToLevel = input(2);
|
||||
readonly collapsableLevel = input<number | undefined>();
|
||||
readonly expandableLevel = input(2);
|
||||
@@ -64,14 +62,17 @@ export class NavigationList {
|
||||
return items.some((item) => !!item.category);
|
||||
}
|
||||
|
||||
protected groupItems(items: NavigationItem[]): Map<string, NavigationItem[]> {
|
||||
protected groupItems(
|
||||
items: NavigationItem[],
|
||||
preserveOtherCategoryOrder: boolean,
|
||||
): Map<string, NavigationItem[]> {
|
||||
const hasCategories = this.hasCategories(items);
|
||||
if (hasCategories) {
|
||||
const others: NavigationItem[] = [];
|
||||
const categorizedItems = new Map<string, NavigationItem[]>();
|
||||
for (const item of items) {
|
||||
const category = item.category || 'Other';
|
||||
if (category === 'Other') {
|
||||
if (!preserveOtherCategoryOrder && category === 'Other') {
|
||||
others.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<docs-text-field
|
||||
[autofocus]="true"
|
||||
[hideIcon]="true"
|
||||
[formControl]="searchControl"
|
||||
[formField]="searchForm"
|
||||
[resetLabel]="'Clear the search'"
|
||||
class="docs-search-input"
|
||||
placeholder="Search docs"
|
||||
@@ -59,7 +59,7 @@
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
} @else if (history.hasItems() && !this.searchControl.value.length) {
|
||||
} @else if (history.hasItems() && !this.searchForm().value().length) {
|
||||
<docs-search-history />
|
||||
} @else {
|
||||
<div class="docs-search-results docs-mini-scroll-track">
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ApplicationRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
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 {By} from '@angular/platform-browser';
|
||||
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
|
||||
import {Router, provideRouter} from '@angular/router';
|
||||
import {ApplicationRef, provideZonelessChangeDetection, ResourceStatus} from '@angular/core';
|
||||
import {SearchResult} from '../../interfaces';
|
||||
|
||||
describe('SearchDialog', () => {
|
||||
@@ -34,7 +34,6 @@ describe('SearchDialog', () => {
|
||||
imports: [SearchDialog],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideZonelessChangeDetection(),
|
||||
{provide: ENVIRONMENT, useValue: {algolia: {index: 'fakeIndex'}}},
|
||||
{provide: ALGOLIA_CLIENT, useValue: {search: searchResults}},
|
||||
{provide: WINDOW, useValue: fakeWindow},
|
||||
@@ -42,8 +41,8 @@ describe('SearchDialog', () => {
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(SearchDialog);
|
||||
fixture.detectChanges();
|
||||
search = TestBed.inject(Search);
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should navigate to active item when user pressed Enter', async () => {
|
||||
@@ -98,7 +97,6 @@ describe('SearchDialog', () => {
|
||||
|
||||
it('should display `Start typing to see results` message when there are no provided query', () => {
|
||||
searchResults.and.returnValue(undefined);
|
||||
fixture.detectChanges();
|
||||
|
||||
const startTypingContainer = fixture.debugElement.query(
|
||||
By.css('.docs-search-results__start-typing'),
|
||||
|
||||
@@ -7,32 +7,32 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
DestroyRef,
|
||||
ElementRef,
|
||||
Injector,
|
||||
afterNextRender,
|
||||
effect,
|
||||
ElementRef,
|
||||
inject,
|
||||
Injector,
|
||||
output,
|
||||
viewChild,
|
||||
viewChildren,
|
||||
} from '@angular/core';
|
||||
|
||||
import {WINDOW} from '../../providers';
|
||||
import {ClickOutside, SearchItem} from '../../directives';
|
||||
import {WINDOW} from '../../providers';
|
||||
import {Search, SearchHistory} from '../../services';
|
||||
|
||||
import {TextField} from '../text-field/text-field.component';
|
||||
import {FormControl, ReactiveFormsModule} from '@angular/forms';
|
||||
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {form, FormField} from '@angular/forms/signals';
|
||||
import {Router, RouterLink} from '@angular/router';
|
||||
import {fromEvent} from 'rxjs';
|
||||
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-search-dialog',
|
||||
@@ -40,7 +40,7 @@ import {SearchHistoryComponent} from '../search-history/search-history.component
|
||||
imports: [
|
||||
ClickOutside,
|
||||
TextField,
|
||||
ReactiveFormsModule,
|
||||
FormField,
|
||||
SearchItem,
|
||||
AlgoliaIcon,
|
||||
RelativeLink,
|
||||
@@ -67,22 +67,14 @@ export class SearchDialog {
|
||||
this.injector,
|
||||
).withWrap();
|
||||
|
||||
readonly searchQuery = this.search.searchQuery;
|
||||
readonly resultsResource = this.search.resultsResource;
|
||||
readonly searchResults = this.search.searchResults;
|
||||
|
||||
// We use a FormControl instead of relying on NgModel+signal to avoid
|
||||
// the issue https://github.com/angular/angular/issues/13568
|
||||
// TODO: Use signal forms when available
|
||||
searchControl = new FormControl(this.searchQuery(), {nonNullable: true});
|
||||
searchForm = form(this.search.searchQuery);
|
||||
|
||||
constructor() {
|
||||
inject(DestroyRef).onDestroy(() => this.keyManager.destroy());
|
||||
|
||||
this.searchControl.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
|
||||
this.searchQuery.set(value);
|
||||
});
|
||||
|
||||
// Thinking about refactoring this to a single afterRenderEffect ?
|
||||
// Answer: It won't have the same behavior
|
||||
effect(() => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {provideZonelessChangeDetection, ApplicationRef} from '@angular/core';
|
||||
import {ApplicationRef} from '@angular/core';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {provideRouter} from '@angular/router';
|
||||
@@ -75,11 +75,7 @@ describe('SearchHistoryComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
providers: [
|
||||
provideZonelessChangeDetection(),
|
||||
{provide: LOCAL_STORAGE, useClass: MockLocalStorage},
|
||||
provideRouter([]),
|
||||
],
|
||||
providers: [{provide: LOCAL_STORAGE, useClass: MockLocalStorage}, provideRouter([])],
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(SearchHistoryComponent);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<select
|
||||
[attr.id]="id()"
|
||||
[attr.name]="name()"
|
||||
[ngModel]="selectedOption()"
|
||||
(ngModelChange)="setOption($event)"
|
||||
[value]="value()"
|
||||
(change)="setOption($any($event.target).value)"
|
||||
>
|
||||
@for (item of options(); track item) {
|
||||
<option [value]="item.value">{{ item.label }}</option>
|
||||
<option [value]="item.value" [selected]="value() === item.value">{{ item.label }}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@@ -9,17 +9,12 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {Select} from './select.component';
|
||||
import {provideZonelessChangeDetection} from '@angular/core';
|
||||
|
||||
describe('Select', () => {
|
||||
let component: Select;
|
||||
let fixture: ComponentFixture<Select>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [Select],
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
});
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(Select);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
@@ -28,7 +23,7 @@ describe('Select', () => {
|
||||
fixture.componentRef.setInput('name', 'name');
|
||||
fixture.componentRef.setInput('options', []);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule} from '@angular/forms';
|
||||
import {ChangeDetectionStrategy, Component, model, forwardRef, input, signal} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, input, model} from '@angular/core';
|
||||
import {FormValueControl} from '@angular/forms/signals';
|
||||
|
||||
type SelectOptionValue = string | number | boolean;
|
||||
|
||||
@@ -19,59 +19,25 @@ export interface SelectOption {
|
||||
@Component({
|
||||
selector: 'docs-select',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [FormsModule],
|
||||
templateUrl: './select.component.html',
|
||||
styleUrls: ['./select.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => Select),
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
host: {
|
||||
class: 'docs-form-element',
|
||||
},
|
||||
})
|
||||
export class Select implements ControlValueAccessor {
|
||||
export class Select implements FormValueControl<string | null> {
|
||||
readonly value = model<string | null>(null);
|
||||
|
||||
readonly id = input.required<string>({alias: 'selectId'});
|
||||
readonly name = input.required<string>();
|
||||
readonly options = input.required<SelectOption[]>();
|
||||
readonly disabled = model(false);
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
private onChange: (value: SelectOptionValue) => void = (_: SelectOptionValue) => {};
|
||||
private onTouched: () => void = () => {};
|
||||
|
||||
protected readonly selectedOption = signal<SelectOptionValue | null>(null);
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
writeValue(value: SelectOptionValue): void {
|
||||
this.selectedOption.set(value);
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled.set(isDisabled);
|
||||
}
|
||||
readonly disabled = input(false);
|
||||
|
||||
setOption($event: SelectOptionValue): void {
|
||||
if (this.disabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedOption.set($event);
|
||||
this.onChange($event);
|
||||
this.onTouched();
|
||||
this.value.set($event as string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,17 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {SlideToggle} from './slide-toggle.component';
|
||||
import {provideZonelessChangeDetection} from '@angular/core';
|
||||
|
||||
describe('SlideToggle', () => {
|
||||
let component: SlideToggle;
|
||||
let fixture: ComponentFixture<SlideToggle>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SlideToggle],
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
});
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(SlideToggle);
|
||||
fixture.componentRef.setInput('buttonId', 'id');
|
||||
fixture.componentRef.setInput('label', 'foo');
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should toggle the value when clicked', () => {
|
||||
@@ -36,26 +31,13 @@ describe('SlideToggle', () => {
|
||||
expect(component['checked']()).toBeTrue();
|
||||
});
|
||||
|
||||
it('should call onChange and onTouched when toggled', () => {
|
||||
const onChangeSpy = jasmine.createSpy('onChangeSpy');
|
||||
const onTouchedSpy = jasmine.createSpy('onTouchedSpy');
|
||||
component.registerOnChange(onChangeSpy);
|
||||
component.registerOnTouched(onTouchedSpy);
|
||||
|
||||
component.toggle();
|
||||
|
||||
expect(onChangeSpy).toHaveBeenCalled();
|
||||
expect(onChangeSpy).toHaveBeenCalledWith(true);
|
||||
expect(onTouchedSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set active class for button when is checked', () => {
|
||||
component.writeValue(true);
|
||||
component.checked.set(true);
|
||||
fixture.detectChanges();
|
||||
const buttonElement: HTMLButtonElement = fixture.nativeElement.querySelector('input');
|
||||
expect(buttonElement.classList.contains('docs-toggle-active')).toBeTrue();
|
||||
|
||||
component.writeValue(false);
|
||||
component.checked.set(false);
|
||||
fixture.detectChanges();
|
||||
expect(buttonElement.classList.contains('docs-toggle-active')).toBeFalse();
|
||||
});
|
||||
|
||||
@@ -6,52 +6,21 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, Component, forwardRef, model, input, signal} from '@angular/core';
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
|
||||
import {ChangeDetectionStrategy, 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'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SlideToggle),
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class SlideToggle implements ControlValueAccessor {
|
||||
export class SlideToggle implements FormCheckboxControl {
|
||||
readonly buttonId = input.required<string>();
|
||||
readonly label = input.required<string>();
|
||||
readonly disabled = model(false);
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
private onChange: (value: boolean) => void = (_: boolean) => {};
|
||||
private onTouched: () => void = () => {};
|
||||
|
||||
protected readonly checked = signal(false);
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
writeValue(value: boolean): void {
|
||||
this.checked.set(value);
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled.set(isDisabled);
|
||||
}
|
||||
readonly checked = model(false);
|
||||
|
||||
// Toggles the checked state of the slide-toggle.
|
||||
toggle(): void {
|
||||
@@ -60,7 +29,5 @@ export class SlideToggle implements ControlValueAccessor {
|
||||
}
|
||||
|
||||
this.checked.update((checked) => !checked);
|
||||
this.onChange(this.checked());
|
||||
this.onTouched();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,21 +9,20 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {TabGroup} from './tab-group.component';
|
||||
import {provideZonelessChangeDetection} from '@angular/core';
|
||||
|
||||
describe('TabGroup', () => {
|
||||
let fixture: ComponentFixture<TabGroup>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(TabGroup);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should update tabs and tabpanels', () => {
|
||||
it('should update tabs and tabpanels', async () => {
|
||||
const testPanel = document.createElement('div');
|
||||
testPanel.textContent = 'panel 1';
|
||||
fixture.componentRef.setInput('tabs', [{label: 'tab 1', panel: testPanel}]);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const tabs = fixture.nativeElement.querySelectorAll('.docs-tab');
|
||||
const tabpanels = fixture.nativeElement.querySelectorAll('.docs-tab-panel');
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
afterRenderEffect,
|
||||
Renderer2,
|
||||
ElementRef,
|
||||
ChangeDetectionStrategy,
|
||||
} from '@angular/core';
|
||||
import {_IdGenerator} from '@angular/cdk/a11y';
|
||||
|
||||
@@ -31,6 +32,7 @@ import {_IdGenerator} from '@angular/cdk/a11y';
|
||||
host: {
|
||||
class: 'docs-tab-group',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TabGroup {
|
||||
private readonly _renderer = inject(Renderer2);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {provideRouter} from '@angular/router';
|
||||
import {TableOfContentsItem, TableOfContentsLevel} from '../../interfaces/index';
|
||||
import {TableOfContentsLoader} from '../../services/index';
|
||||
import {WINDOW} from '../../providers/index';
|
||||
import {provideZonelessChangeDetection, signal} from '@angular/core';
|
||||
|
||||
describe('TableOfContents', () => {
|
||||
let component: TableOfContents;
|
||||
@@ -40,11 +39,10 @@ describe('TableOfContents', () => {
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TableOfContents],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideZonelessChangeDetection(),
|
||||
{
|
||||
provide: WINDOW,
|
||||
useValue: fakeWindow,
|
||||
@@ -59,7 +57,7 @@ describe('TableOfContents', () => {
|
||||
fixture.componentRef.setInput('contentSourceElement', document.createElement('div'));
|
||||
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
@@ -69,8 +67,6 @@ describe('TableOfContents', () => {
|
||||
it('should call scrollToTop when user click on Back to the top button', () => {
|
||||
const spy = spyOn(component, 'scrollToTop');
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const button: HTMLButtonElement = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
|
||||
@@ -78,8 +74,6 @@ describe('TableOfContents', () => {
|
||||
});
|
||||
|
||||
it('should render items when tableOfContentItems has value', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const renderedItems = fixture.nativeElement.querySelectorAll('li');
|
||||
|
||||
expect(renderedItems.length).toBe(3);
|
||||
@@ -87,8 +81,6 @@ describe('TableOfContents', () => {
|
||||
});
|
||||
|
||||
it('should append level class to element', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const h2Items = fixture.nativeElement.querySelectorAll('li.docs-toc-item-h2');
|
||||
const h3Items = fixture.nativeElement.querySelectorAll('li.docs-toc-item-h3');
|
||||
|
||||
@@ -97,8 +89,6 @@ describe('TableOfContents', () => {
|
||||
});
|
||||
|
||||
it('should append active class when item is active', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const activeItem = fixture.nativeElement.querySelector('.docs-faceted-list-item-active');
|
||||
expect(activeItem).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -9,29 +9,24 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {TextField} from './text-field.component';
|
||||
import {provideZonelessChangeDetection} from '@angular/core';
|
||||
|
||||
describe('TextField', () => {
|
||||
let component: TextField;
|
||||
let fixture: ComponentFixture<TextField>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TextField],
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
});
|
||||
beforeEach(async () => {
|
||||
fixture = TestBed.createComponent(TextField);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should update DOM when setting the value via the CVA', () => {
|
||||
it('should update DOM when setting the value via the CVA', async () => {
|
||||
component.setValue('test');
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.nativeElement.querySelector('input').value).toBe('test');
|
||||
// If we were using ngModel instead of the value binding, we would get an empty string
|
||||
|
||||
@@ -7,91 +7,58 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
input,
|
||||
afterNextRender,
|
||||
forwardRef,
|
||||
signal,
|
||||
model,
|
||||
viewChild,
|
||||
} from '@angular/core';
|
||||
import {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR} from '@angular/forms';
|
||||
import {FormValueControl} from '@angular/forms/signals';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
|
||||
@Component({
|
||||
selector: 'docs-text-field',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [FormsModule, IconComponent],
|
||||
imports: [IconComponent],
|
||||
templateUrl: './text-field.component.html',
|
||||
styleUrls: ['./text-field.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => TextField),
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
host: {
|
||||
class: 'docs-form-element',
|
||||
},
|
||||
})
|
||||
export class TextField implements ControlValueAccessor {
|
||||
export class TextField implements FormValueControl<string | null> {
|
||||
readonly input = viewChild.required<ElementRef<HTMLInputElement>>('inputRef');
|
||||
|
||||
readonly name = input<string | null>(null);
|
||||
readonly name = input<string>('');
|
||||
readonly value = model<string | null>(null);
|
||||
readonly placeholder = input<string | null>(null);
|
||||
readonly disabled = model<boolean>(false);
|
||||
readonly hideIcon = input<boolean>(false);
|
||||
readonly autofocus = input<boolean>(false);
|
||||
readonly resetLabel = input<string | null>(null);
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
private onChange: (value: string) => void = (_: string) => {};
|
||||
private onTouched: () => void = () => {};
|
||||
|
||||
protected readonly value = signal<string | null>(null);
|
||||
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
if (this.autofocus()) {
|
||||
this.input().nativeElement.focus();
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
writeValue(value: unknown): void {
|
||||
this.value.set(typeof value === 'string' ? value : null);
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
registerOnTouched(fn: any): void {
|
||||
this.onTouched = fn;
|
||||
}
|
||||
|
||||
// Implemented as part of ControlValueAccessor.
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled.set(isDisabled);
|
||||
}
|
||||
|
||||
setValue(value: string): void {
|
||||
if (this.disabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.value.set(value);
|
||||
this.onChange(value);
|
||||
this.onTouched();
|
||||
}
|
||||
|
||||
clearTextField() {
|
||||
this.setValue('');
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.input().nativeElement.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,13 +34,13 @@ describe('TopLevelBannerComponent', () => {
|
||||
fixture.componentRef.setInput('id', EXAMPLE_ID);
|
||||
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should render an anchor element when link is provided', () => {
|
||||
it('should render an anchor element when link is provided', async () => {
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.componentRef.setInput('link', EXAMPLE_LINK);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const bannerElement = fixture.nativeElement.querySelector('a.docs-top-level-banner');
|
||||
expect(bannerElement).toBeTruthy();
|
||||
@@ -48,49 +48,49 @@ describe('TopLevelBannerComponent', () => {
|
||||
expect(bannerElement.textContent).toContain(EXAMPLE_TEXT);
|
||||
});
|
||||
|
||||
it('should render a div element when link is not provided', () => {
|
||||
it('should render a div element when link is not provided', async () => {
|
||||
const EXAMPLE_TEXT = 'No Link Available';
|
||||
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const bannerElement = fixture.nativeElement.querySelector('div.docs-top-level-banner');
|
||||
expect(bannerElement).toBeTruthy();
|
||||
expect(bannerElement.textContent).toContain(EXAMPLE_TEXT);
|
||||
});
|
||||
|
||||
it('should correctly render the text input', () => {
|
||||
it('should correctly render the text input', async () => {
|
||||
const EXAMPLE_TEXT = 'Lorem ipsum dolor...';
|
||||
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const bannerElement = fixture.nativeElement.querySelector('.docs-top-level-banner-cta');
|
||||
expect(bannerElement).toBeTruthy();
|
||||
expect(bannerElement.textContent).toBe(EXAMPLE_TEXT);
|
||||
});
|
||||
|
||||
it('should set hasClosed to true if the banner was closed before', () => {
|
||||
it('should set hasClosed to true if the banner was closed before', async () => {
|
||||
mockLocalStorage.getItem.and.returnValue('true');
|
||||
|
||||
fixture = TestBed.createComponent(TopLevelBannerComponent);
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.componentRef.setInput('id', EXAMPLE_ID);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.hasClosed()).toBeTrue();
|
||||
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`);
|
||||
});
|
||||
|
||||
it('should set hasClosed to false if the banner was not closed before', () => {
|
||||
it('should set hasClosed to false if the banner was not closed before', async () => {
|
||||
mockLocalStorage.getItem.and.returnValue('false');
|
||||
|
||||
fixture = TestBed.createComponent(TopLevelBannerComponent);
|
||||
fixture.componentRef.setInput('text', EXAMPLE_TEXT);
|
||||
fixture.componentRef.setInput('id', EXAMPLE_ID);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.hasClosed()).toBeFalse();
|
||||
expect(mockLocalStorage.getItem).toHaveBeenCalledWith(`${STORAGE_KEY_PREFIX}${EXAMPLE_ID}`);
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
display: inline-block;
|
||||
color: inherit;
|
||||
max-width: 100%;
|
||||
text-wrap: pretty;
|
||||
|
||||
code {
|
||||
// This can be useful on mobile if the heading contains a long code block
|
||||
|
||||
@@ -19,11 +19,9 @@ import {NavigationState} from '../../../services';
|
||||
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
|
||||
import {CopyLinkButton} from '../../copy-link-anchor/copy-link-anchor.component';
|
||||
import {TableOfContents} from '../../table-of-contents/table-of-contents.component';
|
||||
import {provideZonelessChangeDetection} from '@angular/core';
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
|
||||
describe('DocViewer', () => {
|
||||
let fixture: ComponentFixture<DocViewer>;
|
||||
let exampleContentSpy: jasmine.SpyObj<ExampleViewerContentLoader>;
|
||||
let navigationStateSpy: jasmine.SpyObj<NavigationState>;
|
||||
|
||||
@@ -83,18 +81,15 @@ describe('DocViewer', () => {
|
||||
navigationStateSpy = jasmine.createSpyObj(NavigationState, ['activeNavigationItem']);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [DocViewer],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
provideZonelessChangeDetection(),
|
||||
{provide: EXAMPLE_VIEWER_CONTENT_LOADER, useValue: exampleContentSpy},
|
||||
{provide: NavigationState, useValue: navigationStateSpy},
|
||||
],
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(DocViewer);
|
||||
});
|
||||
|
||||
it('should load doc into innerHTML', async () => {
|
||||
|
||||
+14
-15
@@ -10,7 +10,7 @@ import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {ExampleViewer} from './example-viewer.component';
|
||||
import {ExampleMetadata, ExampleViewerContentLoader} from '../../../interfaces';
|
||||
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers';
|
||||
import {Component, provideZonelessChangeDetection, ComponentRef, signal} from '@angular/core';
|
||||
import {Component, ComponentRef, signal} from '@angular/core';
|
||||
import {HarnessLoader} from '@angular/cdk/testing';
|
||||
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
|
||||
import {Clipboard} from '@angular/cdk/clipboard';
|
||||
@@ -32,10 +32,9 @@ describe('ExampleViewer', () => {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ExampleViewer],
|
||||
providers: [
|
||||
provideZonelessChangeDetection(),
|
||||
{provide: EXAMPLE_VIEWER_CONTENT_LOADER, useValue: exampleContentSpy},
|
||||
{provide: ActivatedRoute, useValue: {snapshot: {fragment: 'fragment'}}},
|
||||
],
|
||||
@@ -44,7 +43,7 @@ describe('ExampleViewer', () => {
|
||||
component = fixture.componentInstance;
|
||||
componentRef = fixture.componentRef;
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should set file extensions as tab names when all files have different extension', async () => {
|
||||
@@ -131,7 +130,7 @@ describe('ExampleViewer', () => {
|
||||
);
|
||||
|
||||
await component.renderExample();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const hiddenLine = fixture.debugElement.query(By.css('div[class="line hidden"]'));
|
||||
expect(hiddenLine).toBeTruthy();
|
||||
@@ -155,13 +154,13 @@ describe('ExampleViewer', () => {
|
||||
);
|
||||
|
||||
await component.renderExample();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const expandButton = fixture.debugElement.query(
|
||||
By.css('button[aria-label="Expand code example"]'),
|
||||
);
|
||||
expandButton.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const hiddenLine = fixture.debugElement.query(By.css('div[class="line hidden"]'));
|
||||
expect(hiddenLine).toBeNull();
|
||||
@@ -191,7 +190,7 @@ describe('ExampleViewer', () => {
|
||||
);
|
||||
componentRef.setInput('githubUrl', 'https://github.com/');
|
||||
await component.renderExample();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const githubButton = fixture.debugElement.query(
|
||||
By.css('a[aria-label="Open example on GitHub"]'),
|
||||
@@ -212,7 +211,7 @@ describe('ExampleViewer', () => {
|
||||
componentRef.setInput('stackblitzUrl', 'https://stackblitz.com/');
|
||||
|
||||
await component.renderExample();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const stackblitzButton = fixture.debugElement.query(
|
||||
By.css('a[aria-label="Edit example in StackBlitz"]'),
|
||||
@@ -261,7 +260,7 @@ describe('ExampleViewer', () => {
|
||||
it('should call clipboard service when clicked on copy example link', async () => {
|
||||
componentRef.setInput('metadata', getMetadata());
|
||||
component.expanded.set(true);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const clipboardService = TestBed.inject(Clipboard);
|
||||
const spy = spyOn(clipboardService, 'copy');
|
||||
@@ -283,7 +282,7 @@ describe('ExampleViewer', () => {
|
||||
);
|
||||
|
||||
await component.renderExample();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
// Initially, the code should be hidden.
|
||||
expect(component.showCode()).toBeFalse();
|
||||
@@ -295,7 +294,7 @@ describe('ExampleViewer', () => {
|
||||
componentRef.setInput('metadata', getMetadata());
|
||||
|
||||
await component.renderExample();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
// Initially, the code should be visible.
|
||||
expect(component.showCode()).toBeTrue();
|
||||
@@ -304,14 +303,14 @@ describe('ExampleViewer', () => {
|
||||
|
||||
const codeToggleButton = fixture.debugElement.query(By.css('.docs-example-code-toggle'));
|
||||
codeToggleButton.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.showCode()).toBeFalse();
|
||||
codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
|
||||
expect(codeContainer).toBeNull();
|
||||
|
||||
codeToggleButton.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.showCode()).toBeTrue();
|
||||
codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
|
||||
@@ -328,7 +327,7 @@ describe('ExampleViewer', () => {
|
||||
}),
|
||||
);
|
||||
await component.renderExample();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
expect(component.exampleComponent).toBeDefined();
|
||||
|
||||
const previewContainer = fixture.debugElement.query(By.css('.docs-example-viewer-preview'));
|
||||
|
||||
@@ -16,14 +16,14 @@ describe('ClickOutside', () => {
|
||||
let component: ExampleComponent;
|
||||
let fixture: ComponentFixture<ExampleComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ExampleComponent],
|
||||
providers: [provideRouter([])],
|
||||
});
|
||||
fixture = TestBed.createComponent(ExampleComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should docsClickOutside be emitted when user click outside `content` element', () => {
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('ExternalLink', () => {
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ExampleComponentWithLinks],
|
||||
providers: [
|
||||
@@ -33,7 +33,7 @@ describe('ExternalLink', () => {
|
||||
],
|
||||
});
|
||||
fixture = TestBed.createComponent(ExampleComponentWithLinks);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should external link have target=_blank attribute', () => {
|
||||
|
||||
@@ -17,4 +17,5 @@ export interface NavigationItem {
|
||||
contentPath?: string;
|
||||
status?: 'new' | 'updated';
|
||||
category?: string;
|
||||
preserveOtherCategoryOrder?: boolean; // true by default
|
||||
}
|
||||
|
||||
@@ -126,11 +126,11 @@ export interface EnumEntry extends DocEntry {
|
||||
/** Documentation entity for an Angular decorator. */
|
||||
export interface DecoratorEntry extends DocEntry {
|
||||
decoratorType: DecoratorType;
|
||||
members: PropertyEntry[] | null;
|
||||
signatures?: {
|
||||
parameters: ParameterEntry[];
|
||||
jsdocTags: JsDocTagEntry[];
|
||||
}[];
|
||||
members: MemberEntry[];
|
||||
}
|
||||
|
||||
/** Documentation entity for an Angular directives and components. */
|
||||
@@ -161,6 +161,9 @@ export type FunctionEntry = FunctionDefinitionEntry &
|
||||
implementation: FunctionSignatureMetadata;
|
||||
};
|
||||
|
||||
/** Documentation entity for a block. */
|
||||
export interface BlockEntry extends DocEntry {}
|
||||
|
||||
/** Interface describing a function with overload signatures. */
|
||||
export interface FunctionDefinitionEntry {
|
||||
name: string;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
BlockEntry,
|
||||
ClassEntry,
|
||||
ConstantEntry,
|
||||
DecoratorEntry,
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
import {CliCommand} from '../cli-entities.mjs';
|
||||
|
||||
import {
|
||||
BlockEntryRenderable,
|
||||
ClassEntryRenderable,
|
||||
ConstantEntryRenderable,
|
||||
DecoratorEntryRenderable,
|
||||
@@ -107,6 +109,13 @@ export function isFunctionEntry(entry: DocEntry): entry is FunctionEntry {
|
||||
return entry.entryType === EntryType.Function;
|
||||
}
|
||||
|
||||
/** Gets whether the given entry represents a block */
|
||||
export function isBlockEntry(entry: DocEntryRenderable): entry is BlockEntryRenderable;
|
||||
export function isBlockEntry(entry: DocEntry): entry is BlockEntry;
|
||||
export function isBlockEntry(entry: DocEntry): entry is BlockEntry {
|
||||
return entry.entryType === EntryType.Block;
|
||||
}
|
||||
|
||||
export function isInitializerApiFunctionEntry(
|
||||
entry: DocEntryRenderable,
|
||||
): entry is InitializerApiFunctionRenderable;
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ParameterEntry,
|
||||
PipeEntry,
|
||||
TypeAliasEntry,
|
||||
EntryType,
|
||||
} from '../entities.mjs';
|
||||
|
||||
import {CliCommand, CliOption} from '../cli-entities.mjs';
|
||||
@@ -75,7 +76,11 @@ export type PipeEntryRenderable = PipeEntry &
|
||||
members: MemberEntryRenderable[];
|
||||
};
|
||||
|
||||
export type DecoratorEntryRenderable = DecoratorEntry & DocEntryRenderable & HasRenderableToc;
|
||||
export type DecoratorEntryRenderable = Omit<DecoratorEntry, 'members'> &
|
||||
DocEntryRenderable &
|
||||
HasRenderableToc & {
|
||||
members: PropertyEntryRenderable[];
|
||||
};
|
||||
|
||||
/** Documentation entity for a TypeScript enum augmented transformed content for rendering. */
|
||||
export type EnumEntryRenderable = EnumEntry &
|
||||
@@ -102,6 +107,9 @@ export type FunctionSignatureMetadataRenderable = FunctionSignatureMetadata &
|
||||
params: ParameterEntryRenderable[];
|
||||
};
|
||||
|
||||
/** Documentation entity for a block augmented with transformed content for rendering. */
|
||||
export type BlockEntryRenderable = DocEntry & DocEntryRenderable;
|
||||
|
||||
/** Sub-entry for a single class or enum member augmented with transformed content for rendering. */
|
||||
export interface MemberEntryRenderable extends MemberEntry {
|
||||
htmlDescription: string;
|
||||
@@ -116,6 +124,10 @@ export interface MemberEntryRenderable extends MemberEntry {
|
||||
experimental: {version: string | undefined} | undefined;
|
||||
}
|
||||
|
||||
export interface PropertyEntryRenderable extends MemberEntryRenderable {
|
||||
type: string;
|
||||
}
|
||||
|
||||
/** Sub-entry for a class method augmented transformed content for rendering. */
|
||||
export type MethodEntryRenderable = MemberEntryRenderable &
|
||||
FunctionEntryRenderable & {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {JsDocTagEntry, MemberEntry, ParameterEntry} from '../entities.mjs';
|
||||
import {JsDocTagEntry, MemberEntry, ParameterEntry, PropertyEntry} from '../entities.mjs';
|
||||
|
||||
import {
|
||||
CodeLineRenderable,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
LinkEntryRenderable,
|
||||
MemberEntryRenderable,
|
||||
ParameterEntryRenderable,
|
||||
PropertyEntryRenderable,
|
||||
} from './renderables.mjs';
|
||||
|
||||
/** A doc entry that has jsdoc tags. */
|
||||
@@ -56,6 +57,10 @@ export interface HasMembers {
|
||||
members: MemberEntry[];
|
||||
}
|
||||
|
||||
export interface HasPropertyMembers {
|
||||
members: PropertyEntry[];
|
||||
}
|
||||
|
||||
/** A doc entry that has members groups transformed for rendering. */
|
||||
export interface HasRenderableMembersGroups {
|
||||
membersGroups: Map<string, MemberEntryRenderable[]>;
|
||||
@@ -66,6 +71,10 @@ export interface HasRenderableMembers {
|
||||
members: MemberEntryRenderable[];
|
||||
}
|
||||
|
||||
export interface HasRenderablePropertyMembers {
|
||||
members: PropertyEntryRenderable[];
|
||||
}
|
||||
|
||||
/** A doc entry that has an associated JS module name. */
|
||||
export interface HasModuleName {
|
||||
moduleName: string;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'fs';
|
||||
import {mkdirSync, readFileSync, writeFileSync} from 'fs';
|
||||
import path from 'path';
|
||||
import {CliCommand} from './cli-entities.mjs';
|
||||
import {DocEntry} from './entities.mjs';
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
isInitializerApiFunctionEntry,
|
||||
isInterfaceEntry,
|
||||
isTypeAliasEntry,
|
||||
isBlockEntry,
|
||||
} from './entities/categorization.mjs';
|
||||
import {CliCommandRenderable, DocEntryRenderable} from './entities/renderables.mjs';
|
||||
import {getClassRenderable} from './transforms/class-transforms.mjs';
|
||||
@@ -39,6 +40,7 @@ import {
|
||||
import {addModuleName} from './transforms/module-name.mjs';
|
||||
import {addRepo} from './transforms/repo.mjs';
|
||||
import {getTypeAliasRenderable} from './transforms/type-alias-transforms.mjs';
|
||||
import {getBlockRenderable} from './transforms/block-transforms.mjs';
|
||||
|
||||
export async function getRenderable(
|
||||
entry: DocEntry | CliCommand,
|
||||
@@ -73,6 +75,9 @@ export async function getRenderable(
|
||||
if (isInitializerApiFunctionEntry(entry)) {
|
||||
return getInitializerApiFunctionRenderable(entry, moduleName, repo);
|
||||
}
|
||||
if (isBlockEntry(entry)) {
|
||||
return getBlockRenderable(entry, moduleName, repo);
|
||||
}
|
||||
|
||||
// Fallback to an uncategorized renderable.
|
||||
return setEntryFlags(
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
isInitializerApiFunctionEntry,
|
||||
isInterfaceEntry,
|
||||
isTypeAliasEntry,
|
||||
isBlockEntry,
|
||||
} from './entities/categorization.mjs';
|
||||
import {CliCommandRenderable, DocEntryRenderable} from './entities/renderables.mjs';
|
||||
import {ClassReference} from './templates/class-reference';
|
||||
@@ -30,6 +31,7 @@ import {InitializerApiFunction} from './templates/initializer-api-function';
|
||||
import {TypeAliasReference} from './templates/type-alias-reference';
|
||||
import {DecoratorReference} from './templates/decorator-reference';
|
||||
import {setCurrentSymbol} from './symbol-context.mjs';
|
||||
import {BlockReference} from './templates/block-reference';
|
||||
|
||||
/** Given a doc entry, get the transformed version of the entry for rendering. */
|
||||
export function renderEntry(renderable: DocEntryRenderable | CliCommandRenderable): string {
|
||||
@@ -59,6 +61,9 @@ export function renderEntry(renderable: DocEntryRenderable | CliCommandRenderabl
|
||||
if (isInitializerApiFunctionEntry(renderable)) {
|
||||
return render(InitializerApiFunction(renderable));
|
||||
}
|
||||
if (isBlockEntry(renderable)) {
|
||||
return render(BlockReference(renderable));
|
||||
}
|
||||
|
||||
// Fall back rendering nothing while in development.
|
||||
return render(DocsReference(renderable));
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
// TODO(jelbourn): all of these CSS classes should use the `docs-` prefix.
|
||||
|
||||
export const API_REFERENCE_CONTAINER = 'docs-api';
|
||||
|
||||
export const PARAM_KEYWORD_CLASS_NAME = 'docs-param-keyword';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* @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 {h} from 'preact';
|
||||
import {BlockEntryRenderable} from '../entities/renderables.mjs';
|
||||
import {HeaderApi} from './header-api';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {API_REFERENCE_CONTAINER} from '../styling/css-classes.mjs';
|
||||
|
||||
/** Component to render a block API reference document. */
|
||||
export function BlockReference(entry: BlockEntryRenderable) {
|
||||
return (
|
||||
<div className={API_REFERENCE_CONTAINER}>
|
||||
<HeaderApi entry={entry} hideDescription={true} />
|
||||
<RawHtml value={entry.htmlDescription} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
} from '../entities/categorization.mjs';
|
||||
import {MemberEntryRenderable, MethodEntryRenderable} from '../entities/renderables.mjs';
|
||||
import {
|
||||
PARAM_KEYWORD_CLASS_NAME,
|
||||
REFERENCE_MEMBER_CARD,
|
||||
REFERENCE_MEMBER_CARD_BODY,
|
||||
REFERENCE_MEMBER_CARD_HEADER,
|
||||
|
||||
@@ -12,15 +12,18 @@ import {
|
||||
ParameterEntryRenderable,
|
||||
} from '../entities/renderables.mjs';
|
||||
import {PARAM_KEYWORD_CLASS_NAME, REFERENCE_MEMBER_CARD_ITEM} from '../styling/css-classes.mjs';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
import {DeprecatedLabel} from './deprecated-label';
|
||||
import {Parameter} from './parameter';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
|
||||
/**
|
||||
* Component to render the method-specific parts of a class's API reference.
|
||||
*/
|
||||
export function ClassMethodInfo(props: {entry: FunctionSignatureMetadataRenderable}) {
|
||||
export function ClassMethodInfo(props: {
|
||||
entry: FunctionSignatureMetadataRenderable;
|
||||
hideUsageNotes?: boolean;
|
||||
}) {
|
||||
const entry = props.entry;
|
||||
|
||||
return (
|
||||
@@ -43,7 +46,7 @@ export function ClassMethodInfo(props: {entry: FunctionSignatureMetadataRenderab
|
||||
<span className={PARAM_KEYWORD_CLASS_NAME}>@returns</span>
|
||||
<CodeSymbol code={entry.returnType} />
|
||||
</div>
|
||||
{entry.htmlUsageNotes ? (
|
||||
{entry.htmlUsageNotes && !props.hideUsageNotes ? (
|
||||
<div className={'docs-usage-notes'}>
|
||||
<span className={'docs-usage-notes-heading'}>Usage notes</span>
|
||||
<RawHtml value={entry.htmlUsageNotes} />
|
||||
|
||||
@@ -7,12 +7,45 @@
|
||||
*/
|
||||
|
||||
import {h} from 'preact';
|
||||
import {DecoratorEntryRenderable, PropertyEntryRenderable} from '../entities/renderables.mjs';
|
||||
import {
|
||||
API_REFERENCE_CONTAINER,
|
||||
REFERENCE_MEMBER_CARD,
|
||||
REFERENCE_MEMBER_CARD_BODY,
|
||||
REFERENCE_MEMBER_CARD_HEADER,
|
||||
REFERENCE_MEMBER_CARD_ITEM,
|
||||
REFERENCE_MEMBERS,
|
||||
} from '../styling/css-classes.mjs';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
import {HeaderApi} from './header-api';
|
||||
import {RawHtml} from './raw-html';
|
||||
import {SectionApi} from './section-api';
|
||||
import {SectionDescription} from './section-description';
|
||||
import {SectionUsageNotes} from './section-usage-notes';
|
||||
import {SectionApi} from './section-api';
|
||||
import {API_REFERENCE_CONTAINER} from '../styling/css-classes.mjs';
|
||||
import {DecoratorEntryRenderable} from '../entities/renderables.mjs';
|
||||
|
||||
export const signatureCard = (
|
||||
name: string,
|
||||
member: PropertyEntryRenderable,
|
||||
opts: {id: string},
|
||||
) => {
|
||||
return (
|
||||
<div id={opts.id} class={REFERENCE_MEMBER_CARD}>
|
||||
<header class={REFERENCE_MEMBER_CARD_HEADER}>
|
||||
<h3>{name}</h3>
|
||||
<div>
|
||||
<CodeSymbol code={member.type} />
|
||||
</div>
|
||||
</header>
|
||||
<div class={REFERENCE_MEMBER_CARD_BODY}>
|
||||
<div className={`${REFERENCE_MEMBER_CARD_ITEM}`}>
|
||||
<p>
|
||||
<RawHtml value={member.htmlDescription} />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** Component to render a decorator API reference document. */
|
||||
export function DecoratorReference(entry: DecoratorEntryRenderable) {
|
||||
@@ -20,6 +53,13 @@ export function DecoratorReference(entry: DecoratorEntryRenderable) {
|
||||
<div className={API_REFERENCE_CONTAINER}>
|
||||
<HeaderApi entry={entry} />
|
||||
<SectionApi entry={entry} />
|
||||
<div className={REFERENCE_MEMBERS}>
|
||||
{entry.members.map((member, index) =>
|
||||
signatureCard(member.name, member, {
|
||||
id: `${member.name}_${index}`,
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
<SectionDescription entry={entry} />
|
||||
<SectionUsageNotes entry={entry} />
|
||||
</div>
|
||||
|
||||
@@ -22,23 +22,22 @@ import {printInitializerFunctionSignatureLine} from '../transforms/code-transfor
|
||||
import {getFunctionMetadataRenderable} from '../transforms/function-transforms.mjs';
|
||||
import {ClassMethodInfo} from './class-method-info';
|
||||
import {CodeSymbol} from './code-symbols';
|
||||
import {DeprecationWarning} from './deprecation-warning';
|
||||
import {HeaderApi} from './header-api';
|
||||
import {HighlightTypeScript} from './highlight-ts';
|
||||
import {SectionApi} from './section-api';
|
||||
import {SectionDescription} from './section-description';
|
||||
import {SectionUsageNotes} from './section-usage-notes';
|
||||
import {DeprecationWarning} from './deprecation-warning';
|
||||
|
||||
export const signatureCard = (
|
||||
name: string,
|
||||
signature: FunctionSignatureMetadataRenderable,
|
||||
opts: {id: string},
|
||||
printSignaturesAsHeader: boolean,
|
||||
opts: {id: string; printSignaturesAsHeader: boolean; hideUsageNotes?: boolean},
|
||||
) => {
|
||||
return (
|
||||
<div id={opts.id} class={REFERENCE_MEMBER_CARD}>
|
||||
<header class={REFERENCE_MEMBER_CARD_HEADER}>
|
||||
{printSignaturesAsHeader ? (
|
||||
{opts.printSignaturesAsHeader ? (
|
||||
<HighlightTypeScript
|
||||
code={printInitializerFunctionSignatureLine(
|
||||
name,
|
||||
@@ -57,7 +56,7 @@ export const signatureCard = (
|
||||
)}
|
||||
</header>
|
||||
<div class={REFERENCE_MEMBER_CARD_BODY}>
|
||||
<ClassMethodInfo entry={signature} />
|
||||
<ClassMethodInfo entry={signature} hideUsageNotes={opts.hideUsageNotes} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -75,14 +74,11 @@ export function FunctionReference(entry: FunctionEntryRenderable) {
|
||||
<SectionApi entry={entry} />
|
||||
<div className={REFERENCE_MEMBERS}>
|
||||
{entry.signatures.map((s, i) =>
|
||||
signatureCard(
|
||||
s.name,
|
||||
getFunctionMetadataRenderable(s, entry.moduleName, entry.repo),
|
||||
{
|
||||
id: `${s.name}_${i}`,
|
||||
},
|
||||
signatureCard(s.name, getFunctionMetadataRenderable(s, entry.moduleName, entry.repo), {
|
||||
id: `${s.name}_${i}`,
|
||||
printSignaturesAsHeader,
|
||||
),
|
||||
hideUsageNotes: true,
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {DocsPillRow} from './docs-pill-row';
|
||||
export function HeaderApi(props: {
|
||||
entry: DocEntryRenderable | PipeEntryRenderable;
|
||||
showFullDescription?: boolean;
|
||||
hideDescription?: boolean;
|
||||
}) {
|
||||
const entry = props.entry;
|
||||
|
||||
@@ -60,12 +61,14 @@ export function HeaderApi(props: {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<section
|
||||
className={'docs-reference-description'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: props.showFullDescription ? entry.htmlDescription : entry.shortHtmlDescription,
|
||||
}}
|
||||
></section>
|
||||
{!props.hideDescription && (
|
||||
<section
|
||||
className={'docs-reference-description'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: props.showFullDescription ? entry.htmlDescription : entry.shortHtmlDescription,
|
||||
}}
|
||||
></section>
|
||||
)}
|
||||
|
||||
<DocsPillRow links={entry.additionalLinks} />
|
||||
</header>
|
||||
@@ -123,13 +126,6 @@ function tagInVersionString(label: string, tag: {version: string | undefined} |
|
||||
return <>{label}</>;
|
||||
}
|
||||
|
||||
function tagInVersionTooltip(
|
||||
label: string,
|
||||
tag: {version: string | undefined} | undefined,
|
||||
): string {
|
||||
return tag?.version ? `${label} since ${tag.version}` : label;
|
||||
}
|
||||
|
||||
function getEntryTypeDisplayName(entryType: EntryType | string): string {
|
||||
switch (entryType) {
|
||||
case EntryType.NgModule:
|
||||
|
||||
@@ -40,14 +40,10 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
|
||||
|
||||
<div class={REFERENCE_MEMBERS}>
|
||||
{entry.callFunction.signatures.map((s, i) =>
|
||||
signatureCard(
|
||||
s.name,
|
||||
getFunctionMetadataRenderable(s, entry.moduleName, entry.repo),
|
||||
{
|
||||
id: `${s.name}_${i}`,
|
||||
},
|
||||
signatureCard(s.name, getFunctionMetadataRenderable(s, entry.moduleName, entry.repo), {
|
||||
id: `${s.name}_${i}`,
|
||||
printSignaturesAsHeader,
|
||||
),
|
||||
}),
|
||||
)}
|
||||
|
||||
{entry.subFunctions.reduce(
|
||||
@@ -59,8 +55,8 @@ export function InitializerApiFunction(entry: InitializerApiFunctionRenderable)
|
||||
getFunctionMetadataRenderable(s, entry.moduleName, entry.repo),
|
||||
{
|
||||
id: `${entry.name}_${s.name}_${i}`,
|
||||
printSignaturesAsHeader,
|
||||
},
|
||||
printSignaturesAsHeader,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {DocEntry} from '../entities.mjs';
|
||||
import {BlockEntryRenderable} from '../entities/renderables.mjs';
|
||||
import {
|
||||
addHtmlAdditionalLinks,
|
||||
addHtmlDescription,
|
||||
addHtmlJsDocTagComments,
|
||||
addHtmlUsageNotes,
|
||||
setEntryFlags,
|
||||
} from './jsdoc-transforms.mjs';
|
||||
import {addModuleName} from './module-name.mjs';
|
||||
import {addRepo} from './repo.mjs';
|
||||
|
||||
/** Given an unprocessed block entry, get the fully renderable block entry. */
|
||||
export function getBlockRenderable(
|
||||
blockEntry: DocEntry,
|
||||
moduleName: string,
|
||||
repo: string,
|
||||
): BlockEntryRenderable {
|
||||
return setEntryFlags(
|
||||
addHtmlAdditionalLinks(
|
||||
addHtmlDescription(
|
||||
addHtmlUsageNotes(
|
||||
addHtmlJsDocTagComments(addRepo(addModuleName(blockEntry, moduleName), repo)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
addHtmlUsageNotes,
|
||||
setEntryFlags,
|
||||
} from './jsdoc-transforms.mjs';
|
||||
import {addRenderablePropertyMembers} from './member-transforms.mjs';
|
||||
import {addModuleName} from './module-name.mjs';
|
||||
import {addRepo} from './repo.mjs';
|
||||
|
||||
@@ -25,14 +26,24 @@ export async function getDecoratorRenderable(
|
||||
moduleName: string,
|
||||
repo: string,
|
||||
): Promise<DecoratorEntryRenderable> {
|
||||
const decoratorEntryWithMembers = {
|
||||
...decoratorEntry,
|
||||
members: decoratorEntry.members ?? [],
|
||||
};
|
||||
|
||||
return setEntryFlags(
|
||||
await addRenderableCodeToc(
|
||||
addHtmlAdditionalLinks(
|
||||
addHtmlUsageNotes(
|
||||
addHtmlJsDocTagComments(
|
||||
addHtmlDescription(addRepo(addModuleName(decoratorEntry, moduleName), repo)),
|
||||
await addRenderablePropertyMembers(
|
||||
addHtmlAdditionalLinks(
|
||||
addHtmlUsageNotes(
|
||||
addHtmlJsDocTagComments(
|
||||
addHtmlDescription(
|
||||
addRepo(addModuleName(decoratorEntryWithMembers, moduleName), repo),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
decoratorEntry.name,
|
||||
),
|
||||
),
|
||||
) as DecoratorEntryRenderable;
|
||||
|
||||
@@ -10,7 +10,14 @@ import {MemberEntry, MemberTags, MemberType, type DocEntry} from '../entities.mj
|
||||
import {isHiddenEntry} from '../entities/categorization.mjs';
|
||||
import type {MemberEntryRenderable} from '../entities/renderables.mjs';
|
||||
|
||||
import {HasMembers, HasModuleName, HasRenderableMembers, HasRepo} from '../entities/traits.mjs';
|
||||
import {
|
||||
HasMembers,
|
||||
HasModuleName,
|
||||
HasPropertyMembers,
|
||||
HasRenderableMembers,
|
||||
HasRenderablePropertyMembers,
|
||||
HasRepo,
|
||||
} from '../entities/traits.mjs';
|
||||
import {addRenderableCodeToc} from './code-transforms.mjs';
|
||||
|
||||
import {
|
||||
@@ -85,8 +92,7 @@ export async function addRenderableMembers<T extends HasMembers & HasModuleName
|
||||
): Promise<T & HasRenderableMembers> {
|
||||
const members = (
|
||||
await Promise.all(
|
||||
// TODO: remove `?? []` when components repo is updated to include members array on type aliases.
|
||||
(entry.members ?? [])
|
||||
entry.members
|
||||
.filter((member) => !isHiddenEntry(member))
|
||||
.map((member) => {
|
||||
if (member.memberType === MemberType.Interface) {
|
||||
@@ -112,3 +118,11 @@ export async function addRenderableMembers<T extends HasMembers & HasModuleName
|
||||
members,
|
||||
};
|
||||
}
|
||||
|
||||
export async function addRenderablePropertyMembers<
|
||||
T extends HasPropertyMembers & HasModuleName & HasRepo,
|
||||
>(entry: T, parentName: string): Promise<T & HasRenderablePropertyMembers> {
|
||||
return {
|
||||
...((await addRenderableMembers(entry, parentName)) as T & HasRenderablePropertyMembers),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ async function getFileAndContent(workingDir: string, path: string): Promise<File
|
||||
return {content, path};
|
||||
}
|
||||
|
||||
// Use once we restore the StackBlitz feature
|
||||
async function createPostData(
|
||||
exampleDir: string,
|
||||
config: ZipConfig,
|
||||
|
||||
@@ -18,6 +18,11 @@ const LINK_EXEMPT = new Set([
|
||||
'readonly',
|
||||
'disabled',
|
||||
'hidden',
|
||||
'state',
|
||||
'group',
|
||||
'animation',
|
||||
'transition',
|
||||
'trigger',
|
||||
]);
|
||||
|
||||
export function shouldLinkSymbol(symbol: string): boolean {
|
||||
|
||||
@@ -6,3 +6,9 @@ this is code
|
||||
<docs-code path="./example-with-region.ts" />
|
||||
|
||||
<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="./messages.fr.xlf" />
|
||||
|
||||
<docs-code header="Property names should not be linked" language="ts">
|
||||
const form = {
|
||||
state: ['']
|
||||
};
|
||||
</docs-code>
|
||||
|
||||
@@ -47,4 +47,9 @@ describe('markdown to html', () => {
|
||||
const codeBlock = markdownDocument.querySelectorAll('code')[3];
|
||||
expect(codeBlock).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not link property names in object literals', () => {
|
||||
const codeBlock = markdownDocument.querySelectorAll('code')[4];
|
||||
expect(codeBlock?.innerHTML).not.toContain('<a href="/api/animations/state">state</a>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,5 @@
|
||||
import {InjectionToken, signal} from '@angular/core';
|
||||
|
||||
export const IS_SEARCH_DIALOG_OPEN = new InjectionToken('', {
|
||||
providedIn: 'root',
|
||||
factory: () => signal(false),
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ import {isPlatformBrowser} from '@angular/common';
|
||||
import {InjectionToken, PLATFORM_ID, inject} from '@angular/core';
|
||||
|
||||
export const LOCAL_STORAGE = new InjectionToken<Storage | null>('LOCAL_STORAGE', {
|
||||
providedIn: 'root',
|
||||
factory: () => getStorage(inject(PLATFORM_ID)),
|
||||
});
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ export class NavigationState {
|
||||
|
||||
private expand(item: NavigationItem): void {
|
||||
// Add item to the expanded items list
|
||||
this._expandedItems.update((expandedItems) => {
|
||||
this._expandedItems.update(() => {
|
||||
return [...(this.actualExpandedItems() ?? []), {...item, isExpanded: true}];
|
||||
});
|
||||
|
||||
|
||||
@@ -105,17 +105,23 @@ export class Search {
|
||||
const content = hitItem._snippetResult.content;
|
||||
const hierarchy = hitItem._snippetResult.hierarchy;
|
||||
const category = hitItem.hierarchy?.lvl0 ?? null;
|
||||
const hasSubLabel = hierarchy?.lvl2 || hierarchy?.lvl3 || hierarchy?.lvl4;
|
||||
|
||||
const lvl1Value = hierarchy?.lvl1?.value || hitItem.hierarchy?.lvl1;
|
||||
|
||||
const sublabelSnippet = this.getBestSnippetForMatch(hitItem);
|
||||
|
||||
// If no lvl1, promote sublabel to label to avoid empty titles
|
||||
const label = lvl1Value || sublabelSnippet || '';
|
||||
|
||||
const hasSubLabel = lvl1Value && (hierarchy?.lvl2 || hierarchy?.lvl3 || hierarchy?.lvl4);
|
||||
|
||||
return {
|
||||
id: hitItem.objectID,
|
||||
type: hitItem.hierarchy.lvl0 === 'Tutorials' ? 'code' : 'doc',
|
||||
url: hitItem.url,
|
||||
|
||||
labelHtml: this.parseLabelToHtml(hitItem._snippetResult.hierarchy?.lvl1?.value ?? ''),
|
||||
subLabelHtml: this.parseLabelToHtml(
|
||||
hasSubLabel ? this.getBestSnippetForMatch(hitItem) : null,
|
||||
),
|
||||
labelHtml: this.parseLabelToHtml(label),
|
||||
subLabelHtml: this.parseLabelToHtml(hasSubLabel ? sublabelSnippet : null),
|
||||
contentHtml: content ? this.parseLabelToHtml(content.value) : null,
|
||||
package: category === 'Reference' ? extractPackageNameFromUrl(hitItem.url) : null,
|
||||
|
||||
@@ -222,16 +228,19 @@ function matched(snippet: SnippetResult | undefined): boolean {
|
||||
*/
|
||||
function wait(ms: number, signal: AbortSignal): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => resolve(), ms);
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
signal.addEventListener(
|
||||
'abort',
|
||||
() => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error('Operation aborted'));
|
||||
},
|
||||
{once: true},
|
||||
);
|
||||
const onAbort = () => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error('Operation aborted'));
|
||||
};
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
signal.removeEventListener('abort', onAbort);
|
||||
resolve();
|
||||
}, ms);
|
||||
|
||||
signal.addEventListener('abort', onAbort, {once: true});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
padding: 0.75rem 1.5rem;
|
||||
width: max-content;
|
||||
color: transparent;
|
||||
user-select: none;
|
||||
|
||||
// border gradient / background
|
||||
--angle: 90deg;
|
||||
@@ -66,7 +67,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.3s ease, background 0.3s ease;
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
background 0.3s ease;
|
||||
color: var(--primary-contrast);
|
||||
}
|
||||
|
||||
@@ -80,7 +83,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.3s ease, background 0.3s ease;
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
background 0.3s ease;
|
||||
color: var(--primary-contrast);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
--symbolic-pink: oklch(63.67% 0.254 13.47); // #ff025c
|
||||
--symbolic-orange: oklch(64.73% 0.23769984683784018 33.18328352127882); // #fe3700
|
||||
--symbolic-yellow: oklch(78.09% 0.163 65.69); // #fd9f28
|
||||
--symbolic-green: oklch(67.83% 0.229 142.73); // #00b80a
|
||||
--symbolic-green: oklch(48% 0.16 145); // #008B38
|
||||
--symbolic-cyan: oklch(67.05% 0.1205924489987394 181.34025902203868); // #00ad9a
|
||||
--symbolic-magenta: oklch(51.74% 0.25453048882711515 315.26261625862725); // #9c00c8
|
||||
--symbolic-teal: oklch(57.59% 0.083 230.58); // #3f82a1
|
||||
@@ -179,7 +179,8 @@
|
||||
--white-to-light-blue-diagonal-gradient: linear-gradient(
|
||||
132.83deg,
|
||||
hsl(from color-mix(in srgb, var(--bright-blue), white 60%) h s l / 0) 45.38%,
|
||||
hsl(from color-mix(in srgb, var(--bright-blue), white 60%) h s l / 0.3) 159.03%);
|
||||
hsl(from color-mix(in srgb, var(--bright-blue), white 60%) h s l / 0.3) 159.03%
|
||||
);
|
||||
|
||||
--white-to-light-pink-diagonal-gradient: linear-gradient(
|
||||
135deg,
|
||||
@@ -230,8 +231,7 @@
|
||||
// for the "unfilled" portion of the word that hasn't
|
||||
// been highlighted by the gradient
|
||||
--gray-unfilled: var(--gray-400);
|
||||
--webgl-page-background: var(var(--page-background))
|
||||
--webgl-gray-unfilled: var(--gray-400);
|
||||
--webgl-page-background: var(var(--page-background)) --webgl-gray-unfilled: var(--gray-400);
|
||||
}
|
||||
|
||||
@mixin dark-mode-definitions() {
|
||||
@@ -261,9 +261,16 @@
|
||||
--super-green: color-mix(in srgb, oklch(79.12% 0.257 155.13), var(--full-contrast) 70%);
|
||||
--light-vivid-pink-to-light-electric-violet-horizontal-gradient:
|
||||
linear-gradient(hsl(from var(--gray-1000) h s l / 0.9), hsl(from var(--gray-1000) h s l / 0.9)),
|
||||
linear-gradient(hsl(from var(--full-contrast) h s l / 0.2), hsl(from var(--full-contrast) h s l / 0.2)),
|
||||
linear-gradient(
|
||||
hsl(from var(--full-contrast) h s l / 0.2),
|
||||
hsl(from var(--full-contrast) h s l / 0.2)
|
||||
),
|
||||
var(--pink-to-purple-horizontal-gradient);
|
||||
--white-to-light-pink-diagonal-gradient: color-mix(in srgb, var(--french-violet), var(--full-contrast) 60%);
|
||||
--white-to-light-pink-diagonal-gradient: color-mix(
|
||||
in srgb,
|
||||
var(--french-violet),
|
||||
var(--full-contrast) 60%
|
||||
);
|
||||
|
||||
--deprecated-dark: oklch(30% 0.02 304.04); // Base color
|
||||
--deprecated-docs-bg: var(--deprecated-dark); // Alias specifically for deprecated docs
|
||||
@@ -281,7 +288,7 @@
|
||||
var(--full-contrast) 65%
|
||||
);
|
||||
--symbolic-yellow: color-mix(in srgb, oklch(78.09% 0.163 65.69), var(--full-contrast) 65%);
|
||||
--symbolic-green: color-mix(in srgb, oklch(67.83% 0.229 142.73), var(--full-contrast) 65%);
|
||||
--symbolic-green: color-mix(in srgb, oklch(48% 0.16 145), var(--full-contrast) 65%);
|
||||
--symbolic-cyan: color-mix(
|
||||
in srgb,
|
||||
oklch(67.05% 0.1205924489987394 181.34025902203868),
|
||||
@@ -301,10 +308,7 @@
|
||||
);
|
||||
|
||||
--page-bg-radial-gradient: radial-gradient(circle, black 0%, black 100%);
|
||||
--soft-pink-radial-gradient:
|
||||
linear-gradient(119.77deg,
|
||||
hsl(from color-mix(in srgb, var(--french-violet), white 60%) h s l / 0) 24.32%,
|
||||
hsl(from color-mix(in srgb, var(--french-violet), white 60%) h s l / 0.3) 116.25%);
|
||||
--soft-pink-radial-gradient: var(--hot-pink-to-electric-violet-radial-gradient);
|
||||
|
||||
// Use light values hot pink and electric violet since this gradient used for
|
||||
// the decorative header which looks the same in both modes
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@use './links' as links;
|
||||
|
||||
/* Common styles for the API & CLI references */
|
||||
@mixin reference-common() {
|
||||
.docs-code {
|
||||
@@ -62,10 +64,10 @@
|
||||
}
|
||||
|
||||
.docs-reference-section-heading {
|
||||
padding-block-start: 3rem;
|
||||
margin-block-start: 3rem;
|
||||
|
||||
&--sub {
|
||||
padding-block-start: 1rem;
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
& > a {
|
||||
@@ -73,18 +75,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
.docs-api {
|
||||
.docs-api,
|
||||
.docs-cli {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
.docs-anchor {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
// Show copy link button when hovering the heading
|
||||
&:hover docs-copy-link-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
a:not(.docs-github-links):not(.docs-pill):not(.docs-example-github-link) {
|
||||
&[href^='http:'],
|
||||
&[href^='https:'] {
|
||||
@include links.external-link-with-icon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.docs-reference-members {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
--z-index-top-level-banner: 150;
|
||||
--z-index-nav: 100;
|
||||
--z-index-cookie-consent: 200;
|
||||
--z-index-nav-tutorial: 75;
|
||||
--z-index-content: 50;
|
||||
--z-index-icon: 10;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef} from '@angular/core';
|
||||
import {
|
||||
DirEnt,
|
||||
FSWatchCallback,
|
||||
@@ -65,14 +64,6 @@ export class MockLocalStorage implements Pick<Storage, 'getItem' | 'setItem'> {
|
||||
}
|
||||
}
|
||||
|
||||
export class FakeChangeDetectorRef implements ChangeDetectorRef {
|
||||
markForCheck(): void {}
|
||||
detach(): void {}
|
||||
checkNoChanges(): void {}
|
||||
reattach(): void {}
|
||||
detectChanges(): void {}
|
||||
}
|
||||
|
||||
export class FakeWebContainer extends WebContainer {
|
||||
fakeSpawn: FakeWebContainerProcess | undefined = undefined;
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Languages</h2>
|
||||
<h2>Community translations</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://angular.cn/" title="简体中文版">简体中文版</a>
|
||||
@@ -107,6 +107,9 @@
|
||||
<li>
|
||||
<a href="https://angular.kr/" title="한국어">한국어</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://angular-docs.ru/" title="Русский">Русский</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('Footer', () => {
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [Footer],
|
||||
providers: [
|
||||
@@ -34,7 +34,7 @@ describe('Footer', () => {
|
||||
});
|
||||
fixture = TestBed.createComponent(Footer);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('Navigation', () => {
|
||||
const fakeSearch = {};
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
TestBed.configureTestingModule({
|
||||
imports: [Navigation],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
@@ -58,23 +58,23 @@ describe('Navigation', () => {
|
||||
|
||||
fixture = TestBed.createComponent(Navigation);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should append active class to DOCS_ROUTE when DOCS_ROUTE is active', () => {
|
||||
it('should append active class to DOCS_ROUTE when DOCS_ROUTE is active', async () => {
|
||||
component.activeRouteItem.set(PAGE_PREFIX.DOCS);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const docsLink = fixture.debugElement.query(By.css('a[href="/docs"]')).parent?.nativeElement;
|
||||
|
||||
expect(docsLink).toHaveClass('adev-nav-item--active');
|
||||
});
|
||||
|
||||
it('should not have active class when activeRouteItem is null', () => {
|
||||
it('should not have active class when activeRouteItem is null', async () => {
|
||||
component.activeRouteItem.set(null);
|
||||
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const docsLink = fixture.debugElement.query(By.css('a[href="/docs"]')).nativeElement;
|
||||
const referenceLink = fixture.debugElement.query(By.css('a[href="/reference"]')).nativeElement;
|
||||
@@ -83,14 +83,14 @@ describe('Navigation', () => {
|
||||
expect(referenceLink).not.toHaveClass('adev-nav-item--active');
|
||||
});
|
||||
|
||||
it('should call themeManager.setTheme(dark) when user tries to set dark theme', () => {
|
||||
it('should call themeManager.setTheme(dark) when user tries to set dark theme', async () => {
|
||||
const openThemePickerButton = fixture.debugElement.query(
|
||||
By.css('button[aria-label^="Change theme. Current theme:"]'),
|
||||
).nativeElement;
|
||||
const setThemeSpy = spyOn(fakeThemeManager, 'setTheme');
|
||||
|
||||
openThemePickerButton.click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const setDarkModeButton = fixture.debugElement.query(
|
||||
By.css('button[aria-label="Set dark theme"]'),
|
||||
|
||||
@@ -17,14 +17,14 @@ describe('ProgressBarComponent', () => {
|
||||
let fixture: ComponentFixture<ProgressBarComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ProgressBarComponent],
|
||||
providers: [provideRouter([])],
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(ProgressBarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
// This test often timeouts
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
[navigationItems]="navigationItems"
|
||||
[displayItemsToLevel]="maxVisibleLevelsOnSecondaryNav()"
|
||||
[expandableLevel]="maxVisibleLevelsOnSecondaryNav()"
|
||||
[preserveOtherCategoryOrder]="true"
|
||||
(linkClicked)="close()"
|
||||
/>
|
||||
}
|
||||
@@ -26,6 +27,7 @@
|
||||
[collapsableLevel]="level + maxVisibleLevelsOnSecondaryNav()"
|
||||
[expandableLevel]="level + maxVisibleLevelsOnSecondaryNav() + 1"
|
||||
[navigationItems]="[item]"
|
||||
[preserveOtherCategoryOrder]="item.preserveOtherCategoryOrder ?? false"
|
||||
[displayItemsToLevel]="level + maxVisibleLevelsOnSecondaryNav() + 1"
|
||||
(linkClicked)="close()"
|
||||
/>
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ describe('SecondaryNavigation', () => {
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [SecondaryNavigation],
|
||||
providers: [
|
||||
@@ -33,7 +33,7 @@ describe('SecondaryNavigation', () => {
|
||||
});
|
||||
fixture = TestBed.createComponent(SecondaryNavigation);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
|
||||
@@ -19,7 +19,7 @@ import {EmbeddedTutorialManager} from '../embedded-tutorial-manager.service';
|
||||
|
||||
import {CodeEditor, REQUIRED_FILES} from './code-editor.component';
|
||||
import {CodeMirrorEditor} from './code-mirror-editor.service';
|
||||
import {FakeChangeDetectorRef, TutorialType} from '@angular/docs';
|
||||
import {TutorialType} from '@angular/docs';
|
||||
import {MatTooltipHarness} from '@angular/material/tooltip/testing';
|
||||
|
||||
const files = [
|
||||
@@ -45,7 +45,6 @@ class FakeCodeMirrorEditor implements Partial<CodeMirrorEditor> {
|
||||
openFiles = this.files;
|
||||
}
|
||||
const codeMirrorEditorService = new FakeCodeMirrorEditor();
|
||||
const fakeChangeDetectorRef = new FakeChangeDetectorRef();
|
||||
|
||||
describe('CodeEditor', () => {
|
||||
let component: CodeEditor;
|
||||
@@ -63,10 +62,6 @@ describe('CodeEditor', () => {
|
||||
provide: CodeMirrorEditor,
|
||||
useValue: codeMirrorEditorService,
|
||||
},
|
||||
{
|
||||
provide: ChangeDetectorRef,
|
||||
useValue: fakeChangeDetectorRef,
|
||||
},
|
||||
{
|
||||
provide: EmbeddedTutorialManager,
|
||||
useValue: {
|
||||
@@ -93,7 +88,7 @@ describe('CodeEditor', () => {
|
||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
@@ -105,7 +100,7 @@ describe('CodeEditor', () => {
|
||||
const codeMirrorEditorInitSpy = spyOn(codeMirrorEditorService, 'init');
|
||||
|
||||
fixture = TestBed.createComponent(CodeEditor);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
component = fixture.componentInstance;
|
||||
|
||||
expect(component.codeEditorWrapperRef()).toBeDefined();
|
||||
|
||||
@@ -50,8 +50,6 @@ describe('Preview', () => {
|
||||
const component = fixture.componentInstance;
|
||||
const iframeElement = fixture.debugElement.query(By.css('iframe'));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
return {
|
||||
fixture,
|
||||
component,
|
||||
@@ -64,27 +62,28 @@ describe('Preview', () => {
|
||||
};
|
||||
};
|
||||
|
||||
it('should set iframe src', () => {
|
||||
it('should set iframe src', async () => {
|
||||
const {fixture, PREVIEW_URL} = beforeEach();
|
||||
await fixture.whenStable();
|
||||
const iframeElement = fixture.debugElement.query(By.css('iframe'));
|
||||
expect(iframeElement?.nativeElement?.src).toBe(PREVIEW_URL);
|
||||
});
|
||||
|
||||
it('should not render loading elements if the loadingStep is READY or ERROR', () => {
|
||||
it('should not render loading elements if the loadingStep is READY or ERROR', async () => {
|
||||
const {fixture, fakeNodeRuntimeState, getLoadingElementsWrapper} = beforeEach();
|
||||
|
||||
fakeNodeRuntimeState.loadingStep.set(LoadingStep.READY);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getLoadingElementsWrapper()).toBeNull();
|
||||
|
||||
fakeNodeRuntimeState.loadingStep.set(LoadingStep.ERROR);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(getLoadingElementsWrapper()).toBeNull();
|
||||
});
|
||||
|
||||
it('should render the correct loading element based on loadingStep', () => {
|
||||
it('should render the correct loading element based on loadingStep', async () => {
|
||||
const {fixture, fakeNodeRuntimeState} = beforeEach();
|
||||
|
||||
function getDebugElements(): Record<number, DebugElement | null> {
|
||||
@@ -113,7 +112,7 @@ describe('Preview', () => {
|
||||
componentLoadingStep++
|
||||
) {
|
||||
fakeNodeRuntimeState.loadingStep.set(componentLoadingStep);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const loadingElements = getDebugElements();
|
||||
|
||||
@@ -136,8 +135,6 @@ describe('Preview', () => {
|
||||
|
||||
fakeNodeRuntimeState.loadingStep.set(LoadingStep.ERROR);
|
||||
fakeNodeRuntimeState.error.set({message: 'Error message', type: undefined});
|
||||
fixture.detectChanges();
|
||||
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(fixture.debugElement.query(By.directive(PreviewError))).toBeDefined();
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('DocsComponent', () => {
|
||||
getContent: (id: string) => undefined,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [DocsComponent],
|
||||
providers: [
|
||||
@@ -40,7 +40,7 @@ describe('DocsComponent', () => {
|
||||
});
|
||||
fixture = TestBed.createComponent(DocsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
|
||||
@@ -81,13 +81,9 @@ describe('Animation', () => {
|
||||
const layerObjects = new Map<string, HTMLElement>();
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AnimationHost],
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(AnimationHost);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
animation = component.animation;
|
||||
|
||||
// Store all layer objects in a map for easier access.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {Component, inject, signal} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, inject, signal} from '@angular/core';
|
||||
import {ActivatedRoute, RouterLink, UrlSegment} from '@angular/router';
|
||||
import {Search, SearchItem, RelativeLink, SearchResultItem} from '@angular/docs';
|
||||
|
||||
@@ -14,6 +14,7 @@ import {Search, SearchItem, RelativeLink, SearchResultItem} from '@angular/docs'
|
||||
imports: [SearchItem, RouterLink, RelativeLink],
|
||||
templateUrl: './not-found.html',
|
||||
styleUrl: './not-found.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class NotFound {
|
||||
private readonly search = inject(Search);
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('TutorialPlayground', () => {
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
class FakeEmbeddedTutorialManager {
|
||||
fetchAndSetTutorialFiles() {}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ describe('TutorialPlayground', () => {
|
||||
|
||||
fixture = TestBed.createComponent(TutorialPlayground);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
|
||||
@@ -15,24 +15,21 @@ describe('ApiItemLabel', () => {
|
||||
let fixture: ComponentFixture<ApiItemLabel>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ApiItemLabel],
|
||||
});
|
||||
fixture = TestBed.createComponent(ApiItemLabel);
|
||||
});
|
||||
|
||||
it('should by default display short label for Class', () => {
|
||||
it('should by default display short label for Class', async () => {
|
||||
fixture.componentRef.setInput('type', ApiItemType.CLASS);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const label = fixture.nativeElement.innerText;
|
||||
|
||||
expect(label).toBe('C');
|
||||
});
|
||||
|
||||
it('should display short label for Class', () => {
|
||||
it('should display short label for Class', async () => {
|
||||
fixture.componentRef.setInput('type', ApiItemType.CLASS);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const label = fixture.nativeElement.innerText;
|
||||
|
||||
|
||||
+4
-4
@@ -54,18 +54,18 @@ describe('ApiItemsSection', () => {
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render list of all APIs of provided group', () => {
|
||||
it('should render list of all APIs of provided group', async () => {
|
||||
fixture.componentRef.setInput('group', fakeGroup);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const apis = fixture.debugElement.queryAll(By.css('.adev-api-items-section-grid li'));
|
||||
|
||||
expect(apis.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should display deprecated icon for deprecated API', () => {
|
||||
it('should display deprecated icon for deprecated API', async () => {
|
||||
fixture.componentRef.setInput('group', fakeGroup);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const deprecatedApiIcons = fixture.debugElement.queryAll(
|
||||
By.css('.adev-api-items-section-grid li .adev-item-attribute'),
|
||||
|
||||
-1
@@ -54,7 +54,6 @@ describe('ApiReferenceDetailsPage', () => {
|
||||
const harness = await RouterTestingHarness.create();
|
||||
fixture = harness.fixture;
|
||||
component = await harness.navigateByUrl('/', ApiReferenceDetailsPage);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
|
||||
+8
-2
@@ -4,7 +4,13 @@
|
||||
|
||||
<div class="adev-reference-list-filter">
|
||||
<div class="adev-reference-list-query-filter">
|
||||
<docs-text-field name="query" placeholder="Filter" [(ngModel)]="query" />
|
||||
<docs-text-field placeholder="Filter" [formField]="form.query" />
|
||||
|
||||
<docs-select
|
||||
selectId="package-entry-filter"
|
||||
[options]="packageOptions()"
|
||||
[formField]="form.selectedPackage"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="adev-reference-list-status">
|
||||
@@ -23,7 +29,7 @@
|
||||
@for (itemType of itemTypes; track itemType) {
|
||||
<li
|
||||
class="adev-reference-list-type-filter-item"
|
||||
[class.adev-reference-list-type-filter-item-active]="type() === itemType"
|
||||
[class.adev-reference-list-type-filter-item-active]="form.type().value() === itemType"
|
||||
(click)="setItemType(itemType)"
|
||||
>
|
||||
<docs-api-item-label [type]="itemType" mode="short" class="docs-api-item-label" />
|
||||
|
||||
+36
-36
@@ -8,16 +8,16 @@
|
||||
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {Location} from '@angular/common';
|
||||
import {signal} from '@angular/core';
|
||||
import {TextField} from '@angular/docs';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {RouterTestingHarness} from '@angular/router/testing';
|
||||
import {ApiItem} from '../interfaces/api-item';
|
||||
import {ApiItemType} from '../interfaces/api-item-type';
|
||||
import ApiReferenceList, {ALL_TYPES_KEY, STATUSES} from './api-reference-list.component';
|
||||
import {ApiReferenceManager} from './api-reference-manager.service';
|
||||
import {signal} from '@angular/core';
|
||||
import {ApiItemType} from '../interfaces/api-item-type';
|
||||
import {RouterTestingHarness} from '@angular/router/testing';
|
||||
import {provideRouter} from '@angular/router';
|
||||
import {Location} from '@angular/common';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {TextField} from '@angular/docs';
|
||||
import {ApiItem} from '../interfaces/api-item';
|
||||
|
||||
describe('ApiReferenceList', () => {
|
||||
let component: ApiReferenceList;
|
||||
@@ -93,7 +93,7 @@ describe('ApiReferenceList', () => {
|
||||
}),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ApiReferenceList],
|
||||
providers: [
|
||||
@@ -103,52 +103,52 @@ describe('ApiReferenceList', () => {
|
||||
});
|
||||
fixture = TestBed.createComponent(ApiReferenceList);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display only items which contains provided query when query is not empty', () => {
|
||||
it('should display only items which contains provided query when query is not empty', async () => {
|
||||
fixture.componentRef.setInput('query', 'Item1');
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.filteredGroups()![0].items).toEqual([fakeItem1]);
|
||||
expect(component.filteredGroups()[0].items).toEqual([fakeItem1]);
|
||||
});
|
||||
|
||||
it('should display items which match the query by group title', () => {
|
||||
it('should display items which match the query by group title', async () => {
|
||||
fixture.componentRef.setInput('query', 'Fake Group');
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
// Should find all items whose group title contains the query
|
||||
expect(component.filteredGroups()![0].items.length).toBeGreaterThan(0);
|
||||
expect(component.filteredGroups()![0].items).toContain(fakeItem1);
|
||||
expect(component.filteredGroups()[0].items.length).toBeGreaterThan(0);
|
||||
expect(component.filteredGroups()[0].items).toContain(fakeItem1);
|
||||
});
|
||||
|
||||
it('should display only class items when user selects Class in the Type select', () => {
|
||||
it('should display only class items when user selects Class in the Type select', async () => {
|
||||
fixture.componentRef.setInput('type', ApiItemType.CLASS);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.type()).toEqual(ApiItemType.CLASS);
|
||||
expect(component.filteredGroups()![0].items).toEqual([fakeItem2]);
|
||||
expect(component.form.type().value()).toEqual(ApiItemType.CLASS);
|
||||
expect(component.filteredGroups()[0].items).toEqual([fakeItem2]);
|
||||
});
|
||||
|
||||
it('should set selected type when provided type is different than selected', async () => {
|
||||
expect(component.type()).toBe(ALL_TYPES_KEY);
|
||||
expect(component.form.type().value()).toBe(ALL_TYPES_KEY);
|
||||
component.setItemType(ApiItemType.BLOCK);
|
||||
await RouterTestingHarness.create(`/api?type=${ApiItemType.BLOCK}`);
|
||||
expect(component.type()).toBe(ApiItemType.BLOCK);
|
||||
expect(component.form.type().value()).toBe(ApiItemType.BLOCK);
|
||||
});
|
||||
|
||||
it('should reset selected type when provided type is equal to selected', async () => {
|
||||
component.setItemType(ApiItemType.BLOCK);
|
||||
const harness = await RouterTestingHarness.create(`/api?type=${ApiItemType.BLOCK}`);
|
||||
expect(component.type()).toBe(ApiItemType.BLOCK);
|
||||
expect(component.form.type().value()).toBe(ApiItemType.BLOCK);
|
||||
|
||||
component.setItemType(ApiItemType.BLOCK);
|
||||
harness.navigateByUrl(`/api`);
|
||||
expect(component.type()).toBe(ALL_TYPES_KEY);
|
||||
expect(component.form.type().value()).toBe(ALL_TYPES_KEY);
|
||||
});
|
||||
|
||||
it('should set the value of the queryParam equal to the query text field', async () => {
|
||||
@@ -164,15 +164,15 @@ describe('ApiReferenceList', () => {
|
||||
const location = TestBed.inject(Location);
|
||||
|
||||
const textField = fixture.debugElement.query(By.directive(TextField));
|
||||
(textField.componentInstance as TextField).setValue('item1');
|
||||
fixture.componentInstance.form.query().value.set('item1');
|
||||
await fixture.whenStable();
|
||||
expect(location.path()).toBe(`?query=item1`);
|
||||
|
||||
component.setItemType(ApiItemType.BLOCK);
|
||||
fixture.componentInstance.form.type().value.set(ApiItemType.BLOCK);
|
||||
await fixture.whenStable();
|
||||
expect(location.path()).toBe(`?query=item1&type=${ApiItemType.BLOCK}`);
|
||||
|
||||
fixture.componentRef.setInput('status', STATUSES.experimental);
|
||||
fixture.componentInstance.form.status().value.set(STATUSES.experimental);
|
||||
await fixture.whenStable();
|
||||
expect(location.path()).toBe(
|
||||
`?query=item1&type=${ApiItemType.BLOCK}&status=${STATUSES.experimental}`,
|
||||
@@ -192,30 +192,30 @@ describe('ApiReferenceList', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not display deprecated and developer-preview and experimental items when status is set to stable', () => {
|
||||
it('should not display deprecated and developer-preview and experimental items when status is set to stable', async () => {
|
||||
fixture.componentRef.setInput('status', STATUSES.stable);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.filteredGroups()![0].items).toEqual([fakeItem1, fakeItem2]);
|
||||
});
|
||||
|
||||
it('should only display deprecated items when status is set to deprecated', () => {
|
||||
it('should only display deprecated items when status is set to deprecated', async () => {
|
||||
fixture.componentRef.setInput('status', STATUSES.deprecated);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.filteredGroups()![0].items).toEqual([fakeDeprecatedFeaturedItem]);
|
||||
});
|
||||
|
||||
it('should only display developer-preview items when status is set to developer-preview', () => {
|
||||
it('should only display developer-preview items when status is set to developer-preview', async () => {
|
||||
fixture.componentRef.setInput('status', STATUSES.developerPreview);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.filteredGroups()![0].items).toEqual([fakeDeveloperPreviewItem]);
|
||||
});
|
||||
|
||||
it('should only display experimental items when status is set to experimental', () => {
|
||||
it('should only display experimental items when status is set to experimental', async () => {
|
||||
fixture.componentRef.setInput('status', STATUSES.experimental);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
expect(component.filteredGroups()![0].items).toEqual([fakeExperimentalItem]);
|
||||
});
|
||||
|
||||
+57
-34
@@ -6,33 +6,33 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {CdkMenuModule} from '@angular/cdk/menu';
|
||||
import {KeyValuePipe} from '@angular/common';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
EnvironmentInjector,
|
||||
afterNextRender,
|
||||
computed,
|
||||
effect,
|
||||
inject,
|
||||
input,
|
||||
linkedSignal,
|
||||
viewChild,
|
||||
afterNextRender,
|
||||
EnvironmentInjector,
|
||||
effect,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import ApiItemsSection from '../api-items-section/api-items-section.component';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {TextField} from '@angular/docs';
|
||||
import {KeyValuePipe} from '@angular/common';
|
||||
import {Params, Router} from '@angular/router';
|
||||
import {ApiItemType} from '../interfaces/api-item-type';
|
||||
import {ApiReferenceManager} from './api-reference-manager.service';
|
||||
import ApiItemLabel from '../api-item-label/api-item-label.component';
|
||||
import {ApiLabel} from '../pipes/api-label.pipe';
|
||||
import {ApiItemsGroup} from '../interfaces/api-items-group';
|
||||
import {CdkMenuModule} from '@angular/cdk/menu';
|
||||
import {Select, SelectOption, TextField} from '@angular/docs';
|
||||
import {FormField, form} from '@angular/forms/signals';
|
||||
import {MatChipsModule} from '@angular/material/chips';
|
||||
import {Params, Router} from '@angular/router';
|
||||
import ApiItemLabel from '../api-item-label/api-item-label.component';
|
||||
import ApiItemsSection from '../api-items-section/api-items-section.component';
|
||||
import {ApiItemType} from '../interfaces/api-item-type';
|
||||
import {ApiItemsGroup} from '../interfaces/api-items-group';
|
||||
import {ApiLabel} from '../pipes/api-label.pipe';
|
||||
import {ApiReferenceManager} from './api-reference-manager.service';
|
||||
|
||||
export const ALL_TYPES_KEY = 'All';
|
||||
export const ALL_PACKAGES = 'All';
|
||||
export const STATUSES = {
|
||||
stable: 1,
|
||||
developerPreview: 2,
|
||||
@@ -46,12 +46,13 @@ export const DEFAULT_STATUS = STATUSES.stable | STATUSES.developerPreview | STAT
|
||||
imports: [
|
||||
ApiItemsSection,
|
||||
ApiItemLabel,
|
||||
FormsModule,
|
||||
TextField,
|
||||
ApiLabel,
|
||||
CdkMenuModule,
|
||||
MatChipsModule,
|
||||
KeyValuePipe,
|
||||
Select,
|
||||
FormField,
|
||||
],
|
||||
templateUrl: './api-reference-list.component.html',
|
||||
styleUrls: ['./api-reference-list.component.scss'],
|
||||
@@ -61,19 +62,33 @@ export default class ApiReferenceList {
|
||||
// services
|
||||
private readonly apiReferenceManager = inject(ApiReferenceManager);
|
||||
private readonly router = inject(Router);
|
||||
private readonly filterInput = viewChild.required(TextField, {read: ElementRef});
|
||||
private readonly filterInput = viewChild.required(TextField);
|
||||
private readonly injector = inject(EnvironmentInjector);
|
||||
|
||||
// inputs
|
||||
readonly queryInput = input<string | undefined>('', {alias: 'query'});
|
||||
readonly typeInput = input<string | undefined>(ALL_TYPES_KEY, {alias: 'type'});
|
||||
readonly statusInput = input<number | undefined>(DEFAULT_STATUS, {alias: 'status'});
|
||||
protected selectedPackageInput = input<string | undefined>(ALL_PACKAGES, {alias: 'package'});
|
||||
|
||||
// inputs are route binded, they can reset to undefined
|
||||
// also we want a writable state, so we use a linked signal
|
||||
protected query = linkedSignal(() => this.queryInput() ?? '');
|
||||
public type = linkedSignal(() => this.typeInput() ?? ALL_TYPES_KEY);
|
||||
private status = linkedSignal(() => this.statusInput() ?? DEFAULT_STATUS);
|
||||
public form = form(
|
||||
linkedSignal(() => ({
|
||||
query: this.queryInput() ?? '',
|
||||
status: this.statusInput() ?? DEFAULT_STATUS,
|
||||
type: this.typeInput() ?? ALL_TYPES_KEY,
|
||||
selectedPackage: this.selectedPackageInput() ?? ALL_PACKAGES,
|
||||
})),
|
||||
);
|
||||
|
||||
protected packageOptions = computed<SelectOption[]>(() => [
|
||||
{label: 'All Packages', value: ALL_PACKAGES},
|
||||
...this.apiReferenceManager.apiGroups().map((group) => ({
|
||||
label: group.title,
|
||||
value: group.id,
|
||||
})),
|
||||
]);
|
||||
|
||||
// const state
|
||||
protected readonly itemTypes = Object.values(ApiItemType);
|
||||
@@ -87,9 +102,9 @@ export default class ApiReferenceList {
|
||||
};
|
||||
|
||||
readonly filteredGroups = computed((): ApiItemsGroup[] => {
|
||||
const query = this.query().toLocaleLowerCase();
|
||||
const status = this.status();
|
||||
const type = this.type();
|
||||
const query = this.form.query().value().toLocaleLowerCase();
|
||||
const status = this.form.status().value();
|
||||
const type = this.form.type().value();
|
||||
return this.apiReferenceManager
|
||||
.apiGroups()
|
||||
.map((group) => ({
|
||||
@@ -112,7 +127,12 @@ export default class ApiReferenceList {
|
||||
);
|
||||
}),
|
||||
}))
|
||||
.filter((group) => group.items.length > 0);
|
||||
.filter(
|
||||
(group) =>
|
||||
group.items.length > 0 &&
|
||||
(this.form.selectedPackage().value() === ALL_PACKAGES ||
|
||||
group.id === this.form.selectedPackage().value()),
|
||||
);
|
||||
});
|
||||
|
||||
constructor() {
|
||||
@@ -121,10 +141,9 @@ export default class ApiReferenceList {
|
||||
afterNextRender(
|
||||
{
|
||||
write: () => {
|
||||
// Lord forgive me for I have sinned
|
||||
// Use the CVA to focus when https://github.com/angular/angular/issues/31133 is implemented
|
||||
// Why not improve this once https://github.com/orgs/angular/projects/60?pane=issue&itemId=143488813 is done
|
||||
if (matchMedia('(hover: hover) and (pointer:fine)').matches) {
|
||||
scheduleOnIdle(() => filterInput.nativeElement.querySelector('input').focus());
|
||||
scheduleOnIdle(() => filterInput.focus());
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -135,9 +154,13 @@ export default class ApiReferenceList {
|
||||
effect(() => {
|
||||
// We'll only set the params if we deviate from the default values
|
||||
const params: Params = {
|
||||
'query': this.query() || null,
|
||||
'type': this.type() === ALL_TYPES_KEY ? null : this.type(),
|
||||
'status': this.status() === DEFAULT_STATUS ? null : this.status(),
|
||||
'query': this.form.query().value() || null,
|
||||
'type': this.form.type().value() === ALL_TYPES_KEY ? null : this.form.type().value(),
|
||||
'status': this.form.status().value() === DEFAULT_STATUS ? null : this.form.status().value(),
|
||||
'package':
|
||||
this.form.selectedPackage().value() === ALL_PACKAGES
|
||||
? null
|
||||
: this.form.selectedPackage().value(),
|
||||
};
|
||||
|
||||
this.router.navigate([], {
|
||||
@@ -152,11 +175,11 @@ export default class ApiReferenceList {
|
||||
}
|
||||
|
||||
setItemType(itemType: ApiItemType): void {
|
||||
this.type.update((type) => (type === itemType ? ALL_TYPES_KEY : itemType));
|
||||
this.form.type().value.update((type) => (type === itemType ? ALL_TYPES_KEY : itemType));
|
||||
}
|
||||
|
||||
setStatus(status: number): void {
|
||||
this.status.update((previousStatus) => {
|
||||
this.form.status().value.update((previousStatus) => {
|
||||
if (this.isStatusSelected(status)) {
|
||||
return previousStatus & ~status; // Clear the bit
|
||||
} else {
|
||||
@@ -166,7 +189,7 @@ export default class ApiReferenceList {
|
||||
}
|
||||
|
||||
isStatusSelected(status: number): boolean {
|
||||
return (this.status() & status) === status;
|
||||
return (this.form.status().value() & status) === status;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user