Compare commits

..

330 Commits

Author SHA1 Message Date
Miles Malerba 4039f073f8 Merge remote-tracking branch 'upstream/main' into prototype/signal-forms 2025-08-26 15:14:08 -07:00
Taygan Caldwell a43057c059 refactor(core): Create a base effect interface and prototype to be used by both angular and wiz. (#62931)
Add a common effect interface and prototype to be used to create the wiz and angular effects.

PR Close #62931
2025-08-26 14:48:32 -07:00
Miles Malerba 7f19f7b825 refactor(forms): rename private access utilities (#63407) 2025-08-26 14:38:59 -07:00
Miles Malerba b23c31a496 fix(forms): fix some things that g3 complains about (#63405)
* fix(forms): fix some things that g3 complains about

* fixup! fix(forms): fix some things that g3 complains about
2025-08-26 12:09:56 -07:00
Joey Perrott dfa2044af9 build: fix strict deps failure (#63403)
Fix the remaining strict deps failure

PR Close #63403
2025-08-26 11:46:43 -07:00
Jessica Janiuk a0388409e3 fix(compiler): fixes animations on elements with structural directives (#63390)
The animate instructions were getting applied to the container comment nodes as well as the element nodes. This prevents that on the compiler level.

fixes: #63371

PR Close #63390
2025-08-26 09:47:07 -07:00
Joey Perrott c15b8ed631 build: enable strict deps enforcement for ts_project (#63375)
Enable strict_deps testings for all ts_project and ng_project targets in the repo

PR Close #63375
2025-08-26 09:46:30 -07:00
Avcharov Hryhorii aff8bb222c refactor(devtools): provide signalGraphEnabled via settings service (#63374)
signalGraphEnabled was previously passed down the component tree.
This change refactors the logic to use the settings service instead,
which already holds the value and allows sharing it across components.

PR Close #63374
2025-08-26 09:42:12 -07:00
Avcharov Hryhorii 1cb113cdec fix(devtools): prevent profiler bars flickering after change detection (#63350)
Without trackBy cdkVirtualFor rerenders full list after in each update

PR Close #63350
2025-08-26 09:41:08 -07:00
Miles Malerba aef4b05c62 build: cleanup accidental changes from merging main 2025-08-25 18:53:25 -07:00
Miles Malerba 52c7bde082 build: fix outdated build rule references 2025-08-25 17:13:20 -07:00
Miles Malerba 71557463f0 Merge remote-tracking branch 'upstream/main' into prototype/signal-forms 2025-08-25 17:09:02 -07:00
Miles Malerba 447b6e958f refactor(forms): move addDefaultField out of public API (#63389) 2025-08-25 17:05:25 -07:00
Angular Robot 67d4449db4 build: update cross-repo angular dependencies (#63379)
See associated pull request for more information.

PR Close #63379
2025-08-25 16:29:23 -07:00
Joey Perrott 2fcafb65c5 build: rename defaults2.bzl to defaults.bzl (#63383)
Use defaults.bzl for the common macros

PR Close #63383
2025-08-25 15:45:01 -07:00
Angular Robot 1febf523e3 build: update dependency @bazel/ibazel to v0.26.10 (#63373)
See associated pull request for more information.

PR Close #63373
2025-08-25 15:39:30 -07:00
Angular Robot fca6122d09 build: lock file maintenance (#63359)
See associated pull request for more information.

PR Close #63359
2025-08-25 15:35:54 -07:00
Matthieu Riegler 47dc4ffd1a docs(docs-infra): share markdown rendering and highlighting code between api-gen and guides (#63357)
This reduces code duplication and improves the maintability.

PR Close #63357
2025-08-25 15:33:55 -07:00
Miles Malerba f90ca1cfcf Merge remote-tracking branch 'upstream/main' into prototype/signal-forms 2025-08-25 15:17:37 -07:00
Angular Robot 5a111064b8 build: update cross-repo angular dependencies (#63338)
See associated pull request for more information.

PR Close #63338
2025-08-25 10:49:29 -07:00
Miles Malerba d416beee7b fix(form): set select value after option renders (#63340) 2025-08-25 09:24:29 -07:00
arturovt 1ebba5442f refactor(common): drop X-Request-URL (#61943)
This commit drops the `X-Request-URL` header. It was a non-standard HTTP response header, deprecated long ago and never part of any official specification. Modern browsers now expose the final URL via the `XMLHttpRequest.responseURL` property, as defined in the WHATWG spec.

PR Close #61943
2025-08-25 08:45:19 -07:00
Angular Robot d025a07329 build: update all non-major dependencies (#63344)
See associated pull request for more information.

PR Close #63344
2025-08-25 08:37:54 -07:00
Angular Robot c716ccffec build: update bazel dependencies (#63343)
See associated pull request for more information.

PR Close #63343
2025-08-25 08:36:09 -07:00
Jessica Janiuk 9139fe0d95 fix(core): Fixes animate.leave binding to a string with spaces (#63366)
This fixes the rare case that someone uses binding syntax with `animate.leave` providing a value with a string that has spaces in it. For example:

```
<example `[animate.leave]="'class-a class-b"` />
```

fixes: #63365

PR Close #63366
2025-08-25 08:33:30 -07:00
AleksanderBodurri 4dec06d724 fix(devtools): correct path to load fonts for devtools devserver (#63358)
This broke in c35c0c7f2f. This commit changes the path to load the external stylesheet to reflect the new directory path.

PR Close #63358
2025-08-25 08:31:56 -07:00
Jessica Janiuk 5af3222e35 docs: update paths and references to animations guides (#63368)
This updates the adev navigation to consider the new enter-and-leave path to be the definitive animations guide.
It also prefixes all the prior animations package docs with legacy and updates all links.

PR Close #63368
2025-08-25 08:21:57 -07:00
Jessica Janiuk 1e3196b0c9 docs: add important service worker note (#63364)
This adds an important note about the service worker to indicate we are not accepting new features.

PR Close #63364
2025-08-25 08:18:31 -07:00
Jessica Janiuk 3b674e8cb3 docs: update animations guide important note (#63363)
PR Close #63363
2025-08-25 08:17:43 -07:00
Matthieu Riegler 0cd75257de refactor(docs-infra): ensure we're not creating nested anchor elements. (#63352)
With #63162, we added automatic link creation on API symbols. The side effect was that we created HTML in some specific cases. For example with the docs-card we could have nested anchor elements which is invalid.

This commit prevent creating nested links inside docs-cards.
fixes #63351

PR Close #63352
2025-08-25 08:16:20 -07:00
Matthieu Riegler 05df189731 refactor(docs-infra): rename testing into test. (#63352)
We usually name `test` our test directories and `testing` for the testing helpers.

PR Close #63352
2025-08-25 08:16:20 -07:00
Joey Perrott 3df1dccebe refactor: various build and import specificer fixes for strict deps (#63323)
Change direct deps in bazel targets and import specifiers within files to maintain strict deps requirements ahead of enabling strict deps tests in the repo

PR Close #63323
2025-08-22 14:45:00 -07:00
Shuaib Hasan Akib a9d4ccfff3 docs(docs-infra): Replace page enums by constants (#63335)
PR Close #63335
2025-08-22 14:42:52 -07:00
Matthieu Riegler 983cc2f6fd ci: add myself to review fw-general (#63337)
Also adding myself to be assigned some adev PRs

PR Close #63337
2025-08-22 12:27:36 -07:00
Shuaib Hasan Akib a84227263d docs(docs-infra): fix spelling (#63334)
PR Close #63334
2025-08-22 12:01:49 -07:00
Angular Robot 56a8408de4 build: update github/codeql-action action to v3.29.11 (#63325)
See associated pull request for more information.

PR Close #63325
2025-08-22 11:27:52 -07:00
Angular Robot c6c08f6365 build: update cross-repo angular dependencies (#63315)
See associated pull request for more information.

PR Close #63315
2025-08-22 11:25:30 -07:00
Jessica Janiuk 37ead5ffb5 fix(core): Ensures @for loop animations never get cancelled (#63328)
There's special logic in place to prevent duplicate nodes from showing up in the case when an `@if` toggles a view quickly. This had the unfortunate side effect of causing `@for` leave animations to get cancelled when an add and remove happened simultaneously, even if it was a different index. This fix prevents that from happening in the `@for` loop case.

fixes: #63307

PR Close #63328
2025-08-22 11:23:15 -07:00
Jessica Janiuk 18c9352d21 fix(core): fix memory leak with leaving nodes tracking (#63328)
This ensures the nodes are properly cleaned up from the leaving nodes map.

PR Close #63328
2025-08-22 11:23:15 -07:00
Matthieu Riegler d16a992b12 refactor(core): remove the deprecated ImportedNgModuleProviders type. (#63277)
Use the `EnvironmentProviders` type instead

PR Close #63277
2025-08-22 11:22:22 -07:00
Miles Malerba 0b00039a45 fix(forms): ensure pending status propagates to parent (#63333)
* fix(forms): ensure pending status propagates to parent

* test(forms): add some additional tests for pending status

* refactor(forms): better variable naming

use more descriptive names for variables

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

---------

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
2025-08-22 11:09:13 -07:00
Miles Malerba 4065c5f715 build(forms): restore api golden generation (#63336) 2025-08-22 11:07:39 -07:00
Miles Malerba 37661b6ec1 Merge remote-tracking branch 'upstream/main' into prototype/signal-forms 2025-08-21 17:18:20 -07:00
Miles Malerba 170a4376ea docs(forms): address feedback on #63293 (#63322)
* docs(forms): address feedback on #63293

* another round of feedback addressed
2025-08-21 17:12:44 -07:00
Leon Senft 197b393aa6 perf(forms): enable tree shaking of unused errors and properties
The `ValidationError` and `AggregateProperty` classes used static
factory methods to create instances. This pattern created a hard
dependency from the base classes to all of their subclasses and
variations.

As a result, any application importing even a single validator (e.g.,
required) or property would inadvertently pull in the code for all other
validators and properties, preventing effective tree-shaking and
increasing bundle size.

This refactoring removes the static factory methods and replaces them
with standalone, exported functions or properties. This decouples the
individual validators and properties, allowing bundlers to properly
perform dead code elimination.
2025-08-21 16:58:59 -07:00
Miles Malerba 2bbd1c972d feat(forms): allow setting custom error message (#63314)
* feat(forms): allow setting custom error message

allows standard validator logic to specify custom error message

* address feedback

* address feedback
2025-08-21 14:54:57 -07:00
Miles Malerba 6434bcce0c fix(forms): minor cleanup for disabled API (#63318)
* fix(forms): minor cleanup for disabled API

rename DisabledReason.reason to DisabledReason.message for consistency with errors, and allow unconditional disabling with message

* address feedback
2025-08-21 14:48:40 -07:00
Angular Robot 6f541b81d2 build: update dependency rollup to v4.47.1 (#63304)
See associated pull request for more information.

PR Close #63304
2025-08-21 14:13:28 -07:00
Angular Robot 36a36ea034 docs: update Angular CLI help (#63296)
Updated Angular CLI help contents.

PR Close #63296
2025-08-21 14:11:36 -07:00
Angular Robot 69df2c0f8a build: update dependency cypress to v15 (#63303)
See associated pull request for more information.

PR Close #63303
2025-08-21 11:43:24 -07:00
aparziale 7a336c159d refactor(migrations): centralize parseTemplate method (#62983)
refactor - Moved parseTemplate logic to a shared utility function to improve code reuse and maintainability

PR Close #62983
2025-08-21 11:41:32 -07:00
aparziale 6ddb250391 feat(migrations): add migration to convert ngClass to use class (#62983)
feat #61661 - add migration to convert ngClass to use class

PR Close #62983
2025-08-21 11:41:32 -07:00
Matthieu Riegler 7d6ae95807 docs(docs-infra): fix mermaid rendering (#63311)
A recent update of mermaid change the html output. `rect` became `path`.

fixes #63308

PR Close #63311
2025-08-21 10:06:32 -07:00
Matthieu Riegler da06239a41 docs(docs-infra): Add exemption list for linking symbols. (#63309)
PR Close #63309
2025-08-21 10:05:55 -07:00
Matthieu Riegler 0dd95c503f feat(forms): Add FormArrayDirective (#55880)
The `FormArrayDirective` will allow to have a `FormArray` as a top-level form object.

* `NgControlStatusGroup` directive will be applied to the `FormArrayDirective`
* `NgForm` will still create a `FormGroup`

Fixes angular#30264

BREAKING CHANGE: This new directive will conflict with existing FormArray directives or formArray inputs on the same element.

PR Close #55880
2025-08-21 09:38:37 -07:00
Matthieu Riegler 318718ce64 refactor(forms): extract shared logic from FormGroupDirective (#55880)
Ahead of the implementation of `FormArrayDirective`, extract the shared logic into an abstract class.

PR Close #55880
2025-08-21 09:38:37 -07:00
Kristiyan Kostadinov 1b0c84a5ec docs: release notes for the v20.2.1 release 2025-08-21 12:54:22 +02:00
Matthieu Riegler 827c3c15f4 fix(compiler): Keep paraenthesis in Nullish + Boolean expression. (#63292)
Ts 5.9 introduced a regression coming from 5.8 when parenthesis aren't generated for expressions like (`(a ?? b) && c`).
This fix works around this explicitly specifying that we want to keep those parenthesis that we're aware of in this specific case;

This change can be reverted if the root issue (https://github.com/microsoft/TypeScript/issues/61369) is fixed. (but let's keep the tests in any case for the coverage)

fixes #63287

PR Close #63292
2025-08-21 12:15:52 +02:00
Miles Malerba aa51dcdcdc feat(forms): add support for binding to select (#63300)
Adds test for select, and textarea (which was untested)
2025-08-20 14:14:06 -07:00
Jan Martin 62c00ab254 release: bump Angular DevTools version to 1.0.43 (#63299)
PR Close #63299
2025-08-20 13:39:25 -07:00
Miles Malerba 46e3de0f0a cleanup unrelated formatting changes on prototype branch 2025-08-20 10:14:44 -07:00
Miles Malerba dc966ceb6a Merge remote-tracking branch 'upstream/main' into prototype/signal-forms 2025-08-20 10:06:52 -07:00
Matthieu Riegler edbbbb1e44 refactor(core): remove NgProbeToken class (#63279)
This class served no purpose and was deprecated in by #51396

G3 also has no usage anymore.

PR Close #63279
2025-08-20 16:21:38 +00:00
Angular Robot 6d88ce1525 build: update @angular/ng-dev digest to c93dfc8 (#63289)
See associated pull request for more information.

PR Close #63289
2025-08-20 16:17:10 +00:00
Joey Perrott 009b068988 build: reenable firefox tests for devtools (#63288)
Reenable firefox tests for devtools as the spaces issues with path names is addressed post v7 of bazel

PR Close #63288
2025-08-20 16:08:21 +00:00
Alan Agius 1c5fb1e5c1 build: enforce frozen lockfile mode (#63260)
This commit adds `lockfile_mode=error` to the `.bazelrc` file. This change ensures that any future builds will fail if the lock file is not up-to-date with the `BUILD.bazel` file, preventing inconsistencies and encouraging developers to commit updated lock files.

PR Close #63260
2025-08-20 16:00:05 +00:00
Angular Robot 790143eb9b build: update cross-repo angular dependencies (#63284)
See associated pull request for more information.

PR Close #63284
2025-08-20 15:58:42 +00:00
Matthieu Riegler d8bdf56145 refactor(core): remove deprecated PACKAGE_ROOT_URL token (#63274)
The symbol was deprecated by #51222 and already has no usages in g3.

PR Close #63274
2025-08-20 15:45:12 +00:00
Matthieu Riegler 239fe6f83e refactor(core): remove public symbol for defineInjectable (#63270)
This public symbol was deprecated in v8.
The private symbol `ɵɵdefineInjectable` remains available.

PR Close #63270
2025-08-20 15:44:14 +00:00
Devin Chasanoff fd66f97885 docs: add ai design patterns to adev/ai (#63095)
PR Close #63095
2025-08-20 15:43:36 +00:00
Leon Senft c303673a65 refactor(forms): relocate the signal based forms implementation
Move the signal based forms implementation from
`@angular/forms/experimental` to `@angular/forms/signals` in preparation
of merging into `main`.
2025-08-20 08:15:02 -07:00
Matthieu Riegler 25f593ce2a refactor(common): removengModuleFactory input of NgComponentOutlet (#62838)
This was deprecated by #44815

BREAKING CHANGE: NgModuleFactory has been removed, use NgModule instead.

PR Close #62838
2025-08-20 13:27:18 +00:00
Matthieu Riegler 4dfa6f1942 docs(docs-infra): auto-links to API entries (#63162)
This is a first step to create links to API entries automatically.
It adds support for
- code spans (`` `symbol` ``)
- code blocks (`` ```some code here``` ``)

Links are generated from on a K/V map of API entries (symbol => package name).
The map is generated from the API manifest that we're already generating for the API docs.

For the moment, entries with identical names are skipped.

PR Close #63162
2025-08-20 13:05:48 +00:00
Matthieu Riegler 2f34f9f272 docs: remove IsolatedShadowDom from the 20.2 release (#63281)
The change has been reverted by #63142

PR Close #63281
2025-08-20 13:05:12 +00:00
Jessica Janiuk a0ee6bdfe4 release: cut the v21.0.0-next.0 release 2025-08-20 14:45:26 +02:00
Jessica Janiuk d1df128725 docs: release notes for the v20.2.0 release 2025-08-20 14:31:15 +02:00
Jessica Janiuk e6fea458ed docs: release notes for the v20.1.8 release 2025-08-20 14:14:32 +02:00
Kristiyan Kostadinov 7bf4906f27 fix(compiler-cli): account for expression with type arguments during HMR extraction (#63261)
Fixes that the HMR extraction logic didn't accoubnt for expressions with type arguments (e.g. `viewChild('foo', {read: TemplateRef<unknown>})`).

Fixes #63240.

PR Close #63261
2025-08-20 11:31:43 +00:00
Angular Robot 3d7b31c113 build: update dependency playwright-core to v1.55.0 (#63272)
See associated pull request for more information.

PR Close #63272
2025-08-20 11:14:22 +00:00
Angular Robot 086ae5e1b5 build: update all non-major dependencies (#63264)
See associated pull request for more information.

PR Close #63264
2025-08-20 09:53:35 +00:00
ivanwonder eeeaadc7e9 fix(language-service): Support to resolve the re-export component. (#62585)
In the context of TypeScript (TS), a re-exported symbol is considered a distinct symbol.
This means that a developer can choose to import either the re-exported symbol or
the original symbol. However, in the context of Angular, the re-exported symbol
is treated as the same component because it uses the same selector.

This pull request will utilize the most recent re-export component file to
resolve the module specifier.

PR Close #62585
2025-08-20 09:39:56 +00:00
Matthieu Riegler 6a6cb01bb3 docs: update NG_VALIDATORS examples to use forwardRef (#63247)
PR Close #63247
2025-08-20 09:27:20 +00:00
Matthieu Riegler 2aa9775078 docs(docs-infra): remove temporary shiki dts (#63253)
The tooling now supports reading `d.mts` typing files

PR Close #63253
2025-08-20 09:26:17 +00:00
Matthieu Riegler c3c9a9eed3 refactor(devtools): fix renamed function. (#63268)
`getRoots` was renamed `getAppRoots` in a prior commit.

PR Close #63268
2025-08-20 09:24:43 +00:00
Matthieu Riegler 85468ef630 docs: rewording heading for define option (#63009)
This is an attemp to improve search results

PR Close #63009
2025-08-20 09:04:57 +00:00
hawkgs 863401b858 refactor(devtools): add property show graph button (#62853)
Add "Show graph" button to the signal properties in the side pane only for Angular apps. This required storing the signal graph in a separate service.

PR Close #62853
2025-08-20 09:04:24 +00:00
Angular Robot c5c28f9f52 build: update cross-repo angular dependencies (#63263)
See associated pull request for more information.

PR Close #63263
2025-08-20 09:00:56 +00:00
Jessica Janiuk 70afb41f8e fix(core): handle cases where classes added have no animations (#63242)
In the case that someone wants to disable animations via selector specificity, for example by adding an `.animate-disabled` class to a parent node, we need to make sure the animate instructions don't misbehave. Now we detect if animations exist in the provided classes and react accordingly.

fixes: #63161

PR Close #63242
2025-08-20 08:59:03 +00:00
tjshiu 1feadba215 docs: remove redundant Angular docs link in tailwind guide (#63205)
The Tailwind CSS guide already resides within the Angular documentation, making a link to the Angular documentation redundant. This commit removes the unnecessary link.

PR Close #63205
2025-08-20 08:30:33 +00:00
tjshiu cf0ae2ec45 docs: update tailwind guide and navigation (#63205)
PR Close #63205
2025-08-20 08:30:33 +00:00
tjshiu b0e2cdd470 docs: add guide for using Tailwind CSS (#63205)
Adds a new guide that explains how to set up and use Tailwind CSS in an Angular project. This guide covers installation, configuration, and provides a basic usage example.

PR Close #63205
2025-08-20 08:30:33 +00:00
Angular Robot c8f61d295a build: update pnpm to v10.15.0 (#63257)
See associated pull request for more information.

PR Close #63257
2025-08-20 08:28:54 +00:00
Joey Perrott 718bfd2fb7 build: move from using WORKSPACE to MODULE.bazel (#63246)
Update to use MODULE.bazel

PR Close #63246
2025-08-20 08:26:17 +00:00
cexbrayat bf9db2897e docs: animationsEnabled defaults to false (#63262)
PR Close #63262
2025-08-20 08:25:26 +00:00
Angular Robot 8a6cc39aec build: update all non-major dependencies (#63256)
See associated pull request for more information.

PR Close #63256
2025-08-20 08:21:30 +00:00
Miles Malerba 9866f91ef6 Review pass & fixup of the control directive & related code (#63068)
* feat(forms): add missing builtin property hooks for custom controls

* docs(forms): address some todos

* feat(forms): add missing form control inputs & tsdoc

* fix(forms): correct some properties on InteropNgControl

Also notes a few properties in comments that we should consider supporting if possible

* docs(forms): tsdoc and minor fixup for controls/control.ts

* test: move control directive test to web test

this allows cleaning up hacky test code to run it in node

* fixup! test: move control directive test to web test

* fixup! feat(forms): add missing form control inputs & tsdoc

* fixup! docs(forms): tsdoc and minor fixup for controls/control.ts

* fixup! fixup! feat(forms): add missing form control inputs & tsdoc

* address review comments

* fix comment

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

* test: cleanup test

* remove extraneous undefined

* resolve some TODOs

* test: move to new inputBinding syntax

* test: update goldens

* fix test

---------

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
2025-08-19 16:09:05 -07:00
Leon Senft b86b1e8810 feat(forms): add error summaries to fields
Add an `errorSummary` signal to `FieldState` which contains all of the
validation errors of a field and its descendants. This can be used to
conveniently collect all form validation errors by reading it from the
root field.
2025-08-19 14:16:04 -07:00
Leon Senft 768f0410b9 feat(forms): add associated field to all validation errors
Previously only tree validation errors carried their associated fields.
Regular validation errors didn't carry their field because it was
assumed it could be trivially inferred from context.

Error summaries (which are forthcoming) break this assumption, as they
combine both regular and tree validation errors from an entire form into
a single list. Without explicitly carrying their associated field,
there's no way of knowing to which field a regular validation error is
associated when obtained through an error summary.

Note that while all validation errors now have a field, it's not
explicitly required to define one when returning an error from a logic
function callback in `validateTree()`, nor is it allowed in
`validate()`. These functions will automatically add the current field
by default to any errors missing a field definition. This ensures that
all validation errors returned from fields (e.g. `field.path.errors()`)
_do_ have a defined field.

This change makes testing errors by equality more tedious; however, we
only do so in our own tests to be comprehensive. In reality, it's
expected that developers would simply check for an expected `error.kind`
or similar property of a custom error.
2025-08-19 13:01:42 -07:00
Angular Robot b7f2a8b180 build: update cross-repo angular dependencies (#63243)
See associated pull request for more information.

PR Close #63243
2025-08-19 15:57:06 +00:00
Julien Zapata Duque 72ae1064fe docs: use correct closing quote in input and model import usage notes (#63237)
PR Close #63237
2025-08-19 12:33:22 +00:00
Angular Robot 942517966b build: update dependency shiki to v3.10.0 (#63235)
See associated pull request for more information.

PR Close #63235
2025-08-19 11:15:29 +00:00
Shuaib Hasan Akib 6712456847 refactor(core): update NG0303 error message (#63222)
Ensure consistency in error message wording by aligning the NG0303
error with other Angular error strings. This improves clarity and
maintains a uniform developer experience

PR Close #63222
2025-08-19 08:25:49 +00:00
Angular Robot 101f072b74 build: update dependency mermaid to v11.10.0 (#63234)
See associated pull request for more information.

PR Close #63234
2025-08-19 08:21:55 +00:00
Angular Robot 44b94b1564 docs: update Angular CLI help (#63225)
Updated Angular CLI help contents.

PR Close #63225
2025-08-19 08:21:25 +00:00
Angular Robot d5a337dee1 build: update github/codeql-action action to v3.29.10 (#63230)
See associated pull request for more information.

PR Close #63230
2025-08-19 08:05:06 +00:00
Angular Robot f7de67ba75 build: update dependency preact to v10.27.1 (#63229)
See associated pull request for more information.

PR Close #63229
2025-08-19 08:03:39 +00:00
Avetik Harutyunyan f50fa15fa3 docs: fix typo in SSR section (#63224)
PR Close #63224
2025-08-19 08:01:27 +00:00
Matthieu Riegler 78f3d849ba docs: update version compatiblity. (#63223)
TS 5.9 support landed with #62939

PR Close #63223
2025-08-19 08:00:54 +00:00
Paweł Kawula c4faee4722 docs: fixed mismatch between supported node versions (#63218)
node versions listed here https://angular.dev/reference/versions
and minimal listed in installation.md are different

PR Close #63218
2025-08-19 07:59:21 +00:00
Matthieu Riegler 9b539a18ba docs(docs-infra): lift circular imports (#63186)
This commit also adds adev on the cyclic import check.

PR Close #63186
2025-08-19 07:58:45 +00:00
Miles Malerba 9a0c96489a fix(forms): align FieldPath type with implementation (#63100)
* test(forms): rename outdated test file name

* split recursive logic tests into a separate file

* add tests to demonstrate issue

* fix(forms): align FieldPath type with implementation

In particular when dealing with model values that are potentially
undefined, the typing did not extract the `undefined` outside. With this
fix the `undefined` is extracted, preventing people form defining logic
on fields that might not actually exist.

* fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* address feedback

* ci: update goldens

* type cleanup

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
2025-08-18 17:06:26 -07:00
Leon Senft 1545ddcb4b refactor(forms): reduce FieldState API to a minimal set of properties
Remove the following properties from `FieldState`

* `syncErrors`
* `syncValid`

These properties are implementation details which can be accessed
internally through the field node's `validationState` where necessary.
2025-08-18 16:02:12 -07:00
Leon Senft fdbb09f2ec refactor(forms): fix typo in class name
Rename `NonMergableLogicNodeBuilder` to `NonMergeableLogicNodeBuilder`.
2025-08-18 09:33:59 -07:00
Jessica Janiuk cab3adfdd1 fix(compiler): fixes animation event host bindings not firing (#63217)
Host bindings for `(animate.enter)` and `(animate.leave)` were not firing properly. This fixes the compiler ingest to make sure they do fire.

fixes: #63199

PR Close #63217
2025-08-18 15:56:49 +00:00
AleksanderBodurri 5115050928 fix(devtools): DOM traversal bug (#62719)
Previously, Angular devtools would mistakenly traverse the same DOM elements multiple times while doing traversal for the component tree explorer. This error case would occur when more than 1 Angular application root component was present on the same page and in distinct DOM branches.

Some example cases that did work previously:

```html
<app-root>
...
</app-root>
```

```html
<app-root>
...
<app-root-2></app-root-2>
...
</app-root>
```

An example of where it would enter the irregular behaviour

```html
<app-root>
...
</app-root>
<app-root-2>
...
</app-root-2>
```

Now, we properly ignore duplicate DOM paths when looking for application and non-application root component to begin the Angular DevTools component discovery logic.

PR Close #62719
2025-08-18 15:43:09 +00:00
Angular Robot d0cf624305 build: update cross-repo angular dependencies (#63220)
See associated pull request for more information.

PR Close #63220
2025-08-18 15:21:24 +00:00
Joey Perrott 1aa0a8800c build: update the failure message with the correct command (#63219)
Update the failure message for compliance tests to use the correct command for updating the goldens

PR Close #63219
2025-08-18 15:08:56 +00:00
Jessica Janiuk a7cfb42c03 fix(core): ensure animate events do not have duplicate elements (#63216)
This applies the same fix that fixed the class version to the event binding version. It prevents duplicate elements from being on screen when animations have been toggled too fast.

fixes: #63127

PR Close #63216
2025-08-18 12:42:16 +00:00
Paul Gschwendtner 6c8d000016 ci: mark myself as unavailable during OOO (#63214)
Marks myself as unavailable during OOO to not block reviews :)

PR Close #63214
2025-08-18 12:53:19 +02:00
Alan Agius 494385cac0 fix(docs-infra): allow wrapping of types-and-default CLI options (#63211)
This change fixes an issue where long lists of enum values would display on a single line, running off the screen, especially on mobile devices.

PR Close #63211
2025-08-18 12:52:03 +02:00
Alan Agius 2c34e37df9 fix(docs-infra): add spacing between Alias and Value (#63211)
Prior to this change there was noi spacing between the Alias and Value

PR Close #63211
2025-08-18 12:52:03 +02:00
Alan Agius 1714e9ea0e fix(docs-infra): display value type in CLI pages (#63211)
The value type was not being displayed when a value had an enum, which was misleading. This change ensures the value type is always shown, so users know whether to expect a single value or an array of values.

PR Close #63211
2025-08-18 12:52:03 +02:00
Angular Robot 032017174e build: lock file maintenance (#63209)
See associated pull request for more information.

PR Close #63209
2025-08-18 12:50:49 +02:00
Walter Breakell ddee59f58c docs: fix grammatical mistake in installation guide (#63202)
PR Close #63202
2025-08-18 12:50:00 +02:00
cexbrayat e9b2fe7ce5 refactor(core): improve animation instruction robustness and maintainability (#63163)
This commit extracts helper functions to reduce code duplication across animation instructions and adds early return when animations are disabled.

PR Close #63163
2025-08-18 12:49:17 +02:00
Matthieu Riegler ab067a3d2e docs(docs-infra): remove @adev/constant path (#63213)
PR Close #63213
2025-08-18 10:06:23 +02:00
Alan Agius 856c5f02c5 build: remove extra comma in JSON (#63212)
This fixes CI which is currently broken.

PR Close #63212
2025-08-18 09:44:00 +02:00
Matthieu Riegler ff01e33d69 docs: revert remove of build-and-link (#63187)
This works again since angular/dev-infra#2922 landed

PR Close #63187
2025-08-18 09:14:36 +02:00
Angular Robot a4e27e9a03 build: update cross-repo angular dependencies (#63190)
See associated pull request for more information.

PR Close #63190
2025-08-18 09:13:52 +02:00
Angular Robot f54fdc5f76 docs: update Angular CLI help (#63194)
Updated Angular CLI help contents.

PR Close #63194
2025-08-18 09:10:25 +02:00
Gerome Grignon e6029bcf2c docs(): remove double parenthesis (#63196)
PR Close #63196
2025-08-18 09:09:50 +02:00
Shuaib Hasan Akib 4787eced1b docs(docs-infra): add paths for constants and missing link (#63197)
PR Close #63197
2025-08-18 09:08:59 +02:00
Angular Robot cdafe8b01e build: update all non-major dependencies (#63203)
See associated pull request for more information.

PR Close #63203
2025-08-18 09:08:22 +02:00
Alan Agius d0d6079f54 refactor: remove version 20 migrations (#63207)
This commit remove the non optional migrations for version 20 as these have already been executed.

PR Close #63207
2025-08-18 09:06:51 +02:00
Shuaib Hasan Akib 3f88fbdfa7 docs(docs-infra): replace deprecated router method (#63183)
PR Close #63183
2025-08-15 13:50:57 +02:00
Angular Robot abfd7783b0 build: update dependency @codemirror/language to v6.11.3 (#63184)
See associated pull request for more information.

PR Close #63184
2025-08-15 13:50:17 +02:00
Shuaib Hasan Akib 6f91972ae4 docs(docs-infra): replace Input by signal one (#63169)
PR Close #63169
2025-08-15 13:42:39 +02:00
Cheng-Hsuan Tsai 0f17623398 docs(docs-infra): add dark mode to example preview (#63171)
PR Close #63171
2025-08-15 13:41:08 +02:00
cexbrayat d3a0c2559a docs: add --yes option to mcp commands (#63173)
This makes it consistent with the message output by the CLI in v20.2 (see https://github.com/angular/angular-cli/commit/8b4de57af1)

PR Close #63173
2025-08-15 12:32:28 +02:00
Angular Robot 9a59f7613c build: update all non-major dependencies (#63181)
See associated pull request for more information.

PR Close #63181
2025-08-15 12:31:24 +02:00
Angular Robot 47b09f8f18 build: update cross-repo angular dependencies (#63178)
See associated pull request for more information.

PR Close #63178
2025-08-15 11:40:20 +02:00
Kristiyan Kostadinov 5b3933f1ae fix(compiler): error when ng-content fallback has translated children (#63156)
Fixes that the pipeline wasn't processing the fallback content of `ng-content` for i18n which resulted in a compiler error further down the line.

Fixes #63065.

PR Close #63156
2025-08-15 09:47:17 +02:00
Kristiyan Kostadinov 9ac638548f fix(compiler): incorrect source span for expression AST inside template attribute (#63175)
Similar fix as #63082, but for template attributes. The root cause is the same where we should be using `fullStart` instead of `start` in order to account for whitespaces being skipped.

Fixes #63157.

PR Close #63175
2025-08-15 09:44:35 +02:00
Alan Agius e7a3452852 build: bump @angular/ssr to 20.2.0-rc.1 (#63176)
This contains the fix for the router `lastSuccessfulNavigation` breaking change

https://github.com/angular/angular-cli/pull/30923

PR Close #63176
2025-08-15 09:44:00 +02:00
Alan Agius fa40feee1d Revert "ci: redirect adev to index.csr.html (#63168)" (#63177)
This reverts commit 5129daed1b.

PR Close #63177
2025-08-15 09:43:14 +02:00
Matthieu Riegler 5129daed1b ci: redirect adev to index.csr.html (#63168)
The build doesn't produce an `index.html`

PR Close #63168
2025-08-14 21:15:47 +00:00
Angular Robot d733c69ce9 build: update all non-major dependencies (#62935)
See associated pull request for more information.

PR Close #62935
2025-08-14 22:09:29 +02:00
Charles Lyding 9c0ad1b434 docs: update MCP server documentation for 20.2 (#63141)
This commit updates the MCP server documentation with the following changes:

- Updates the description of the `ng mcp` command behavior.
- Adds a configuration example for the Gemini CLI.
- Adds a new section for command options, including `--read-only` and `--local-only`.
- Alphabetizes the IDE configuration sections for better readability.
- Alphabetizes the "Available Tools" table.

PR Close #63141
2025-08-14 22:07:55 +02:00
Angular Robot fad3d8e3a5 build: update cross-repo angular dependencies (#63149)
See associated pull request for more information.

PR Close #63149
2025-08-14 22:05:48 +02:00
Cheng-Hsuan Tsai b9a134b8eb docs(docs-infra): fix codeblock styles (#63153)
PR Close #63153
2025-08-14 22:04:39 +02:00
Shuaib Hasan Akib 54485f7f4b docs(docs-infra): enhance method name following the guideline (#63164)
PR Close #63164
2025-08-14 22:03:16 +02:00
Joey Perrott bf2ba7a059 build: run integration tests using remote execution (#63118)
With the change to size tracking integration test can now be executed on our RBE platform

PR Close #63118
2025-08-14 22:02:32 +02:00
Ben Hong dc8f11b4fd docs: add rendering strategies guide for router (#62859)
PR Close #62859
2025-08-14 22:01:56 +02:00
Matthieu Riegler 4bed062bc9 feat(http): Provide http services in root (#56212)
The changes introduced in this commit allows to use the HttpClient without the provider function.

PR Close #56212
2025-08-14 13:04:35 +02:00
Jonathan Meier be7110342b fix(compiler-cli): disallow compiling with the emitDeclarationOnly TS compiler option enabled (#61609)
Running the Angular compiler with declaration-only emission is dangerous
because Angular does not yet support this mode as it relies on the Ivy
compilation which does not run in this mode

In the best case, everything works fine as incidentally there's no
difference in the emitted type declarations (e.g. this is the case for
TS files containing no Angular annotations or only `@Injectable`
annotations).

In the worst case, compilation silently fails in that the compilation
succeeds but the resulting type declarations are missing the Angular
type information and are therefore incomplete. This happens for all
components, directives and modules.

BREAKING CHANGE: The Angular compiler now produces an error when the
the `emitDeclarationOnly` TS compiler option is enabled as this mode is
not supported.

PR Close #61609
2025-08-14 13:03:54 +02:00
Matthieu Riegler 93c735cae7 docs: fix edit links for block/element api entries (#62766)
PR Close #62766
2025-08-14 13:03:00 +02:00
Matthieu Riegler c8349ef2f6 docs: update testing scenarios and remove imports. (#63076)
With standalone, `imports` are not necessary anymore when configuring the Testing module.

Also
fixes #58073

PR Close #63076
2025-08-14 13:02:17 +02:00
Joey Perrott c35c0c7f2f build: update to bazel 7.6.0 (#63096)
Update to later version of bazel

PR Close #63096
2025-08-14 13:01:30 +02:00
Matthieu Riegler a59c3fe74e build: remove patch-package dependency (#63126)
This dependency is no longer necessary as `pnpm` provides the same natively

PR Close #63126
2025-08-14 12:59:46 +02:00
Andrew Scott e7cc89e3b0 docs: release notes for the v20.2.0-rc.1 release 2025-08-13 13:28:46 -07:00
Andrew Scott 74e1796a1e docs: release notes for the v20.1.7 release 2025-08-13 13:18:56 -07:00
Matthieu Riegler 4e0fc81491 feat(router): convert lastSuccessfulNavigation to signal (#63057)
This commit also include an `ng update` migration to ensure `lastSuccessfulNavigation` is invoked.

BREAKING CHANGE: `lastSuccessfulNavigation` is now a signal and needs to be invoked

PR Close #63057
2025-08-13 11:29:01 -07:00
Jeremy Elbourn 55cd8c2d73 docs: add rxjs-interop section for rxResource (#63125)
This commit adds brief documentation for `rxResource` to the "Using
Angular with RxJS" guide. The only available documentation for
`rxResource` today is the API reference, which lacks any code examples.

PR Close #63125
2025-08-13 09:29:33 -07:00
hawkgs 7ecf13db06 refactor(devtools): improve router tree search UX (#63107)
Improve the search functionality of the router tree by making the results more distinguishable.

PR Close #63107
2025-08-13 09:27:26 -07:00
Angular Robot 071ab50c7c docs: update Angular CLI help (#63138)
Updated Angular CLI help contents.

PR Close #63138
2025-08-13 09:25:37 -07:00
Angular Robot 21b0fd3d13 build: update cross-repo angular dependencies (#62952)
See associated pull request for more information.

PR Close #62952
2025-08-13 09:21:28 -07:00
cexbrayat 1269d2e4f2 docs: update best-practices (#63132)
This removes the common pitfall for if blocks in the best practices, as this is no longer true in Angular v20.2, see angular/angular@a409534

PR Close #63132
2025-08-13 09:20:30 -07:00
Alan Agius 32ba8b5025 docs: add link to ai-config command (#63139)
This adds a link to the new command.

PR Close #63139
2025-08-13 09:14:18 -07:00
Angular Robot 91b3466e97 build: update actions/checkout action to v5 (#63114)
See associated pull request for more information.

PR Close #63114
2025-08-12 19:08:49 -07:00
Matthieu Riegler 657061e528 build: patch @angular/build to allow fw/cli mismatch (#63098)
This will allow us to build adev while the CLI and the FW major versions are mismatched.
we can remove this patch once the CLI has published a v21 pre-release (or a flag to disable that error).

PR Close #63098
2025-08-12 18:46:22 -07:00
Andrew Scott aaf053c288 build: update main branch to target 21 (#63098)
main branch should go to 21 not 20.3

PR Close #63098
2025-08-12 18:46:22 -07:00
Jeremy Elbourn 4d9385b5fd docs: add topic for takeUntilDestroyed (#63122)
This was missing from the RxJS interop guide. I came across it while
cleaning up some Google-internal docs and realized I had nothing on adev
to which I could link.

PR Close #63122
2025-08-12 18:41:18 -07:00
Angular Robot d7a5eadbfa build: update pnpm to v10 (#63087)
See associated pull request for more information.

PR Close #63087
2025-08-12 16:53:31 -07:00
Paul Gschwendtner ea78278635 refactor(compiler-cli): ensure JIT transforms with queries work with closure property renaming (#63123)
This map for the JIT transforms accidentally is property-renamed,
breaking the lookups below. This commit fixes this.

PR Close #63123
2025-08-12 13:24:09 -07:00
Jeremy Elbourn e64271df92 fix(docs-infra): put content in <main> element (#63121)
This fix updates the adev `DocViewer` component to support rendering with the `main[docsViewer` selector so that the primary content for the page is inside of a `<main>` element (without introducing an extra DOM element).

PR Close #63121
2025-08-12 11:05:13 -07:00
ivanwonder b695ff02cb feat(language-service): support auto-import for attribute completions (#62797)
Add logic to provide code actions for auto-importing directives that
are out of scope.

PR Close #62797
2025-08-12 10:57:08 -07:00
Angular Robot 6c51c29925 build: lock file maintenance (#62977)
See associated pull request for more information.

PR Close #62977
2025-08-12 10:05:28 -07:00
Angular Robot 11b527cecd build: update scorecard action dependencies (#63086)
See associated pull request for more information.

PR Close #63086
2025-08-12 09:58:11 -07:00
Angular Robot 01fb2b1b80 build: update actions/checkout action to v5 (#63102)
See associated pull request for more information.

PR Close #63102
2025-08-12 09:55:54 -07:00
Matthieu Riegler 09df37507f docs: add missing supported != operator (#63108)
fixes 63103

PR Close #63108
2025-08-12 09:39:05 -07:00
Alan Agius 54fe7ca055 build: use link: instead of file: (#63111)
When using `file:` renovate updates fails due to ` ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND ` this is due to a different behaviour between `link:` amd `file:`. `link:` however will not fail when the directory does not exist.

PR Close #63111
2025-08-12 09:37:54 -07:00
Alan Agius 5d9f124876 build: migrate config renovate.json (#63110)
Closes #62955 as a pr takeover

PR Close #63110
2025-08-12 09:37:04 -07:00
Alan Agius 1846874f1f ci: correctly configure ignored filenames (#63105)
Previously, the path did not match a file.

PR Close #63105
2025-08-12 10:02:00 +02:00
Alan Agius 434ff7f1a0 build: resolve pnpm workspace dependency on compiler-cli (#63105)
The `pnpm install` command was failing for the `packages/core/test/bundling` project with the error:
`ERR_PNPM_NO_MATCHING_VERSION No matching version found for @angular/compiler-cli@0.0.0-PLACEHOLDER`

This happenes only in renovate updates.

PR Close #63105
2025-08-12 10:02:00 +02:00
Alan Agius 91f8963d86 build: fix ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND for typings tests (#63105)
During `pnpm install` in a monorepo setup, the `packages/zone.js/test/typings` project would fail with the error: `ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND: Could not install from "/.../node_modules/typescript" as it does not exist.`

PR Close #63105
2025-08-12 10:01:59 +02:00
Alan Agius a786bb6f4b build: remove redundant yarn.lock files (#63105)
These are now managed by pnpm.

PR Close #63105
2025-08-12 10:01:59 +02:00
Matthieu Riegler 9aef481172 refactor(zone.js): remove unused jasmine globalerror monkey patching. (#63077)
This is for testing purposes for now. See discussion on the related issue.

fixes #63072

PR Close #63077
2025-08-11 16:41:09 -07:00
Matthieu Riegler f9a38a4b73 refactor(core): remove temporary resource setter function. (#63097)
Now that G3 has been fully migrated to the new throwing behavior, we don't need that setter anymore.

PR Close #63097
2025-08-11 15:43:45 -07:00
hawkgs d47ea29b89 refactor(devtools): keep TreeVisualizer snapped node on focus (#62982)
Keep `TreeVisualizer` snapped node on focus when the container/split is being resized.

PR Close #62982
2025-08-11 12:26:31 -07:00
Matthieu Riegler baa31fb1a1 docs: remove router testing from the testing scenarios guide (#63078)
This commit also adds the router testing guide to the testing section

PR Close #63078
2025-08-11 12:15:39 -07:00
Kristiyan Kostadinov d17555a7c6 fix(compiler): incorrect spans for AST inside input value with leading space (#63082)
When parsing expressions inside a bound attribute, we offset all of its spans by an `absoluteOffset` in order to get the right spans in the source file. The offset was incorrect when parsing an attribute with leading spaces during the construction of the Ivy AST, because of the combination of:
1. We were setting the offset by looking at `valueSpan.start`.
2. The Ivy parser sets `leadingTriviaChars: [' ', '\n']` which means that spaces and new lines will be ignored in the `sourceSpan.start`.

These changes resolve the issue by using `valueSpan.fullStart` which includes the leading spaces.

Fixes #63069.

PR Close #63082
2025-08-11 11:57:05 -07:00
Alan Agius 06f5879e42 ci: several fixes for renovate (#63083)
This commit contains several fixes for renovate to work properly with `pnpm`

PR Close #63083
2025-08-11 11:03:28 -07:00
Joey Perrott c4dcfdf644 build: update to the latest version of devinfra and rename npm2 workspace (#63093)
Rename the npm2 workspace to npm and use the latest version of devinfra which drops rules_nodejs

PR Close #63093
2025-08-11 10:35:32 -07:00
Kirill Cherkashin 04f225d516 Add forms adapter (#62940)
* refactor(forms): Add adapter support

Adapter allowing customization of the creation logic for a field and its associated structure and state.
2025-08-11 15:29:30 +02:00
Kristiyan Kostadinov 57184d0895 docs: release notes for the v20.2.0-rc.0 release 2025-08-11 10:42:32 +02:00
Kristiyan Kostadinov 2e4db98877 release: bump the next branch to v20.3.0-next.0 2025-08-11 10:42:32 +02:00
Miles Malerba 2d03187a1d feat(forms): track bound controls as field state (#63060)
* feat(forms): track bound controls as field state

Adds a signal to the field state that tracks the instances of `Control` that are currently bound to the field. This is useful for error summaries that want to support jumping to fields with errors

* docs(forms): fix typo

fix typo blah blah this commit message is long enough

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-08-09 10:25:52 +09:00
Miles Malerba cb826866f3 refactor(forms): reorganize control directive & add disabled/readonly (#63058)
* refactor(forms): reorganize control directive & add disabled/readonly

* fix(forms): control directive propagates metadata

* feat(forms): support FieldState.name() & propagate to controls

* feat(forms): improve custom control support

Split `FormUiControl` into `FormValueControl` and `FormCheckboxControl`,
which capture the two main types of UI control:

 * those that expose a typed `.value()`
 * those that expose a boolean `.checked()`

Implement both in the `Control` directive.

* fix(forms): set required inputs with somewhat correct timing

If `[control]` is applied to a custom UI control, it wants to synchronize
state in the field w/ the inputs of that custom control. This is difficult
to do in user-land. We use `effect`, but effects don't run before the
lifecycle hooks of the component. This is usually okay, but has one
significant issue: the UI control's required inputs won't be set in time for
those lifecycle hooks to run.

Eventually we can build custom functionality for the `Control` directive
into the framework, but for now we work around this limitation with a hack.
We use an `@Input` instead of a signal-based `input()` for the `[control]`
to hook the exact moment inputs are being set, before the important
lifecycle hooks of the UI control. We can then initialize all our effects
and force them to run immediately, ensuring all required inputs have values.

* feat(forms): make `Control` a no-op for components with a control input

When a component is used which represents a subform, it can use a `control`
input to receive and pass down a `Field` to child controls. In that case,
the `Control` directive will match against the component, but should do
nothing since the component is already handling the `Field` propagation
internally.

* test(forms): switch tests to zoneless

Switch the test configuration to avoid pulling in zone.js, purely to avoid
the console warning spam.

* refactor(forms): address review feedback

address feedback on #62930 and  #62933

* docs(forms): add TODOs for open questions

adds TODOs for open questions on the original PRs, they can be settled in a followup

closes #62930
closes #62933

* build(forms): update api goldens

---------

Co-authored-by: Alex Rickabaugh <alx+alxhub@alxandria.net>
2025-08-08 10:41:25 +09:00
Miles Malerba e127c420f6 feat(forms): switch to RegExp in pattern validator (#63045)
* feat(forms): switch to RegExp in pattern validator

No other libraries use string patterns for validation

* refactor(forms): update PATTERN to refer to a RegExp

addresses feedback from the original PR

* docs(forms): update docs for pattern logic function

aligns the tsdoc with other validatior logic functions

* build: update api goldens

updates the api goldens to reflect changes in this PR

---------

Co-authored-by: Kirill Cherkashin <kirjs@google.com>
2025-08-08 09:18:15 +09:00
Leon Senft 21838501b6 feat(forms): prevent invalid form submission
Return from `submit()` before running the `action` callback if the form
is invalid. Note that `submit()` will still mark all fields as touched
before returning to ensure that all validation errors are displayed.
2025-08-05 13:55:24 -07:00
Leon Senft 6dbe4b7369 fix(forms): memoize deepSignal value
Test that submission errors are cleared from their corresponding field
when its value is changed. Submission errors should not be cleared from
fields whose values remain unchanged. This behavior relies on fields
memoizing their value, which `deepSignal()` now does.
2025-08-05 13:48:26 -07:00
Leon Senft cd8e0c3ae0 Merge remote-tracking branch 'upstream/main' into prototype/signal-forms 2025-08-05 11:53:19 -07:00
Miles Malerba 615f85f84e refactor(forms): reorganize and improve tsdoc for the schema (#62915)
* refactor(forms): move schema impl under a schema directory

* refactor(forms): separate out logic.ts and logic_node.ts

* docs: improve tsdoc for schema.ts

* docs: improve tsdoc for path_node.ts

* docs: improve tsdoc for logic.ts and logic_node.ts

* docs: fix a gazillion typos

* fixup! docs: fix a gazillion typos

* fixup! docs: fix a gazillion typos

* fixup! refactor(forms): separate out logic.ts and logic_node.ts

* fixup! refactor(forms): separate out logic.ts and logic_node.ts

* fixup! docs: improve tsdoc for path_node.ts

* fixup! docs: improve tsdoc for path_node.ts

* fixup! refactor(forms): separate out logic.ts and logic_node.ts

* fixup! 6e425b880a
2025-08-03 20:47:03 +09:00
Miles Malerba 0c82de5fe5 refactor(forms): cleanup util (#62911)
Move illegal.ts into util and consolidate multiple implementations of
isObejct
2025-08-01 16:08:54 +09:00
Miles Malerba 9349bcfee7 fix(forms): fix triage logic for server errors and improve field tsdoc (#62910)
* docs(forms): Improve tsdoc for field structure

Adds / improves tsdoc for field/structure.ts

* fix(forms): fix server error triage logic

fixes a bug where server errors could be lost if targeting multiple of
them at the same field

* docs: fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* docs: fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* docs: fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* docs: address feedback

* refactor(forms): address ai review

address comments from gemini bot

* refactor(forms): clean up set server errors

Clean up the set server errors logic so it doesn't allocate unnecessary data structures.

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

* fixup! docs(forms): Improve tsdoc for field structure

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
2025-08-01 15:25:11 +09:00
Kirill Cherkashin 3a4caec61c fix(forms): expose and test hidden (#62905)
This would allow using hidden

Co-authored-by: kirjs <kirjs@google.com>
2025-07-31 12:11:13 -04:00
Miles Malerba b24fec7215 docs and cleanup for field implementation (#62888)
* refactor(forms): improve docs and typing for field

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

* fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

---------

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-07-31 11:04:16 +09:00
Leon Senft 5747c7a48b refactor(forms): remove unsubmitted and submitted field states
* `FieldState.submittedStatus` is replaced by `FieldState.submitting`,
  which tracks only whether the form is currently being submitted. This
  state is propagated downwards to all descendants of the submitted field.

* `submit()` now marks the field and all of its descendants as touched
  to ensure that any validation errors are displayed upon submission
  attempts.
2025-07-30 11:18:13 -07:00
Leon Senft cf2be036c9 test(forms): update failing API golden test
Fix the last remaining CI failure on the prototype/signal-forms branch
for now.
2025-07-30 10:21:50 -07:00
Leon Senft b93a1b8df7 Fix CI (#62887)
* fix(forms): fix `yarn ts-circular-deps:check` errors

Remove circular dependencies.

* Use `import type` where possible instead of `import`.
* Split the contents of `resolution.ts` out from `context.ts`.
* Import symbols directly from their defining files rather than through
  `public_api.ts`.

* fix(forms): fix failing test case for async validation using a standard schema

This test case relied on Zone.js converting `async () => true` to a
`Promise`, which was necessary to make validation run asynchronously.
After merging the `main` branch, this test started failing because the
validator completed synchronously despite the use of an `async`
function. Using `Promise.resolve()` guarantees that the validation will
run asynchronously and fixes the failure.
2025-07-30 23:29:03 +09:00
Leon Senft 6dd4f7e67a fix(signal-forms): fix tslint errors
Add explicit types to fix `no-exported-inferred-call-type` violations.
2025-07-29 13:37:23 -07:00
Leon Senft e6e0c72118 Merge remote-tracking branch 'upstream/main' into prototype/signal-forms 2025-07-29 13:21:25 -07:00
Miles Malerba 32c9aadd6a docs/test for async validation running only after sync valid (#62830)
* add test for validateHttp on root path

* docs: calrify that async validation only runs when sync valid

* address ai review
2025-07-29 09:04:03 +09:00
Miles Malerba 862e7708e3 cleanup & type improvements for standard schema validator (#62845)
* move standard schema validator under validators

* make zod a dev dep and update to v4

* fixes and tests for validateStandardSchema typing

* switch node tests to zoneless and remove web tests

This resolves the issue with our node tests that forced us to split the
Zod tests out into a separate web test. With this commit the node tests
are fixed and the Zod-based tests are moved back under node.

* minor docs fixup for validation errors

* support deep instances of z.looseObject

* address ai review

* address feedback
2025-07-29 09:03:02 +09:00
Miles Malerba 88614f0a1b Code cleanup and docs for some of the field node implementation (#62847)
* cleanup for field/context

* cleanup for field/util

* cleanup for field/manager

* cleanup for field/node

* cleanup for field/property

* update wording

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

* fix typo

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

---------

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
2025-07-29 08:33:15 +09:00
Miles Malerba 21ca05d5c9 refactor(signal-forms): cleanup for api/logic & api/structure (#62810)
* remove leftover todo

* docs pass for api/logic

* remove false from ValidationResult

In practice I found the ability to return false quite confusing.
It meant you could do `x && ValidationError.custom()`, but not
`x || ValidationError.custom()`. Making both of those work would mean
that returning `true` from `validate` vs returning `false` would mean
the exact same thing (no errors). That's also super odd, so I opted to
just remove `false` as a possibility.

* cleanup/docs pass for api/structure

* fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* address feedback

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-07-25 13:22:34 -07:00
Leon Senft e30ff9f688 refactor(signal-forms): add dependency on @standard-schema/spec 2025-07-24 16:07:26 -07:00
Miles Malerba f1105f9bab refactor(signal-forms): some cleanup for deepSignal & types (#62753)
* cleanup/docs pass for deepSignal

* cleanup/docs for api/types

* refactor the tree & async validation result types

These types have been made more accurate, to reflect the fact that a
tree or async validator could return a mix of errors with and without
fields.

Also added our own copy of isArray since the built-in one doesn't handle
readonly arrays properly.

* fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* address comments

* fix typo

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
2025-07-24 14:06:33 -07:00
Leon Senft b0c962db4a refactor(signal-forms): remove unnecessary no-op schema
Use the function overload for `form()` without a schema.
2025-07-23 11:56:38 -07:00
Leon Senft c1085fc134 fixup! feat(signal-forms): add support for resetting forms 2025-07-23 11:56:38 -07:00
Leon Senft 124fab9c91 feat(signal-forms): add support for resetting forms
Introduce a `reset()` method that restores the touched and dirty signals
of a field and its descendants to their initial states. `reset()` may be
called on the root `form()` field to reset the entire form.
2025-07-23 11:56:38 -07:00
Miles Malerba e96d9e3e51 refactor(signal-forms): rework metadata API (#62713)
* rename metadata key types

* fix metadata reduce to not confuse item and acc types

* remove defineComputed, defineResource

* rename define and meatdata logic binding fns

* move setMetadata next to addToMetadata

* address comments

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* pattern cleanup

* rename metadata concept to property

* consolidate field property state

* rename metadata.ts to property.ts

* add 2-param variant of property rule

* rename getDefault to getInitial for alignment with standard reduce

* address ai review

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-07-21 22:10:30 -07:00
Miles Malerba 04ba24eeeb fix bad rebase 2025-07-18 19:02:26 +00:00
Miles Malerba eefc3896f8 docs(signal-forms): add some tsdoc for async validation api (#62693)
* docs(signal-forms): add some tsdoc for async validation api

* address ai review

* address feedback
2025-07-18 11:20:06 -07:00
Miles Malerba 1243113feb Review of api/validators (#62659)
* Review of api/validators

* improve ergonomics of errors option on validators

Improves the ergonomics of the `errors` option offered by the standard
validators.
1. Renames it to `error` because in the majority of cases it will be
   used to return a single `ValidationError`.
2. Allows directly using a constant `ValidationError` insetad of having
   to create a function like `() => ValidationError.custom()`

* handle NaN case in min/max

* switch ValidationError to use camelCase names

* memoize result of logic functions

* allow any value with length or size for minlength/maxlength

* fix typos

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* address feedback

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-07-18 11:19:19 -07:00
Miles Malerba 63b4448a05 refactor(signal-forms): unify how users access metadata added with define and metadata (#62656)
* refactor(signal-forms): Replace DataKey with StaticMetadataKey

* Remove `data` from the `FieldState`, it is now accessed via `metadata`

* fix typo

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-07-17 17:12:35 -07:00
Miles Malerba ebb678616a feat(signal-forms): allow validation logic to return null and false (#62657)
* feat(signal-forms): allow validation logic to return null and false

* address feedback

* address feedback
2025-07-16 15:05:45 -07:00
Miles Malerba 74d83f4f3d refactor(signal-forms): more ergonomic metadata key creation (#62653)
* refactor(signal-forms): more ergonomic metadata key creation

* address feedback and add doc comments

* address ai feedback
2025-07-16 09:30:25 -07:00
Miles Malerba 2648e75441 refactor(signal-forms): remove error logic function (#62634)
We have gotten feedback from multiple sources that the distinction
between `error` and `validate` is confusing. Removing `error` for now
and we will explore alternate method signatures for `validate` instead
in future PRs
2025-07-15 13:11:48 -07:00
Miles Malerba 07939d671c cleanup unnecessary cast (#62635) 2025-07-15 13:04:08 -07:00
Miles Malerba 372f6559db refactor(signal-forms): use discriminated union for errors (#62446)
* refactor(signal-forms): use discriminated union for errors

* incorporate feedback

* Update packages/forms/experimental/src/api/validation_errors.ts

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* fix test

* refactor: base the union on real classes

* address ai review

* cleanup

* address feedback

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-07-15 11:26:11 -07:00
Kirill Cherkashin f8f0af5310 fix(forms): drop unused import (#62551)
This is also incorrect and breaks things

Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-07-09 12:41:28 -04:00
DESKTOP-V2DGHM1\Icecream e539e16073 docs(signal-forms): fix typos in example code (chandlerfang@gmail.com) 2025-07-08 17:42:00 -07:00
Miles Malerba bb12572d8e fix(signal-forms): fix broken test (#62532) 2025-07-08 16:27:19 -04:00
Miles Malerba 68643d2ddc feat(signal-forms): Add key & index support in FieldContext (#62442)
* feat(signal-forms): Add key & index support in FieldContext

* WIP: add PathKind type param to FieldPath

* WIP: add PathKind type param to logic functions

* fix remaining issues

* schema must take a root path kind

* add @template doc for TPathKind

* Apply suggestion from @leonsenft

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

* address comments

* address ai code review

* address comments

---------

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
2025-07-08 15:11:59 -04:00
Miles Malerba fead94d4ec standard schema support (#61438)
* add standard schema support

* move node tests under test/node

* Apply suggestions from code review

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>

* Update packages/forms/experimental/test/web/standard_schema.spec.ts

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* fix return type docs

---------

Co-authored-by: Leon Senft <leonsenft@users.noreply.github.com>
Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-07-01 16:30:23 -07:00
Leon Senft 090f7a38f1 docs(forms): fix typos (#62348) 2025-06-30 09:28:18 -07:00
Miles Malerba 92e7a45d43 Migrate to LogicNodeBuilder & LogicNode (#62220)
* migrate to logic node 2

* add some additional tests for predefined schemas

* fix resolve behavior

* tests and fixes for recursive logic

* post-rebase fixup

* add test for recursive logic w/ predicates

* fix predicates in recursive logic

* misc cleanup

* compile schemas once per form

* add test for recursive logic with arrays

* fix typo

* address review comments
2025-06-26 14:33:56 -07:00
Miles Malerba 3fa15e7bb9 refactor: add a new version of the logic node (#61079)
* refactor: add a new version of the logic node

Adds a new version of logic node that is created via builders. The
builder / actual logic node split allows the final logic for a node to
be lazily merged together.

My plan is to eventually swap this in for FieldLogicNode and have the
schema methods (`validate`, `apply`, etc.) work with the builders. The
FieldNode will then build its logic when it is first created.

This should be the other main piece aside from relative paths which is
needed to make self-referential logic work.

* refactor: maintain predicates as list

* refactor: refactor builder api

* refactor: defer predicate list expansion until merge time

* post-rebase fixup

* add support for new logic since last rebase

* add tsdoc
2025-06-23 09:08:59 -07:00
Kirill Cherkashin a833eb283f Forms: Always use provided injector on the form level (#62051)
Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-06-18 14:23:24 -04:00
Kirill Cherkashin 54d1d3e453 Forms: Allow dynamically disabling built-in validators by returning u… (#62121)
* Forms: Allow dynamically disabling built-in validators by returning undefined.

* fixup: drop binding built-in metadata to inputs for now

---------

Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-06-18 14:21:16 -04:00
Kirill Cherkashin acee6ce483 Forms: Minor clean up of tutorial v2 (#62049)
* Forms: Minor clean up of tutorial v2

* Forms: Minor clean up of tutorial v2

* Forms: Minor clean up of tutorial v2

* Forms: Minor clean up of tutorial v2

---------

Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-06-13 12:19:50 -04:00
Kirill Cherkashin 0b5592dcc9 Forms: Clarify array tracking (#62035)
Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-06-12 14:58:36 -04:00
Kirill Cherkashin d296791daa V2 tutorial (#62014)
* docs(forms): Add a tutorial for v2

* fixup: Address feedback

---------

Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-06-11 17:15:30 -04:00
Alex Rickabaugh 19ecc2f300 fix(forms): handle undefined nodes in resolve() 2025-06-09 11:30:48 -07:00
Alex Rickabaugh eb6c2ecf2a refactor(forms): split FieldNode into structure, validation, context, etc.
This commit should make the code much easier to follow by breaking apart
`FieldNode` into various pieces, each focused on a single concern.
2025-06-09 11:10:27 -07:00
Alex Rickabaugh 14587546aa fix(forms): properly handle fields moving around within arrays
`FieldNode`s were previously constructed with a static 'key', used for a few
purposes including `.value` operations (read/write). This meant that when a
`FieldNode` moved in an array via the tracking mechanism, it would be
resolved via the new path, but still write to the old path (a major bug).

This commit fixes the issue by introducing a proper reactive concept for the
field's current property key in the parent, which changes as the field moves
around.
2025-06-09 11:10:27 -07:00
Alex Rickabaugh 5fd1b8fb77 refactor(forms): properly handle dynamic keyed FieldNodes 2025-06-09 11:10:27 -07:00
Alex Rickabaugh 3a7e531526 refactor(forms): undefined model values don't have fields
Adjust the behavior and typing of forms when a model value is `undefined`.
In such cases, the form is not considered to have a field for that key of
the model, and navigation attempts should return `undefined`. The type
should also reflect the field's potential non-existence, as well as
correctly narrow the type when the field is non-null asserted.
2025-06-09 11:10:27 -07:00
Kirill Cherkashin d9533445dd refactor(forms): Make imports consistent (#61908)
Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-06-05 15:40:46 -04:00
michael-small 5e675b1354 docs: align jsdocs of experimental sf logic & structure functions (#61887) 2025-06-05 09:44:39 -04:00
Miles Malerba 00f9d5a321 switch from $state to () (#61834) 2025-06-04 10:46:36 -07:00
Kirill Cherkashin d2bca12049 The rest of the built-in validators (#61758)
* forms: Add remaining build-in validators

* fixup: Address feedback

* fixup: Clean up

---------

Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-06-04 12:50:49 -04:00
Miles Malerba 7ecec96d3b refactor(forms): update status states based on prev discussion (#61852)
As discussed:
- Rename `.hasPendingValidators()` to just `.pending()`
- Eliminate `.status()` as a public API

Note:
- `.status()` retained internally as part of the implementation
- renamed the `'pending'` status option to `'unknown'` to avoid
  confusion with `.pending()`
2025-06-03 13:28:55 -07:00
Miles Malerba d19cb9e485 inherit disabled reason to children (#61829)
* inherit disabled reason to children

* update docs
2025-06-03 11:59:22 -07:00
Alex Rickabaugh 69f18f99d9 refactor(forms): rename FieldDirective to Control 2025-06-03 08:22:14 -07:00
Miles Malerba 7ebf93f9f8 update docs (#61711)
* update docs

Updates docs to:
- Show usage of the `schema()` function
- Mention need to run in injection context (& fix examples)

Also updates the `form` API to accept both `form(model, options)` and
`form(model, schema)`

* fixup! update docs

* add a brief explanation of each logic function

* add docs for async validation

* remove defaultValue signatures of validateHttp

* Update packages/forms/experimental/docs/signal-forms.md

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>

* fixup! Update packages/forms/experimental/docs/signal-forms.md

---------

Co-authored-by: michael-small <33669563+michael-small@users.noreply.github.com>
2025-05-30 09:59:28 -07:00
Alex Rickabaugh 7b68bf01fb feat(forms): support automatic array tracking
This feature adds a symbol tag to values in arrays, which is used to
automatically track those values as they move around in the array. It's one
way of achieving array tracking without a lot of overhead or configuration.

Primitive values can't be patched like this, and are still tracked by index.
2025-05-28 10:16:19 -07:00
Kirill Cherkashin 6410539818 Adding min and max validators. (#61474)
* feat(forms): Adding min and max validators.
Creating a separate file for built-in validators.
2025-05-28 09:30:18 -04:00
Miles Malerba e3221efa7f add shorthand to validate with http resource (#61707) 2025-05-27 13:55:45 -07:00
Kirill Cherkashin ecb8db04e4 forms: Drop data from context, as it is already on the state (#61646)
Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-05-27 12:47:09 -04:00
Miles Malerba 37c5ff61c6 refactor validation status (#61546) 2025-05-22 15:52:46 -07:00
Kirill Cherkashin ea589628a7 Drop resolve from everywhere (#61614)
* forms: Drop resolve, and use the new API everywhere

* fixup: Minor cleanup

---------

Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-05-22 11:27:36 -04:00
Kirill Cherkashin e221067e1c Adding state/field/valueOf/StateOf/FieldOf to field context (#61378)
* feat(forms): Adding state/field/valueOf/StateOf/FieldOf to field context
I'm currently keeping `resolve` so we don't have to do big changes until we finalize the API.
2025-05-21 10:59:33 -04:00
Kirill Cherkashin e61da79b3c feat(forms): Support marking a field as dirty (#61183)
Co-authored-by: kirjs <kirjs@users.noreply.github.com>
2025-05-20 15:52:23 -04:00
Miles Malerba aa318860db refactor(forms): add back schema function (#61379) 2025-05-20 11:58:40 -07:00
Alex Rickabaugh 359eb4df1b Merge branch 'main' of github.com:angular/angular into prototype/signal-forms 2025-05-20 09:57:25 -07:00
Miles Malerba dc404f0a8e add support for readonly (#61369)
* add support for readonly

* skip validation on readonly fields
2025-05-15 12:33:21 -07:00
Alex Rickabaugh 383543e5f9 wip: async validation! 2025-05-14 14:00:37 -07:00
Miles Malerba 0c99e56f97 add default touched event logic for ui controls (#61288) 2025-05-13 08:49:00 -07:00
Alex Rickabaugh a12a9fe4f9 wip: injection, data deps, resources 2025-05-12 11:42:15 -07:00
Miles Malerba 8b5aeebb54 test: switch tests to jasmine_node_test (#61074) 2025-04-30 16:03:48 -04:00
Miles Malerba 02f26cf56a Revert "Async WIP"
This reverts commit 94e1ee9eef.
2025-04-28 21:03:55 +00:00
kirjs 94e1ee9eef Async WIP 2025-04-28 16:57:31 -04:00
Miles Malerba 9385dc457c refactor: use relative FieldPathNodes (#60791)
* refactor: use relative FieldPathNodes

Switch `apply` behavior to create a brand new root path whenever a new
schema is applied. This effectively changes our `FieldPathNode` concept
from an absolute path to a relative path.

To record how the relative path nodes are connected, introduce a
`rootPaths` map to each logic node, and when we `apply`, copy all roots
from the sub-schema to the root of the current schema. This results in
the top-level schema having a mapping to all roots under it, allowing it
to construct the full absolute path.

Note: this breaks the current resolve logic, resolve needs to be
rewritten to understand the new path structure.

* refactor: merge logic nodes

Adds a method to merge one logic node into another. This allows them to
be built up separaterly and then joined, instead of always applying
rules to the same logic instance.

* fix: update resolve to handle relative paths

* fix: preserve predicates while merging

* refactor: cleanup code organization

* refactor: move subroots concept from logic to path

* refactor: Differentiate PathNode vs RootPathNode

* refactor: Optimize resolve logic

Optimize the resolve logic by only calling `getChild` from the common
ancestor field rather than the root field of the entire form. This
ensures we do not overreact to changes that won't affect the resolved
field.
2025-04-22 09:39:25 -07:00
kirjs 8b4d217466 tutorial: Move list of features to the doc 2025-04-16 13:19:19 -04:00
kirjs 16d637eb6a Expose metadata through public api 2025-04-16 12:35:13 -04:00
kirjs 05a4299ea6 Expose metadata through public api 2025-04-16 12:06:13 -04:00
kirjs 1b607655b6 Add hidden section to the tutorial
Improve applyWhen explanation
Move metadata stuff to public API
2025-04-16 11:53:02 -04:00
kirjs 9089293adf Add spaces to properly format code things 2025-04-11 12:45:27 -04:00
kirjs e91434fd71 Space feedback form description better, so markdown picks it up as code 2025-04-11 08:44:21 -04:00
kirjs 01e18cde8e Final tutorial polish 2025-04-10 18:17:56 -04:00
kirjs 3712624dfa Update tutorial and resolve TODOs 2025-04-10 15:50:00 -04:00
kirjs 7f341416f9 Add forms tutorial 2025-04-10 15:29:04 -04:00
kirjs d962dc9be1 Add Validator<T> type 2025-04-08 12:50:39 -04:00
kirjs 0cb0cd47af Only export public stuff 2025-04-07 13:38:41 -04:00
kirjs 6d21572825 Rexport everything through public_api.ts 2025-04-07 11:51:26 -04:00
kirjs 1dc28b05c0 Update readme with things that are not yet supported 2025-04-04 15:59:29 -04:00
Miles Malerba aee6ab93d6 Update docs to reflect renamed API concepts (#60734)
* Update docs to reflect renamed API concepts

* fixup! Update docs to reflect renamed API concepts

* Update docs to reflect removal of `schema()` function
2025-04-04 08:32:53 -07:00
Miles Malerba df80ab9d4c Simplify concept of schemas to just a type (#60732) 2025-04-03 16:49:51 -07:00
Miles Malerba 1be49f70d0 More followup renaming 2025-04-03 21:02:59 +00:00
Miles Malerba f142d48b8d Update docs and names to better align with the new API names 2025-04-03 20:31:33 +00:00
Miles Malerba 95fb3b4c14 Rename APIs as discussed last session 2025-04-03 18:24:49 +00:00
kirjs 2bf5732026 Move more changes 2025-04-03 12:38:07 -04:00
Miles Malerba 2a52ce8019 Add disabled reason support 2025-04-03 16:26:42 +00:00
kirjs 0f7c4338dc Fix imports in node spec 2025-04-03 12:16:13 -04:00
kirjs 29384b5344 Bring back node spec 2025-04-03 12:07:38 -04:00
Miles Malerba 86f3816abd Add some user docs 2025-04-03 15:52:04 +00:00
kirjs c71cc659f9 Make sure all files have licence header. 2025-04-02 13:29:12 -04:00
kirjs ee492c979b Move rule-based forms prototype into the main repo.
Note: this is a prototype, and is still work in progress

Get rid of old stuff
2025-04-02 13:21:24 -04:00
kirjs b167365bde Allow marking things as not dirty 2025-03-05 15:54:33 -05:00
Miles Malerba 069282f462 wip ui test 2025-03-03 19:42:18 +00:00
Alex Rickabaugh 51e79aaf73 impl: validation & errors
Propagation of validation and errors (untested)
2025-02-28 14:02:07 -08:00
Miles Malerba 001b0ba185 cleanup some old stuff 2025-02-28 01:06:19 +00:00
Miles Malerba 0ee9c7037b add form proxy 2025-02-28 01:00:15 +00:00
Miles Malerba 4f3ea59c1e work on schema for dynamic els 2025-02-26 22:08:27 +00:00
Alex Rickabaugh 1ff640dfb3 impl: working node, arrays, & logic 2025-02-24 13:59:42 -08:00
Miles Malerba 32cc250080 nested arrays test 2025-02-21 22:18:46 +00:00
Miles Malerba 75d394eb2e more thinking & prototyping 2025-02-21 21:59:40 +00:00
Alex Rickabaugh 794e80b057 refactor: sketch outline of FormNode (replacement for FormFieldImpl) 2025-02-19 14:03:29 -08:00
Miles Malerba 7d37cf7877 minor cleanup for engine 2025-02-04 00:14:35 +00:00
Miles Malerba 85f8db7539 directive stand-in for the field concept 2025-02-03 23:33:58 +00:00
Alex Rickabaugh 7ce2640a28 refactor: add linked signal to FormFieldImpl 2025-01-29 14:05:38 -08:00
Miles Malerba 68a4bf35e9 refactoring pass & metadata 2025-01-28 17:40:25 +00:00
Alex Rickabaugh 882c0ea8a1 refactor(forms): add engine & tests 2025-01-27 13:58:17 -08:00
Miles Malerba 1c8ad18dd6 cleanup some earlier prototyping 2025-01-24 22:43:38 +00:00
Miles Malerba afdbeae46c add some ideas about schema merging 2025-01-21 18:05:24 +00:00
Miles Malerba ebecdea206 prototype2: dynamic objects & zod 2025-01-21 18:01:21 +00:00
Miles Malerba 47333e4aa0 add another prototype idea 2025-01-12 21:25:18 +00:00
kirjs 68eba763fe Add custom schema experiments 2025-01-07 14:12:38 -05:00
kirjs ab17a84da6 Actually mention signals in readme :) 2024-12-26 14:44:39 -05:00
kirjs b2d971b143 Add my thoughts on potential signal forms API 2024-12-26 14:36:54 -05:00
Miles Malerba a94d0db4db refactor: update idea1 into a working minimal prototype 2024-12-23 23:29:42 +00:00
Miles Malerba fb67fc2a0e add an idea for binding field to elements 2024-12-15 22:17:40 +00:00
Miles Malerba 8e830f942f refactor: cleanup idea1 and add notes 2024-12-15 19:05:46 +00:00
Miles Malerba 405f2f0a41 refactor: noop impl of initial concept 2024-12-10 23:40:20 +00:00
Alex Rickabaugh 139a4afdbd refactor: move PACKAGE.md to README.md and include entrypoint in build 2024-12-10 14:18:56 -08:00
Miles Malerba 8550bd0683 refactor: remove forms-experimental, moved to forms/experimental 2024-12-10 21:19:08 +00:00
Miles Malerba 78505746ee refactor: add experimental forms package
Co-authored-by: Alex Rickabaugh <alxhub@users.noreply.github.com>
2024-12-10 19:24:08 +00:00
Miles Malerba d29ced91a1 refactor: add experimental forms package
Co-authored-by: Alex Rickabaugh <alxhub@users.noreply.github.com>
2024-12-10 01:16:54 +00:00
1111 changed files with 285658 additions and 176487 deletions
+2 -6
View File
@@ -35,11 +35,10 @@ integration/standalone-bootstrap/node_modules
integration/terser/node_modules
integration/trusted-types/node_modules
integration/typings_test_rxjs7/node_modules
integration/legacy-animations/node_modules
integration/legacy-animations-async/node_modules
integration/typings_test_ts59/node_modules
integration/typings_test_ts58/node_modules
modules/ssr-benchmarks/node_modules
# For rules_js
adev/node_modules
adev/shared-docs/node_modules
@@ -62,10 +61,7 @@ packages/platform-server/node_modules
packages/platform-browser-dynamic/node_modules
packages/router/node_modules
packages/zone.js/node_modules
packages/zone.js/test/typings/node_modules
packages/upgrade/node_modules
packages/benchpress/node_modules
packages/service-worker/node_modules
packages/zone.js/test/typings/node_modules
packages/zone.js/node_modules
tools/bazel/rules_angular_store/node_modules
+2
View File
@@ -169,6 +169,8 @@ test:saucelabs --flaky_test_attempts=1
# --ng_perf will ask the Ivy compiler to produce performance results for each build.
build --flag_alias=ng_perf=//packages/compiler-cli:ng_perf
# --full_build_adev will run adev build/serve in a full mode, performing prerendering and DCE.
build --flag_alias=full_build_adev=//adev:full_build_adev
####################################################
# User bazel configuration
@@ -12,7 +12,6 @@ esbuild_checked_in(
entry_point = ":lib/main.mts",
external = [
"undici",
"pnpapi",
],
metafile = False,
platform = "node",
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -5,9 +5,9 @@ runs:
using: 'composite'
steps:
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/saucelabs@80db036355181684ed07021a175e9f039190d3d6
- name: Starting Saucelabs tunnel service
shell: bash
run: ./tools/saucelabs/sauce-service.sh run &
+7 -7
View File
@@ -15,22 +15,22 @@ permissions: read-all
jobs:
adev-build:
runs-on: ubuntu-latest-8core
runs-on: ubuntu-latest
if: |
(github.event.action == 'labeled' && github.event.label.name == 'adev: preview') ||
(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@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build adev
run: pnpm bazel build //adev:build.production
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- name: Build adev to ensure it continues to work
run: pnpm bazel build //adev:build --full_build_adev --config=release
- uses: angular/dev-infra/github-actions/previews/pack-and-upload-artifact@80db036355181684ed07021a175e9f039190d3d6
with:
workflow-artifact-name: 'adev-preview'
pull-number: '${{github.event.pull_request.number}}'
+1 -1
View File
@@ -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@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- uses: angular/dev-infra/github-actions/previews/upload-artifacts-to-firebase@80db036355181684ed07021a175e9f039190d3d6
with:
github-token: '${{secrets.GITHUB_TOKEN}}'
workflow-artifact-name: 'adev-preview'
@@ -16,6 +16,6 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: angular/dev-infra/github-actions/branch-manager@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- uses: angular/dev-infra/github-actions/branch-manager@80db036355181684ed07021a175e9f039190d3d6
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
- run: pnpm install --frozen-lockfile
- uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
with:
bazelrc: ./.bazelrc.user
+46 -38
View File
@@ -21,7 +21,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Check code lint
@@ -39,20 +41,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
disable-package-manager-cache: true
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Cache downloaded Cypress binary
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: '~/.cache/Cypress'
key: cypress-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run unit tests
@@ -66,17 +63,18 @@ jobs:
start: pnpm bazel run //devtools/src:devserver
wait-on: 'http://localhost:4200'
wait-on-timeout: 300
install: false
test:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -85,14 +83,16 @@ jobs:
run: pnpm test:ci
integration-tests:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -102,14 +102,14 @@ jobs:
adev:
runs-on:
labels: ubuntu-latest
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -117,20 +117,20 @@ jobs:
- name: Run tests
run: pnpm bazel test //adev/...
- name: Build adev in fast mode to ensure it continues to work
run: pnpm bazel build //adev:build
run: pnpm bazel build //adev:build --config=release
publish-snapshots:
runs-on:
labels: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- run: echo "https://${{secrets.SNAPSHOT_BUILDS_GITHUB_TOKEN}}:@github.com" > ${HOME}/.git_credentials
@@ -139,14 +139,20 @@ jobs:
zone-js:
runs-on:
labels: ubuntu-latest
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
node-module-directories: |
./node_modules
./packages/zone.js/node_modules
./packages/zone.js/test/typings/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
with:
google_credential: ${{ secrets.RBE_TRUSTED_BUILDS_USER }}
- name: Install node modules
@@ -180,12 +186,14 @@ jobs:
- run: pnpm -C packages/zone.js/test/typings test
# saucelabs:
# runs-on: ubuntu-latest
# runs-on: ubuntu-latest-4core
# env:
# SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
# steps:
# - name: Initialize environment
# uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b5a3609f89c06eb4037dce22a93641213a5d1508
# with:
# cache-node-modules: true
# - name: Install node modules
# run: pnpm install --frozen-lockfile
# - uses: ./.github/actions/saucelabs-legacy
@@ -193,18 +201,18 @@ jobs:
adev-deploy:
needs: [adev]
if: needs.adev.result == 'success'
runs-on: ubuntu-latest-8core
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Build adev
run: pnpm bazel build //adev:build.production --config=release
- name: Build adev to ensure it continues to work
run: pnpm bazel build //adev:build --full_build_adev --config=release
- name: Deploy to firebase
uses: ./.github/actions/deploy-docs-site
with:
+2 -2
View File
@@ -13,13 +13,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: angular/dev-infra/github-actions/pull-request-labeling@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- uses: angular/dev-infra/github-actions/pull-request-labeling@80db036355181684ed07021a175e9f039190d3d6
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
post_approval_changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: angular/dev-infra/github-actions/post-approval-changes@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- uses: angular/dev-infra/github-actions/post-approval-changes@80db036355181684ed07021a175e9f039190d3d6
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: angular/dev-infra/github-actions/google-internal-tests@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- uses: angular/dev-infra/github-actions/google-internal-tests@80db036355181684ed07021a175e9f039190d3d6
with:
run-tests-guide-url: http://go/angular-g3sync-start
github-token: ${{ secrets.GITHUB_TOKEN }}
+5 -5
View File
@@ -8,22 +8,22 @@ jobs:
# Bazel saucelabs job resides in `manual.yml` because it's currently unstable, but
# kept as "runnable" for debugging/stabilization effort purposes.
bazel-saucelabs:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-4core
env:
JOBS: 2
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Saucelabs Variables
uses: angular/dev-infra/github-actions/saucelabs@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/saucelabs@80db036355181684ed07021a175e9f039190d3d6
- name: Set up Sauce Tunnel Daemon
run: pnpm bazel run //tools/saucelabs-daemon/background-service -- $JOBS &
env:
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
status:
runs-on: ubuntu-latest
steps:
- uses: angular/dev-infra/github-actions/unified-status-check@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- uses: angular/dev-infra/github-actions/unified-status-check@80db036355181684ed07021a175e9f039190d3d6
with:
angular-robot-key: ${{ secrets.ANGULAR_ROBOT_PRIVATE_KEY }}
+4 -4
View File
@@ -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@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- id: workflows
@@ -36,16 +36,16 @@ jobs:
workflow: ${{ fromJSON(needs.list.outputs.workflows) }}
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- 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
# identity federation. This allows us to request short lived credentials on demand, rather than storing
# credentials in secrets long term. More information can be found at:
# https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform
- uses: 'google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed' # v2
- uses: 'google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f' # v2
with:
project_id: 'internal-200822'
workload_identity_provider: 'projects/823469418460/locations/global/workloadIdentityPools/measurables-tracking/providers/angular'
+40 -30
View File
@@ -19,7 +19,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Check code lint
@@ -37,7 +39,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@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/linting/licenses@80db036355181684ed07021a175e9f039190d3d6
with:
allow-dependencies-licenses: 'pkg:npm/google-protobuf@'
@@ -45,18 +47,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
disable-package-manager-cache: true
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
- name: Cache downloaded Cypress binary
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: '~/.cache/Cypress'
key: cypress-cache-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run unit tests
@@ -70,17 +67,18 @@ jobs:
start: pnpm bazel run //devtools/src:devserver
wait-on: 'http://localhost:4200'
wait-on-timeout: 300
install: false
test:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-8core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run CI tests for framework
@@ -97,14 +95,16 @@ jobs:
retention-days: 1
integration-tests:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel Remote Caching
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run integration CI tests for framework
@@ -112,31 +112,37 @@ jobs:
adev:
runs-on:
labels: ubuntu-latest
labels: ubuntu-latest-8core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm bazel test //adev/...
- name: Build adev in fast mode to ensure it continues to work
run: pnpm bazel build //adev:build
run: pnpm bazel build //adev:build --config=release
zone-js:
runs-on:
labels: ubuntu-latest
labels: ubuntu-latest-4core
steps:
- name: Initialize environment
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@80db036355181684ed07021a175e9f039190d3d6
with:
cache-node-modules: true
node-module-directories: |
./node_modules
./packages/zone.js/node_modules
./packages/zone.js/test/typings/node_modules
- name: Setup Bazel
uses: angular/dev-infra/github-actions/bazel/setup@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/setup@80db036355181684ed07021a175e9f039190d3d6
- name: Setup Bazel RBE
uses: angular/dev-infra/github-actions/bazel/configure-remote@b7672ff60456719e6d9b0cc052abc73a7adc8df2
uses: angular/dev-infra/github-actions/bazel/configure-remote@80db036355181684ed07021a175e9f039190d3d6
- name: Install node modules
run: pnpm install --frozen-lockfile
- run: |
@@ -157,6 +163,8 @@ jobs:
cp dist/bin/packages/zone.js/npm_package/bundles/zone-patch-electron.umd.js ./packages/zone.js/test/extra/
cp dist/bin/packages/zone.js/test/closure/zone.closure.js ./packages/zone.js/build/test/zone.closure.mjs
# Install
- run: pnpm -C packages/zone.js install --frozen-lockfile
# Run zone.js tools tests
- run: pnpm -C packages/zone.js promisefinallytest
- run: pnpm -C packages/zone.js jest:test
@@ -166,12 +174,14 @@ jobs:
- run: pnpm -C packages/zone.js/test/typings test
# saucelabs:
# runs-on: ubuntu-latest
# runs-on: ubuntu-latest-4core
# env:
# SAUCE_TUNNEL_IDENTIFIER: angular-framework-${{ github.run_number }}
# steps:
# - name: Initialize environment
# uses: angular/dev-infra/github-actions/npm/checkout-and-setup-node@b5a3609f89c06eb4037dce22a93641213a5d1508
# with:
# cache-node-modules: true
# - name: Install node modules
# run: pnpm install --frozen-lockfile
# - uses: ./.github/actions/saucelabs-legacy
+2 -2
View File
@@ -30,7 +30,7 @@ jobs:
persist-credentials: false
- name: 'Run analysis'
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
@@ -47,6 +47,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11
with:
sarif_file: results.sarif
@@ -37,7 +37,7 @@ jobs:
env:
ANGULAR_CDK_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CDK_BUILDS_READONLY_GITHUB_TOKEN }}
- name: Create a PR CDK apis (if necessary)
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@v7.0.8 # v7.0.8
with:
token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
push-to-fork: 'angular-robot/angular'
@@ -52,7 +52,6 @@ jobs:
labels: |
action: merge
area: docs
target: automation
commit-message: |
docs: update Angular CDK apis
@@ -67,7 +66,7 @@ jobs:
env:
ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN: ${{ secrets.ANGULAR_CLI_BUILDS_READONLY_GITHUB_TOKEN }}
- name: Create a PR CLI help (if necessary)
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
uses: peter-evans/create-pull-request@v7.0.8 # v7.0.8
with:
token: ${{ secrets.ANGULAR_ROBOT_ACCESS_TOKEN }}
push-to-fork: 'angular-robot/angular'
@@ -82,7 +81,6 @@ jobs:
labels: |
action: merge
area: docs
target: automation
commit-message: |
docs: update Angular CLI help
+1
View File
@@ -15,6 +15,7 @@ export const format: FormatConfig = {
// Both third_party and .yarn are directories containing copied code which should
// not be modified.
'!third_party/**',
'!.yarn/**',
// Do not format the locale files which are checked-in for Google3, but generated using
// the `generate-locales-tool` from `packages/common/locales`.
'!packages/core/src/i18n/locale_en.ts',
+1
View File
@@ -2,6 +2,7 @@ import {ReleaseConfig} from '@angular/ng-dev';
/** Configuration for the `ng-dev release` command. */
export const release: ReleaseConfig = {
rulesJsInteropMode: true,
publishRegistry: 'https://wombat-dressing-room.appspot.com',
representativeNpmPackage: '@angular/core',
npmPackages: [
+1 -1
View File
@@ -1 +1 @@
22.20.0
22.18.0
+5 -22
View File
@@ -56,8 +56,8 @@
version: 3
#availability:
# users_unavailable: []
availability:
users_unavailable: ['devversion']
# Meta field that goes unused by PullApprove to allow for defining aliases to be
# used throughout the config.
@@ -135,9 +135,6 @@ groups:
- kirjs
- JoostK
- mmalerba
- ~amishne
- ~leonsenft
- ~mattrbeck
# =========================================================
# Framework: General (most code in our packages)
@@ -181,9 +178,6 @@ groups:
- ~pkozlowski-opensource
- mmalerba
- JeanMeche
- ~amishne
- ~leonsenft
- ~mattrbeck
# =========================================================
# Framework: Security-sensitive files which require extra review
@@ -237,9 +231,6 @@ groups:
- thePunderWoman
- ~pkozlowski-opensource
- mmalerba
- ~amishne
- ~leonsenft
- ~mattrbeck
# =========================================================
# Tooling: Compiler API shared with Angular CLI
@@ -293,9 +284,6 @@ groups:
- MarkTechson
- mmalerba
- ~hawkgs
- ~amishne
- ~leonsenft
- ~mattrbeck
# =========================================================
# Angular DevTools
@@ -333,6 +321,7 @@ groups:
'.gemini/**/{*,.*}',
'.ng-dev/**/{*,.*}',
'.vscode/**/{*,.*}',
'.yarn/**/{*,.*}',
'contributing-docs/*.md',
'contributing-docs/images/**/{*,.*}',
'goldens/{*,.*}',
@@ -409,12 +398,9 @@ groups:
- ~iteriani
- ~tbondwilkinson
- ~rahatarmanahmed
- ~amishne
- ~leonsenft
- ~mattrbeck
reviews:
request: 2
required: 1
request: 3 # Request reviews from 3 people
required: 2 # Require that 2 people approve
reviewed_for: required
# ================================================
@@ -439,9 +425,6 @@ groups:
- thePunderWoman
- ~pkozlowski-opensource
- mmalerba
- ~amishne
- ~leonsenft
- ~mattrbeck
reviews:
request: 2 # Request reviews from 2 people
required: 1 # Require that 1 person approve
+13
View File
@@ -0,0 +1,13 @@
# Yarn Vendoring
We utilize Yarn's `yarn-path` configuration in a shared `.yarnrc` file to enforce
everyone using the same version of Yarn. Yarn checks the `.yarnrc` file to
determine if yarn should delegate the command to a vendored version at the
provided path.
## How to update
To update to the latest version of Yarn as our vendored version:
- Run this command
```sh
yarn policies set-version latest
```
- Remove the previous version
+148049
View File
File diff suppressed because one or more lines are too long
+5
View File
@@ -0,0 +1,5 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
yarn-path ".yarn/releases/yarn-1.22.22.cjs"
+14 -7
View File
@@ -1,7 +1,7 @@
load("@aspect_rules_js//js:defs.bzl", "js_library")
load("@devinfra//bazel/validation:defs.bzl", "validate_ts_version_matching")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("//tools:defaults.bzl", "copy_to_bin")
load("//:yarn.bzl", "YARN_PATH")
load("//tools:defaults.bzl", "copy_to_bin", "js_binary")
package(default_visibility = ["//visibility:public"])
@@ -9,7 +9,9 @@ exports_files([
"LICENSE",
"karma-js.conf.js",
"browser-providers.conf.js",
YARN_PATH,
"package.json",
"angular.json",
])
npm_link_all_packages(
@@ -23,11 +25,6 @@ filegroup(
srcs = ["package.json"],
)
validate_ts_version_matching(
module_lock_file = "MODULE.bazel.lock",
package_json = "package.json",
)
alias(
name = "tsconfig.json",
actual = "//packages:tsconfig-build.json",
@@ -63,6 +60,16 @@ config_setting(
values = {"stamp": "true"},
)
js_binary(
name = "yarn_vendored",
data = [".yarn/releases/yarn-1.22.22.cjs"],
entry_point = ".yarn/releases/yarn-1.22.22.cjs",
visibility = [
"//integration:__subpackages__",
"//modules/ssr-benchmarks:__subpackages__",
],
)
alias(
name = "sauce_connect",
actual = select({
+59 -143
View File
@@ -1,146 +1,3 @@
<a name="20.3.3"></a>
# 20.3.3 (2025-10-02)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [f51ab32fb3](https://github.com/angular/angular/commit/f51ab32fb3000ae34c077b049ff2f7b8e3e22d14) | fix | recover template literals with broken expressions ([#64150](https://github.com/angular/angular/pull/64150)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [542cd0019a](https://github.com/angular/angular/commit/542cd0019aa509e399282ccf7cb5fa6208cef70e) | fix | do not rename ARIA property bindings to attributes ([#64089](https://github.com/angular/angular/pull/64089)) |
| [0e928fbc4a](https://github.com/angular/angular/commit/0e928fbc4a351303c4ce081a679f4a38c0acd5e6) | fix | Fixes animations in conjunction with content projection ([#63776](https://github.com/angular/angular/pull/63776)) |
| [e5157bd933](https://github.com/angular/angular/commit/e5157bd933c41836fb431659f42dfb4cdbe0d2d1) | fix | prevents unintended early termination of leave animations and hoisting ([#64088](https://github.com/angular/angular/pull/64088)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [1710cbd7d4](https://github.com/angular/angular/commit/1710cbd7d484ccd5e9ab39b95a44e2d222f4262d) | fix | handle shorthand property declarations in NgModule ([#64160](https://github.com/angular/angular/pull/64160)) |
| [77b6305a4b](https://github.com/angular/angular/commit/77b6305a4b5db88f9c1130acf80095b502a0eca1) | fix | skip migration for inputs with 'this' references ([#64142](https://github.com/angular/angular/pull/64142)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.2"></a>
# 20.3.2 (2025-09-24)
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [ba40153ac0](https://github.com/angular/angular/commit/ba40153ac07fc721585a1224fda09a654672cb74) | fix | capture metadata for undecorated fields ([#63904](https://github.com/angular/angular/pull/63904)) |
| [1d4f81c8ee](https://github.com/angular/angular/commit/1d4f81c8eedf5ea69c51c720f8dc5c5d12a62ba2) | fix | resolve import alias in defer blocks ([#63966](https://github.com/angular/angular/pull/63966)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [9515a70933](https://github.com/angular/angular/commit/9515a709331883f0ca9857ed46a5262b01979a26) | fix | fix narrowing of `Resource.hasValue()` ([#63994](https://github.com/angular/angular/pull/63994)) |
| [e78451cf8a](https://github.com/angular/angular/commit/e78451cf8a48322879e83b33fecc0b5854947afb) | fix | prevent animations renderer from impacting `animate.leave` ([#63921](https://github.com/angular/angular/pull/63921)) |
### forms
| Commit | Type | Description |
| -- | -- | -- |
| [1fd8d5d446](https://github.com/angular/angular/commit/1fd8d5d446f909a16a127ba117a0f423c7a5db0c) | fix | Emit `FormResetEvent` when resetting control ([#64034](https://github.com/angular/angular/pull/64034)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [16d0d43ad4](https://github.com/angular/angular/commit/16d0d43ad4903b69b8dcd9b76c48b5089e7f82ee) | fix | handle import aliases to the same module name ([#63934](https://github.com/angular/angular/pull/63934)) |
| [3ebaeccb46](https://github.com/angular/angular/commit/3ebaeccb466119ee43eeaa486f5e132c85e9caa2) | fix | handle reused templates in control flow migration ([#63996](https://github.com/angular/angular/pull/63996)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.1"></a>
# 20.3.1 (2025-09-17)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [7fb5a8087e](https://github.com/angular/angular/commit/7fb5a8087ee8fb0451cedbe6ac4ce972eca4b56e) | fix | Add support for `aria-invalid` ([#63748](https://github.com/angular/angular/pull/63748)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [8843707919](https://github.com/angular/angular/commit/88437079190cef9ee522a3e2defa6e2672c2d030) | fix | only bind inputs that are part of microsyntax to a structural directive ([#52453](https://github.com/angular/angular/pull/52453)) |
| [38c9921ff3](https://github.com/angular/angular/commit/38c9921ff387d235981a79e26dc8bc7e60a2e10c) | fix | signal not invoked diagnostic not raised when input has same name in template ([#63754](https://github.com/angular/angular/pull/63754)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [802dbcc2a0](https://github.com/angular/angular/commit/802dbcc2a0c5d3784cb04b4c78ea71ed0925327c) | fix | prevent animation events from being cleaned up on destroy ([#63414](https://github.com/angular/angular/pull/63414)) |
| [3ec8a5c753](https://github.com/angular/angular/commit/3ec8a5c7536cdd2c1db7db4bfbc2d4995156a833) | fix | Prevent leave animations on a move operation ([#63745](https://github.com/angular/angular/pull/63745)) |
### migrations
| Commit | Type | Description |
| -- | -- | -- |
| [6e54bdfdcb](https://github.com/angular/angular/commit/6e54bdfdcb01522ee46865fadec911f960fff730) | fix | fix route-lazy-loading migration ([#63818](https://github.com/angular/angular/pull/63818)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.3.0"></a>
# 20.3.0 (2025-09-10)
## Breaking Changes
### core
- The server-side bootstrapping process has been changed to eliminate the reliance on a global platform injector.
Before:
```ts
const bootstrap = () => bootstrapApplication(AppComponent, config);
```
After:
```ts
const bootstrap = (context: BootstrapContext) =>
bootstrapApplication(AppComponent, config, context);
```
A schematic is provided to automatically update `main.server.ts` files to pass the `BootstrapContext` to the `bootstrapApplication` call.
In addition, `getPlatform()` and `destroyPlatform()` will now return `null` and be a no-op respectively when running in a server environment.
(cherry picked from commit 8bf80c9d2314b4f2bcf3df83ae01552a6fc49834)
###
| Commit | Type | Description |
| -- | -- | -- |
| [a3f808d7c8](https://github.com/angular/angular/commit/a3f808d7c8cc59a4fd69f2e4b8d21a6510efa046) | fix | remove refresh button from transfer state tab ([#63592](https://github.com/angular/angular/pull/63592)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [6117ccee2e](https://github.com/angular/angular/commit/6117ccee2e1507fb00549cd70e064282645db803) | feat | introduce `BootstrapContext` for improved server bootstrapping ([#63636](https://github.com/angular/angular/pull/63636)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.2.4"></a>
# 20.2.4 (2025-09-03)
### core
| Commit | Type | Description |
| -- | -- | -- |
| [dc64f3e478](https://github.com/angular/angular/commit/dc64f3e478c5cc1e354a0ff7cf5965b817b345d6) | fix | Fixed inject migration schematics for migrate destructured properties ([#62832](https://github.com/angular/angular/pull/62832)) |
### platform-server
| Commit | Type | Description |
| -- | -- | -- |
| [d1d32db972](https://github.com/angular/angular/commit/d1d32db97260c1e57c2937588002feb4271c7774) | fix | prevent false warning for duplicate state serialization ([#63525](https://github.com/angular/angular/pull/63525)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.2.3"></a>
# 20.2.3 (2025-08-29)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [479a919f42](https://github.com/angular/angular/commit/479a919f42517193653384220adab5b89dd74e3d) | fix | fixes regression with event parsing and animate prefix ([#63470](https://github.com/angular/angular/pull/63470)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [f87fad3fff](https://github.com/angular/angular/commit/f87fad3fff62cebf2868e06cba48e0f27b719d24) | fix | avoid injecting internal error handler from a destroyed injector ([#62275](https://github.com/angular/angular/pull/62275)) |
| [114906d2d6](https://github.com/angular/angular/commit/114906d2d68d98c98961d858abd3ae714d4809a3) | fix | Fix cancellation of animation enter classes ([#63442](https://github.com/angular/angular/pull/63442)) |
| [596b545130](https://github.com/angular/angular/commit/596b5451309b8ce4f08a1cd36e6b3610507d52f9) | fix | Prevent an error on cleanup when an `rxResource` `stream` threw before returning an `Observable` ([#63342](https://github.com/angular/angular/pull/63342)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.2.2"></a>
# 20.2.2 (2025-08-27)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [d7b6045d61](https://github.com/angular/angular/commit/d7b6045d61582d20a17802e769dc1441984988f0) | fix | fixes animations on elements with structural directives ([#63390](https://github.com/angular/angular/pull/63390)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [6c421ed65d](https://github.com/angular/angular/commit/6c421ed65d050765a18eafc51fe7257abc5682ce) | fix | Ensures `@for` loop animations never get cancelled ([#63328](https://github.com/angular/angular/pull/63328)) |
| [9093e0e132](https://github.com/angular/angular/commit/9093e0e132f99c2b590c31b299871bcd493b7de0) | fix | fix memory leak with leaving nodes tracking ([#63328](https://github.com/angular/angular/pull/63328)) |
| [c8f07daf8f](https://github.com/angular/angular/commit/c8f07daf8f2c7e8c6641eb4368379a3f5f1d1f52) | fix | Fixes `animate.leave` binding to a string with spaces ([#63366](https://github.com/angular/angular/pull/63366)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.2.1"></a>
# 20.2.1 (2025-08-21)
### compiler
@@ -150,6 +7,30 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="21.0.0-next.0"></a>
# 21.0.0-next.0 (2025-08-20)
## Breaking Changes
### compiler-cli
- The Angular compiler now produces an error when the
the `emitDeclarationOnly` TS compiler option is enabled as this mode is
not supported.
### router
- `lastSuccessfulNavigation` is now a signal and needs to be invoked
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [be7110342b](https://github.com/angular/angular/commit/be7110342b61d837822524d4f60f56a7f859f594) | fix | disallow compiling with the `emitDeclarationOnly` TS compiler option enabled ([#61609](https://github.com/angular/angular/pull/61609)) |
### http
| Commit | Type | Description |
| -- | -- | -- |
| [4bed062bc9](https://github.com/angular/angular/commit/4bed062bc9f2a0a66c9af3cb8aeb42ee023c6393) | feat | Provide http services in root ([#56212](https://github.com/angular/angular/pull/56212)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [4e0fc81491](https://github.com/angular/angular/commit/4e0fc81491bfe6e4eac5c59ef0bde908a3d58413) | feat | convert `lastSuccessfulNavigation` to signal ([#63057](https://github.com/angular/angular/pull/63057)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.2.0"></a>
# 20.2.0 (2025-08-20)
## Deprecations
@@ -234,6 +115,41 @@
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.1.8"></a>
# 20.1.8 (2025-08-20)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [691f5ed033](https://github.com/angular/angular/commit/691f5ed0332d813801f599448577a2c1d450a5ad) | fix | error when ng-content fallback has translated children ([#63156](https://github.com/angular/angular/pull/63156)) |
| [b1dec9bc50](https://github.com/angular/angular/commit/b1dec9bc50f5694cfa1e3629fd48543126debd10) | fix | incorrect source span for expression AST inside template attribute ([#63175](https://github.com/angular/angular/pull/63175)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [cda402f1d8](https://github.com/angular/angular/commit/cda402f1d8bddeedc9aca1979a9bf01be32f81b2) | fix | account for expression with type arguments during HMR extraction ([#63261](https://github.com/angular/angular/pull/63261)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.1.7"></a>
# 20.1.7 (2025-08-13)
### compiler
| Commit | Type | Description |
| -- | -- | -- |
| [d9e37908a5](https://github.com/angular/angular/commit/d9e37908a5f42a4226fd6e2d3493abf35ee5a99a) | fix | incorrect spans for AST inside input value with leading space ([#63082](https://github.com/angular/angular/pull/63082)) |
### compiler-cli
| Commit | Type | Description |
| -- | -- | -- |
| [4aa120ac00](https://github.com/angular/angular/commit/4aa120ac000a569a29e45e9c6db9e4f32c61d183) | fix | error when type checking host bindings of generic directive ([#63061](https://github.com/angular/angular/pull/63061)) |
### core
| Commit | Type | Description |
| -- | -- | -- |
| [322042c5b3](https://github.com/angular/angular/commit/322042c5b30e181019bfdaa6a57fc5abaea7adc1) | fix | destroying the effect on `afterRenderEffect` ([#63001](https://github.com/angular/angular/pull/63001)) |
### router
| Commit | Type | Description |
| -- | -- | -- |
| [5fd79424e3](https://github.com/angular/angular/commit/5fd79424e34ea4bbbfca68bf80ca5541aece829f) | fix | attempt to resolve component resources in JIT mode ([#63062](https://github.com/angular/angular/pull/63062)) |
<!-- CHANGELOG SPLIT MARKER -->
<a name="20.1.6"></a>
# 20.1.6 (2025-08-06)
+16 -23
View File
@@ -5,41 +5,41 @@ module(
)
bazel_dep(name = "rules_pkg", version = "1.1.0")
bazel_dep(name = "rules_nodejs", version = "6.5.2")
bazel_dep(name = "rules_nodejs", version = "6.5.0")
bazel_dep(name = "aspect_rules_ts", version = "3.7.0")
bazel_dep(name = "aspect_rules_js", version = "2.6.0")
bazel_dep(name = "aspect_rules_js", version = "2.5.0")
bazel_dep(name = "aspect_rules_esbuild", version = "0.22.1")
bazel_dep(name = "aspect_rules_jasmine", version = "2.0.0")
bazel_dep(name = "aspect_rules_rollup", version = "2.0.1")
bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "aspect_bazel_lib", version = "2.21.2")
bazel_dep(name = "tar.bzl", version = "0.5.6")
bazel_dep(name = "yq.bzl", version = "0.3.1")
bazel_dep(name = "bazel_skylib", version = "1.8.1")
bazel_dep(name = "aspect_bazel_lib", version = "2.21.1")
bazel_dep(name = "tar.bzl", version = "0.5.5")
bazel_dep(name = "yq.bzl", version = "0.2.0")
bazel_dep(name = "rules_angular")
git_override(
module_name = "rules_angular",
commit = "4010ef96de0c46db7764adc2f262258c9de3d718",
commit = "17eac47ea99057f7473a7d93292e76327c894ed9",
remote = "https://github.com/devversion/rules_angular.git",
)
bazel_dep(name = "devinfra")
git_override(
module_name = "devinfra",
commit = "b7672ff60456719e6d9b0cc052abc73a7adc8df2",
commit = "00a4cf14cdc374867673a52ecb556f356da8bec0",
remote = "https://github.com/angular/dev-infra.git",
)
bazel_dep(name = "rules_sass")
git_override(
module_name = "rules_sass",
commit = "4a54e0e4e75fc6456ff874e53fb4a78bdfeb44b9",
commit = "76078d5e9776a0080dcee496e90b88d8a6179c19",
remote = "https://github.com/devversion/rules_sass.git",
)
bazel_dep(name = "rules_browsers")
git_override(
module_name = "rules_browsers",
commit = "6749ba4c0dc5e04536369fa91fdb2d827b6705ff",
commit = "8ee9ae3216ef26516c8ef20537c89857343cdc3a",
remote = "https://github.com/devversion/rules_browsers.git",
)
@@ -58,10 +58,6 @@ use_repo(node, "nodejs_linux_s390x")
use_repo(node, "nodejs_windows_amd64")
pnpm = use_extension("@aspect_rules_js//npm:extensions.bzl", "pnpm")
pnpm.pnpm(
name = "pnpm",
pnpm_version = "10.16.1",
)
use_repo(pnpm, "pnpm")
npm = use_extension("@aspect_rules_js//npm:extensions.bzl", "npm")
@@ -108,20 +104,17 @@ rules_ts_ext.deps(
name = "angular_npm_typescript",
# Obtained by: curl --silent https://registry.npmjs.org/typescript/5.9.2 | jq -r '.dist.integrity'
ts_integrity = "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
ts_version = "5.9.2",
ts_version_from = "//:package.json",
)
use_repo(rules_ts_ext, **{"npm_typescript": "angular_npm_typescript"})
# TODO: Figure out how to make ng_project update whenever the packages/core::pkg target changes.
rules_angular = use_extension("@rules_angular//setup:extensions.bzl", "rules_angular")
use_repo_rule("@rules_angular//setup:repositories.bzl", "configurable_deps_repo")(
name = "rules_angular_configurable_deps",
angular_compiler_cli = "@angular//:node_modules/@angular/compiler-cli",
typescript = "@angular//:node_modules/typescript",
rules_angular.setup(
name = "components_rules_angular_configurable_deps",
angular_compiler_cli = "//:node_modules/@angular/compiler-cli",
typescript = "//:node_modules/typescript",
)
override_repo(rules_angular, "rules_angular_configurable_deps")
use_repo(rules_angular, **{"rules_angular_configurable_deps": "components_rules_angular_configurable_deps"})
register_toolchains(
"@devinfra//bazel/git-toolchain:git_linux_toolchain",
+98 -42
View File
@@ -16,8 +16,8 @@
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "cb1ba9f9999ed0bc08600c221f532c1ddd8d217686b32ba7d45b0713b5131452",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.14.0/MODULE.bazel": "2b31ffcc9bdc8295b2167e07a757dbbc9ac8906e7028e5170a3708cecaac119f",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.19.3/MODULE.bazel": "253d739ba126f62a5767d832765b12b59e9f8d2bc88cc1572f4a73e46eb298ca",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.2/MODULE.bazel": "276347663a25b0d5bd6cad869252bea3e160c4d980e764b15f3bae7f80b30624",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.2/source.json": "f42051fa42629f0e59b7ac2adf0a55749144b11f1efcd8c697f0ee247181e526",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.1/MODULE.bazel": "07e3ce3eaaa50dbd0be7fa0094e36890478937adc780ec53e77fd9fe543af8b1",
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.21.1/source.json": "cb7d22ce044efa47c6e251107a35b8a919f5cd35254190d825adff1b7ae21e6e",
"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",
@@ -27,8 +27,8 @@
"https://bcr.bazel.build/modules/aspect_rules_jasmine/2.0.0/source.json": "45fa9603cdfe100575a12d8b65fa425fe8713dd8c9f0cdf802168b670bc0e299",
"https://bcr.bazel.build/modules/aspect_rules_js/2.0.0/MODULE.bazel": "b45b507574aa60a92796e3e13c195cd5744b3b8aff516a9c0cb5ae6a048161c5",
"https://bcr.bazel.build/modules/aspect_rules_js/2.4.2/MODULE.bazel": "0d01db38b96d25df7ed952a5e96eac4b3802723d146961974bf020f6dd07591d",
"https://bcr.bazel.build/modules/aspect_rules_js/2.6.0/MODULE.bazel": "5a6f8dbc5b170769453f1819d469fe54b0e4b86a0e82e13fde8e5a45438a1890",
"https://bcr.bazel.build/modules/aspect_rules_js/2.6.0/source.json": "e1e20b259d4f1b0a5d8b99d57b418c96f306e1c77f96e9ea1c9f1068a628b211",
"https://bcr.bazel.build/modules/aspect_rules_js/2.5.0/MODULE.bazel": "12bb9ffdfda5b952644ffa75a69fac1e63da788ad445b056d3ccc70ad39825ac",
"https://bcr.bazel.build/modules/aspect_rules_js/2.5.0/source.json": "884ab90109fb7b92488d8187dfd8e0b93be105d2e42b06d887ab4730ba7d77da",
"https://bcr.bazel.build/modules/aspect_rules_rollup/2.0.1/MODULE.bazel": "296e3a053658c2af989ba9bd62a205e6d1fa84bdd6dd5249196546e6b84770ec",
"https://bcr.bazel.build/modules/aspect_rules_rollup/2.0.1/source.json": "2fe8ac1ccb4de74bf884761e070010280b272d94e3997205b361b91c75409726",
"https://bcr.bazel.build/modules/aspect_rules_ts/3.6.3/MODULE.bazel": "d09db394970f076176ce7bab5b5fa7f0d560fd4f30b8432ea5e2c2570505b130",
@@ -49,8 +49,6 @@
"https://bcr.bazel.build/modules/bazel_features/1.34.0/source.json": "dfa5c4b01110313153b484a735764d247fee5624bbab63d25289e43b151a657a",
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
"https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/MODULE.bazel": "407729e232f611c3270005b016b437005daa7b1505826798ea584169a476e878",
"https://bcr.bazel.build/modules/bazel_lib/3.0.0-beta.1/source.json": "72bfbe19a3936675719157798de64631e9ac54c2b41f13b544b821d094f4840a",
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
"https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
"https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
@@ -64,8 +62,7 @@
"https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
"https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6",
"https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67",
"https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb",
"https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a",
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
"https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8",
@@ -152,8 +149,7 @@
"https://bcr.bazel.build/modules/rules_nodejs/6.2.0/MODULE.bazel": "ec27907f55eb34705adb4e8257952162a2d4c3ed0f0b3b4c3c1aad1fac7be35e",
"https://bcr.bazel.build/modules/rules_nodejs/6.3.0/MODULE.bazel": "45345e4aba35dd6e4701c1eebf5a4e67af4ed708def9ebcdc6027585b34ee52d",
"https://bcr.bazel.build/modules/rules_nodejs/6.5.0/MODULE.bazel": "546d0cf79f36f9f6e080816045f97234b071c205f4542e3351bd4424282a8810",
"https://bcr.bazel.build/modules/rules_nodejs/6.5.2/MODULE.bazel": "7f9ea68a0ce6d82905ce9f74e76ab8a8b4531ed4c747018c9d76424ad0b3370d",
"https://bcr.bazel.build/modules/rules_nodejs/6.5.2/source.json": "6a6ca0940914d55c550d1417cad13a56c9900e23f651a762d8ccc5a64adcf661",
"https://bcr.bazel.build/modules/rules_nodejs/6.5.0/source.json": "ac075bc5babebc25a0adc88ee885f2c8d8520d141f6e139ba9dfa0eedb5be908",
"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",
@@ -187,13 +183,12 @@
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
"https://bcr.bazel.build/modules/tar.bzl/0.2.1/MODULE.bazel": "52d1c00a80a8cc67acbd01649e83d8dd6a9dc426a6c0b754a04fe8c219c76468",
"https://bcr.bazel.build/modules/tar.bzl/0.5.1/MODULE.bazel": "7c2eb3dcfc53b0f3d6f9acdfd911ca803eaf92aadf54f8ca6e4c1f3aee288351",
"https://bcr.bazel.build/modules/tar.bzl/0.5.6/MODULE.bazel": "c5b8bfa6bc7b640883d41bfec4a200fb45c16a2a7b2fb081323f499197cfcc09",
"https://bcr.bazel.build/modules/tar.bzl/0.5.6/source.json": "848b3a4eaf2bb4a09ab78533c348e19a6723c7d5f820eb1dceb0eeb5e5914ac1",
"https://bcr.bazel.build/modules/tar.bzl/0.5.5/MODULE.bazel": "4bfab9bbc7a1966c2c5f7371f5848f5e2d27c465951b4435adc9aaf00ed681da",
"https://bcr.bazel.build/modules/tar.bzl/0.5.5/source.json": "67c322bd9f9a6714b9d55d4df36ddc222976a7fbb2070410ef036f68cdf2eeb7",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072",
"https://bcr.bazel.build/modules/yq.bzl/0.2.0/MODULE.bazel": "6f3a675677db8885be4d607fde14cc51829715e3a879fb016eb9bf336786ce6d",
"https://bcr.bazel.build/modules/yq.bzl/0.3.1/MODULE.bazel": "9bcb7151b3cd4681b89d350530eaf7b45e32a44dda94843b8932b0cb1cd4594a",
"https://bcr.bazel.build/modules/yq.bzl/0.3.1/source.json": "f0b0f204a2a6b0e34b4c9541efe8c04f2ef1af65948daa784eccea738b21dbd2",
"https://bcr.bazel.build/modules/yq.bzl/0.2.0/source.json": "ff33c6f75da6848caade494240b6824cf00e7e6b8892100f4253984e1dfae2af",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
@@ -232,7 +227,7 @@
},
"@@aspect_rules_esbuild~//esbuild:extensions.bzl%esbuild": {
"general": {
"bzlTransitiveDigest": "NeP8heP5Z49UtK5ZkdaSGN2wdf2vyTLxJfpSPeyvV24=",
"bzlTransitiveDigest": "D8qNgGrrdZct3S3KMxl6kgSKedwrEWvW34y5AVoV4PQ=",
"usagesDigest": "u8wMZJd6Ovxb3YTmhoM3sMbh11Qwrv5EHaggdNi5Wb8=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@@ -412,8 +407,8 @@
},
"@@aspect_rules_js~//npm:extensions.bzl%pnpm": {
"general": {
"bzlTransitiveDigest": "eebwHza1XBCmrNmoTUnW56khfYFfcwZpPWjM1Nvu3sk=",
"usagesDigest": "LXGqD89vujoiYUGLlPI1UcHEdy3p899TrYwtpQUR2hM=",
"bzlTransitiveDigest": "0Kn7fvnVxLaVWd5Q+UPHLEIH0A8dYAnRoL8iW6cFIJI=",
"usagesDigest": "iPZn5zAjYcx2ly5xwkl7dCcCxl5n+ty6enw5W6/RG3o=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
@@ -423,11 +418,11 @@
"ruleClassName": "npm_import_rule",
"attributes": {
"package": "pnpm",
"version": "10.16.1",
"version": "8.6.7",
"root_package": "",
"link_workspace": "",
"link_packages": {},
"integrity": "sha512-DhVaomKduGcrSehHXaYiaqS96oX9zf3BU1CHSUbU88kfqvZMvcSl0auAAvRz1cP87c0ZeYnPA5D5ut08BGeHBg==",
"integrity": "sha512-vRIWpD/L4phf9Bk2o/O2TDR8fFoJnpYrp2TKqTIZF/qZ2/rgL3qKXzHofHgbXsinwMoSEigz28sqk3pQ+yMEQQ==",
"url": "",
"commit": "",
"patch_args": [
@@ -452,7 +447,7 @@
"ruleClassName": "npm_import_links",
"attributes": {
"package": "pnpm",
"version": "10.16.1",
"version": "8.6.7",
"dev": false,
"root_package": "",
"link_packages": {},
@@ -550,8 +545,10 @@
"@@aspect_rules_ts~//ts:extensions.bzl%ext": {
"general": {
"bzlTransitiveDigest": "9IJp6IlB/FMHFBJe4MX/DQM4zi3oArC8yqYE/+NyPwk=",
"usagesDigest": "dQksgKjptt43p6K/aEOYjcWKS47HbtBzBSMhEYqUYts=",
"usagesDigest": "1rX5zKEA5AF0V7MwOa4Uu00O9/xtmOXAgV3jUGUxeQk=",
"recordedFileInputs": {
"@@//package.json": "ec188498a5070d792486104602dac93c675ca86076b1b91bcd9927985ea3d738",
"@@devinfra~//bazel/package.json": "960bcecf963a211f96a3967c7cfb5d3e1cea08d94b27056a3e8dbf2fad1e2dd3",
"@@rules_browsers~//package.json": "45572077938c7a4916e4aaedf7db7ce8425854ab92f35348cff02a2134023bb8"
},
"recordedDirentsInputs": {},
@@ -561,7 +558,8 @@
"bzlFile": "@@aspect_rules_ts~//ts/private:npm_repositories.bzl",
"ruleClassName": "http_archive_version",
"attributes": {
"version": "5.9.2",
"version": "",
"version_from": "@@//:package.json",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"urls": [
"https://registry.npmjs.org/typescript/-/typescript-{}.tgz"
@@ -583,7 +581,8 @@
"bzlFile": "@@aspect_rules_ts~//ts/private:npm_repositories.bzl",
"ruleClassName": "http_archive_version",
"attributes": {
"version": "5.9.2",
"version": "",
"version_from": "@@devinfra~//bazel:package.json",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"urls": [
"https://registry.npmjs.org/typescript/-/typescript-{}.tgz"
@@ -625,7 +624,7 @@
"@@aspect_tools_telemetry~//:extension.bzl%telemetry": {
"general": {
"bzlTransitiveDigest": "gA7tPEdJXhskzPIEUxjX9IdDrM6+WjfbgXJ8Ez47umk=",
"usagesDigest": "GEL1puRyMuoDKpELV0EDVcA/hQ0OdxZ9UR/t2Vtj7a0=",
"usagesDigest": "tH6KsPz1uWO2OjNaRD1bEd5rnHtyNuMwWpFkIds3hg0=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
@@ -636,7 +635,7 @@
"attributes": {
"deps": {
"aspect_rules_ts": "3.7.0",
"aspect_rules_js": "2.6.0",
"aspect_rules_js": "2.5.0",
"aspect_tools_telemetry": "0.2.8"
}
}
@@ -695,11 +694,19 @@
"@@rules_angular~//setup:extensions.bzl%rules_angular": {
"general": {
"bzlTransitiveDigest": "fkaH7HMicL3g7/NDaFzlq39kcLopMyQ3KdbDn+5CRzA=",
"usagesDigest": "c/HgGZ21I+QxW0XkaMGJK0N3nV5rdEiNEcPkToa4i9M=",
"usagesDigest": "1oeycgV+w0mSegt2ycKii5zoBkwEZzF4+hcp4rOX7sg=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"components_rules_angular_configurable_deps": {
"bzlFile": "@@rules_angular~//setup:repositories.bzl",
"ruleClassName": "configurable_deps_repo",
"attributes": {
"angular_compiler_cli": "@@rules_angular~//:node_modules/@angular/compiler-cli",
"typescript": "@@rules_angular~//:node_modules/typescript"
}
},
"rules_angular_configurable_deps": {
"bzlFile": "@@rules_angular~//setup:repositories.bzl",
"ruleClassName": "configurable_deps_repo",
@@ -1129,8 +1136,8 @@
},
"@@rules_nodejs~//nodejs:extensions.bzl%node": {
"general": {
"bzlTransitiveDigest": "FmfMiNXAxRoLWw3NloQbssosE1egrSvzirbQnso7j7E=",
"usagesDigest": "xHkFztDWYtKJ37XnwOK4sKovvRwpnGtzZns6k+dDdyI=",
"bzlTransitiveDigest": "hdICB1K7PX7oWtO8oksVTBDNt6xxiNERpcO4Yxoa0Gc=",
"usagesDigest": "wKnieRDVg5P05DNKjiUSVbzaCxzv9IOZ9TE2JMxlfrg=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
@@ -4132,7 +4139,7 @@
},
"@@rules_sass~//src/toolchain:extensions.bzl%sass": {
"general": {
"bzlTransitiveDigest": "RA58Nyrsn03Z5YmQnpmBw3mqlVck++XIrx34amsqU/E=",
"bzlTransitiveDigest": "+GauQp6nWf/mHsJ/XVWUL2JTuC15MuxATrVcAgDpclc=",
"usagesDigest": "FPXQ5+6+DFGdSdCMXLwFaruzstMFlLH6N0TRxi0sSH8=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
@@ -4150,18 +4157,6 @@
]
}
},
"linux_arm64_sass": {
"bzlFile": "@@rules_sass~//src/toolchain:configure_sass.bzl",
"ruleClassName": "configure_sass",
"attributes": {
"file": "@rules_sass//src/compiler/built:sass_linux_arm",
"sha256": "",
"constraints": [
"@@platforms//os:linux",
"@@platforms//cpu:arm64"
]
}
},
"darwin_amd64_sass": {
"bzlFile": "@@rules_sass~//src/toolchain:configure_sass.bzl",
"ruleClassName": "configure_sass",
@@ -4190,10 +4185,71 @@
"recordedRepoMappingEntries": []
}
},
"@@tar.bzl~//tar:extensions.bzl%toolchains": {
"general": {
"bzlTransitiveDigest": "x8T4avQwaccwFRDkBObSMray93ZBHwpcjsZTPQOyII0=",
"usagesDigest": "aQJiuhjXhigIjDvDZxsHPfosrrHvNBHV55yj8QdZQgs=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"bsd_tar_toolchains": {
"bzlFile": "@@tar.bzl~//tar/toolchain:toolchain.bzl",
"ruleClassName": "tar_toolchains_repo",
"attributes": {
"user_repository_name": "bsd_tar_toolchains"
}
},
"bsd_tar_toolchains_darwin_amd64": {
"bzlFile": "@@tar.bzl~//tar/toolchain:platforms.bzl",
"ruleClassName": "bsdtar_binary_repo",
"attributes": {
"platform": "darwin_amd64"
}
},
"bsd_tar_toolchains_darwin_arm64": {
"bzlFile": "@@tar.bzl~//tar/toolchain:platforms.bzl",
"ruleClassName": "bsdtar_binary_repo",
"attributes": {
"platform": "darwin_arm64"
}
},
"bsd_tar_toolchains_linux_amd64": {
"bzlFile": "@@tar.bzl~//tar/toolchain:platforms.bzl",
"ruleClassName": "bsdtar_binary_repo",
"attributes": {
"platform": "linux_amd64"
}
},
"bsd_tar_toolchains_linux_arm64": {
"bzlFile": "@@tar.bzl~//tar/toolchain:platforms.bzl",
"ruleClassName": "bsdtar_binary_repo",
"attributes": {
"platform": "linux_arm64"
}
},
"bsd_tar_toolchains_windows_amd64": {
"bzlFile": "@@tar.bzl~//tar/toolchain:platforms.bzl",
"ruleClassName": "bsdtar_binary_repo",
"attributes": {
"platform": "windows_amd64"
}
},
"bsd_tar_toolchains_windows_arm64": {
"bzlFile": "@@tar.bzl~//tar/toolchain:platforms.bzl",
"ruleClassName": "bsdtar_binary_repo",
"attributes": {
"platform": "windows_arm64"
}
}
},
"recordedRepoMappingEntries": []
}
},
"@@yq.bzl~//yq:extensions.bzl%yq": {
"general": {
"bzlTransitiveDigest": "61Uz+o5PnlY0jJfPZEUNqsKxnM/UCLeWsn5VVCc8u5Y=",
"usagesDigest": "nLGf5KtJBc5QgdHE6qeQEjHPMTR04zHAEmjUJdZfZyo=",
"usagesDigest": "hTf7RXXlm85DQds3r44JRfN0JwKFbTZsXmDKAzPHvAo=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
+40 -47
View File
@@ -1,3 +1,4 @@
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("@rules_angular//src/architect:ng_application.bzl", "ng_application")
load("@rules_angular//src/architect:ng_config.bzl", "ng_config")
@@ -62,12 +63,7 @@ APPLICATION_DEPS = [
"//adev:node_modules/@types/node",
"//adev:node_modules/@types/dom-navigation",
# The imports below are packages that our code does not directly import, but our upstream deps import from
# these locations directly. By design, rather than hoisting these packages into the "root" node_modules the
# dependency is found in (i.e. ./adev/node_modules), rules_js places all of each package's dependencies in
# a "hoisted" location within the `.aspect_rules_js/` directories in the node_modules directory. This prevents
# the bundler from being able to find it as expected. We instead specifically provide it as a dep within bazel
# as a mechanism of "hoisting" the package for our usage by this target.
# Deep imports
"//adev:node_modules/@algolia/client-common",
"//adev:node_modules/@algolia/requester-browser-xhr",
"//adev:node_modules/@algolia/requester-node-http",
@@ -98,56 +94,53 @@ TEST_DEPS = [
ng_config(name = "ng_config")
ng_application(
name = "build",
srcs = APPLICATION_FILES + APPLICATION_DEPS,
args = [
bool_flag(
name = "full_build_adev",
build_setting_default = False,
)
config_setting(
name = "prod_build",
flag_values = {
":full_build_adev": "true",
},
)
config_setting(
name = "dev_build",
flag_values = {
":full_build_adev": "false",
},
)
config_based_architect_env = select({
":dev_build": {
"NG_BUILD_PARTIAL_SSR": "1",
},
":prod_build": {
"NG_BUILD_OPTIMIZE_CHUNKS": "1",
},
})
config_based_architect_flags = select({
":dev_build": [
"--configuration",
"development",
],
env = {
"NG_BUILD_PARTIAL_SSR": "1",
},
ng_config = ":ng_config",
node_modules = "//adev:node_modules",
project_name = "angular-dev",
serve_args = [
"--port",
"4201",
],
tags = [
# Tagged as manual as both build and build.production use the same output directory.
"manual",
# RBE significantly slows down the build because the CLI executes multiple CPU-intensive instances of esbuild.
# Furthermore, the current RBE machine pool lacks high-spec machines.
"no-remote-exec",
],
)
ng_application(
name = "build.production",
srcs = APPLICATION_FILES + APPLICATION_DEPS,
args = [
":prod_build": [
"--configuration",
"production",
],
env = {
"NG_BUILD_OPTIMIZE_CHUNKS": "1",
},
})
ng_application(
name = "build",
srcs = APPLICATION_FILES + APPLICATION_DEPS,
args = config_based_architect_flags,
env = config_based_architect_env,
ng_config = ":ng_config",
node_modules = "//adev:node_modules",
project_name = "angular-dev",
serve_args = [
"--port",
"4201",
],
tags = [
# Tagged as manual as both build and build.production use the same output directory.
"manual",
# RBE significantly slows down the build because the CLI executes multiple CPU-intensive instances of esbuild.
# Furthermore, the current RBE machine pool lacks high-spec machines.
"no-remote-exec",
],
)
ng_test(
-11
View File
@@ -51,14 +51,3 @@ pnpm bazel clean
# If that doesn't work, try it with the expunge flag
pnpm bazel clean --expunge
```
## High level architecture
The angular.dev website is a modern Angular application that utilizes Static Site Generation (SSG) to deliver pre-rendered, high-performance content to users.
The architecture is designed for efficiency and maintainability:
Content Sources: The documentation is sourced from two main places within the monorepo. The guides and tutorials are written in Markdown files, while the API reference pages are automatically generated by extracting documentation directly from the TypeScript source code of the Angular framework packages.
Build Process: During the build process, the Markdown files are converted into HTML. Simultaneously, the API documentation is extracted from the code comments. This content is then integrated into the Angular application.
+7 -7
View File
@@ -1,21 +1,21 @@
{
"dependencies": {
"@angular-devkit/build-angular": "20.3.3",
"@angular-devkit/build-angular": "21.0.0-next.0",
"@angular/animations": "workspace:*",
"@angular/build": "20.3.3",
"@angular/cdk": "20.2.7",
"@angular/cli": "20.3.3",
"@angular/build": "21.0.0-next.0",
"@angular/cdk": "21.0.0-next.0",
"@angular/cli": "21.0.0-next.0",
"@angular/common": "workspace:*",
"@angular/compiler-cli": "workspace:*",
"@angular/compiler": "workspace:*",
"@angular/core": "workspace:*",
"@angular/docs": "workspace:*",
"@angular/forms": "workspace:*",
"@angular/material": "20.2.7",
"@angular/material": "21.0.0-next.0",
"@angular/platform-browser": "workspace:*",
"@angular/platform-server": "workspace:*",
"@angular/router": "workspace:*",
"@angular/ssr": "20.3.3",
"@angular/ssr": "21.0.0-next.0",
"@codemirror/autocomplete": "6.18.6",
"@codemirror/commands": "6.8.1",
"@codemirror/lang-angular": "0.1.4",
@@ -53,7 +53,7 @@
"karma-jasmine-html-reporter": "2.1.0",
"karma-jasmine": "5.1.0",
"marked": "16.2.0",
"mermaid": "11.10.0",
"mermaid": "11.10.1",
"ngx-progressbar": "14.0.0",
"open-in-idx": "0.1.1",
"playwright-core": "1.55.0",
+50 -72
View File
@@ -7,7 +7,6 @@
*/
//tslint:disable:no-console
import assert from 'node:assert';
import {execSync} from 'node:child_process';
import {existsSync, constants as fsConstants} from 'node:fs';
import {copyFile, mkdtemp, readdir, readFile, realpath, unlink, writeFile} from 'node:fs/promises';
@@ -20,93 +19,72 @@ export async function copyJsonAssets({repo, githubApi, assetsPath, destPath}) {
throw new Error(`${buildInfoPath} does not exist.`);
}
assert(process.env.GITHUB_REF);
const currentBranch = process.env.GITHUB_REF;
const branch = process.env.GITHUB_REF;
const {sha: currentSha} = JSON.parse(await readFile(buildInfoPath, 'utf-8'));
const latestSha = await githubApi.getShaForBranch(branch);
const {sha: storedSha, branchName: storedBranch} = JSON.parse(
await readFile(buildInfoPath, 'utf-8'),
);
let downstreamBranch = currentBranch;
let latestSha = await githubApi.getShaForBranch(currentBranch);
if (
latestSha.includes('No commit') &&
currentBranch !== 'refs/heads/main' &&
currentBranch !== storedBranch
) {
// In some cases, such as when a new branch is created for a feature,
// the branch may not exist in the downstream repo. For example, during an
// exceptional minor release (e.g. FW 20.3.x and Components: 20.2.x).
// In such scenarios, we fallback to the last known branch.
latestSha = await githubApi.getShaForBranch(storedBranch);
downstreamBranch = storedBranch;
}
console.log(`Comparing ${storedSha}...${latestSha}.`);
const affectedFiles = await githubApi.getAffectedFiles(storedSha, latestSha);
console.log(`Comparing ${currentSha}...${latestSha}.`);
const affectedFiles = await githubApi.getAffectedFiles(currentSha, latestSha);
const changedFiles = affectedFiles.filter((file) => file.startsWith(`${assetsPath}/`));
let shaWhenFilesChanged;
if (changedFiles.length > 0) {
console.log(
`The below files changed between ${storedSha} and ${latestSha}:\n` +
changedFiles.map((f) => '* ' + f).join('\n'),
);
const temporaryDir = await realpath(await mkdtemp(join(tmpdir(), 'copy-json-assets-')));
const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
execSync('git init', execOptions);
execSync(`git remote add origin https://github.com/${repo}.git`, execOptions);
// fetch a commit
execSync(`git fetch origin ${latestSha}`, execOptions);
// reset this repository's main branch to the commit of interest
execSync('git reset --hard FETCH_HEAD', execOptions);
// get sha when files where changed
shaWhenFilesChanged = execSync(`git rev-list -1 ${latestSha} "${assetsPath}/"`, {
encoding: 'utf8',
cwd: temporaryDir,
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
// Delete existing asset files.
const apiFilesUnlink = (await readdir(destPath))
.filter((f) => f.endsWith('.json'))
.map((f) => unlink(join(destPath, f)));
await Promise.allSettled(apiFilesUnlink);
// Copy new asset files
const tempAssetsDir = join(temporaryDir, assetsPath);
const assetFilesCopy = (await readdir(tempAssetsDir)).map((f) => {
const src = join(tempAssetsDir, f);
const dest = join(destPath, f);
return copyFile(src, dest, fsConstants.COPYFILE_FICLONE);
});
await Promise.allSettled(assetFilesCopy);
console.log(`Successfully updated asset files in '${destPath}'.\n`);
} else {
console.log(`No '${assetsPath}/**' files changed between ${storedSha} and ${latestSha}.`);
if (changedFiles.length === 0) {
console.log(`No '${assetsPath}/**' files changed between ${currentSha} and ${latestSha}.`);
return;
}
console.log(
`The below files changed between ${currentSha} and ${latestSha}:\n` +
changedFiles.map((f) => '* ' + f).join('\n'),
);
const temporaryDir = await realpath(await mkdtemp(join(tmpdir(), 'copy-json-assets-')));
const execOptions = {cwd: temporaryDir, stdio: 'inherit'};
execSync('git init', execOptions);
execSync(`git remote add origin https://github.com/${repo}.git`, execOptions);
// fetch a commit
execSync(`git fetch origin ${latestSha}`, execOptions);
// reset this repository's main branch to the commit of interest
execSync('git reset --hard FETCH_HEAD', execOptions);
// get sha when files where changed
const shaWhenFilesChanged = execSync(`git rev-list -1 ${latestSha} "${assetsPath}/"`, {
encoding: 'utf8',
cwd: temporaryDir,
stdio: ['ignore', 'pipe', 'ignore'],
}).trim();
// Delete existing asset files.
const apiFilesUnlink = (await readdir(destPath))
.filter((f) => f.endsWith('.json'))
.map((f) => unlink(join(destPath, f)));
await Promise.allSettled(apiFilesUnlink);
// Copy new asset files
const tempAssetsDir = join(temporaryDir, assetsPath);
const assetFilesCopy = (await readdir(tempAssetsDir)).map((f) => {
const src = join(tempAssetsDir, f);
const dest = join(destPath, f);
return copyFile(src, dest, fsConstants.COPYFILE_FICLONE);
});
await Promise.allSettled(assetFilesCopy);
// Write SHA to file.
await writeFile(
buildInfoPath,
JSON.stringify(
{
branchName: downstreamBranch,
sha: shaWhenFilesChanged ?? storedSha,
branchName: branch,
sha: shaWhenFilesChanged,
},
undefined,
2,
),
);
// The below command will show uncommitted changes.
// This is expected because the framework repo and component/cli might have different minors
// (e.g. during exceptional minor releases), leading to changes that need to be committed.
console.log('\nChanges: ');
execSync(`git status --porcelain`, {stdio: 'inherit'});
console.log(`Successfully updated asset files in '${destPath}'.\n`);
}
@@ -21,7 +21,7 @@ import {RouterLink} from '@angular/router';
export class Breadcrumb {
private readonly navigationState = inject(NavigationState);
readonly breadcrumbItems = computed(() => {
breadcrumbItems = computed(() => {
const breadcrumbs: NavigationItem[] = [];
let activeItem = this.navigationState.activeNavigationItem()?.parent;
@@ -4,7 +4,7 @@
<div>
<a href="https://policies.google.com/technologies/cookies" target="_blank" rel="noopener">
<button type="button" class="docs-primary-btn" [attr.text]="'Learn more'" aria-label="Learn More">
<button class="docs-primary-btn" [attr.text]="'Learn more'" aria-label="Learn More">
Learn more
</button>
</a>
@@ -11,7 +11,7 @@ import {LOCAL_STORAGE} from '../../providers/index';
import {setCookieConsent} from '../../utils';
/**
* Declare gtag as part of the window in this file as gtag is expected to already be loaded.
* Decelare gtag as part of the window in this file as gtag is expected to already be loaded.
*/
declare const window: Window & typeof globalThis & {gtag?: Function};
@@ -27,7 +27,7 @@ export class CookiePopup {
private readonly localStorage = inject(LOCAL_STORAGE);
/** Whether the user has accepted the cookie disclaimer. */
readonly hasAccepted = signal(false);
hasAccepted = signal<boolean>(false);
constructor() {
// Needs to be in a try/catch, because some browsers will
@@ -15,6 +15,7 @@ import {
inject,
signal,
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {Clipboard} from '@angular/cdk/clipboard';
import {IconComponent} from '../icon/icon.component';
@@ -23,7 +24,7 @@ export const CONFIRMATION_DISPLAY_TIME_MS = 2000;
@Component({
selector: 'button[docs-copy-source-code]',
imports: [IconComponent],
imports: [CommonModule, IconComponent],
templateUrl: './copy-source-code-button.component.html',
host: {
'type': 'button',
@@ -20,7 +20,6 @@ ng_project(
"//adev:node_modules/@angular/material",
"//adev:node_modules/@angular/router",
"//adev/shared-docs/components/icon",
"//adev/shared-docs/components/slide-toggle",
"//adev/shared-docs/interfaces",
"//adev/shared-docs/pipes",
"//adev/shared-docs/services",
@@ -3,115 +3,106 @@
class="docs-navigation-list docs-faceted-list"
[class.docs-navigation-list-dropdown]="isDropdownView()"
>
@for (itemGroup of groupItems(navigationItems); track $index) {
@let groupLabel = itemGroup[0];
@let items = itemGroup[1];
@if (groupLabel) {
<li class="docs-navigation-group">{{groupLabel | titlecase}}</li>
}
@for (item of items; track $index) {
<li class="docs-faceted-list-item">
@if (item.path) {
@if (item.isExternal) {
<a
[href]="item.path"
target="_blank"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
@for (item of navigationItems; track $index) {
<li class="docs-faceted-list-item">
@if (item.path) {
@if (item.isExternal) {
<a
[href]="item.path"
target="_blank"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
<span
[class.docs-external-link]="item.isExternal"
class="docs-faceted-list-item-text"
>
<span
[class.docs-external-link]="item.isExternal"
class="docs-faceted-list-item-text"
>
{{ item.label }}
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
</span>
{{ item.label }}
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
</span>
@if (item.children && item.level! > 1 && !item.isExpanded) {
<docs-icon>chevron_right</docs-icon>
}
</a>
} @else {
<a
[routerLink]="'/' + item.path"
routerLinkActive="docs-faceted-list-item-active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="emitClickOnLink()"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
<span class="docs-faceted-list-item-text">
{{item.label}}
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
</span>
@if (item.children && !item.isExpanded) {
<docs-icon>chevron_right</docs-icon>
}
</a>
}
@if (item.children && item.level! > 1 && !item.isExpanded) {
<docs-icon>chevron_right</docs-icon>
}
</a>
} @else {
<!-- Nav Section Header -->
@if (item.level !== collapsableLevel() && item.level !== expandableLevel()) {
<div
class="docs-secondary-nav-header"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
<span class="docs-faceted-list-item-text">
{{item.label}}
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
</span>
</div>
}
<a
[routerLink]="'/' + item.path"
routerLinkActive="docs-faceted-list-item-active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="emitClickOnLink()"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
<span class="docs-faceted-list-item-text">
{{item.label}}
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
</span>
<!-- Nav Button Expand/Collapse -->
@if (
(item.children && item.level === expandableLevel()) || item.level === collapsableLevel()
) {
<button
type="button"
(click)="toggle(item)"
[attr.aria-label]="(item.isExpanded ? 'Collapse' : 'Expand') + ' ' + item.label"
[attr.aria-expanded]="item.isExpanded"
class="docs-secondary-nav-button"
[class.docs-faceted-list-item-active]="item | isActiveNavigationItem: activeItem()"
[class.docs-expanded-button]="item.children && item.level == collapsableLevel()"
[class.docs-not-expanded-button]="item.children && item.level === expandableLevel()"
[class.docs-nav-item-has-icon]="
@if (item.children && !item.isExpanded) {
<docs-icon>chevron_right</docs-icon>
}
</a>
}
} @else {
<!-- Nav Section Header -->
@if (item.level !== collapsableLevel() && item.level !== expandableLevel()) {
<div
class="docs-secondary-nav-header"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
<span class="docs-faceted-list-item-text">
{{item.label}}
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
</span>
</div>
}
<!-- Nav Button Expand/Collapse -->
@if (
(item.children && item.level === expandableLevel()) || item.level === collapsableLevel()
) {
<button
type="button"
(click)="toggle(item)"
[attr.aria-label]="(item.isExpanded ? 'Collapse' : 'Expand') + ' ' + item.label"
[attr.aria-expanded]="item.isExpanded"
class="docs-secondary-nav-button"
[class.docs-faceted-list-item-active]="item | isActiveNavigationItem: activeItem()"
[class.docs-expanded-button]="item.children && item.level == collapsableLevel()"
[class.docs-not-expanded-button]="item.children && item.level === expandableLevel()"
[class.docs-nav-item-has-icon]="
item.children && item.level === expandableLevel() && !item.isExpanded"
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
@if (item.children && item.level === collapsableLevel()) {
<docs-icon>arrow_back</docs-icon>
}
<span class="docs-faceted-list-item-text">
{{ item.label }}
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
</span>
</button>
}
[matTooltip]="item.label"
[matTooltipDisabled]="item.label.length < 27"
matTooltipPosition="after"
[attr.aria-label]="item.label"
[matTooltipClass]="'API-tooltip'"
>
@if (item.children && item.level === collapsableLevel()) {
<docs-icon>arrow_back</docs-icon>
}
<span class="docs-faceted-list-item-text">
{{ item.label }}
<ng-container *ngTemplateOutlet="itemStatus; context: {$implicit: item}" />
</span>
</button>
}
@if (displayItemsToLevel() > item.level && item.children?.length > 0) {
<ng-container
*ngTemplateOutlet="navigationList; context: {$implicit: item.children, level: item.level}"
/>
}
</li>
}
}
@if (displayItemsToLevel() > item.level && item.children?.length > 0) {
<ng-container *ngTemplateOutlet="navigationList; context: {$implicit: item.children}" />
}
</li>
}
</ul>
</ng-template>
@@ -185,11 +185,3 @@ a,
white-space: pre;
}
}
.docs-navigation-group {
padding: 0.5rem 0.5rem 0.5rem 1rem;
background: var(--septenary-contrast);
&:not(:first-child) {
margin-top: 2rem;
}
}
@@ -12,9 +12,8 @@ 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 {NgTemplateOutlet} from '@angular/common';
import {MatTooltipModule} from '@angular/material/tooltip';
import {ReactiveFormsModule} from '@angular/forms';
@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,10 +31,10 @@ import {ReactiveFormsModule} from '@angular/forms';
})
export class NavigationList {
readonly navigationItems = input.required<NavigationItem[]>();
readonly displayItemsToLevel = input(2);
readonly displayItemsToLevel = input<number>(2);
readonly collapsableLevel = input<number | undefined>();
readonly expandableLevel = input(2);
readonly isDropdownView = input(false);
readonly expandableLevel = input<number>(2);
readonly isDropdownView = input<boolean>(false);
readonly linkClicked = output<void>();
@@ -59,33 +56,4 @@ export class NavigationList {
emitClickOnLink(): void {
this.linkClicked.emit();
}
private hasCategories(items: NavigationItem[]): boolean {
return items.some((item) => !!item.category);
}
protected groupItems(items: NavigationItem[]): 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') {
others.push(item);
continue;
}
if (!categorizedItems.has(category)) {
categorizedItems.set(category, []);
}
categorizedItems.get(category)!.push(item);
}
if (others.length) {
categorizedItems.set('Other', others);
}
return categorizedItems;
} else {
return new Map([['', items]]);
}
}
}
@@ -12,49 +12,35 @@
@if (searchResults().length) {
<ul class="docs-search-results docs-mini-scroll-track">
@for (result of searchResults(); track result.id) {
<li docsSearchItem [item]="result" class="docs-search-result">
<li docsSearchItem [item]="result">
<a
[routerLink]="'/' + result.url | relativeLink: 'pathname'"
[fragment]="result.url | relativeLink: 'hash'"
(click)="history.addItem(result)"
>
<div>
<p class="docs-search-result__label">
<div class="docs-result-icon-and-type">
<!-- Icon -->
<span class="docs-search-result-icon" aria-hidden="true">
<i role="presentation" class="material-symbols-outlined docs-icon-small">
{{ result.type === 'code' ? 'code' : 'description'}}
</i>
</span>
<!-- Page title -->
<span [innerHtml]="result.labelHtml"></span>
@if (result.package) {
<span
[innerHTML]="result.package"
class="docs-search-result__label__package"
></span>
}
</p>
<!-- Results type -->
<span class="docs-search-results__type" [innerHtml]="result.labelHtml"></span>
</div>
@if (result.subLabelHtml) {
<p class="docs-search-result__sub-label">
<span class="docs-search-result-icon" aria-hidden="true">
<i role="presentation" class="material-symbols-outlined docs-icon-small">
grid_3x3
</i>
</span>
<span [innerHtml]="result.subLabelHtml"></span>
</p>
}
@if (result.contentHtml) {
<p class="docs-search-result__content" [innerHtml]="result.contentHtml"></p>
<span
class="docs-search-results__type docs-search-results__lvl2"
[innerHtml]="result.subLabelHtml"
>
</span>
}
</div>
<p class="docs-search-result__category">
{{ result.category }}
</p>
<!-- Page title -->
<span class="docs-result-page-title">{{ result.category }}</span>
</a>
</li>
}
@@ -38,21 +38,11 @@ dialog {
margin: 0;
border-block-end: 1px solid var(--senary-contrast);
.docs-search-result {
li {
border-inline-start: 2px solid var(--senary-contrast);
margin-inline-start: 1rem;
display: block;
font-size: 0.875rem;
.docs-search-result-icon {
display: inline-block;
i {
display: flex;
align-items: center;
font-size: 1.2rem;
}
}
padding-inline-end: 1rem;
padding-block: 0.25rem;
/**
* This rule needs ng-deep to be applied to elements that are created via a [innerHTML] binding
@@ -71,50 +61,17 @@ dialog {
color: var(--secondary-contrast);
display: flex;
justify-content: space-between;
padding: 1rem;
gap: 0.5rem;
}
&__label,
&__sub-label,
&__content {
transition: color 0.3s ease;
}
&__label,
&__sub-label {
display: flex;
align-items: center;
gap: 0.75rem;
margin: 0;
}
&__label {
font-weight: 600;
flex-wrap: wrap;
&__package {
font-size: 0.75rem;
.docs-search-result-icon {
i {
display: flex;
align-items: center;
font-size: 1.2rem;
}
}
}
&__content,
&__category {
margin: 0;
}
&__sub-label,
&__content {
margin-block-start: 0.375rem;
margin-inline-start: 2rem;
color: var(--quaternary-contrast);
}
&__category {
font-weight: 400;
color: var(--quaternary-contrast);
}
&.active {
background-color: var(--septenary-contrast); // stylelint-disable-line
}
@@ -123,14 +80,44 @@ dialog {
&.active {
background-color: var(--octonary-contrast); // stylelint-disable-line
border-inline-start: 2px solid var(--primary-contrast);
.docs-search-result__label,
.docs-search-result__sub-label,
.docs-search-result__content {
color: var(--primary-contrast);
a {
span:not(.docs-result-page-title),
.docs-search-results__type {
color: var(--primary-contrast);
i {
color: var(--primary-contrast);
}
}
}
}
}
.docs-search-result-icon,
.docs-search-results__type,
.docs-result-page-title {
color: var(--quaternary-contrast);
display: inline-block;
font-size: 0.875rem;
transition: color 0.3s ease;
padding: 0.75rem;
padding-inline-end: 0;
}
.docs-search-results__lvl2 {
display: inline-block;
margin-inline-start: 2rem;
padding-block-start: 0;
}
.docs-search-results__lvl3 {
margin-inline-start: 2rem;
padding-block-start: 0;
}
}
.docs-result-page-title {
font-size: 0.875rem;
font-weight: 400;
}
}
@@ -140,6 +127,14 @@ dialog {
color: var(--gray-400);
}
.docs-result-icon-and-type {
display: flex;
.docs-search-results__type {
padding-inline-start: 0;
}
}
.docs-search-footer {
display: flex;
align-items: center;
@@ -51,10 +51,10 @@ import {SearchHistoryComponent} from '../search-history/search-history.component
styleUrls: ['./search-dialog.component.scss'],
})
export class SearchDialog {
readonly onClose = output();
readonly dialog = viewChild.required<ElementRef<HTMLDialogElement>>('searchDialog');
readonly items = viewChildren(SearchItem);
readonly textField = viewChild(TextField);
onClose = output();
dialog = viewChild.required<ElementRef<HTMLDialogElement>>('searchDialog');
items = viewChildren(SearchItem);
textField = viewChild(TextField);
readonly history = inject(SearchHistory);
private readonly search = inject(Search);
@@ -67,9 +67,9 @@ export class SearchDialog {
this.injector,
).withWrap();
readonly searchQuery = this.search.searchQuery;
readonly resultsResource = this.search.resultsResource;
readonly searchResults = this.search.searchResults;
searchQuery = this.search.searchQuery;
resultsResource = this.search.resultsResource;
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
@@ -22,7 +22,6 @@ ng_project(
],
deps = [
"//adev:node_modules/@angular/cdk",
"//adev:node_modules/@angular/common",
"//adev:node_modules/@angular/core",
"//adev:node_modules/@angular/router",
"//adev/shared-docs/directives",
@@ -1,15 +1,3 @@
<ng-template #searchItem let-item="item" let-type="type">
<i role="presentation" class="material-symbols-outlined type-icon" aria-hidden="true">
{{ type === 'recent' ? 'history' : 'star' }}
</i>
<span [innerHTML]="item.labelHtml" class="label"></span>
@if (item.subLabelHtml) {
<i role="presentation" class="material-symbols-outlined" aria-hidden="true"> arrow_right </i>
<span [innerHTML]="item.subLabelHtml" class="sub-label"></span>
}
</ng-template>
@let items = history.items();
@if (items.recent.length) {
@@ -22,10 +10,8 @@
[fragment]="item.url | relativeLink: 'hash'"
(click)="history.addItem(item)"
>
<ng-container
[ngTemplateOutlet]="searchItem"
[ngTemplateOutletContext]="{item, type: 'recent'}"
/>
<i role="presentation" class="material-symbols-outlined" aria-hidden="true"> history </i>
<span [innerHTML]="item.labelHtml"></span>
</a>
<button
@@ -34,9 +20,7 @@
(click)="history.makeFavorite(item)"
title="Make favorite"
>
<i role="presentation" class="material-symbols-outlined type-icon" aria-hidden="true">
star
</i>
<i role="presentation" class="material-symbols-outlined" aria-hidden="true"> star </i>
</button>
<button
type="button"
@@ -64,10 +48,8 @@
[routerLink]="'/' + item.url | relativeLink: 'pathname'"
[fragment]="item.url | relativeLink: 'hash'"
>
<ng-container
[ngTemplateOutlet]="searchItem"
[ngTemplateOutletContext]="{item, type: 'favorite'}"
/>
<i role="presentation" class="material-symbols-outlined" aria-hidden="true"> star </i>
<span [innerHTML]="item.labelHtml"></span>
</a>
<button
@@ -41,23 +41,9 @@
flex: 1;
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--secondary-contrast);
transition: color 0.3s ease;
white-space: nowrap;
overflow: hidden;
.type-icon {
margin-inline-end: 0.75rem;
}
span {
text-overflow: ellipsis;
overflow: hidden;
&.sub-label {
flex: 1;
}
}
&:hover {
color: var(--primary-contrast);
@@ -18,7 +18,6 @@ import {
import {Router, RouterLink} from '@angular/router';
import {toSignal} from '@angular/core/rxjs-interop';
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
import {NgTemplateOutlet} from '@angular/common';
import {SearchHistory} from '../../services';
import {RelativeLink} from '../../pipes';
@@ -26,7 +25,7 @@ import {SearchItem} from '../../directives';
@Component({
selector: 'docs-search-history',
imports: [NgTemplateOutlet, RelativeLink, RouterLink, SearchItem],
imports: [RelativeLink, RouterLink, SearchItem],
templateUrl: './search-history.component.html',
styleUrl: './search-history.component.scss',
host: {
@@ -8,6 +8,7 @@
import {ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule} from '@angular/forms';
import {ChangeDetectionStrategy, Component, model, forwardRef, input, signal} from '@angular/core';
import {CommonModule} from '@angular/common';
type SelectOptionValue = string | number | boolean;
@@ -19,7 +20,7 @@ export interface SelectOption {
@Component({
selector: 'docs-select',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [FormsModule],
imports: [CommonModule, FormsModule],
templateUrl: './select.component.html',
styleUrls: ['./select.component.scss'],
providers: [
@@ -37,7 +38,7 @@ export class Select implements ControlValueAccessor {
readonly id = input.required<string>({alias: 'selectId'});
readonly name = input.required<string>();
readonly options = input.required<SelectOption[]>();
readonly disabled = model(false);
readonly disabled = model<boolean>(false);
// Implemented as part of ControlValueAccessor.
private onChange: (value: SelectOptionValue) => void = (_: SelectOptionValue) => {};
@@ -13,7 +13,6 @@ ng_project(
],
visibility = [
"//adev/shared-docs/components:__pkg__",
"//adev/shared-docs/components/navigation-list:__pkg__",
],
deps = [
"//adev:node_modules/@angular/common",
@@ -7,10 +7,12 @@
*/
import {ChangeDetectionStrategy, Component, forwardRef, model, input, signal} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
@Component({
selector: 'docs-slide-toggle',
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './slide-toggle.component.html',
styleUrls: ['./slide-toggle.component.scss'],
@@ -25,7 +27,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
export class SlideToggle implements ControlValueAccessor {
readonly buttonId = input.required<string>();
readonly label = input.required<string>();
readonly disabled = model(false);
readonly disabled = model<boolean>(false);
// Implemented as part of ControlValueAccessor.
private onChange: (value: boolean) => void = (_: boolean) => {};
@@ -38,7 +38,7 @@ export class TableOfContents {
tableOfContentItems = this.tableOfContentsLoader.tableOfContentItems;
readonly activeItemId = signal<string | null>(null);
activeItemId = signal<string | null>(null);
TableOfContentsLevel = TableOfContentsLevel;
constructor() {
@@ -17,13 +17,14 @@ import {
model,
viewChild,
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR} from '@angular/forms';
import {IconComponent} from '../icon/icon.component';
@Component({
selector: 'docs-text-field',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [FormsModule, IconComponent],
imports: [CommonModule, FormsModule, IconComponent],
templateUrl: './text-field.component.html',
styleUrls: ['./text-field.component.scss'],
providers: [
@@ -29,16 +29,16 @@ export class TopLevelBannerComponent {
* separately for different events or instances. Without a unique ID,
* closing one banner could inadvertently hide other banners for different events.
*/
readonly id = input.required<string>();
id = input.required<string>();
// Optional URL link that the banner should navigate to when clicked.
readonly link = input<string>();
link = input<string>();
// Text content to be displayed in the banner.
readonly text = input.required<string>();
text = input.required<string>();
// Optional expiry date. Setting the default expiry as a future date so we
// don't have to deal with undefined signal values.
readonly expiry = input(new Date('3000-01-01'), {transform: parseDate});
expiry = input(new Date('3000-01-01'), {transform: parseDate});
// Whether the user has closed the banner or the survey has expired.
readonly hasClosed = linkedSignal(() => {
hasClosed = linkedSignal(() => {
const expired = Date.now() > this.expiry().getTime();
// Needs to be in a try/catch, because some browsers will
@@ -33,7 +33,7 @@
// $screen-xl is the breakpoint where Main content stop being fluid and made fixed
>*:not(docs-table-of-contents) {
max-width: calc(mq.$screen-xl - var(--secondary-nav-width) - var(--primary-nav-width) - var(--layout-padding)*2)
max-width: calc(mq.$screen-xxl - var(--secondary-nav-width) - var(--primary-nav-width) - var(--layout-padding)*2)
}
pre {
@@ -109,10 +109,7 @@ describe('DocViewer', () => {
expect(exampleViewer).not.toBeNull();
expect(exampleViewer.componentInstance.view()).toBe(CodeExampleViewMode.SNIPPET);
const copySourceCodeButton = fixture.debugElement.query(By.directive(CopySourceCodeButton));
expect(copySourceCodeButton).not.toBeNull();
const checkIcon = copySourceCodeButton.query(By.directive(IconComponent));
const checkIcon = fixture.debugElement.query(By.directive(IconComponent));
expect((checkIcon.nativeElement as HTMLElement).classList).toContain(
`material-symbols-outlined`,
);
@@ -61,8 +61,8 @@ export const GITHUB_CONTENT_URL = 'https://github.com/angular/angular/blob/main/
},
})
export class DocViewer {
readonly docContent = input<string | undefined>();
readonly hasToc = input(false);
docContent = input<string | undefined>();
hasToc = input(false);
readonly contentLoaded = output<void>();
private readonly destroyRef = inject(DestroyRef);
@@ -188,7 +188,6 @@ export class DocViewer {
path: string,
): Promise<void> {
const preview = Boolean(placeholder.getAttribute('preview'));
const hideCode = Boolean(placeholder.getAttribute('hideCode'));
const title = placeholder.getAttribute('header') ?? undefined;
const firstCodeSnippetTitle =
snippets.length > 0 ? (snippets[0].title ?? snippets[0].name) : undefined;
@@ -200,7 +199,6 @@ export class DocViewer {
path,
files: snippets,
preview,
hideCode,
id: this.countOfExamples,
});
@@ -1,42 +1,23 @@
<div class="docs-example-viewer" role="group">
<header class="docs-example-viewer-actions">
@if (showCode()) {
@if (view() === CodeExampleViewMode.SNIPPET) {
<span>{{ exampleMetadata()?.title }}</span>
}
@if (view() === CodeExampleViewMode.SNIPPET) {
<span>{{ exampleMetadata()?.title }}</span>
}
@if (view() === CodeExampleViewMode.MULTI_FILE) {
<mat-tab-group #codeTabs animationDuration="0ms" mat-stretch-tabs="false">
@for (tab of tabs(); track tab) {
<mat-tab [label]="tab.name" />
}
</mat-tab-group>
}
} @else {
<!-- Title placeholder -->
<span aria-hidden="true">&nbsp;</span>
@if (view() === CodeExampleViewMode.MULTI_FILE) {
<mat-tab-group #codeTabs animationDuration="0ms" mat-stretch-tabs="false">
@for (tab of tabs(); track tab) {
<mat-tab [label]="tab.name"></mat-tab>
}
</mat-tab-group>
}
<div class="docs-example-viewer-icons">
<button
type="button"
role="switch"
class="docs-example-code-toggle"
[attr.aria-checked]="showCode()"
[attr.aria-label]="showCode() ? 'Hide code' : 'Show code'"
(click)="showCode.set(!showCode())"
[matTooltip]="showCode() ? 'Hide code' : 'Show code'"
matTooltipPosition="above"
>
<docs-icon>{{showCode() ? 'code_off' : 'code'}}</docs-icon>
</button>
<button
type="button"
class="docs-example-copy-link"
[attr.aria-label]="'Copy link to ' + exampleMetadata()?.title + ' example to the clipboard'"
(click)="copyLink()"
matTooltip="Copy link to example"
matTooltipPosition="above"
>
<i aria-hidden="true">
<svg
@@ -60,9 +41,8 @@
<button
type="button"
(click)="toggleExampleVisibility()"
[attr.title]="(expanded() ? 'Collapse' : 'Expand') + ' example'"
[attr.aria-label]="(expanded() ? 'Collapse' : 'Expand') + ' code example'"
matTooltip="{{ expanded() ? 'Collapse' : 'Expand' }} example"
matTooltipPosition="above"
>
<i aria-hidden="true">
@if (!expanded()) {
@@ -97,18 +77,16 @@
</div>
</header>
@if (showCode()) {
<div
class="docs-example-viewer-code-wrapper"
[class.docs-example-viewer-snippet]="view() === CodeExampleViewMode.SNIPPET"
[class.docs-example-viewer-multi-file]="view() === CodeExampleViewMode.MULTI_FILE"
>
<button docs-copy-source-code></button>
@if (snippetCode()?.sanitizedContent; as content) {
<div [innerHTML]="content"></div>
}
</div>
}
<div
class="docs-example-viewer-code-wrapper"
[class.docs-example-viewer-snippet]="view() === CodeExampleViewMode.SNIPPET"
[class.docs-example-viewer-multi-file]="view() === CodeExampleViewMode.MULTI_FILE"
>
<button docs-copy-source-code></button>
@if (snippetCode()?.sanitizedContent; as content) {
<div [innerHTML]="content"></div>
}
</div>
@if (exampleComponent) {
<div class="docs-example-viewer-preview">
@@ -122,10 +100,9 @@
<a
[href]="githubUrl()"
target="_blank"
title="Open example on GitHub"
class="docs-example-github-link"
aria-label="Open example on GitHub"
matTooltip="Open example on GitHub"
matTooltipPosition="above"
>
<i aria-hidden="true">
<svg
@@ -151,9 +128,8 @@
[href]="stackblitzUrl()"
target="_blank"
class="docs-example-stackblitz-link"
aria-label="Edit example in StackBlitz"
matTooltip="Edit example in StackBlitz"
matTooltipPosition="above"
title="Edit this example in StackBlitz"
aria-label="Edit this example in StackBlitz"
>
<i aria-hidden="true">
<svg
@@ -57,25 +57,25 @@
display: flex;
gap: 0.75rem;
a,
button {
padding: 0;
margin: 0;
cursor: pointer;
height: 24px;
width: 24px;
color: var(--gray-400);
svg {
fill: var(--gray-400);
}
}
path {
transition: fill 0.3s ease;
}
a,
button {
padding: 0;
margin: 0;
cursor: pointer;
height: 24px;
width: 24px;
path {
transition: fill 0.3s ease;
}
&:hover {
svg {
fill: currentColor;
}
&:hover {
color: var(--tertiary-contrast);
fill: var(--tertiary-contrast);
}
}
}
@@ -35,6 +35,7 @@ describe('ExampleViewer', () => {
await TestBed.configureTestingModule({
imports: [ExampleViewer],
providers: [
// TODO: Find why tests warn that zone.js is still loaded
provideZonelessChangeDetection(),
{provide: EXAMPLE_VIEWER_CONTENT_LOADER, useValue: exampleContentSpy},
{provide: ActivatedRoute, useValue: {snapshot: {fragment: 'fragment'}}},
@@ -215,7 +216,7 @@ describe('ExampleViewer', () => {
fixture.detectChanges();
const stackblitzButton = fixture.debugElement.query(
By.css('a[aria-label="Edit example in StackBlitz"]'),
By.css('a[aria-label="Edit this example in StackBlitz"]'),
);
expect(stackblitzButton).toBeTruthy();
expect(stackblitzButton.nativeElement.href).toBe(component.stackblitzUrl());
@@ -272,50 +273,6 @@ describe('ExampleViewer', () => {
button.click();
expect(spy.calls.argsFor(0)[0].trim()).toBe(`${window.location.href}#example-1`);
});
it('should hide code content when `hideCode` is true', async () => {
componentRef.setInput(
'metadata',
getMetadata({
hideCode: true,
}),
);
await component.renderExample();
fixture.detectChanges();
// Initially, the code should be hidden.
expect(component.showCode()).toBeFalse();
let codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
expect(codeContainer).toBeNull();
});
it('should expand/collapse code content with toggle button.', async () => {
componentRef.setInput('metadata', getMetadata());
await component.renderExample();
fixture.detectChanges();
// Initially, the code should be visible.
expect(component.showCode()).toBeTrue();
let codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
expect(codeContainer).not.toBeNull();
const codeToggleButton = fixture.debugElement.query(By.css('.docs-example-code-toggle'));
codeToggleButton.nativeElement.click();
fixture.detectChanges();
expect(component.showCode()).toBeFalse();
codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
expect(codeContainer).toBeNull();
codeToggleButton.nativeElement.click();
fixture.detectChanges();
expect(component.showCode()).toBeTrue();
codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
expect(codeContainer).not.toBeNull();
});
});
const getMetadata = (value: Partial<ExampleMetadata> = {}): ExampleMetadata => {
@@ -326,7 +283,6 @@ const getMetadata = (value: Partial<ExampleMetadata> = {}): ExampleMetadata => {
{name: 'example.css', sanitizedContent: ''},
],
preview: false,
hideCode: false,
...value,
};
};
@@ -21,15 +21,13 @@ import {
Type,
viewChild,
} from '@angular/core';
import {DOCUMENT, NgTemplateOutlet} from '@angular/common';
import {CommonModule, DOCUMENT} from '@angular/common';
import {MatTabGroup, MatTabsModule} from '@angular/material/tabs';
import {Clipboard} from '@angular/cdk/clipboard';
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
import {IconComponent} from '../../icon/icon.component';
import {ExampleMetadata, Snippet} from '../../../interfaces/index';
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {MatTooltipModule} from '@angular/material/tooltip';
export enum CodeExampleViewMode {
SNIPPET = 'snippet',
@@ -43,7 +41,7 @@ export const HIDDEN_CLASS_NAME = 'hidden';
@Component({
selector: 'docs-example-viewer',
imports: [CopySourceCodeButton, MatTabsModule, MatTooltipModule, IconComponent, NgTemplateOutlet],
imports: [CommonModule, CopySourceCodeButton, MatTabsModule],
templateUrl: './example-viewer.component.html',
styleUrls: ['./example-viewer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -73,18 +71,17 @@ export class ExampleViewer {
CodeExampleViewMode = CodeExampleViewMode;
exampleComponent?: Type<unknown>;
readonly expandable = signal<boolean>(false);
readonly expanded = signal<boolean>(false);
readonly snippetCode = signal<Snippet | undefined>(undefined);
readonly showCode = signal<boolean>(true);
readonly tabs = computed(() =>
expandable = signal<boolean>(false);
expanded = signal<boolean>(false);
snippetCode = signal<Snippet | undefined>(undefined);
tabs = computed(() =>
this.exampleMetadata()?.files.map((file) => ({
name:
file.title ?? (this.shouldDisplayFullName() ? file.name : this.getFileExtension(file.name)),
code: file.sanitizedContent,
})),
);
readonly view = computed(() =>
view = computed(() =>
this.exampleMetadata()?.files.length === 1
? CodeExampleViewMode.SNIPPET
: CodeExampleViewMode.MULTI_FILE,
@@ -100,10 +97,6 @@ export class ExampleViewer {
this.snippetCode.set(this.exampleMetadata()?.files[0]);
if (this.exampleMetadata()?.hideCode) {
this.showCode.set(false);
}
afterNextRender(
() => {
// Several function below query the DOM directly, we need to wait until the DOM is rendered.
@@ -21,7 +21,7 @@ export class SearchItem implements Highlightable {
// We can't migrate them to signals yet
@Input() disabled = false;
readonly item = input<SearchResultItem | undefined>();
item = input<SearchResultItem | undefined>();
private readonly elementRef = inject(ElementRef<HTMLLIElement>);
@@ -38,6 +38,4 @@ export interface ExampleMetadata {
files: Snippet[];
/** True when ExampleViewer should have preview */
preview: boolean;
/** Whether to hide code example by default. */
hideCode: boolean;
}
@@ -16,5 +16,4 @@ export interface NavigationItem {
parent?: NavigationItem;
contentPath?: string;
status?: 'new' | 'updated';
category?: string;
}
@@ -60,8 +60,6 @@ export interface SearchResultItem {
type: 'doc' | 'code';
labelHtml: string | null;
subLabelHtml: string | null;
contentHtml: string | null;
package: string | null;
url: string;
id: string;
+8 -8
View File
@@ -2,14 +2,14 @@
"name": "@angular/docs",
"version": "0.0.0-PLACEHOLDER",
"peerDependencies": {
"@angular/cdk": "^20.2.0-next",
"@angular/common": "^20.2.0-next",
"@angular/core": "^20.2.0-next",
"@angular/forms": "^20.2.0-next",
"@angular/material": "^20.2.0-next",
"@angular/platform-browser": "^20.2.0-next",
"@angular/router": "^20.2.0-next",
"@angular/ssr": "^20.2.0-next",
"@angular/cdk": "^21.0.0-next",
"@angular/common": "^21.0.0-next",
"@angular/core": "^21.0.0-next",
"@angular/forms": "^21.0.0-next",
"@angular/material": "^21.0.0-next",
"@angular/platform-browser": "^21.0.0-next",
"@angular/router": "^21.0.0-next",
"@angular/ssr": "^21.0.0-next",
"algoliasearch": "^5.0.0",
"rxjs": "^7.8.1"
},
@@ -12,7 +12,6 @@ import type {DocEntry, EntryCollection, FunctionEntry, JsDocTagEntry} from '@ang
export interface ManifestEntry {
name: string;
type: string;
category: string | undefined;
deprecated: {version: string | undefined} | undefined;
developerPreview: {version: string | undefined} | undefined;
experimental: {version: string | undefined} | undefined;
@@ -86,7 +85,6 @@ export function generateManifest(apiCollections: EntryCollection[]): Manifest {
developerPreview: getTagSinceVersion(entry, 'developerPreview'),
experimental: getTagSinceVersion(entry, 'experimental'),
stable: getTagSinceVersion(entry, 'publicApi'),
category: getTag(entry, 'category')?.comment.trim(),
}));
const existingEntry = manifest.find((entry) => entry.moduleName === collection.moduleName);
@@ -50,7 +50,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
{
name: 'PI',
@@ -59,7 +58,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -75,7 +73,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -113,7 +110,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -129,7 +125,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -167,7 +162,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
{
name: 'TAO',
@@ -176,7 +170,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -210,7 +203,6 @@ describe('api manifest generation', () => {
deprecated: {version: undefined},
experimental: undefined,
stable: undefined,
category: undefined,
},
{
name: 'XI',
@@ -219,7 +211,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: {version: undefined},
stable: undefined,
category: undefined,
},
],
},
@@ -280,7 +271,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -341,7 +331,6 @@ describe('api manifest generation', () => {
deprecated: {version: undefined},
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -381,7 +370,6 @@ describe('api manifest generation', () => {
deprecated: {version: undefined},
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -397,7 +385,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
],
},
@@ -435,7 +422,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: undefined,
stable: undefined,
category: undefined,
},
{
name: 'XI',
@@ -444,7 +430,6 @@ describe('api manifest generation', () => {
deprecated: undefined,
experimental: {version: undefined},
stable: undefined,
category: undefined,
},
],
},
@@ -5,10 +5,6 @@ package(default_visibility = ["//adev/shared-docs/pipeline/api-gen:__subpackages
esbuild(
name = "bin",
entry_point = ":index.mts",
external = [
"jsdom",
"playwright-core",
],
format = "esm",
output = "bin.mjs",
platform = "node",
@@ -103,6 +103,13 @@ async function main() {
const [srcs, outputFilenameExecRootRelativePath] = rawParamLines;
// TODO: Remove when we are using Bazel v6+
// On RBE, the output directory is not created properly due to a bug.
// https://github.com/bazelbuild/bazel/commit/4310aeb36c134e5fc61ed5cdfdf683f3e95f19b7.
if (!existsSync(outputFilenameExecRootRelativePath)) {
mkdirSync(outputFilenameExecRootRelativePath, {recursive: true});
}
// Docs rendering happens in three phases that occur here:
// 1) Aggregate all the raw extracted doc info.
// 2) Transform the raw data to a renderable state.
@@ -21,10 +21,6 @@ export function setCurrentSymbol(symbol: string): void {
currentSymbol = symbol;
}
export function getSymbols() {
return symbols;
}
export function getCurrentSymbol(): string | undefined {
return currentSymbol;
}
@@ -79,14 +79,12 @@ describe('markdown to html', () => {
// In the description
const descriptionItem = entry.querySelector('.docs-reference-description')!;
expect(descriptionItem.innerHTML).toContain(
'<a href="/api/core/afterRender"><code>afterRender</code></a>',
);
expect(descriptionItem.innerHTML).toContain('<a href="/api/core/afterRender">afterRender</a>');
// In the card
const cardItem = entry.querySelectorAll('.docs-reference-card-item')[1];
expect(cardItem.innerHTML).toContain(
'<a href="/api/core/AfterRenderPhase#MixedReadWrite"><code>AfterRenderPhase.MixedReadWrite</code></a>',
'<a href="/api/core/AfterRenderPhase#MixedReadWrite">AfterRenderPhase.MixedReadWrite</a>',
);
});
});
@@ -6,10 +6,8 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {initHighlighter} from '../../../../shared/shiki.mjs';
import {setHighlighterInstance} from '../../shiki/shiki.mjs';
import {setCurrentSymbol, setSymbols} from '../../symbol-context.mjs';
import {addHtmlAdditionalLinks, addHtmlDescription} from '../../transforms/jsdoc-transforms.mjs';
import {addHtmlAdditionalLinks} from '../../transforms/jsdoc-transforms.mjs';
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
describe('jsdoc transforms', () => {
@@ -153,40 +151,4 @@ describe('jsdoc transforms', () => {
expect(entryFn).toThrowError(/Forbidden relative link: cli\/build ng build/);
});
it('should parse markdown in descriptions', async () => {
setHighlighterInstance(await initHighlighter());
setSymbols(
Object.fromEntries([
['Route', 'test'],
['Router', 'angular/router'],
['Router.someMethod', 'test'],
['Router.someMethodWithParenthesis', 'test'],
['FormGroup', 'test'],
['FormGroup.someMethod', 'test'],
]),
);
const entry = addHtmlDescription({
description: `
\`\`\`angular-ts
import { Router } from '@angular/router';
function setupRouter() {
const router = inject(Router);
}
\`\`\`
`,
moduleName: 'test',
});
// Should have some shiki variables (meaning the description was highlighted).
expect(entry.htmlDescription).toContain('--shiki');
// Having docs-code means that the description was parsed and formatted correctly (by the shared marked renderer)
expect(entry.htmlDescription).toContain('class="docs-code"');
expect(entry.htmlDescription).toContain('/api/angular/router/Router');
});
});
@@ -14,17 +14,13 @@ import {
CliCommandRenderable,
CliOptionRenderable,
} from '../entities/renderables.mjs';
import {parseMarkdown} from '../../../shared/marked/parse.mjs';
import {getHighlighterInstance} from '../shiki/shiki.mjs';
/** Given an unprocessed CLI entry, get the fully renderable CLI entry. */
export function getCliRenderable(command: CliCommand): CliCommandRenderable {
return {
...command,
subcommands: command.subcommands?.map((sub) => getCliRenderable(sub)),
htmlDescription: parseMarkdown(command.longDescription ?? command.shortDescription, {
highlighter: getHighlighterInstance(),
}),
htmlDescription: marked.parse(command.longDescription ?? command.shortDescription) as string,
cards: getCliCardsRenderable(command),
argumentsLabel: getArgumentsLabel(command),
hasOptions: getOptions(command).length > 0,
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {marked} from 'marked';
import {JsDocTagEntry} from '../entities.mjs';
import {getDeprecatedEntry, getTagSinceVersion} from '../entities/categorization.mjs';
@@ -25,14 +26,7 @@ import {
} from '../entities/traits.mjs';
import {addApiLinksToHtml} from './code-transforms.mjs';
import {
getCurrentSymbol,
getSymbols,
getSymbolUrl,
unknownSymbolMessage,
} from '../symbol-context.mjs';
import {parseMarkdown} from '../../../shared/marked/parse.mjs';
import {getHighlighterInstance} from '../shiki/shiki.mjs';
import {getCurrentSymbol, getSymbolUrl, unknownSymbolMessage} from '../symbol-context.mjs';
const JS_DOC_USAGE_NOTE_TAGS: Set<string> = new Set(['remarks', 'usageNotes', 'example']);
export const JS_DOC_SEE_TAG = 'see';
@@ -105,11 +99,7 @@ export function addHtmlUsageNotes<T extends HasJsDocTags>(entry: T): T & HasHtml
/** Given a markdown JsDoc text, gets the rendered HTML. */
function getHtmlForJsDocText(text: string): string {
const mdToParse = convertLinks(wrapExampleHtmlElementsWithCode(text));
const parsed = parseMarkdown(mdToParse, {
apiEntries: getSymbols(),
highlighter: getHighlighterInstance(),
});
const parsed = marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text))) as string;
return addApiLinksToHtml(parsed);
}
@@ -41,7 +41,7 @@ export const docsAlertExtension = {
let match: DocsAlert | undefined;
for (const key of Object.keys(AlertSeverityLevel)) {
// Capture group 1: all alert text content after the severity level
const rule = new RegExp('^s*' + key + ': (.*?)(?:\n{2,}|\s*$)', 's');
const rule = new RegExp('^s*' + key + ': (.*?)\n(\n|$)', 's');
const possibleMatch = rule.exec(src);
if (possibleMatch?.[1]) {
@@ -72,7 +72,7 @@ function getStandardCard(renderer: RendererThis, token: DocsCardToken) {
return `
<a href="${token.href}" ${anchorTarget(token.href)} class="docs-card">
<div>
${token.title ? `<h3>${token.title}</h3>` : ''}
<h3>${token.title}</h3>
${parseWithoutCreatingLinks(renderer, token)}
</div>
<span>${token.link ? token.link : 'Learn more'}</span>
@@ -82,7 +82,7 @@ function getStandardCard(renderer: RendererThis, token: DocsCardToken) {
return `
<div class="docs-card">
<div>
${token.title ? `<h3>${token.title}</h3>` : ''}
<h3>${token.title}</h3>
${renderer.parser.parse(token.tokens)}
</div>
${token.link ? `<span>${token.link}</span>` : ''}
@@ -111,7 +111,7 @@ function getCardWithSvgIllustration(renderer: RendererThis, token: DocsCardToken
${illustration}
<div class="docs-card-text-content">
<div>
${token.title ? `<h3>${token.title}</h3>` : ''}
<h3>${token.title}</h3>
${renderer.parser.parse(token.tokens)}
</div>
<span>${token.link ? token.link : 'Learn more'}</span>
@@ -123,7 +123,7 @@ function getCardWithSvgIllustration(renderer: RendererThis, token: DocsCardToken
<div class="docs-card docs-card-with-svg">
${illustration}
<div class="docs-card-text-content">
${token.title ? `<h3>${token.title}</h3>` : ''}
<h3>${token.title}</h3>
${renderer.parser.parse(token.tokens)}
</div>
</div>
@@ -20,8 +20,6 @@ export interface DocsCodeMultifileToken extends Tokens.Generic {
paneTokens: Token[];
// True if we should display preview
preview: boolean;
/** Whether to hide code example by default. */
hideCode: boolean;
}
// Capture group 1: all attributes on the opening tag
@@ -30,7 +28,6 @@ const multiFileCodeRule = /^\s*<docs-code-multifile(.*?)>(.*?)<\/docs-code-multi
const pathRule = /path="([^"]*)"/;
const previewRule = /preview/;
const hideCodeRule = /hideCode/;
export const docsCodeMultifileExtension = {
name: 'docs-code-multifile',
@@ -45,7 +42,6 @@ export const docsCodeMultifileExtension = {
const attr = match[1].trim();
const path = pathRule.exec(attr);
const preview = previewRule.exec(attr) ? true : false;
const hideCode = hideCodeRule.exec(attr) ? true : false;
const token: DocsCodeMultifileToken = {
type: 'docs-code-multifile',
@@ -54,7 +50,6 @@ export const docsCodeMultifileExtension = {
panes: match[2].trim(),
paneTokens: [],
preview: preview,
hideCode,
};
this.lexer.blockTokens(token.panes, token.paneTokens);
return token;
@@ -74,9 +69,6 @@ export const docsCodeMultifileExtension = {
if (token.preview) {
el.setAttribute('preview', `${token.preview}`);
}
if (token.hideCode) {
el.setAttribute('hideCode', 'true');
}
return el.outerHTML;
},
@@ -32,7 +32,6 @@ const languageRule = /language="([^"]*)"/;
const visibleLinesRule = /visibleLines="([^"]*)"/;
const visibleRegionRule = /visibleRegion="([^"]*)"/;
const previewRule = /preview/;
const hideCodeRule = /hideCode/;
export const docsCodeExtension = {
name: 'docs-code',
@@ -54,7 +53,6 @@ export const docsCodeExtension = {
const visibleLines = visibleLinesRule.exec(attr);
const visibleRegion = visibleRegionRule.exec(attr);
const preview = previewRule.exec(attr) ? true : false;
const hideCode = hideCodeRule.exec(attr) ? true : false;
const classes = classRule.exec(attr);
let code = match[2]?.trim() ?? '';
@@ -78,7 +76,6 @@ export const docsCodeExtension = {
visibleLines: visibleLines?.[1],
visibleRegion: visibleRegion?.[1],
preview: preview,
hideCode,
classes: classes?.[1]?.split(' '),
};
return token;
@@ -36,8 +36,6 @@ export interface CodeToken extends Tokens.Generic {
visibleRegion?: string;
/* Whether we should display preview */
preview?: boolean;
/** Whether to hide code example by default. */
hideCode?: boolean;
/* The lines to display highlighting on */
highlight?: string;
@@ -65,7 +63,7 @@ export function formatCode(token: CodeToken, context: RendererContext): string {
`).firstElementChild!;
applyContainerAttributesAndClasses(containerEl, token);
processForApiLinks(containerEl, context.apiEntries ?? {});
processForApiLinks(containerEl, context.apiEntries);
return containerEl.outerHTML;
}
@@ -123,9 +121,6 @@ function applyContainerAttributesAndClasses(el: Element, token: CodeToken) {
if (token.preview) {
el.setAttribute('preview', 'true');
}
if (token.hideCode) {
el.setAttribute('hideCode', 'true');
}
if (token.language === 'mermaid') {
el.setAttribute('mermaid', 'true');
}
@@ -13,12 +13,12 @@ import {listRender} from './transformations/list.mjs';
import {imageRender} from './transformations/image.mjs';
import {textRender} from './transformations/text.mjs';
import {headingRender} from './transformations/heading.mjs';
import {codeRender, codespanRender} from './transformations/code.mjs';
import {codespanRender} from './transformations/code.mjs';
import {HighlighterGeneric} from 'shiki';
export interface RendererContext {
markdownFilePath?: string;
apiEntries?: Record<string, string>;
markdownFilePath: string;
apiEntries: Record<string, string>;
highlighter: HighlighterGeneric<any, any>;
}
@@ -40,5 +40,4 @@ export class AdevDocsRenderer extends Renderer {
override text = textRender;
override heading = headingRender;
override codespan = codespanRender;
override code = codeRender;
}
@@ -16,5 +16,3 @@ CRITICAL: Use Critical to call out potential bad stuff or alert the reader they
IMPORTANT: Use Important for information that's crucial to comprehending the text or to completing some task.
HELPFUL: Use Best practice to call out practices that are known to be successful or better than alternatives.
NOTE: THIS NOTE WITHOUT A LINE RETURN
@@ -34,10 +34,4 @@ describe('markdown to html', () => {
expect(noteEl?.textContent?.trim()).toContain(`This is a multiline note`);
});
it(`should handle alerts without a line return`, () => {
const noteEl = markdownDocument.querySelector(`.docs-alert-note:last-of-type`);
expect(noteEl?.textContent?.trim()).toContain(`THIS NOTE WITHOUT A LINE RETURN`);
});
});
@@ -2,7 +2,4 @@
<docs-card title="Link Card" link="Try It Now" href="in/app/link">
Card Content with a symbol: `CommonModule`
</docs-card>
<docs-card title="Image Card" imgSrc="./angular.svg"></docs-card>
<docs-card title="" link="Open on Playground" href="/playground">
The fastest way to play with an Angular app. No setup required.
</docs-card>
<docs-card title="Image Card" imgSrc="./angular.svg"></docs-card>
@@ -27,7 +27,7 @@ describe('markdown to html', () => {
expect(cardEl.tagName).not.toBe('A');
});
it('creates cards with links', () => {
it('creates cards withs links', () => {
const cardEl = markdownDocument.querySelectorAll('.docs-card')[1];
expect(cardEl.querySelector('h3')?.textContent?.trim()).toBe('Link Card');
@@ -48,12 +48,4 @@ describe('markdown to html', () => {
expect(cardEl.querySelector('h3')?.textContent?.trim()).toBe('Image Card');
expect(cardEl.querySelector('svg')).toBeTruthy();
});
it('does not create empty h3 tags when title is empty', () => {
const cardEl = markdownDocument.querySelectorAll('.docs-card')[3];
expect(cardEl.querySelector('h3')).toBeNull();
expect(cardEl.tagName).toBe('A');
expect(cardEl.getAttribute('href')).toBe('/playground');
});
});
@@ -1,4 +1,3 @@
[Angular Site](https://angular.dev)
[same page](#test)
[same site](../other/page)
[npm packages](https://docs.npmjs.com/getting-started/what-is-npm "What is npm?")
[same site](../other/page)
@@ -31,10 +31,4 @@ describe('markdown to html', () => {
it('should render internal links that are relative paths', () => {
expect(parsedMarkdown).toContain('<a href="../other/page">same site</a>');
});
it('should render links with title attributes properly quoted', () => {
expect(parsedMarkdown).toContain(
'<a href="https://docs.npmjs.com/getting-started/what-is-npm" title="What is npm?" target="_blank">npm packages</a>',
);
});
});
@@ -16,6 +16,7 @@ describe('markdown to html', () => {
beforeAll(async () => {
(global as any).HANDLE_MERMAID = true;
// This test was flaky, 1st attemp to fix it is by inlining the markdown content
const markdownContent = `
\`\`\`mermaid
graph TD;
@@ -51,7 +52,7 @@ describe('markdown to html', () => {
});
markdownDocument = JSDOM.fragment(await markedInstance.parse(markdownContent));
}, 25_000);
}, 15_000);
it('should create an svg for each mermaid code block', () => {
expect(markdownDocument.querySelectorAll('svg')).toHaveSize(3);
@@ -9,10 +9,9 @@
import {Tokens} from 'marked';
import {AdevDocsRenderer} from '../renderer.mjs';
import {getSymbolUrl} from '../../linking.mjs';
import {codeToHtml} from '../../shiki.mjs';
export function codespanRender(this: AdevDocsRenderer, token: Tokens.Codespan) {
const apiLink = getSymbolUrl(token.text, this.context.apiEntries ?? {});
const apiLink = getSymbolUrl(token.text, this.context.apiEntries);
if (apiLink) {
const htmlToken: Tokens.HTML = {
type: 'html',
@@ -32,15 +31,3 @@ export function codespanRender(this: AdevDocsRenderer, token: Tokens.Codespan) {
}
return this.defaultRenderer.codespan(token);
}
export function codeRender(this: AdevDocsRenderer, {text, lang}: Tokens.Code): string {
const highlightResult = codeToHtml(this.context.highlighter, text, lang)
// remove spaces/line-breaks between elements to not mess-up `pre` style
.replace(/>\s+</g, '><');
return `
<div class="docs-code" role="group">
${highlightResult}
</div>
`;
}
@@ -17,7 +17,7 @@ export function headingRender(this: AdevDocsRenderer, {depth, tokens}: Tokens.He
export function formatHeading(
{text, depth}: {text: string; depth: number},
markdownFilePath?: string,
markdownFilePath: string,
): string {
if (depth === 1) {
return `
@@ -51,22 +51,18 @@ export function formatHeading(
// TODO(josephperrott): Set edit content url based on the owner, repo and branch.
/** The base url for editing the a file in the repository. */
/** The base url for edting the a file in the repository. */
const GITHUB_EDIT_CONTENT_URL = 'https://github.com/angular/angular/edit/main';
/** Get the page title with edit button to modify the page source. */
export function getPageTitle(text: string, filePath?: string): string {
export function getPageTitle(text: string, filePath: string): string {
return `
<!-- Page title -->
<div class="docs-page-title">
<h1 tabindex="-1">${text}</h1>
${
filePath
? `<a class="docs-github-links" target="_blank" href="${GITHUB_EDIT_CONTENT_URL}/${filePath}" title="Edit this page" aria-label="Edit this page">
<a class="docs-github-links" target="_blank" href="${GITHUB_EDIT_CONTENT_URL}/${filePath}" title="Edit this page" aria-label="Edit this page">
<!-- Pencil -->
<docs-icon role="presentation">edit</docs-icon>
</a>`
: ''
}
</a>
</div>`;
}
@@ -24,6 +24,6 @@ export function linkRender(this: Renderer, {href, title, tokens}: Tokens.Link) {
return this.parser.parseInline(tokens);
}
const titleAttribute = title ? ` title="${title}"` : '';
const titleAttribute = title ? ` title=${title}` : '';
return `<a href="${href}"${titleAttribute}${anchorTarget(href)}>${this.parser.parseInline(tokens)}</a>`;
}
+1
View File
@@ -32,6 +32,7 @@ ng_project(
"//adev:node_modules/rxjs",
"//adev/shared-docs/interfaces",
"//adev/shared-docs/providers",
"//adev/shared-docs/utils",
],
)
@@ -21,7 +21,7 @@ export class NavigationState {
private readonly _isMobileNavVisible = signal<boolean>(false);
private readonly _level = linkedSignal(() => this._expandedItems().length);
readonly primaryActiveRouteItem = signal<string | null>(null);
primaryActiveRouteItem = signal<string | null>(null);
activeNavigationItem = this._activeNavigationItem.asReadonly();
expandedItems = this._expandedItems.asReadonly();
isMobileNavVisible = this._isMobileNavVisible.asReadonly();
@@ -119,7 +119,7 @@ export class NavigationState {
/**
* return the actual navigation items, that is to say the one that match the current level
*/
private actualExpandedItems(): NavigationItem[] {
private actualExpandedItems() {
return this.expandedItems().slice(0, this.level());
}
@@ -19,47 +19,38 @@ export const MAX_RECENT_HISTORY_SIZE = 10;
export interface HistoryItem {
id: string;
labelHtml: string;
subLabelHtml?: string;
url: string;
isFavorite: boolean;
createdAt: number;
}
function cleanUpHtml(label: string | null): string {
return (label || '').replace(/<\/?mark>/g, '');
}
@Injectable({providedIn: 'root'})
export class SearchHistory {
private readonly localStorage = inject(LOCAL_STORAGE);
private readonly history = signal<Map<string, HistoryItem>>(new Map());
private readonly allItems = computed(() =>
private allItems = computed(() =>
Array.from(this.history().values()).sort((a, b) => b.createdAt - a.createdAt),
);
readonly items = computed<{recent: HistoryItem[]; favorite: HistoryItem[]}>(() => ({
items = computed<{recent: HistoryItem[]; favorite: HistoryItem[]}>(() => ({
recent: this.allItems().filter((v) => !v.isFavorite),
favorite: this.allItems().filter((v) => v.isFavorite),
}));
readonly hasItems = computed(() => this.allItems().length > 0);
hasItems = computed(() => this.allItems().length > 0);
constructor() {
this.loadHistory();
}
addItem(item: SearchResultItem | HistoryItem): void {
// We don't want to reset nor update the creation date of favorites
if (this.history().get(item.id)?.isFavorite) {
return;
}
addItem(item: SearchResultItem | HistoryItem) {
this.updateHistory((map) => {
const labelHtml = (item.labelHtml || '').replace(/<\/?mark>/g, '');
map.set(item.id, {
id: item.id,
labelHtml: cleanUpHtml(item.labelHtml),
subLabelHtml: item.subLabelHtml ? cleanUpHtml(item.subLabelHtml) : undefined,
labelHtml,
url: item.url,
isFavorite: false,
createdAt: Date.now(),
@@ -73,13 +64,13 @@ export class SearchHistory {
});
}
removeItem(item: SearchResultItem | HistoryItem): void {
removeItem(item: SearchResultItem | HistoryItem) {
this.updateHistory((map) => {
map.delete(item.id);
});
}
makeFavorite(item: SearchResultItem | HistoryItem): void {
makeFavorite(item: SearchResultItem | HistoryItem) {
this.updateHistory((map) => {
const updated = map.get(item.id);
if (updated) {
@@ -92,7 +83,7 @@ export class SearchHistory {
});
}
private loadHistory(): void {
private loadHistory() {
let parsedData: HistoryItem[];
try {
@@ -109,7 +100,7 @@ export class SearchHistory {
this.history.set(history);
}
private updateHistory(updateFn: (map: Map<string, HistoryItem>) => void): void {
private updateHistory(updateFn: (map: Map<string, HistoryItem>) => void) {
const history = new Map(this.history());
updateFn(history);
this.history.set(history);
+10 -16
View File
@@ -48,7 +48,7 @@ export class Search {
private readonly config = inject(ENVIRONMENT);
private readonly client = inject(ALGOLIA_CLIENT);
readonly resultsResource = resource({
resultsResource = resource({
params: () => this.searchQuery() || undefined, // coerces empty string to undefined
loader: async ({params: query, abortSignal}) => {
// Until we have a better alternative we debounce by awaiting for a short delay.
@@ -97,7 +97,7 @@ export class Search {
},
});
readonly searchResults = linkedSignal<SearchResultItem[] | undefined, SearchResultItem[]>({
searchResults = linkedSignal<SearchResultItem[] | undefined, SearchResultItem[]>({
source: this.resultsResource.value,
computation: (next, prev) => (!next && this.searchQuery() ? prev?.value : next) ?? [],
});
@@ -141,11 +141,10 @@ export class Search {
const items = result.hits as unknown as SearchResult[];
return this.getUniqueSearchResultItems(items).map((hitItem: SearchResult): SearchResultItem => {
return this.getUniqueSearchResultItems(items).map((hitItem: SearchResult) => {
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 hasSubLabel = content || hierarchy?.lvl2 || hierarchy?.lvl3 || hierarchy?.lvl4;
return {
id: hitItem.objectID,
@@ -156,8 +155,6 @@ export class Search {
subLabelHtml: this.parseLabelToHtml(
hasSubLabel ? this.getBestSnippetForMatch(hitItem) : null,
),
contentHtml: content ? this.parseLabelToHtml(content.value) : null,
package: category === 'Reference' ? extractPackageNameFromUrl(hitItem.url) : null,
category: hitItem.hierarchy?.lvl0 ?? null,
};
@@ -165,6 +162,11 @@ export class Search {
}
private getBestSnippetForMatch(result: SearchResult): string {
// if there is content, return it
if (result._snippetResult.content !== undefined) {
return result._snippetResult.content.value;
}
const hierarchy = result._snippetResult.hierarchy;
if (hierarchy === undefined) {
return '';
@@ -217,7 +219,7 @@ function matched(snippet: SnippetResult | undefined): boolean {
/**
* Temporary helper to implement the debounce functionality on the search resource
*/
function wait(ms: number, signal: AbortSignal): Promise<void> {
function wait(ms: number, signal: AbortSignal) {
return new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => resolve(), ms);
@@ -231,11 +233,3 @@ function wait(ms: number, signal: AbortSignal): Promise<void> {
);
});
}
function extractPackageNameFromUrl(url: string): string | null {
const extractedSegment = url.match(/\/api\/(.*)\/.*#?/);
if (extractedSegment == null) {
return null;
}
return `<code>@angular/${extractedSegment[1]}</code>`;
}
-4
View File
@@ -13,7 +13,6 @@
--electric-violet: oklch(53.18% 0.28 296.97); // #8514f5
--french-violet: oklch(47.66% 0.246 305.88); // #8001c6
--vivid-pink: oklch(69.02% 0.277 332.77); // #f637e3
--always-pink: oklch(69.02% 0.277 332.77); // #f637e3
--hot-pink: oklch(59.91% 0.239 8.14); // #e90464
--hot-red: oklch(61.42% 0.238 15.34); // #f11653
--orange-red: oklch(63.32% 0.24 31.68); // #fa2c04
@@ -24,7 +23,6 @@
// subtle-purple is used for inline-code bg, docs-card hover bg & docs-code header bg
--subtle-purple: color-mix(in srgb, var(--bright-blue) 5%, white 10%);
--subtle-blue: color-mix(in srgb, var(--bright-blue) 5%, white 95%);
--light-blue: color-mix(in srgb, var(--bright-blue), white 50%);
--light-violet: color-mix(in srgb, var(--electric-violet), white 65%);
--light-orange: color-mix(in srgb, var(--orange-red), white 50%);
@@ -213,7 +211,6 @@
--electric-violet: color-mix(in srgb, oklch(53.18% 0.28 296.97), var(--full-contrast) 70%);
--french-violet: color-mix(in srgb, oklch(47.66% 0.246 305.88), var(--full-contrast) 70%);
--vivid-pink: color-mix(in srgb, oklch(69.02% 0.277 332.77), var(--full-contrast) 70%);
// --always-pink we keep the same in light mode
--hot-pink: color-mix(in srgb, oklch(59.91% 0.239 8.14), var(--full-contrast) 70%);
--hot-red: color-mix(in srgb, oklch(61.42% 0.238 15.34), var(--full-contrast) 70%);
--orange-red: color-mix(in srgb, oklch(63.32% 0.24 31.68), var(--full-contrast) 60%);
@@ -223,7 +220,6 @@
--deprecated-docs-bg: var(--deprecated-dark); // Alias specifically for deprecated docs
--light-pink: color-mix(in srgb, var(--vivid-pink) 5%, var(--page-background) 75%);
--subtle-blue: color-mix(in srgb, var(--bright-blue) 5%, var(--page-background) 90%);
--symbolic-purple: color-mix(in srgb, oklch(42.86% 0.29 266.4), var(--full-contrast) 65%);
--symbolic-gray: color-mix(in srgb, oklch(66.98% 0 0), var(--full-contrast) 65%);
+8 -20
View File
@@ -5,18 +5,6 @@ $screen-lg: 1200px;
$screen-xl: 1430px;
$screen-xxl: 1800px;
@mixin breakpoints-vars {
// The app-scroller also relies on these values to determine the scroll offset.
:root {
--screen-xs: #{$screen-xs};
--screen-sm: #{$screen-sm};
--screen-md: #{$screen-md};
--screen-lg: #{$screen-lg};
--screen-xl: #{$screen-xl};
--screen-xxl: #{$screen-xxl};
}
}
@mixin for-phone-only {
@media (max-width: $screen-xs) {
@content;
@@ -24,43 +12,43 @@ $screen-xxl: 1800px;
}
@mixin for-tablet-portrait-up {
@media (min-width: calc($screen-xs + 0.01px)) {
@media (min-width: calc($screen-xs + .01px)) {
@content;
}
}
@mixin for-tablet {
@media (min-width: calc($screen-xs + 0.01px)) and (max-width: $screen-md) {
@media (min-width: calc($screen-xs + .01px)) and (max-width: $screen-md) {
@content;
}
}
@mixin for-tablet-up {
@media (min-width: calc($screen-sm + 0.01px)) {
@media (min-width: calc($screen-sm + .01px)) {
@content;
}
}
@mixin for-tablet-landscape-up {
@media (min-width: calc($screen-md + 0.01px)) {
@media (min-width: calc($screen-md + .01px)) {
@content;
}
}
@mixin for-desktop-up {
@media (min-width: calc($screen-lg + 0.01px)) {
@media (min-width: calc($screen-lg + .01px)) {
@content;
}
}
@mixin for-large-desktop-up {
@media (min-width: calc($screen-xl + 0.01px)) {
@media (min-width: calc($screen-xl + .01px)) {
@content;
}
}
@mixin for-extra-large-desktop-up {
@media (min-width: calc($screen-xxl + 0.01px)) {
@media (min-width: calc($screen-xxl + .01px)) {
@content;
}
}
@@ -87,4 +75,4 @@ $screen-xxl: 1800px;
@media (max-width: $screen-sm) {
@content;
}
}
}
+3 -4
View File
@@ -16,7 +16,6 @@
display: flex;
flex-direction: column;
justify-content: space-between;
background: var(--page-background);
color: var(--primary-contrast);
padding: 1.5rem;
border: 1px solid var(--senary-contrast);
@@ -41,7 +40,7 @@
position: relative;
}
* + *:not(a):not(code):not(span):not(div) {
* + *:not(a):not(code):not(span) {
margin-block: 1.5rem;
}
@@ -52,7 +51,6 @@
flex-grow: 1;
margin-block-start: 0;
padding-inline: 1.5rem;
padding-block: 1rem;
display: flex;
flex-direction: column;
justify-content: space-between;
@@ -60,6 +58,7 @@
h3 {
margin-bottom: 0;
margin-block-start: 1rem;
font-size: 1rem;
}
p {
@@ -89,7 +88,7 @@
}
&:hover {
background: var(--subtle-blue);
background: var(--subtle-purple);
span {
background-position: 0% 0%;
+2 -3
View File
@@ -27,7 +27,7 @@
pointer-events: none;
position: absolute;
left: calc(var(--gutter) * -1);
top: 1rem;
top: 2.7rem;
bottom: 0;
&::before {
@@ -39,8 +39,7 @@
border-radius: 50%;
aspect-ratio: 1 / 1;
border: 1px solid transparent;
background-image:
linear-gradient(var(--page-background), var(--page-background)),
background-image: linear-gradient(var(--page-background), var(--page-background)),
var(--pink-to-purple-horizontal-gradient);
background-origin: border-box;
background-clip: content-box, border-box;
@@ -41,7 +41,6 @@
@include api-item-label.api-item-label();
@include faceted-list.faceted-list();
@include mq.breakpoints-vars();
@include mq.for-phone-only();
@include mq.for-tablet-portrait-up();
@include mq.for-tablet-landscape-up();

Some files were not shown because too many files have changed in this diff Show More