From 35ce20f67a4243db87fc5347f7a783549d98d03e Mon Sep 17 00:00:00 2001 From: Jens Date: Mon, 8 Sep 2025 19:15:56 +0200 Subject: [PATCH 1/5] docs: insert canonical link dynamically (#31830) * docs: delete static canonical link * docs: insert, not update dynamic canonical link * docs: always append tag when inserted --- docs/src/app/shared/header-tag-manager.ts | 15 ++++++++++++--- docs/src/index.html | 1 - 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/src/app/shared/header-tag-manager.ts b/docs/src/app/shared/header-tag-manager.ts index 3dc54fed7a37..7dec9c53c12e 100644 --- a/docs/src/app/shared/header-tag-manager.ts +++ b/docs/src/app/shared/header-tag-manager.ts @@ -19,8 +19,8 @@ export class HeaderTagManager { private readonly _document = inject(DOCUMENT); /** - * Sets the canonical link in the header. - * It supposes the header link is already present in the index.html + * Sets the canonical link in the header. If the link already exists, + * it will be updated. Otherwise, a new link will be created and inserted. * * The function behave invariably and will always point to angular.dev, * no matter if it's a specific version build @@ -28,7 +28,16 @@ export class HeaderTagManager { setCanonical(absolutePath: string): void { const pathWithoutFragment = this._normalizePath(absolutePath).split('#')[0]; const fullPath = `${MAT_ANGULAR_DEV}/${pathWithoutFragment}`; - this._document.querySelector('link[rel=canonical]')?.setAttribute('href', fullPath); + let canonicalLink = this._document.querySelector('link[rel=canonical]'); + + if (canonicalLink) { + canonicalLink.setAttribute('href', fullPath); + } else { + canonicalLink = this._document.createElement('link'); + canonicalLink.setAttribute('rel', 'canonical'); + canonicalLink.setAttribute('href', fullPath); + this._document.head.appendChild(canonicalLink); + } } private _normalizePath(path: string): string { diff --git a/docs/src/index.html b/docs/src/index.html index e4a8a63a93b4..853660012186 100644 --- a/docs/src/index.html +++ b/docs/src/index.html @@ -3,7 +3,6 @@ Angular Material UI Component Library - From 755bfa830ac9b56a26a641ae12b54518d8436212 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 8 Sep 2025 18:19:16 +0200 Subject: [PATCH 2/5] fix(cdk/drag-drop): allow axis lock to be reset (#31829) Fixes that users weren't able to reset the `lockAxis` values. Fixes #31825. --- goldens/cdk/drag-drop/index.api.md | 10 +++++----- src/cdk/drag-drop/directives/config.ts | 2 +- src/cdk/drag-drop/directives/drag.ts | 7 ++----- src/cdk/drag-drop/directives/drop-list.ts | 7 ++----- src/cdk/drag-drop/drag-ref.ts | 2 +- src/cdk/drag-drop/drop-list-ref.ts | 2 +- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/goldens/cdk/drag-drop/index.api.md b/goldens/cdk/drag-drop/index.api.md index 2cc3e5d36cab..63b89f3124e8 100644 --- a/goldens/cdk/drag-drop/index.api.md +++ b/goldens/cdk/drag-drop/index.api.md @@ -66,7 +66,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { getFreeDragPosition(): Readonly; getPlaceholderElement(): HTMLElement; getRootElement(): HTMLElement; - lockAxis: DragAxis; + lockAxis: DragAxis | null; readonly moved: Observable>; // (undocumented) static ngAcceptInputType_disabled: unknown; @@ -259,7 +259,7 @@ export class CdkDropList implements OnDestroy { getSortedItems(): CdkDrag[]; hasAnchor: boolean; id: string; - lockAxis: DragAxis; + lockAxis: DragAxis | null; // (undocumented) static ngAcceptInputType_autoScrollDisabled: unknown; // (undocumented) @@ -330,7 +330,7 @@ export interface DragDropConfig extends Partial { // (undocumented) listOrientation?: DropListOrientation; // (undocumented) - lockAxis?: DragAxis; + lockAxis?: DragAxis | null; // (undocumented) previewClass?: string | string[]; // (undocumented) @@ -423,7 +423,7 @@ export class DragRef { getRootElement(): HTMLElement; getVisibleElement(): HTMLElement; isDragging(): boolean; - lockAxis: 'x' | 'y'; + lockAxis: 'x' | 'y' | null; readonly moved: Observable<{ source: DragRef; pointerPosition: { @@ -523,7 +523,7 @@ export class DropListRef { isDragging(): boolean; _isOverContainer(x: number, y: number): boolean; isReceiving(): boolean; - lockAxis: 'x' | 'y'; + lockAxis: 'x' | 'y' | null; readonly receivingStarted: Subject<{ receiver: DropListRef; initiator: DropListRef; diff --git a/src/cdk/drag-drop/directives/config.ts b/src/cdk/drag-drop/directives/config.ts index 2263935d85c0..72794f8277b3 100644 --- a/src/cdk/drag-drop/directives/config.ts +++ b/src/cdk/drag-drop/directives/config.ts @@ -29,7 +29,7 @@ export const CDK_DRAG_CONFIG = new InjectionToken('CDK_DRAG_CONF * items and drop lists within a module or a component. */ export interface DragDropConfig extends Partial { - lockAxis?: DragAxis; + lockAxis?: DragAxis | null; dragStartDelay?: DragStartDelay; constrainPosition?: DragConstrainPosition; previewClass?: string | string[]; diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts index 9e8c738a3b60..436b9935943f 100644 --- a/src/cdk/drag-drop/directives/drag.ts +++ b/src/cdk/drag-drop/directives/drag.ts @@ -91,7 +91,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { @Input('cdkDragData') data: T; /** Locks the position of the dragged element along the specified axis. */ - @Input('cdkDragLockAxis') lockAxis: DragAxis; + @Input('cdkDragLockAxis') lockAxis: DragAxis | null = null; /** * Selector that will be used to determine the root draggable element, starting from @@ -560,10 +560,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { this.disabled = draggingDisabled == null ? false : draggingDisabled; this.dragStartDelay = dragStartDelay || 0; - - if (lockAxis) { - this.lockAxis = lockAxis; - } + this.lockAxis = lockAxis || null; if (constrainPosition) { this.constrainPosition = constrainPosition; diff --git a/src/cdk/drag-drop/directives/drop-list.ts b/src/cdk/drag-drop/directives/drop-list.ts index bab8a65c91d1..7049d55409d0 100644 --- a/src/cdk/drag-drop/directives/drop-list.ts +++ b/src/cdk/drag-drop/directives/drop-list.ts @@ -95,7 +95,7 @@ export class CdkDropList implements OnDestroy { @Input() id: string = inject(_IdGenerator).getId('cdk-drop-list-'); /** Locks the position of the draggable elements inside the container along the specified axis. */ - @Input('cdkDropListLockAxis') lockAxis: DragAxis; + @Input('cdkDropListLockAxis') lockAxis: DragAxis | null = null; /** Whether starting a dragging sequence from this container is disabled. */ @Input({alias: 'cdkDropListDisabled', transform: booleanAttribute}) @@ -425,10 +425,7 @@ export class CdkDropList implements OnDestroy { this.sortingDisabled = sortingDisabled == null ? false : sortingDisabled; this.autoScrollDisabled = listAutoScrollDisabled == null ? false : listAutoScrollDisabled; this.orientation = listOrientation || 'vertical'; - - if (lockAxis) { - this.lockAxis = lockAxis; - } + this.lockAxis = lockAxis || null; } /** Syncs up the registered drag items with underlying drop list ref. */ diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index 7ec72ab499b7..63c83d0881af 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -293,7 +293,7 @@ export class DragRef { private _cachedShadowRoot: ShadowRoot | null | undefined; /** Axis along which dragging is locked. */ - lockAxis: 'x' | 'y'; + lockAxis: 'x' | 'y' | null = null; /** * Amount of milliseconds to wait after the user has put their diff --git a/src/cdk/drag-drop/drop-list-ref.ts b/src/cdk/drag-drop/drop-list-ref.ts index f62ff93616ec..2e2fd8436676 100644 --- a/src/cdk/drag-drop/drop-list-ref.ts +++ b/src/cdk/drag-drop/drop-list-ref.ts @@ -63,7 +63,7 @@ export class DropListRef { sortingDisabled: boolean = false; /** Locks the position of the draggable elements inside the container along the specified axis. */ - lockAxis: 'x' | 'y'; + lockAxis: 'x' | 'y' | null = null; /** * Whether auto-scrolling the view when the user From e89a71ba2f6298252f3d26c7c0ffabb09344b2dc Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 8 Sep 2025 18:05:54 +0200 Subject: [PATCH 3/5] fix(material/slider): incorrect indicator transform origin in M3 (#31834) Fixes that the slider's value indicator was transitioning in from the right in M3. It's because the indicator is rotated in order to render out the M3 shape. Fixes #31827. --- src/material/slider/_m2-slider.scss | 1 + src/material/slider/_m3-slider.scss | 7 +++++-- src/material/slider/slider.scss | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/material/slider/_m2-slider.scss b/src/material/slider/_m2-slider.scss index bdb16a22ed1b..c378cec27be8 100644 --- a/src/material/slider/_m2-slider.scss +++ b/src/material/slider/_m2-slider.scss @@ -28,6 +28,7 @@ slider-with-tick-marks-container-shape: 50%, slider-with-tick-marks-container-size: 2px, slider-with-tick-marks-inactive-container-opacity: 0.6, + slider-value-indicator-transform-origin: bottom, ), color: map.merge(private-get-color-palette-color-tokens($theme, primary), ( slider-disabled-active-track-color: map.get($system, on-surface), diff --git a/src/material/slider/_m3-slider.scss b/src/material/slider/_m3-slider.scss index 5bb89b21e8d7..b6b882310a98 100644 --- a/src/material/slider/_m3-slider.scss +++ b/src/material/slider/_m3-slider.scss @@ -7,6 +7,8 @@ /// @param {String} $color-variant The color variant to use for the component @function get-tokens($theme: m3.$sys-theme, $color-variant: null) { $system: m3-utils.get-system($theme); + $indicator-size: 28px; + @if $color-variant { $system: m3-utils.replace-colors-with-variant($system, primary, $color-variant); } @@ -15,8 +17,8 @@ base: ( slider-value-indicator-opacity: 1, slider-value-indicator-padding: 0, - slider-value-indicator-width: 28px, - slider-value-indicator-height: 28px, + slider-value-indicator-width: $indicator-size, + slider-value-indicator-height: $indicator-size, slider-value-indicator-caret-display: none, slider-value-indicator-border-radius: 50% 50% 50% 0, slider-value-indicator-text-transform: rotate(45deg), @@ -29,6 +31,7 @@ slider-with-tick-marks-active-container-opacity: 0.38, slider-with-tick-marks-container-size: 2px, slider-with-tick-marks-inactive-container-opacity: 0.38, + slider-value-indicator-transform-origin: 0 $indicator-size, ), color: ( slider-active-track-color: map.get($system, primary), diff --git a/src/material/slider/slider.scss b/src/material/slider/slider.scss index 6f2888d041e6..952f722af0c2 100644 --- a/src/material/slider/slider.scss +++ b/src/material/slider/slider.scss @@ -104,7 +104,7 @@ $fallbacks: m3-slider.get-tokens(); display: flex; align-items: center; transform: scale(0); - transform-origin: bottom; + transform-origin: token-utils.slot(slider-value-indicator-transform-origin, $fallbacks); transition: transform 100ms cubic-bezier(0.4, 0, 1, 1); // Stop parent word-break from altering From e1fc8b36c5d864015932dcc87b487f14db18eeb4 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 4 Sep 2025 14:26:20 +0200 Subject: [PATCH 4/5] fix(material/progress-bar): avoid CSP issues due to buffer dots (#31818) Currently the way we render the buffer dots in the progress bar is by using a `data:` URL with an inline SVG. The problem with this approach is that it can trigger CSP errors and require users to allow all `data:` URLs, even if they don't use the buffer mode. These changes work around the issue by rendering the dots using a radial gradient instead. Fixes #31808. --- src/material/progress-bar/progress-bar.scss | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/material/progress-bar/progress-bar.scss b/src/material/progress-bar/progress-bar.scss index 235cb73664da..d5bf593c296f 100644 --- a/src/material/progress-bar/progress-bar.scss +++ b/src/material/progress-bar/progress-bar.scss @@ -1,7 +1,6 @@ @use '@angular/cdk'; @use './m3-progress-bar'; @use '../core/tokens/token-utils'; -@use '../core/style/vendor-prefixes'; $fallbacks: m3-progress-bar.get-tokens(); @@ -104,18 +103,18 @@ $fallbacks: m3-progress-bar.get-tokens(); } .mdc-linear-progress__buffer-dots { - $mask: "data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' " + - "xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' " + - "enable-background='new 0 0 5 2' xml:space='preserve' viewBox='0 0 5 2' " + - "preserveAspectRatio='xMinYMin slice'%3E%3Ccircle cx='1' cy='1' r='1'/%3E%3C/svg%3E"; - - @include vendor-prefixes.mask-image(url($mask)); + $circle-color: token-utils.slot(progress-bar-track-color, $fallbacks); + $circle-size: calc(#{token-utils.slot(progress-bar-track-height, $fallbacks)} / 2); + background-image: radial-gradient(circle, #{$circle-color} #{$circle-size}, transparent 0); background-repeat: repeat-x; + background-size: calc(#{$circle-size} * 5); + // The `background-position` prevents the animation from jumping around when the progress + // changes. Note that we shouldn't invert it in RTL, because the animation direction is reversed. + background-position: left; flex: auto; transform: rotate(180deg); animation: mdc-linear-progress-buffering calc(250ms * var(--mat-progress-bar-animation-multiplier)) infinite linear; - background-color: token-utils.slot(progress-bar-track-color, $fallbacks); @include cdk.high-contrast { background-color: ButtonBorder; From d992aa6ea0f8f466e83754e4264efedd3eb1a0f3 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 3 Sep 2025 08:52:20 +0200 Subject: [PATCH 5/5] build: generate more believable token examples (#31807) Currently we generate the token examples by taking the first two tokens and using a static value for them. This looks weird when the first tokens aren't color-based. These changes try to infer a more believable value from the token's name. Fixes #31796. --- tools/extract-tokens/extract-tokens.mts | 32 +++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/tools/extract-tokens/extract-tokens.mts b/tools/extract-tokens/extract-tokens.mts index 3ea864e46e04..d0b2e3b82cbc 100644 --- a/tools/extract-tokens/extract-tokens.mts +++ b/tools/extract-tokens/extract-tokens.mts @@ -11,7 +11,7 @@ interface ExtractedToken { /** Full prefix of the token. */ prefix: string; /** Type of the token (color, typography etc.) */ - type: string; + type: 'color' | 'typography' | 'density' | 'base'; /** Value of the token. */ value: string | number; /** Name under which the token can be referred to inside the `overrides` mixin. */ @@ -25,7 +25,7 @@ interface Token { /** Full prefix of the token. */ prefix: string; /** Type of the token (color, typography etc.) */ - type: string; + type: 'color' | 'typography' | 'density' | 'base'; /** Name under which the token can be referred to inside the `overrides` mixin. */ overridesName: string; /** Name of the system-level token that this token was derived from. */ @@ -172,8 +172,8 @@ function getUsageExample(themes: ThemeData[]): string | null { `// Customize the entire app. Change :root to your selector if you want to scope the styles.`, `:root {`, ` @include mat.${mixin.overridesMixin}((`, - ` ${firstToken.overridesName}: orange,`, - ...(secondToken ? [` ${secondToken.overridesName}: red,`] : []), + ` ${getTokenExample(firstToken, 'first')},`, + ...(secondToken ? [` ${getTokenExample(secondToken, 'second')},`] : []), ` ));`, `}`, ]; @@ -350,6 +350,30 @@ function jsonStringifyImplementation( `; } +/** Generates an example string for a token. */ +function getTokenExample(token: Token, position: 'first' | 'second'): string { + const name = token.overridesName; + let value: string; + + // Attempt to come up with a real-looking example based on the token's shape. + if (name.includes('shape')) { + value = position === 'first' ? '12px' : '16px'; + } else if (name.includes('opacity')) { + value = position === 'first' ? '0.8' : '0.2'; + } else if (name.includes('size')) { + value = position === 'first' ? '16px' : '10px'; + } else if (name.includes('shadow')) { + value = + position === 'first' + ? '10px 10px 5px 0px rgba(0, 0, 0, 0.75)' + : '-4px -4px 5px 0px rgba(0, 0, 0, 0.5)'; + } else { + value = position === 'first' ? 'orange' : 'red'; + } + + return `${name}: ${value}`; +} + /** * Gets the substring between two strings. * @param text String from which to extract the substring.