diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 6e37f2cc52b5..4a7f801b2bea 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -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@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/upload-sarif@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 with: sarif_file: results.sarif diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3c68183b31..ad466a0985ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ + +# 20.0.5 "agardite-ant" (2025-07-02) +### cdk +| Commit | Type | Description | +| -- | -- | -- | +| [45cc05641](https://github.com/angular/components/commit/45cc05641f89afb760077cb806af9814a01909e8) | fix | **tree:** remove leaking subscription ([#31457](https://github.com/angular/components/pull/31457)) | +### material +| Commit | Type | Description | +| -- | -- | -- | +| [7556beaa7](https://github.com/angular/components/commit/7556beaa7b20247d7c01f9e76d7097a0758f5ce7) | fix | **schematics:** typo in prompt question ([#31449](https://github.com/angular/components/pull/31449)) | +### google-maps +| Commit | Type | Description | +| -- | -- | -- | +| [bdfeb04c3](https://github.com/angular/components/commit/bdfeb04c3a220121d7b98b9ac8147e0cfdcdfeb6) | fix | fix update schematic ([#31448](https://github.com/angular/components/pull/31448)) | + + + # 20.0.4 "strontium-shack" (2025-06-25) ### cdk diff --git a/docs/src/app/shared/doc-viewer/doc-viewer.ts b/docs/src/app/shared/doc-viewer/doc-viewer.ts index 89b3b73cc81b..ed1b7b05f682 100644 --- a/docs/src/app/shared/doc-viewer/doc-viewer.ts +++ b/docs/src/app/shared/doc-viewer/doc-viewer.ts @@ -187,13 +187,17 @@ export class DocViewer implements OnDestroy { const examplePortal = new ComponentPortal(componentClass, this._viewContainerRef); const exampleViewer = portalHost.attach(examplePortal); const exampleViewerComponent = exampleViewer.instance; - if (example !== null && componentClass === ExampleViewer) { - DocViewer._initExampleViewer( - exampleViewerComponent as ExampleViewer, - example, - file, - region, - ); + if (example !== null) { + if (componentClass === ExampleViewer) { + DocViewer._initExampleViewer( + exampleViewerComponent as ExampleViewer, + example, + file, + region, + ); + } else { + (exampleViewerComponent as HeaderLink).example.set(example); + } } this._portalHosts.push(portalHost); }); diff --git a/docs/src/app/shared/doc-viewer/header-link.ts b/docs/src/app/shared/doc-viewer/header-link.ts index a518be234af6..889e078095a8 100644 --- a/docs/src/app/shared/doc-viewer/header-link.ts +++ b/docs/src/app/shared/doc-viewer/header-link.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, inject} from '@angular/core'; +import {Component, computed, inject, signal} from '@angular/core'; import {Router} from '@angular/router'; import {MatIcon} from '@angular/material/icon'; @@ -18,7 +18,7 @@ import {MatIcon} from '@angular/material/icon'; selector: 'header-link', template: ` + [attr.aria-describedby]="example()" [href]="_fragmentUrl()"> link `, @@ -29,18 +29,12 @@ export class HeaderLink { * Id of the anchor element. Note that is uses "example" because we instantiate the * header link components through the ComponentPortal. */ - example: string = ''; + readonly example = signal(''); /** Base URL that is used to build an absolute fragment URL. */ - private _baseUrl: string; + private _baseUrl = inject(Router).url.split('#')[0]; - constructor() { - const router = inject(Router); - - this._baseUrl = router.url.split('#')[0]; - } - - _getFragmentUrl(): string { - return `${this._baseUrl}#${this.example}`; - } + protected readonly _fragmentUrl = computed(() => { + return `${this._baseUrl}#${this.example()}`; + }); } diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 0e7c0a779167..f999763b7602 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -31,6 +31,7 @@ "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "strictInputAccessModifiers": true, - "strictTemplates": true + "strictTemplates": true, + "typeCheckHostBindings": true } } diff --git a/goldens/cdk/a11y/index.api.md b/goldens/cdk/a11y/index.api.md index ecd9b7654403..916d9c30d76c 100644 --- a/goldens/cdk/a11y/index.api.md +++ b/goldens/cdk/a11y/index.api.md @@ -175,7 +175,7 @@ export class FocusKeyManager extends ListKeyManager { // @public export class FocusMonitor implements OnDestroy { constructor(...args: unknown[]); - protected _document?: Document | null | undefined; + protected _document: Document; focusVia(element: HTMLElement, origin: FocusOrigin, options?: FocusOptions_2): void; focusVia(element: ElementRef, origin: FocusOrigin, options?: FocusOptions_2): void; monitor(element: HTMLElement, checkChildren?: boolean): Observable; diff --git a/goldens/cdk/text-field/index.api.md b/goldens/cdk/text-field/index.api.md index cb1f19df72e8..b9ab33d29cf3 100644 --- a/goldens/cdk/text-field/index.api.md +++ b/goldens/cdk/text-field/index.api.md @@ -51,7 +51,7 @@ export class CdkAutofill implements OnDestroy, OnInit { // @public export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { constructor(...args: unknown[]); - protected _document?: Document | null | undefined; + protected _document: Document; get enabled(): boolean; set enabled(value: boolean); get maxRows(): number; diff --git a/package.json b/package.json index 2d5d8f55987a..2b6b137bce71 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "ci-notify-slack-failure": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only scripts/circleci/notify-slack-job-failure.mts", "prepare": "husky" }, - "version": "20.0.4", + "version": "20.0.5", "dependencies": { "@angular-devkit/core": "catalog:", "@angular-devkit/schematics": "catalog:", diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.ts b/src/cdk/a11y/focus-monitor/focus-monitor.ts index dc1770436476..948d0b78cf87 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.ts @@ -142,7 +142,7 @@ export class FocusMonitor implements OnDestroy { }; /** Used to reference correct document/window */ - protected _document? = inject(DOCUMENT, {optional: true}); + protected _document = inject(DOCUMENT); /** Subject for stopping our InputModalityDetector subscription. */ private readonly _stopInputModalityDetector = new Subject(); @@ -206,7 +206,7 @@ export class FocusMonitor implements OnDestroy { // If the element is inside the shadow DOM, we need to bind our focus/blur listeners to // the shadow root, rather than the `document`, because the browser won't emit focus events // to the `document`, if focus is moving within the same shadow root. - const rootNode = _getShadowRoot(nativeElement) || this._getDocument(); + const rootNode = _getShadowRoot(nativeElement) || this._document; const cachedInfo = this._elementInfo.get(nativeElement); // Check if we're already monitoring this element. @@ -280,7 +280,7 @@ export class FocusMonitor implements OnDestroy { options?: FocusOptions, ): void { const nativeElement = coerceElement(element); - const focusedElement = this._getDocument().activeElement; + const focusedElement = this._document.activeElement; // If the element is focused already, calling `focus` again won't trigger the event listener // which means that the focus classes won't be updated. If that's the case, update the classes @@ -303,15 +303,9 @@ export class FocusMonitor implements OnDestroy { this._elementInfo.forEach((_info, element) => this.stopMonitoring(element)); } - /** Access injected document if available or fallback to global document reference */ - private _getDocument(): Document { - return this._document || document; - } - /** Use defaultView of injected document if available or fallback to global window reference */ private _getWindow(): Window { - const doc = this._getDocument(); - return doc.defaultView || window; + return this._document.defaultView || window; } private _getFocusOrigin(focusEventTarget: HTMLElement | null): FocusOrigin { diff --git a/src/cdk/dialog/dialog-container.ts b/src/cdk/dialog/dialog-container.ts index 038a113a028c..67a21c365cd6 100644 --- a/src/cdk/dialog/dialog-container.ts +++ b/src/cdk/dialog/dialog-container.ts @@ -82,7 +82,7 @@ export class CdkDialogContainer private _renderer = inject(Renderer2); private _platform = inject(Platform); - protected _document = inject(DOCUMENT, {optional: true})!; + protected _document = inject(DOCUMENT); /** The portal outlet inside of this container into which the dialog content will be loaded. */ @ViewChild(CdkPortalOutlet, {static: true}) _portalOutlet: CdkPortalOutlet; diff --git a/src/cdk/scrolling/viewport-ruler.ts b/src/cdk/scrolling/viewport-ruler.ts index bd477c3ede4b..66ea77dd9f79 100644 --- a/src/cdk/scrolling/viewport-ruler.ts +++ b/src/cdk/scrolling/viewport-ruler.ts @@ -36,7 +36,7 @@ export class ViewportRuler implements OnDestroy { private readonly _change = new Subject(); /** Used to reference correct document/window */ - protected _document = inject(DOCUMENT, {optional: true})!; + protected _document = inject(DOCUMENT); constructor(...args: unknown[]); diff --git a/src/cdk/text-field/autosize.ts b/src/cdk/text-field/autosize.ts index 5abb0be0f97a..f7dd3cc9f267 100644 --- a/src/cdk/text-field/autosize.ts +++ b/src/cdk/text-field/autosize.ts @@ -122,7 +122,7 @@ export class CdkTextareaAutosize implements AfterViewInit, DoCheck, OnDestroy { private _cachedScrollTop: number; /** Used to reference correct document/window */ - protected _document? = inject(DOCUMENT, {optional: true}); + protected _document = inject(DOCUMENT); private _hasFocus: boolean; diff --git a/src/cdk/tree/tree.md b/src/cdk/tree/tree.md index 30de3315ce20..9f8e0c7163f8 100644 --- a/src/cdk/tree/tree.md +++ b/src/cdk/tree/tree.md @@ -55,11 +55,11 @@ In order to use the tree, you must define a tree node template. There are two ty template defines the look of the tree node, expansion/collapsing control and the structure for nested children nodes. -A node definition is specified via any element with `cdkNodeDef`. This directive exports the node +A node definition is specified via any element with `cdkTreeNodeDef`. This directive exports the node data to be used in any bindings in the node template. ```html - + {{node.key}}: {{node.value}} ``` @@ -80,7 +80,7 @@ When using nested tree nodes, the node template must contain a `cdkTreeNodeOutle where the children of the node will be rendered. ```html - + {{node.value}} @@ -96,7 +96,7 @@ a tree node recursively by setting `[cdkTreeNodeToggleRecursive]` to true. activation. For icon buttons, ensure that `aria-label` is provided. ```html - + @@ -110,7 +110,7 @@ The `cdkTreeNodePadding` directive can be placed in a flat tree's node template information of a flat tree node. ```html - + {{node.value}} ``` @@ -125,10 +125,10 @@ The tree may include multiple node templates, where a template is chosen for a particular data node via the `when` predicate of the template. ```html - + {{node.value}} - + [ A special node {{node.value}} ] ``` @@ -200,11 +200,11 @@ interaction. ```html ``` In this example, `$event` contains the node's data and is equivalent to the implicit data passed in -the `cdkNodeDef` context. +the `cdkTreeNodeDef` context. diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index 497ba28d4b86..e6e485e733f5 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -1400,6 +1400,7 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI .changed.pipe( map(() => this.isExpanded), distinctUntilChanged(), + takeUntil(this._destroyed), ) .subscribe(() => this._changeDetectorRef.markForCheck()); this._tree._setNodeTypeIfUnset(this._type); diff --git a/src/google-maps/schematics/ng-update/index.ts b/src/google-maps/schematics/ng-update/index.ts index e195902f23b5..ca337a1bd02b 100644 --- a/src/google-maps/schematics/ng-update/index.ts +++ b/src/google-maps/schematics/ng-update/index.ts @@ -6,5 +6,9 @@ * found in the LICENSE file at https://angular.dev/license */ +import {Rule} from '@angular-devkit/schematics'; + /** Entry point for the migration schematics with target of Angular Material v20 */ -export function updateToV20(): void {} +export function updateToV20(): Rule { + return () => {}; +} diff --git a/src/material/schematics/ng-generate/theme-color/schema.json b/src/material/schematics/ng-generate/theme-color/schema.json index 6adc7d434fd6..b8e36dc8ba4e 100644 --- a/src/material/schematics/ng-generate/theme-color/schema.json +++ b/src/material/schematics/ng-generate/theme-color/schema.json @@ -49,7 +49,7 @@ "type": "boolean", "default": true, "description": "Whether to generate output file in scss or CSS", - "x-prompt": "Do you want to generated file to be a scss file? This is the recommended way of setting up theming in your application. If not, a CSS file will be generated with all the system variables defined. (Leave blank to generate a scss file)" + "x-prompt": "Do you want the generated file to be a scss file? This is the recommended way of setting up theming in your application. If not, a CSS file will be generated with all the system variables defined. (Leave blank to generate a scss file)" } } } diff --git a/src/material/sidenav/drawer.ts b/src/material/sidenav/drawer.ts index 7b0bfe914e34..f3b288ef7f9c 100644 --- a/src/material/sidenav/drawer.ts +++ b/src/material/sidenav/drawer.ts @@ -182,7 +182,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy { private _ngZone = inject(NgZone); private _renderer = inject(Renderer2); private readonly _interactivityChecker = inject(InteractivityChecker); - private _doc = inject(DOCUMENT, {optional: true})!; + private _doc = inject(DOCUMENT); _container? = inject(MAT_DRAWER_CONTAINER, {optional: true}); private _focusTrap: FocusTrap | null = null; @@ -347,9 +347,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy { constructor() { this.openedChange.pipe(takeUntil(this._destroyed)).subscribe((opened: boolean) => { if (opened) { - if (this._doc) { - this._elementFocusedBeforeDrawerWasOpened = this._doc.activeElement as HTMLElement; - } + this._elementFocusedBeforeDrawerWasOpened = this._doc.activeElement as HTMLElement; this._takeFocus(); } else if (this._isFocusWithinDrawer()) { this._restoreFocus(this._openedVia || 'program'); diff --git a/src/material/tree/tree.md b/src/material/tree/tree.md index a9eba1238d0c..e2411fa4cda0 100644 --- a/src/material/tree/tree.md +++ b/src/material/tree/tree.md @@ -55,11 +55,11 @@ In order to use the tree, you must define a tree node template. There are two ty template defines the look of the tree node, expansion/collapsing control and the structure for nested children nodes. -A node definition is specified via any element with `matNodeDef`. This directive exports the node +A node definition is specified via any element with `matTreeNodeDef`. This directive exports the node data to be used in any bindings in the node template. ```html - + {{node.key}}: {{node.value}} ``` @@ -80,7 +80,7 @@ When using nested tree nodes, the node template must contain a `matTreeNodeOutle where the children of the node will be rendered. ```html - + {{node.value}} @@ -96,7 +96,7 @@ a tree node recursively by setting `[matTreeNodeToggleRecursive]` to true. activation. For icon buttons, ensure that `aria-label` is provided. ```html - + @@ -119,7 +119,7 @@ The `matTreeNodePadding` can be placed in a flat tree's node template to display information of a flat tree node. ```html - + {{node.value}} ``` @@ -134,10 +134,10 @@ The tree may include multiple node templates, where a template is chosen for a particular data node via the `when` predicate of the template. ```html - + {{node.value}} - + [ A special node {{node.value}} ] ``` @@ -209,11 +209,11 @@ interaction. ```html ``` In this example, `$event` contains the node's data and is equivalent to the implicit data passed in -the `matNodeDef` context. +the `matTreeNodeDef` context.