diff --git a/.stylelintrc.json b/.stylelintrc.json index 7d82e06d8a30..a854a9c4f7a9 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -81,7 +81,7 @@ "unit-case": "lower", "unit-no-unknown": true, - "unit-allowed-list": ["px", "%", "deg", "s", "ms", "em", "vh", "vw", "vmin"], + "unit-allowed-list": ["px", "%", "deg", "s", "ms", "em", "rem", "vh", "vw", "vmin"], "value-list-comma-space-after": "always-single-line", "value-list-comma-space-before": "never", diff --git a/CHANGELOG.md b/CHANGELOG.md index a443a430c8cd..b864f6d8cbe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ + +# 18.1.0-rc.0 "zirconium-zoo" (2024-07-03) +### material +| Commit | Type | Description | +| -- | -- | -- | +| [674538b77](https://github.com/angular/components/commit/674538b778e75d229ada06d19c362752db3a18cc) | fix | **core:** add fallback if ripples get stuck ([#29323](https://github.com/angular/components/pull/29323)) | + + + + +# 18.0.6 "gallium-grape" (2024-07-03) +### material +| Commit | Type | Description | +| -- | -- | -- | +| [e5c5f151c](https://github.com/angular/components/commit/e5c5f151cc3a5293f629bfa84bcddb0b391cf268) | fix | **core:** add fallback if ripples get stuck ([#29323](https://github.com/angular/components/pull/29323)) | + + + # 18.1.0-next.4 "plastic-pliers" (2024-06-26) ### cdk diff --git a/package.json b/package.json index 4d0955ceaaad..f3470978485c 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,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": "18.1.0-next.4", + "version": "18.2.0-next.0", "dependencies": { "@angular/animations": "^18.1.0-next.3", "@angular/common": "^18.1.0-next.3", diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts index 8d0f5c120113..f4469556e027 100644 --- a/src/cdk/drag-drop/directives/drag.ts +++ b/src/cdk/drag-drop/directives/drag.ts @@ -30,6 +30,7 @@ import { AfterViewInit, inject, Injector, + numberAttribute, } from '@angular/core'; import {coerceElement, coerceNumberProperty} from '@angular/cdk/coercion'; import {BehaviorSubject, Observable, Observer, Subject, merge} from 'rxjs'; @@ -159,6 +160,13 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { */ @Input('cdkDragPreviewContainer') previewContainer: PreviewContainer; + /** + * If the parent of the dragged element has a `scale` transform, it can throw off the + * positioning when the user starts dragging. Use this input to notify the CDK of the scale. + */ + @Input({alias: 'cdkDragScale', transform: numberAttribute}) + scale: number = 1; + /** Emits when the user starts dragging the item. */ @Output('cdkDragStarted') readonly started: EventEmitter = new EventEmitter(); @@ -261,6 +269,11 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { if (dropContainer) { this._dragRef._withDropContainer(dropContainer._dropListRef); dropContainer.addItem(this); + + // The drop container reads this so we need to sync it here. + dropContainer._dropListRef.beforeStarted.pipe(takeUntil(this._destroyed)).subscribe(() => { + this._dragRef.scale = this.scale; + }); } this._syncInputs(this._dragRef); @@ -448,6 +461,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { ref.disabled = this.disabled; ref.lockAxis = this.lockAxis; + ref.scale = this.scale; ref.dragStartDelay = typeof dragStartDelay === 'object' && dragStartDelay ? dragStartDelay diff --git a/src/cdk/drag-drop/directives/drop-list-shared.spec.ts b/src/cdk/drag-drop/directives/drop-list-shared.spec.ts index 4e9a6bf2b32b..fef579b3891a 100644 --- a/src/cdk/drag-drop/directives/drop-list-shared.spec.ts +++ b/src/cdk/drag-drop/directives/drop-list-shared.spec.ts @@ -5006,6 +5006,7 @@ const DROP_ZONE_FIXTURE_TEMPLATE = ` [cdkDragBoundary]="boundarySelector" [cdkDragPreviewClass]="previewClass" [cdkDragPreviewContainer]="previewContainer" + [cdkDragScale]="scale" [style.height.px]="item.height" [style.margin-bottom.px]="item.margin" (cdkDragStarted)="startedSpy($event)" @@ -5041,6 +5042,7 @@ export class DraggableInDropZone implements AfterViewInit { previewContainer: PreviewContainer = 'global'; dropDisabled = signal(false); dropLockAxis = signal(undefined); + scale = 1; constructor(protected _elementRef: ElementRef) {} diff --git a/src/cdk/drag-drop/directives/single-axis-drop-list.spec.ts b/src/cdk/drag-drop/directives/single-axis-drop-list.spec.ts index 324ccc8b8776..28e8938b6c32 100644 --- a/src/cdk/drag-drop/directives/single-axis-drop-list.spec.ts +++ b/src/cdk/drag-drop/directives/single-axis-drop-list.spec.ts @@ -311,4 +311,29 @@ describe('Single-axis drop list', () => { dispatchMouseEvent(document, 'mouseup'); })); + + it('should lay out the elements correctly when scaled', fakeAsync(() => { + const fixture = createComponent(DraggableInDropZone); + fixture.componentInstance.scale = 0.5; + fixture.detectChanges(); + + const items = fixture.componentInstance.dragItems.map(i => i.element.nativeElement); + const {top, left} = items[0].getBoundingClientRect(); + + startDraggingViaMouse(fixture, items[0], left, top); + + const placeholder = document.querySelector('.cdk-drag-placeholder')! as HTMLElement; + const target = items[1]; + const targetRect = target.getBoundingClientRect(); + + dispatchMouseEvent(document, 'mousemove', targetRect.left, targetRect.top + 5); + fixture.detectChanges(); + + expect(placeholder.style.transform).toBe(`translate3d(0px, ${ITEM_HEIGHT * 2}px, 0px)`); + expect(target.style.transform).toBe(`translate3d(0px, ${-ITEM_HEIGHT * 2}px, 0px)`); + + dispatchMouseEvent(document, 'mouseup'); + fixture.detectChanges(); + flush(); + })); }); diff --git a/src/cdk/drag-drop/directives/standalone-drag.spec.ts b/src/cdk/drag-drop/directives/standalone-drag.spec.ts index 4a7e14a8d9a8..33f0588260cf 100644 --- a/src/cdk/drag-drop/directives/standalone-drag.spec.ts +++ b/src/cdk/drag-drop/directives/standalone-drag.spec.ts @@ -1470,34 +1470,41 @@ describe('Standalone CdkDrag', () => { cleanup(); })); - it( - 'should update the free drag position if the user moves their pointer after the page ' + - 'is scrolled', - fakeAsync(() => { - const fixture = createComponent(StandaloneDraggable); - fixture.detectChanges(); + it('should update the free drag position if the user moves their pointer after the page is scrolled', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.detectChanges(); - const cleanup = makeScrollable(); - const dragElement = fixture.componentInstance.dragElement.nativeElement; + const cleanup = makeScrollable(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; - expect(dragElement.style.transform).toBeFalsy(); - startDraggingViaMouse(fixture, dragElement, 0, 0); - dispatchMouseEvent(document, 'mousemove', 50, 100); - fixture.detectChanges(); + expect(dragElement.style.transform).toBeFalsy(); + startDraggingViaMouse(fixture, dragElement, 0, 0); + dispatchMouseEvent(document, 'mousemove', 50, 100); + fixture.detectChanges(); - expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); + expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); - scrollTo(0, 500); - dispatchFakeEvent(document, 'scroll'); - fixture.detectChanges(); - dispatchMouseEvent(document, 'mousemove', 50, 200); - fixture.detectChanges(); + scrollTo(0, 500); + dispatchFakeEvent(document, 'scroll'); + fixture.detectChanges(); + dispatchMouseEvent(document, 'mousemove', 50, 200); + fixture.detectChanges(); - expect(dragElement.style.transform).toBe('translate3d(50px, 700px, 0px)'); + expect(dragElement.style.transform).toBe('translate3d(50px, 700px, 0px)'); - cleanup(); - }), - ); + cleanup(); + })); + + it('should account for scale when moving the element', fakeAsync(() => { + const fixture = createComponent(StandaloneDraggable); + fixture.componentInstance.scale = 0.5; + fixture.detectChanges(); + const dragElement = fixture.componentInstance.dragElement.nativeElement; + + expect(dragElement.style.transform).toBeFalsy(); + dragElementViaMouse(fixture, dragElement, 50, 100); + expect(dragElement.style.transform).toBe('translate3d(100px, 200px, 0px)'); + })); describe('with a handle', () => { it('should not be able to drag the entire element if it has a handle', fakeAsync(() => { @@ -1718,6 +1725,7 @@ describe('Standalone CdkDrag', () => { [cdkDragFreeDragPosition]="freeDragPosition" [cdkDragDisabled]="dragDisabled()" [cdkDragLockAxis]="dragLockAxis()" + [cdkDragScale]="scale" (cdkDragStarted)="startedSpy($event)" (cdkDragReleased)="releasedSpy($event)" (cdkDragEnded)="endedSpy($event)" @@ -1745,6 +1753,7 @@ class StandaloneDraggable { freeDragPosition?: {x: number; y: number}; dragDisabled = signal(false); dragLockAxis = signal(undefined); + scale = 1; } @Component({ diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index 2b751e373171..a8ed7bb55f5f 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -288,6 +288,12 @@ export class DragRef { /** Class to be added to the preview element. */ previewClass: string | string[] | undefined; + /** + * If the parent of the dragged element has a `scale` transform, it can throw off the + * positioning when the user starts dragging. Use this input to notify the CDK of the scale. + */ + scale: number = 1; + /** Whether starting to drag this element is disabled. */ get disabled(): boolean { return this._disabled || !!(this._dropContainer && this._dropContainer.disabled); @@ -1288,7 +1294,8 @@ export class DragRef { * @param y New transform value along the Y axis. */ private _applyRootElementTransform(x: number, y: number) { - const transform = getTransform(x, y); + const scale = 1 / this.scale; + const transform = getTransform(x * scale, y * scale); const styles = this._rootElement.style; // Cache the previous transform amount only after the first drag sequence, because diff --git a/src/cdk/drag-drop/resets.scss b/src/cdk/drag-drop/resets.scss index f218c05e51cc..530f97dbe5cc 100644 --- a/src/cdk/drag-drop/resets.scss +++ b/src/cdk/drag-drop/resets.scss @@ -6,3 +6,10 @@ color: inherit; } } + +// These elements get `pointer-events: none` when they're created, but any descendants might +// override it back to `auto`. Reset them here since they can affect the pointer position detection. +.cdk-drag-placeholder *, +.cdk-drag-preview * { + pointer-events: none !important; +} diff --git a/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts b/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts index 28ed95c6517b..9d3ad3a99702 100644 --- a/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts +++ b/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts @@ -128,6 +128,8 @@ export class SingleAxisSortStrategy implements DropListSortStrategy { // Update the offset to reflect the new position. sibling.offset += offset; + const transformAmount = Math.round(sibling.offset * (1 / sibling.drag.scale)); + // Since we're moving the items with a `transform`, we need to adjust their cached // client rects to reflect their new position, as well as swap their positions in the cache. // Note that we shouldn't use `getBoundingClientRect` here to update the cache, because the @@ -136,13 +138,13 @@ export class SingleAxisSortStrategy implements DropListSortStrategy { // Round the transforms since some browsers will // blur the elements, for sub-pixel transforms. elementToOffset.style.transform = combineTransforms( - `translate3d(${Math.round(sibling.offset)}px, 0, 0)`, + `translate3d(${transformAmount}px, 0, 0)`, sibling.initialTransform, ); adjustDomRect(sibling.clientRect, 0, offset); } else { elementToOffset.style.transform = combineTransforms( - `translate3d(0, ${Math.round(sibling.offset)}px, 0)`, + `translate3d(0, ${transformAmount}px, 0)`, sibling.initialTransform, ); adjustDomRect(sibling.clientRect, offset, 0); diff --git a/src/cdk/listbox/listbox.md b/src/cdk/listbox/listbox.md index 542428177713..021b24a905aa 100644 --- a/src/cdk/listbox/listbox.md +++ b/src/cdk/listbox/listbox.md @@ -120,23 +120,6 @@ The CDK Listbox supports both template driven forms and reactive forms. "region": "listbox" }) --> -#### Forms validation - -The CDK listbox integrates with Angular's form validation API and has the following built-in -validation errors: - -- `cdkListboxUnexpectedOptionValues` - Raised when the bound value contains values that do not - appear as option value in the listbox. The validation error contains a `values` property that - lists the invalid values -- `cdkListboxUnexpectedMultipleValues` - Raised when a single-selection listbox is bound to a value - containing multiple selected options. - - - ### Disabling options You can disable options for selection by setting `cdkOptionDisabled`. diff --git a/src/components-examples/cdk/listbox/cdk-listbox-forms-validation/cdk-listbox-forms-validation-example.html b/src/components-examples/cdk/listbox/cdk-listbox-forms-validation/cdk-listbox-forms-validation-example.html index 391e26f1a7b2..0acdf0444327 100644 --- a/src/components-examples/cdk/listbox/cdk-listbox-forms-validation/cdk-listbox-forms-validation-example.html +++ b/src/components-examples/cdk/listbox/cdk-listbox-forms-validation/cdk-listbox-forms-validation-example.html @@ -20,8 +20,5 @@ }

Your zodiac sign is: {{signCtrl.value | json}}  -   -   -  

diff --git a/src/components-examples/cdk/listbox/cdk-listbox-forms-validation/cdk-listbox-forms-validation-example.ts b/src/components-examples/cdk/listbox/cdk-listbox-forms-validation/cdk-listbox-forms-validation-example.ts index fef79e8ae553..5da22612c0d8 100644 --- a/src/components-examples/cdk/listbox/cdk-listbox-forms-validation/cdk-listbox-forms-validation-example.ts +++ b/src/components-examples/cdk/listbox/cdk-listbox-forms-validation/cdk-listbox-forms-validation-example.ts @@ -45,13 +45,7 @@ export class CdkListboxFormsValidationExample { if (this.signCtrl.hasError('required')) { errors.push('You must enter your zodiac sign'); } - if (this.signCtrl.hasError('cdkListboxUnexpectedMultipleValues')) { - errors.push('You can only select one zodiac sign'); - } - if (this.signCtrl.hasError('cdkListboxUnexpectedOptionValues')) { - const invalidOptions = this.signCtrl.getError('cdkListboxUnexpectedOptionValues').values; - errors.push(`You entered an invalid zodiac sign: ${invalidOptions[0]}`); - } + return errors.length ? errors : null; } // #enddocregion errors diff --git a/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.ts b/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.ts index 98f37a5ad0a9..6f796f2d569a 100644 --- a/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.ts +++ b/src/components-examples/material/form-field/form-field-prefix-suffix/form-field-prefix-suffix-example.ts @@ -16,7 +16,7 @@ import {MatInputModule} from '@angular/material/input'; export class FormFieldPrefixSuffixExample { hide = signal(true); clickEvent(event: MouseEvent) { - this.hide.set(!this.hide); + this.hide.set(!this.hide()); event.stopPropagation(); } } diff --git a/src/components-examples/material/table/table-http/table-http-example.css b/src/components-examples/material/table/table-http/table-http-example.css index 1ecd1a098c1d..2f3caee0c42a 100644 --- a/src/components-examples/material/table/table-http/table-http-example.css +++ b/src/components-examples/material/table/table-http/table-http-example.css @@ -35,9 +35,9 @@ table { /* Column Widths */ .mat-column-number, .mat-column-state { - max-width: 64px; + width: 64px; } .mat-column-created { - max-width: 124px; + width: 124px; } diff --git a/src/material/button/_button-base.scss b/src/material/button/_button-base.scss index cb5de5769cb2..6885abe5adf5 100644 --- a/src/material/button/_button-base.scss +++ b/src/material/button/_button-base.scss @@ -1,8 +1,5 @@ -@use '@material/touch-target' as mdc-touch-target; - @use '../core/tokens/token-utils'; @use '../core/style/layout-common'; -@use '../core/mdc-helpers/mdc-helpers'; // Adds styles necessary to provide stateful interactions with the button. This includes providing // content for the state container's ::before and ::after so that they can be given a background @@ -126,9 +123,19 @@ // the button itself would require us to wrap it in another div. See: // https://github.com/material-components/material-components-web/tree/master/packages/mdc-button#making-buttons-accessible .mat-mdc-button-touch-target { - @include mdc-touch-target.touch-target( - $set-width: $is-square, - $query: mdc-helpers.$mdc-base-styles-query); + position: absolute; + top: 50%; + height: 48px; + + @if $is-square { + left: 50%; + width: 48px; + transform: translate(-50%, -50%); + } @else { + left: 0; + right: 0; + transform: translateY(-50%); + } @include token-utils.use-tokens($prefix, $slots) { @include token-utils.create-token-slot(display, touch-target-display); diff --git a/src/material/button/_button-theme.scss b/src/material/button/_button-theme.scss index 906466a02fda..ee85325714fb 100644 --- a/src/material/button/_button-theme.scss +++ b/src/material/button/_button-theme.scss @@ -1,8 +1,3 @@ -@use '@material/button/button-text-theme' as mdc-button-text-theme; -@use '@material/button/button-filled-theme' as mdc-button-filled-theme; -@use '@material/button/button-protected-theme' as mdc-button-protected-theme; -@use '@material/button/button-outlined-theme' as mdc-button-outlined-theme; - @use '../core/theming/theming'; @use '../core/theming/inspection'; @use '../core/theming/validation'; @@ -29,7 +24,7 @@ tokens-mat-text-button.get-color-tokens($theme) ); - @include mdc-button-text-theme.theme($mdc-tokens); + @include token-utils.create-token-values(tokens-mdc-text-button.$prefix, $mdc-tokens); @include token-utils.create-token-values(tokens-mat-text-button.$prefix, $mat-tokens); } @@ -44,7 +39,7 @@ tokens-mat-filled-button.get-color-tokens($theme) ); - @include mdc-button-filled-theme.theme($mdc-tokens); + @include token-utils.create-token-values(tokens-mdc-filled-button.$prefix, $mdc-tokens); @include token-utils.create-token-values(tokens-mat-filled-button.$prefix, $mat-tokens); } @@ -59,7 +54,7 @@ tokens-mat-protected-button.get-color-tokens($theme) ); - @include mdc-button-protected-theme.theme($mdc-tokens); + @include token-utils.create-token-values(tokens-mdc-protected-button.$prefix, $mdc-tokens); @include token-utils.create-token-values(tokens-mat-protected-button.$prefix, $mat-tokens); } @@ -74,7 +69,7 @@ tokens-mat-outlined-button.get-color-tokens($theme) ); - @include mdc-button-outlined-theme.theme($mdc-tokens); + @include token-utils.create-token-values(tokens-mdc-outlined-button.$prefix, $mdc-tokens); @include token-utils.create-token-values(tokens-mat-outlined-button.$prefix, $mat-tokens); } @@ -97,10 +92,14 @@ token-utils.get-tokens-for($tokens, tokens-mat-filled-button.$prefix, $options...); $mat-outlined-button-tokens: token-utils.get-tokens-for($tokens, tokens-mat-outlined-button.$prefix, $options...); - @include mdc-button-text-theme.theme($mdc-text-button-tokens); - @include mdc-button-protected-theme.theme($mdc-protected-button-tokens); - @include mdc-button-filled-theme.theme($mdc-filled-button-tokens); - @include mdc-button-outlined-theme.theme($mdc-outlined-button-tokens); + + @include token-utils.create-token-values(tokens-mdc-text-button.$prefix, $mdc-text-button-tokens); + @include token-utils.create-token-values( + tokens-mdc-protected-button.$prefix, $mdc-protected-button-tokens); + @include token-utils.create-token-values( + tokens-mdc-filled-button.$prefix, $mdc-filled-button-tokens); + @include token-utils.create-token-values( + tokens-mdc-outlined-button.$prefix, $mdc-outlined-button-tokens); @include token-utils.create-token-values(tokens-mat-text-button.$prefix, $mat-text-button-tokens); @include token-utils.create-token-values( tokens-mat-protected-button.$prefix, $mat-protected-button-tokens); @@ -119,13 +118,13 @@ } @else { @include sass-utils.current-selector-or-root() { - @include mdc-button-text-theme.theme( + @include token-utils.create-token-values(tokens-mdc-text-button.$prefix, tokens-mdc-text-button.get-unthemable-tokens()); - @include mdc-button-filled-theme.theme( + @include token-utils.create-token-values(tokens-mdc-filled-button.$prefix, tokens-mdc-filled-button.get-unthemable-tokens()); - @include mdc-button-protected-theme.theme( + @include token-utils.create-token-values(tokens-mdc-protected-button.$prefix, tokens-mdc-protected-button.get-unthemable-tokens()); - @include mdc-button-outlined-theme.theme( + @include token-utils.create-token-values(tokens-mdc-outlined-button.$prefix, tokens-mdc-outlined-button.get-unthemable-tokens()); @include token-utils.create-token-values(tokens-mat-text-button.$prefix, @@ -223,14 +222,14 @@ } @else { @include sass-utils.current-selector-or-root() { - @include mdc-button-text-theme.theme( + @include token-utils.create-token-values(tokens-mdc-text-button.$prefix, tokens-mdc-text-button.get-typography-tokens($theme)); - @include mdc-button-filled-theme.theme( + @include token-utils.create-token-values(tokens-mdc-filled-button.$prefix, tokens-mdc-filled-button.get-typography-tokens($theme)); - @include mdc-button-outlined-theme.theme( - tokens-mdc-outlined-button.get-typography-tokens($theme)); - @include mdc-button-protected-theme.theme( + @include token-utils.create-token-values(tokens-mdc-protected-button.$prefix, tokens-mdc-protected-button.get-typography-tokens($theme)); + @include token-utils.create-token-values(tokens-mdc-outlined-button.$prefix, + tokens-mdc-outlined-button.get-typography-tokens($theme)); @include token-utils.create-token-values(tokens-mat-text-button.$prefix, tokens-mat-text-button.get-typography-tokens($theme)); @@ -252,14 +251,14 @@ } @else { @include sass-utils.current-selector-or-root() { - @include mdc-button-text-theme.theme( + @include token-utils.create-token-values(tokens-mdc-text-button.$prefix, tokens-mdc-text-button.get-density-tokens($theme)); - @include mdc-button-filled-theme.theme( + @include token-utils.create-token-values(tokens-mdc-filled-button.$prefix, tokens-mdc-filled-button.get-density-tokens($theme)); - @include mdc-button-outlined-theme.theme( - tokens-mdc-outlined-button.get-density-tokens($theme)); - @include mdc-button-protected-theme.theme( + @include token-utils.create-token-values(tokens-mdc-protected-button.$prefix, tokens-mdc-protected-button.get-density-tokens($theme)); + @include token-utils.create-token-values(tokens-mdc-outlined-button.$prefix, + tokens-mdc-outlined-button.get-density-tokens($theme)); @include token-utils.create-token-values(tokens-mat-text-button.$prefix, tokens-mat-text-button.get-density-tokens($theme)); diff --git a/src/material/button/button.scss b/src/material/button/button.scss index aed1f32c07e5..7cc65bbcd4d0 100644 --- a/src/material/button/button.scss +++ b/src/material/button/button.scss @@ -1,16 +1,6 @@ -@use 'sass:map'; -@use '@material/button/button' as mdc-button; -@use '@material/button/variables' as mdc-button-variables; -@use '@material/button/button-text-theme' as mdc-button-text-theme; -@use '@material/button/button-filled-theme' as mdc-button-filled-theme; -@use '@material/button/button-protected-theme' as mdc-button-protected-theme; -@use '@material/button/button-outlined-theme' as mdc-button-outlined-theme; -@use '@material/typography/typography' as mdc-typography; -@use '@material/theme/custom-properties' as mdc-custom-properties; - @use './button-base'; -@use '../core/mdc-helpers/mdc-helpers'; @use '../core/style/private' as style-private; +@use '../core/style/vendor-prefixes'; @use '../core/tokens/token-utils'; @use '../core/focus-indicators/private' as focus-indicators-private; @use '../core/tokens/m2/mdc/filled-button' as tokens-mdc-filled-button; @@ -22,127 +12,233 @@ @use '../core/tokens/m2/mdc/text-button' as tokens-mdc-text-button; @use '../core/tokens/m2/mat/text-button' as tokens-mat-text-button; -@include mdc-helpers.disable-mdc-fallback-declarations { - @include mdc-button.static-styles-without-ripple($query: mdc-helpers.$mdc-base-styles-query); +.mat-mdc-button-base { + text-decoration: none; } -@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) { - .mat-mdc-button { - $mdc-text-button-slots: tokens-mdc-text-button.get-token-slots(); - - @include mdc-button-text-theme.theme-styles($mdc-text-button-slots); - @include button-base.mat-private-button-horizontal-layout(tokens-mat-text-button.$prefix, - tokens-mat-text-button.get-token-slots(), true); - @include button-base.mat-private-button-ripple(tokens-mat-text-button.$prefix, - tokens-mat-text-button.get-token-slots()); - @include button-base.mat-private-button-touch-target(false, tokens-mat-text-button.$prefix, - tokens-mat-text-button.get-token-slots()); - - @include token-utils.use-tokens(tokens-mdc-text-button.$prefix, $mdc-text-button-slots) { - // We need to re-apply the disabled tokens since MDC uses - // `:disabled` which doesn't apply to anchors. - @include button-base.mat-private-button-disabled { - @include token-utils.create-token-slot(color, disabled-label-text-color); - } - } +.mdc-button { + @include vendor-prefixes.user-select(none); + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-width: 64px; + border: none; + outline: none; + line-height: inherit; + -webkit-appearance: none; + overflow: visible; + vertical-align: middle; + background: transparent; + padding: 0 8px; + + &::-moz-focus-inner { + padding: 0; + border: 0; } - .mat-mdc-unelevated-button { - $mdc-filled-button-slots: tokens-mdc-filled-button.get-token-slots(); - - @include mdc-button-filled-theme.theme-styles($mdc-filled-button-slots); - @include button-base.mat-private-button-horizontal-layout(tokens-mat-filled-button.$prefix, - tokens-mat-filled-button.get-token-slots(), false); - @include button-base.mat-private-button-ripple(tokens-mat-filled-button.$prefix, - tokens-mat-filled-button.get-token-slots()); - @include button-base.mat-private-button-touch-target(false, tokens-mat-filled-button.$prefix, - tokens-mat-filled-button.get-token-slots()); - - @include token-utils.use-tokens(tokens-mdc-filled-button.$prefix, $mdc-filled-button-slots) { - // We need to re-apply the disabled tokens since MDC uses - // `:disabled` which doesn't apply to anchors. - @include button-base.mat-private-button-disabled { - @include token-utils.create-token-slot(color, disabled-label-text-color); - @include token-utils.create-token-slot(background-color, disabled-container-color); - } + &:active { + outline: none; + } + + &:hover { + cursor: pointer; + } + + &:disabled { + cursor: default; + pointer-events: none; + } + + &[hidden] { + display: none; + } + + .mdc-button__label { + position: relative; + } +} + +.mat-mdc-button { + $mat-text-button-slots: tokens-mat-text-button.get-token-slots(); + + @include button-base.mat-private-button-horizontal-layout(tokens-mat-text-button.$prefix, + $mat-text-button-slots, true); + @include button-base.mat-private-button-ripple(tokens-mat-text-button.$prefix, + $mat-text-button-slots); + @include button-base.mat-private-button-touch-target(false, tokens-mat-text-button.$prefix, + $mat-text-button-slots); + + @include token-utils.use-tokens( + tokens-mdc-text-button.$prefix, + tokens-mdc-text-button.get-token-slots() + ) { + @include token-utils.create-token-slot(height, container-height); + @include token-utils.create-token-slot(font-family, label-text-font); + @include token-utils.create-token-slot(font-size, label-text-size); + @include token-utils.create-token-slot(letter-spacing, label-text-tracking); + @include token-utils.create-token-slot(text-transform, label-text-transform); + @include token-utils.create-token-slot(font-weight, label-text-weight); + + &, .mdc-button__ripple { + @include token-utils.create-token-slot(border-radius, container-shape); + } + + &:not(:disabled) { + @include token-utils.create-token-slot(color, label-text-color); + } + + // We need to re-apply the disabled tokens since MDC uses + // `:disabled` which doesn't apply to anchors. + @include button-base.mat-private-button-disabled { + @include token-utils.create-token-slot(color, disabled-label-text-color); } } +} - .mat-mdc-raised-button { - $mdc-button-protected-slots: tokens-mdc-protected-button.get-token-slots(); - - @include mdc-button-protected-theme.theme-styles(map.merge($mdc-button-protected-slots, ( - // Exclude the elevation tokens here since we output them manually below. - container-elevation: null, - hover-container-elevation: null, - disabled-container-elevation: null, - focus-container-elevation: null, - pressed-container-elevation: null, - container-shadow-color: null, - ))); - @include button-base.mat-private-button-horizontal-layout(tokens-mat-protected-button.$prefix, - tokens-mat-protected-button.get-token-slots(), false); - @include button-base.mat-private-button-ripple(tokens-mat-protected-button.$prefix, - tokens-mat-protected-button.get-token-slots()); - @include button-base.mat-private-button-touch-target(false, tokens-mat-protected-button.$prefix, - tokens-mat-protected-button.get-token-slots()); - - @include token-utils.use-tokens( - tokens-mdc-protected-button.$prefix, - $mdc-button-protected-slots) { - @include button-base.mat-private-button-elevation(container-elevation); - - &:hover { - @include button-base.mat-private-button-elevation(hover-container-elevation); - } +.mat-mdc-unelevated-button { + $mat-filled-button-slots: tokens-mat-filled-button.get-token-slots(); + transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); - &:focus { - @include button-base.mat-private-button-elevation(focus-container-elevation); - } + @include button-base.mat-private-button-horizontal-layout(tokens-mat-filled-button.$prefix, + $mat-filled-button-slots, false); + @include button-base.mat-private-button-ripple(tokens-mat-filled-button.$prefix, + $mat-filled-button-slots); + @include button-base.mat-private-button-touch-target(false, tokens-mat-filled-button.$prefix, + $mat-filled-button-slots); - &:active, &:focus:active { - @include button-base.mat-private-button-elevation(pressed-container-elevation); - } + @include token-utils.use-tokens( + tokens-mdc-filled-button.$prefix, + tokens-mdc-filled-button.get-token-slots() + ) { + @include token-utils.create-token-slot(height, container-height); + @include token-utils.create-token-slot(font-family, label-text-font); + @include token-utils.create-token-slot(font-size, label-text-size); + @include token-utils.create-token-slot(letter-spacing, label-text-tracking); + @include token-utils.create-token-slot(text-transform, label-text-transform); + @include token-utils.create-token-slot(font-weight, label-text-weight); + + &:not(:disabled) { + @include token-utils.create-token-slot(color, label-text-color); + @include token-utils.create-token-slot(background-color, container-color); + } - // We need to re-apply the disabled tokens since MDC uses - // `:disabled` which doesn't apply to anchors. - @include button-base.mat-private-button-disabled { - @include token-utils.create-token-slot(color, disabled-label-text-color); - @include token-utils.create-token-slot(background-color, disabled-container-color); + &, .mdc-button__ripple { + @include token-utils.create-token-slot(border-radius, container-shape); + } - &.mat-mdc-button-disabled { - @include button-base.mat-private-button-elevation(disabled-container-elevation); - } - } + // We need to re-apply the disabled tokens since MDC uses + // `:disabled` which doesn't apply to anchors. + @include button-base.mat-private-button-disabled { + @include token-utils.create-token-slot(color, disabled-label-text-color); + @include token-utils.create-token-slot(background-color, disabled-container-color); } } +} + +.mat-mdc-raised-button { + $mat-protected-button-slots: tokens-mat-protected-button.get-token-slots(); + transition: box-shadow 280ms cubic-bezier(0.4, 0, 0.2, 1); + + @include button-base.mat-private-button-horizontal-layout(tokens-mat-protected-button.$prefix, + $mat-protected-button-slots, false); + @include button-base.mat-private-button-ripple(tokens-mat-protected-button.$prefix, + $mat-protected-button-slots); + @include button-base.mat-private-button-touch-target(false, tokens-mat-protected-button.$prefix, + $mat-protected-button-slots); + + @include token-utils.use-tokens( + tokens-mdc-protected-button.$prefix, + tokens-mdc-protected-button.get-token-slots() + ) { + @include button-base.mat-private-button-elevation(container-elevation); + @include token-utils.create-token-slot(height, container-height); + @include token-utils.create-token-slot(font-family, label-text-font); + @include token-utils.create-token-slot(font-size, label-text-size); + @include token-utils.create-token-slot(letter-spacing, label-text-tracking); + @include token-utils.create-token-slot(text-transform, label-text-transform); + @include token-utils.create-token-slot(font-weight, label-text-weight); + + &:not(:disabled) { + @include token-utils.create-token-slot(color, label-text-color); + @include token-utils.create-token-slot(background-color, container-color); + } + + &, .mdc-button__ripple { + @include token-utils.create-token-slot(border-radius, container-shape); + } + + &:hover { + @include button-base.mat-private-button-elevation(hover-container-elevation); + } + + &:focus { + @include button-base.mat-private-button-elevation(focus-container-elevation); + } - .mat-mdc-outlined-button { - $mdc-outlined-button-slots: tokens-mdc-outlined-button.get-token-slots(); - - @include mdc-button-outlined-theme.theme-styles($mdc-outlined-button-slots); - @include button-base.mat-private-button-horizontal-layout(tokens-mat-outlined-button.$prefix, - tokens-mat-outlined-button.get-token-slots(), false); - @include button-base.mat-private-button-ripple(tokens-mat-outlined-button.$prefix, - tokens-mat-outlined-button.get-token-slots()); - @include button-base.mat-private-button-touch-target(false, tokens-mat-outlined-button.$prefix, - tokens-mat-outlined-button.get-token-slots()); - - @include token-utils.use-tokens( - tokens-mdc-outlined-button.$prefix, - $mdc-outlined-button-slots) { - // We need to re-apply the disabled tokens since MDC uses - // `:disabled` which doesn't apply to anchors. - @include button-base.mat-private-button-disabled { - @include token-utils.create-token-slot(color, disabled-label-text-color); - @include token-utils.create-token-slot(border-color, disabled-outline-color); + &:active, &:focus:active { + @include button-base.mat-private-button-elevation(pressed-container-elevation); + } + + // We need to re-apply the disabled tokens since MDC uses + // `:disabled` which doesn't apply to anchors. + @include button-base.mat-private-button-disabled { + @include token-utils.create-token-slot(color, disabled-label-text-color); + @include token-utils.create-token-slot(background-color, disabled-container-color); + + &.mat-mdc-button-disabled { + @include button-base.mat-private-button-elevation(disabled-container-elevation); } } } } -.mat-mdc-button-base { - text-decoration: none; +.mat-mdc-outlined-button { + $mat-outlined-button-slots: tokens-mat-outlined-button.get-token-slots(); + border-style: solid; + transition: border 280ms cubic-bezier(0.4, 0, 0.2, 1); + + @include button-base.mat-private-button-horizontal-layout(tokens-mat-outlined-button.$prefix, + $mat-outlined-button-slots, false); + @include button-base.mat-private-button-ripple(tokens-mat-outlined-button.$prefix, + $mat-outlined-button-slots); + @include button-base.mat-private-button-touch-target(false, tokens-mat-outlined-button.$prefix, + $mat-outlined-button-slots); + + @include token-utils.use-tokens( + tokens-mdc-outlined-button.$prefix, + tokens-mdc-outlined-button.get-token-slots() + ) { + @include token-utils.create-token-slot(height, container-height); + @include token-utils.create-token-slot(font-family, label-text-font); + @include token-utils.create-token-slot(font-size, label-text-size); + @include token-utils.create-token-slot(letter-spacing, label-text-tracking); + @include token-utils.create-token-slot(text-transform, label-text-transform); + @include token-utils.create-token-slot(font-weight, label-text-weight); + @include token-utils.create-token-slot(border-radius, container-shape); + @include token-utils.create-token-slot(border-width, outline-width); + + &:not(:disabled) { + @include token-utils.create-token-slot(color, label-text-color); + @include token-utils.create-token-slot(border-color, outline-color); + } + + // We need to re-apply the disabled tokens since MDC uses + // `:disabled` which doesn't apply to anchors. + @include button-base.mat-private-button-disabled { + @include token-utils.create-token-slot(color, disabled-label-text-color); + @include token-utils.create-token-slot(border-color, disabled-outline-color); + } + + // TODO(crisbeto): this causes a weird gap between the ripple and the + // outline. We should remove it and update the screenshot tests. + .mdc-button__ripple { + @include token-utils.create-token-slot(border-width, outline-width); + border-style: solid; + border-color: transparent; + } + } } .mat-mdc-button, @@ -155,7 +251,7 @@ // Similar to MDC's `_icon-structure`, apart from the margin which we // handle via custom tokens in `mat-private-button-horizontal-layout`. & > .mat-icon { - $icon-size: mdc-typography.px-to-rem(18px); + $icon-size: 1.125rem; display: inline-block; position: relative; vertical-align: top; @@ -174,12 +270,11 @@ // then. See: https://github.com/angular/components/issues/13738 .mat-mdc-outlined-button .mat-mdc-button-ripple, .mat-mdc-outlined-button .mdc-button__ripple { - $offset: -(mdc-button-variables.$outlined-border-width); + $offset: -1px; top: $offset; left: $offset; bottom: $offset; right: $offset; - border-width: $offset; } // For the button element, default inset/offset values are necessary to ensure that diff --git a/src/material/chips/_chips-theme.scss b/src/material/chips/_chips-theme.scss index 736b195ec173..2528a8685bdb 100644 --- a/src/material/chips/_chips-theme.scss +++ b/src/material/chips/_chips-theme.scss @@ -1,5 +1,4 @@ @use 'sass:color'; -@use '@material/chips/chip-theme' as mdc-chip-theme; @use '../core/tokens/m2/mdc/chip' as tokens-mdc-chip; @use '../core/tokens/m2/mat/chip' as tokens-mat-chip; @use '../core/tokens/token-utils'; @@ -17,9 +16,10 @@ } @else { .mat-mdc-standard-chip { - @include mdc-chip-theme.theme(tokens-mdc-chip.get-unthemable-tokens()); @include token-utils.create-token-values( - tokens-mat-chip.$prefix, tokens-mat-chip.get-unthemable-tokens()); + tokens-mdc-chip.$prefix, tokens-mdc-chip.get-unthemable-tokens()); + @include token-utils.create-token-values( + tokens-mat-chip.$prefix, tokens-mat-chip.get-unthemable-tokens()); } } } @@ -35,32 +35,32 @@ } @else { .mat-mdc-standard-chip { - $default-color-tokens: tokens-mdc-chip.get-color-tokens($theme); - @include mdc-chip-theme.theme($default-color-tokens); @include token-utils.create-token-values( - tokens-mat-chip.$prefix, tokens-mat-chip.get-color-tokens($theme)); + tokens-mdc-chip.$prefix, tokens-mdc-chip.get-color-tokens($theme)); + @include token-utils.create-token-values( + tokens-mat-chip.$prefix, tokens-mat-chip.get-color-tokens($theme)); &.mat-mdc-chip-selected, &.mat-mdc-chip-highlighted { &.mat-primary { - $primary-color-tokens: tokens-mdc-chip.get-color-tokens($theme, primary); - @include mdc-chip-theme.theme($primary-color-tokens); @include token-utils.create-token-values( - tokens-mat-chip.$prefix, tokens-mat-chip.get-color-tokens($theme, primary)); + tokens-mdc-chip.$prefix, tokens-mdc-chip.get-color-tokens($theme, primary)); + @include token-utils.create-token-values( + tokens-mat-chip.$prefix, tokens-mat-chip.get-color-tokens($theme, primary)); } &.mat-accent { - $accent-color-tokens: tokens-mdc-chip.get-color-tokens($theme, accent); - @include mdc-chip-theme.theme($accent-color-tokens); @include token-utils.create-token-values( - tokens-mat-chip.$prefix, tokens-mat-chip.get-color-tokens($theme, accent)); + tokens-mdc-chip.$prefix, tokens-mdc-chip.get-color-tokens($theme, accent)); + @include token-utils.create-token-values( + tokens-mat-chip.$prefix, tokens-mat-chip.get-color-tokens($theme, accent)); } &.mat-warn { - $warn-color-tokens: tokens-mdc-chip.get-color-tokens($theme, warn); - @include mdc-chip-theme.theme($warn-color-tokens); @include token-utils.create-token-values( - tokens-mat-chip.$prefix, tokens-mat-chip.get-color-tokens($theme, warn)); + tokens-mdc-chip.$prefix, tokens-mdc-chip.get-color-tokens($theme, warn)); + @include token-utils.create-token-values( + tokens-mat-chip.$prefix, tokens-mat-chip.get-color-tokens($theme, warn)); } } } @@ -74,12 +74,11 @@ @include _theme-from-tokens(inspection.get-theme-tokens($theme, typography)); } @else { - $typography-tokens: tokens-mdc-chip.get-typography-tokens($theme); - .mat-mdc-standard-chip { - @include mdc-chip-theme.theme($typography-tokens); @include token-utils.create-token-values( - tokens-mat-chip.$prefix, tokens-mat-chip.get-typography-tokens($theme)); + tokens-mdc-chip.$prefix, tokens-mdc-chip.get-typography-tokens($theme)); + @include token-utils.create-token-values( + tokens-mat-chip.$prefix, tokens-mat-chip.get-typography-tokens($theme)); } } } @@ -91,12 +90,11 @@ @include _theme-from-tokens(inspection.get-theme-tokens($theme, density)); } @else { - $density-tokens: tokens-mdc-chip.get-density-tokens($theme); - .mat-mdc-chip.mat-mdc-standard-chip { - @include mdc-chip-theme.theme($density-tokens); @include token-utils.create-token-values( - tokens-mat-chip.$prefix, tokens-mat-chip.get-density-tokens($theme)); + tokens-mdc-chip.$prefix, tokens-mdc-chip.get-density-tokens($theme)); + @include token-utils.create-token-values( + tokens-mat-chip.$prefix, tokens-mat-chip.get-density-tokens($theme)); } } } @@ -141,6 +139,6 @@ 'Calls to Angular Material theme mixins with an M3 theme must be wrapped in a selector'); $mdc-chip-tokens: token-utils.get-tokens-for($tokens, tokens-mdc-chip.$prefix, $options...); $mat-chip-tokens: token-utils.get-tokens-for($tokens, tokens-mat-chip.$prefix, $options...); - @include mdc-chip-theme.theme($mdc-chip-tokens); + @include token-utils.create-token-values(tokens-mdc-chip.$prefix, $mdc-chip-tokens); @include token-utils.create-token-values(tokens-mat-chip.$prefix, $mat-chip-tokens); } diff --git a/src/material/chips/chip-grid.spec.ts b/src/material/chips/chip-grid.spec.ts index cb18d49372f9..264675e33f49 100644 --- a/src/material/chips/chip-grid.spec.ts +++ b/src/material/chips/chip-grid.spec.ts @@ -3,6 +3,7 @@ import {Direction, Directionality} from '@angular/cdk/bidi'; import { BACKSPACE, DELETE, + DOWN_ARROW, END, ENTER, HOME, @@ -10,6 +11,7 @@ import { RIGHT_ARROW, SPACE, TAB, + UP_ARROW, } from '@angular/cdk/keycodes'; import { createKeyboardEvent, @@ -309,6 +311,48 @@ describe('MDC-based MatChipGrid', () => { .withContext('Expected focused item not to have changed.') .toBe(previousActiveElement); }); + + it('should focus primary action in next row when pressing DOWN ARROW on primary action', () => { + chips.first.focus(); + expect(document.activeElement).toBe(primaryActions[0]); + + dispatchKeyboardEvent(primaryActions[0], 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(document.activeElement).toBe(primaryActions[1]); + }); + + it('should focus primary action in previous row when pressing UP ARROW on primary action', () => { + const lastIndex = primaryActions.length - 1; + chips.last.focus(); + expect(document.activeElement).toBe(primaryActions[lastIndex]); + + dispatchKeyboardEvent(primaryActions[lastIndex], 'keydown', UP_ARROW); + fixture.detectChanges(); + + expect(document.activeElement).toBe(primaryActions[lastIndex - 1]); + }); + + it('should focus(trailing action in next row when pressing DOWN ARROW on(trailing action', () => { + trailingActions[0].focus(); + expect(document.activeElement).toBe(trailingActions[0]); + + dispatchKeyboardEvent(trailingActions[0], 'keydown', DOWN_ARROW); + fixture.detectChanges(); + + expect(document.activeElement).toBe(trailingActions[1]); + }); + + it('should focus trailing action in previous row when pressing UP ARROW on trailing action', () => { + const lastIndex = trailingActions.length - 1; + trailingActions[lastIndex].focus(); + expect(document.activeElement).toBe(trailingActions[lastIndex]); + + dispatchKeyboardEvent(trailingActions[lastIndex], 'keydown', UP_ARROW); + fixture.detectChanges(); + + expect(document.activeElement).toBe(trailingActions[lastIndex - 1]); + }); }); describe('RTL', () => { @@ -1034,11 +1078,8 @@ describe('MDC-based MatChipGrid', () => { template: ` @for (i of chips; track i) { - - {{name}} {{i + 1}} - -} + {{name}} {{i + 1}} + } `, }) @@ -1056,8 +1097,8 @@ class StandardChipGrid { Add a chip @for (chip of chips; track chip) { - {{chip}} -} + {{chip}} + } @@ -1081,10 +1122,10 @@ class FormFieldChipGrid { New food... @for (food of foods; track food) { - - {{ food.viewValue }} - -} + + {{ food.viewValue }} + + } @for (food of foods; track food) { - - {{food.viewValue}} - -} + {{food.viewValue}} + } Please select a chip, or type to add a new chip @@ -1179,8 +1218,8 @@ class ChipGridWithFormErrorMessages { template: ` @for (i of numbers; track i) { - {{i}} -} + {{i}} + } `, animations: [ @@ -1208,11 +1247,11 @@ class StandardChipGridWithAnimations { @for (i of chips; track i) { - - Chip {{i + 1}} - Remove - -} + + Chip {{i + 1}} + Remove + + } diff --git a/src/material/chips/chip-grid.ts b/src/material/chips/chip-grid.ts index b52641f2d2f9..60cb8f6af3bd 100644 --- a/src/material/chips/chip-grid.ts +++ b/src/material/chips/chip-grid.ts @@ -7,7 +7,7 @@ */ import {Directionality} from '@angular/cdk/bidi'; -import {hasModifierKey, TAB} from '@angular/cdk/keycodes'; +import {DOWN_ARROW, hasModifierKey, TAB, UP_ARROW} from '@angular/cdk/keycodes'; import { AfterContentInit, AfterViewInit, @@ -426,7 +426,10 @@ export class MatChipGrid /** Handles custom keyboard events. */ override _handleKeydown(event: KeyboardEvent) { - if (event.keyCode === TAB) { + const keyCode = event.keyCode; + const activeItem = this._keyManager.activeItem; + + if (keyCode === TAB) { if ( this._chipInput.focused && hasModifierKey(event, 'shiftKey') && @@ -435,8 +438,8 @@ export class MatChipGrid ) { event.preventDefault(); - if (this._keyManager.activeItem) { - this._keyManager.setActiveItem(this._keyManager.activeItem); + if (activeItem) { + this._keyManager.setActiveItem(activeItem); } else { this._focusLastChip(); } @@ -447,7 +450,25 @@ export class MatChipGrid super._allowFocusEscape(); } } else if (!this._chipInput.focused) { - super._handleKeydown(event); + // The up and down arrows are supposed to navigate between the individual rows in the grid. + // We do this by filtering the actions down to the ones that have the same `_isPrimary` + // flag as the active action and moving focus between them ourseles instead of delegating + // to the key manager. For more information, see #29359 and: + // https://www.w3.org/WAI/ARIA/apg/patterns/grid/examples/layout-grids/#ex2_label + if ((keyCode === UP_ARROW || keyCode === DOWN_ARROW) && activeItem) { + const eligibleActions = this._chipActions.filter( + action => action._isPrimary === activeItem._isPrimary && !this._skipPredicate(action), + ); + const currentIndex = eligibleActions.indexOf(activeItem); + const delta = event.keyCode === UP_ARROW ? -1 : 1; + + event.preventDefault(); + if (currentIndex > -1 && this._isValidIndex(currentIndex + delta)) { + this._keyManager.setActiveItem(eligibleActions[currentIndex + delta]); + } + } else { + super._handleKeydown(event); + } } this.stateChanges.next(); diff --git a/src/material/chips/chip-set.scss b/src/material/chips/chip-set.scss index 3ff0bb4d8a37..a4631248db7d 100644 --- a/src/material/chips/chip-set.scss +++ b/src/material/chips/chip-set.scss @@ -1,14 +1,39 @@ -@use '@material/chips/chip-set' as mdc-chip-set; -@use '../core/mdc-helpers/mdc-helpers'; - -@include mdc-chip-set.core-styles($query: mdc-helpers.$mdc-base-styles-query); - // Ensures that the internal chip container spans the entire outer container width, if the // outer container width is customized. This is used by some wrapper components in g3. .mat-mdc-chip-set { + display: flex; + + &:focus { + outline: none; + } + .mdc-evolution-chip-set__chips { min-width: 100%; + margin-left: -8px; + margin-right: 0; + } + + .mdc-evolution-chip { + margin: 4px 0 4px 8px; } + + [dir='rtl'] & { + .mdc-evolution-chip-set__chips { + margin-left: 0; + margin-right: -8px; + } + + .mdc-evolution-chip { + margin-left: 0; + margin-right: 8px; + } + } +} + +.mdc-evolution-chip-set__chips { + display: flex; + flex-flow: wrap; + min-width: 0; } // Angular Material supports vertically-stacked chips, which MDC does not. diff --git a/src/material/chips/chip.scss b/src/material/chips/chip.scss index b40993846fe2..cb7ab142ef12 100644 --- a/src/material/chips/chip.scss +++ b/src/material/chips/chip.scss @@ -1,228 +1,564 @@ @use '@angular/cdk'; -@use '@material/chips/chip' as mdc-chip; -@use '@material/chips/chip-theme' as mdc-chip-theme; -@use '../core/mdc-helpers/mdc-helpers'; @use '../core/style/layout-common'; @use '../core/focus-indicators/private' as focus-indicators-private; @use '../core/tokens/m2/mdc/chip' as tokens-mdc-chip; @use '../core/tokens/m2/mat/chip' as tokens-mat-chip; +@use '../core/style/vendor-prefixes'; @use '../core/tokens/token-utils'; -@use '@material/theme/custom-properties' as mdc-custom-properties; -// The slots for tokens that will be configured in the theme can be emitted with no fallback. -@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) { - $mdc-chip-token-slots: tokens-mdc-chip.get-token-slots(); - $mat-chip-token-slots: tokens-mat-chip.get-token-slots(); +$_checkmark-size: 20px; +$_trailing-icon-size: 18px; +$_action-padding: 12px; +$_graphic-padding: 6px; +$_trailing-action-padding: 8px; +$_avatar-leading-padding: 4px; +$_avatar-trailing-padding: 8px; + +.mdc-evolution-chip, +.mdc-evolution-chip__cell, +.mdc-evolution-chip__action { + display: inline-flex; + align-items: center; +} - // Add the MDC chip static styles. - @include mdc-chip.static-styles(); +.mdc-evolution-chip { + position: relative; + max-width: 100%; +} - .mat-mdc-standard-chip { - // Add the official slots for the MDC chip. - @include mdc-chip-theme.theme-styles($mdc-chip-token-slots); - } - - // The highlighted attribute is used to make the chip appear as selected on-demand, - // aside from showing the selected indicator. We achieve this by re-mapping the base - // tokens to the highlighted ones. Note that we only need to do this for the tokens - // that we don't re-implement ourselves below. - // TODO(crisbeto): with some future refactors we may be able to clean this up. - .mat-mdc-chip-highlighted { - @include token-utils.use-tokens(tokens-mdc-chip.$prefix, $mdc-chip-token-slots) { - $highlighted-remapped-tokens: ( - with-icon-icon-color: with-icon-selected-icon-color, - elevated-container-color: elevated-selected-container-color, - label-text-color: selected-label-text-color, - outline-width: flat-selected-outline-width, - ); +.mdc-evolution-chip__cell, +.mdc-evolution-chip__action { + height: 100%; +} - @each $selected, $base in $highlighted-remapped-tokens { - #{token-utils.get-token-variable($selected)}: var(token-utils.get-token-variable($base)); - } +.mdc-evolution-chip__cell--primary { + // Ensures that the trailing icon is pushed to the end if the chip has a set width. + flex-basis: 100%; + overflow-x: hidden; +} + +.mdc-evolution-chip__cell--trailing { + flex: 1 0 auto; +} + +.mdc-evolution-chip__action { + align-items: center; + background: none; + border: none; + box-sizing: content-box; + cursor: pointer; + display: inline-flex; + justify-content: center; + outline: none; + padding: 0; + text-decoration: none; + color: inherit; +} + +.mdc-evolution-chip__action--presentational { + cursor: auto; +} + +.mdc-evolution-chip--disabled, +.mdc-evolution-chip__action:disabled { + pointer-events: none; +} + +.mdc-evolution-chip__action--primary { + // This element can be placed on a `button` node which usually has some user agent styles. + // Reset the font so that the typography from the root element can propagate down. + font: inherit; + letter-spacing: inherit; + white-space: inherit; + overflow-x: hidden; + + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + .mat-mdc-standard-chip &::before { + @include token-utils.create-token-slot(border-width, outline-width); + @include token-utils.create-token-slot(border-radius, container-shape-radius); + box-sizing: border-box; + content: ''; + height: 100%; + left: 0; + position: absolute; + pointer-events: none; + top: 0; + width: 100%; + z-index: 1; + border-style: solid; + } + + .mat-mdc-standard-chip & { + padding-left: $_action-padding; + padding-right: $_action-padding; + } + + .mat-mdc-standard-chip.mdc-evolution-chip--with-primary-graphic & { + padding-left: 0; + padding-right: $_action-padding; + } + + [dir='rtl'] .mat-mdc-standard-chip.mdc-evolution-chip--with-primary-graphic & { + padding-left: $_action-padding; + padding-right: 0; + } + + .mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) &::before { + @include token-utils.create-token-slot(border-color, outline-color); + } + + &:not(.mdc-evolution-chip__action--presentational):not(.mdc-ripple-upgraded):focus::before { + @include token-utils.create-token-slot(border-color, focus-outline-color); + } + + .mat-mdc-standard-chip.mdc-evolution-chip--disabled &::before { + @include token-utils.create-token-slot(border-color, disabled-outline-color); + } + + .mat-mdc-standard-chip.mdc-evolution-chip--selected &::before { + @include token-utils.create-token-slot(border-width, flat-selected-outline-width); } } - // Add additional slots for the MDC chip tokens, needed in Angular Material. - $disabled-trailing-icon-opacity: 1; + // Keeps basic listbox chips looking consistent with the other variations. Listbox chips don't + // inherit the font size, because they wrap the label in a `button` that has user agent styles. + .mat-mdc-basic-chip & { + font: inherit; + } - @include token-utils.use-tokens(tokens-mdc-chip.$prefix, $mdc-chip-token-slots) { - .mat-mdc-chip-focus-overlay { - @include token-utils.create-token-slot(background, focus-state-layer-color); + // Moved out into variables, because the selectors are too long. + $with-graphic: '.mdc-evolution-chip--with-primary-graphic'; + $with-trailing: '.mdc-evolution-chip--with-trailing-action'; - .mat-mdc-chip-selected &, - .mat-mdc-chip-highlighted & { - @include token-utils.create-token-slot(background, selected-focus-state-layer-color); - } + .mat-mdc-standard-chip#{$with-trailing} & { + padding-left: $_action-padding; + padding-right: 0; + } - .mat-mdc-chip:hover & { - @include token-utils.create-token-slot(background, hover-state-layer-color); - @include token-utils.create-token-slot(opacity, hover-state-layer-opacity); - } + [dir='rtl'] .mat-mdc-standard-chip#{$with-trailing} & { + padding-left: 0; + padding-right: $_action-padding; + } - .mat-mdc-chip-selected:hover, - .mat-mdc-chip-highlighted:hover & { - @include token-utils.create-token-slot(background, selected-hover-state-layer-color); - @include token-utils.create-token-slot(opacity, selected-hover-state-layer-opacity); - } + .mat-mdc-standard-chip#{$with-graphic}#{$with-trailing} & { + padding-left: 0; + padding-right: 0; + } - .mat-mdc-chip.cdk-focused & { - @include token-utils.create-token-slot(background, focus-state-layer-color); - @include token-utils.create-token-slot(opacity, focus-state-layer-opacity); - } + [dir='rtl'] .mat-mdc-standard-chip#{$with-graphic}#{$with-trailing} & { + padding-left: 0; + padding-right: 0; + } - .mat-mdc-chip-selected.cdk-focused &, - .mat-mdc-chip-highlighted.cdk-focused & { - @include token-utils.create-token-slot(background, selected-focus-state-layer-color); - @include token-utils.create-token-slot(opacity, selected-focus-state-layer-opacity); - } + .mdc-evolution-chip--with-avatar#{$with-graphic} & { + padding-left: 0; + padding-right: $_action-padding; + } + + [dir='rtl'] .mdc-evolution-chip--with-avatar#{$with-graphic} & { + padding-left: $_action-padding; + padding-right: 0; + } + + .mdc-evolution-chip--with-avatar#{$with-graphic}#{$with-trailing} & { + padding-left: 0; + padding-right: 0; + } + + [dir='rtl'] .mdc-evolution-chip--with-avatar#{$with-graphic}#{$with-trailing} & { + padding-left: 0; + padding-right: 0; + } +} + +.mdc-evolution-chip__action--trailing { + position: relative; + overflow: visible; + + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + .mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) & { + @include token-utils.create-token-slot(color, with-trailing-icon-trailing-icon-color); } - .mdc-evolution-chip--disabled:not(.mdc-evolution-chip--selected) .mat-mdc-chip-avatar { - @include token-utils.create-token-slot(opacity, with-avatar-disabled-avatar-opacity); + .mat-mdc-standard-chip.mdc-evolution-chip--disabled & { + @include token-utils.create-token-slot(color, + with-trailing-icon-disabled-trailing-icon-color); } + } + + // Moved out into variables, because the selectors are too long. + $with-graphic: '.mdc-evolution-chip--with-primary-graphic'; + $with-trailing: '.mdc-evolution-chip--with-trailing-action'; - .mdc-evolution-chip--disabled .mdc-evolution-chip__icon--trailing { - $disabled-trailing-icon-opacity: - token-utils.get-token-variable(with-trailing-icon-disabled-trailing-icon-opacity); + .mat-mdc-standard-chip#{$with-trailing} & { + padding-left: $_trailing-action-padding; + padding-right: $_trailing-action-padding; + } - @include token-utils.create-token-slot( - opacity, with-trailing-icon-disabled-trailing-icon-opacity); + .mat-mdc-standard-chip#{$with-graphic}#{$with-trailing} & { + padding-left: $_trailing-action-padding; + padding-right: $_trailing-action-padding; + } + + + .mdc-evolution-chip--with-avatar#{$with-graphic}#{$with-trailing} & { + padding-left: $_avatar-trailing-padding; + padding-right: $_avatar-trailing-padding; + } + + [dir='rtl'] .mdc-evolution-chip--with-avatar#{$with-graphic}#{$with-trailing} & { + padding-left: $_avatar-trailing-padding; + padding-right: $_avatar-trailing-padding; + } + +} + +.mdc-evolution-chip__text-label { + @include vendor-prefixes.user-select(none); + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + .mat-mdc-standard-chip & { + @include token-utils.create-token-slot(font-family, label-text-font); + @include token-utils.create-token-slot(line-height, label-text-line-height); + @include token-utils.create-token-slot(font-size, label-text-size); + @include token-utils.create-token-slot(font-weight, label-text-weight); + @include token-utils.create-token-slot(letter-spacing, label-text-tracking); } - .mdc-evolution-chip--disabled.mdc-evolution-chip--selected .mdc-evolution-chip__checkmark { - @include token-utils.create-token-slot(opacity, with-icon-disabled-icon-opacity); + .mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) & { + @include token-utils.create-token-slot(color, label-text-color); } - } - @include token-utils.use-tokens(tokens-mat-chip.$prefix, $mat-chip-token-slots) { - // Historically, MDC did not support disabled chips, so we needed our own disabled styles. - // Now that MDC supports disabled styles, we should switch to using theirs. - .mat-mdc-standard-chip { - &.mdc-evolution-chip--disabled { - @include token-utils.create-token-slot(opacity, disabled-container-opacity); - } + .mat-mdc-standard-chip.mdc-evolution-chip--selected:not(.mdc-evolution-chip--disabled) & { + @include token-utils.create-token-slot(color, selected-label-text-color); + } - &.mdc-evolution-chip--selected, - &.mat-mdc-chip-highlighted { - .mdc-evolution-chip__icon--trailing { - @include token-utils.create-token-slot(color, selected-trailing-icon-color); - } + .mat-mdc-standard-chip.mdc-evolution-chip--disabled &, + .mat-mdc-standard-chip.mdc-evolution-chip--selected.mdc-evolution-chip--disabled & { + @include token-utils.create-token-slot(color, disabled-label-text-color); + } + } +} - &.mdc-evolution-chip--disabled .mdc-evolution-chip__icon--trailing { - @include token-utils.create-token-slot(color, selected-disabled-trailing-icon-color); - } - } +.mdc-evolution-chip__graphic { + align-items: center; + display: inline-flex; + justify-content: center; + overflow: hidden; + pointer-events: none; + position: relative; + flex: 1 0 auto; + + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + .mat-mdc-standard-chip & { + @include token-utils.create-token-slot(width, with-avatar-avatar-size); + @include token-utils.create-token-slot(height, with-avatar-avatar-size); + @include token-utils.create-token-slot(font-size, with-avatar-avatar-size); } + } - .mat-mdc-chip-remove { - @include token-utils.create-token-slot(opacity, trailing-action-opacity); + .mdc-evolution-chip--selecting & { + transition: width 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1); + } - &:focus { - @include token-utils.create-token-slot(opacity, trailing-action-focus-opacity); - } + // Moved out into variables, because the selectors are too long. + $with-icon: '.mdc-evolution-chip--with-primary-icon'; + $with-graphic: '.mdc-evolution-chip--with-primary-graphic'; + $with-trailing: '.mdc-evolution-chip--with-trailing-action'; - &::after { - @include token-utils.create-token-slot(background, trailing-action-state-layer-color); - } + .mdc-evolution-chip--selectable:not(.mdc-evolution-chip--selected):not(#{$with-icon}) & { + width: 0; + } - &:hover::after { - @include token-utils.create-token-slot(opacity, trailing-action-hover-state-layer-opacity); - } + .mat-mdc-standard-chip#{$with-graphic} & { + padding-left: $_graphic-padding; + padding-right: $_graphic-padding; + } - &:focus::after { - @include token-utils.create-token-slot(opacity, trailing-action-focus-state-layer-opacity); - } + .mdc-evolution-chip--with-avatar#{$with-graphic} & { + padding-left: $_avatar-leading-padding; + padding-right: $_avatar-trailing-padding; + } + + [dir='rtl'] .mdc-evolution-chip--with-avatar#{$with-graphic} & { + padding-left: $_avatar-trailing-padding; + padding-right: $_avatar-leading-padding; + } + + .mat-mdc-standard-chip#{$with-graphic}#{$with-trailing} & { + padding-left: $_graphic-padding; + padding-right: $_graphic-padding; + } + + .mdc-evolution-chip--with-avatar#{$with-graphic}#{$with-trailing} & { + padding-left: $_avatar-leading-padding; + padding-right: $_avatar-trailing-padding; + } + + [dir='rtl'] .mdc-evolution-chip--with-avatar#{$with-graphic}#{$with-trailing} & { + padding-left: $_avatar-trailing-padding; + padding-right: $_avatar-leading-padding; + } +} + +.mdc-evolution-chip__checkmark { + position: absolute; + opacity: 0; + top: 50%; + left: 50%; + height: $_checkmark-size; + width: $_checkmark-size; + + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + .mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) & { + @include token-utils.create-token-slot(color, with-icon-selected-icon-color); } - .mat-mdc-chip-selected .mat-mdc-chip-remove::after, - .mat-mdc-chip-highlighted .mat-mdc-chip-remove::after { - @include token-utils.create-token-slot( - background, selected-trailing-action-state-layer-color); + .mat-mdc-standard-chip.mdc-evolution-chip--disabled & { + @include token-utils.create-token-slot(color, with-icon-disabled-icon-color); } + } - // If the trailing icon is a chip-remove button, we have to factor in the trailing action - // opacity as well as the disabled opacity. - .mdc-evolution-chip--disabled .mdc-evolution-chip__icon--trailing { - &.mat-mdc-chip-remove { + .mdc-evolution-chip--selecting & { + transition: transform 150ms 0ms cubic-bezier(0.4, 0, 0.2, 1); + transform: translate(-75%, -50%); + } + + .mdc-evolution-chip--selected & { + transform: translate(-50%, -50%); + opacity: 1; + } +} + +.mdc-evolution-chip__checkmark-svg { + display: block; +} + +.mdc-evolution-chip__checkmark-path { + stroke-width: 2px; + stroke-dasharray: 29.7833385; + stroke-dashoffset: 29.7833385; + stroke: currentColor; + + .mdc-evolution-chip--selecting & { + transition: stroke-dashoffset 150ms 45ms cubic-bezier(0.4, 0, 0.2, 1); + } + + .mdc-evolution-chip--selected & { + stroke-dashoffset: 0; + } + + @include cdk.high-contrast(active, off) { + // SVG colors won't be changed in high contrast mode and since the checkmark is white + // by default, it'll blend in with the background in black-on-white mode. Override the + // color to ensure that it's visible. We need !important, because the theme styles are + // very specific. + stroke: CanvasText !important; + } +} + +.mdc-evolution-chip__icon--trailing { + .mat-mdc-standard-chip & { + height: $_trailing-icon-size; + width: $_trailing-icon-size; + font-size: $_trailing-icon-size; + } + + $disabled-icon-opacity: null; + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + $disabled-icon-opacity: + #{token-utils.get-token-variable(with-trailing-icon-disabled-trailing-icon-opacity)}; + } + + // If the trailing icon is a chip-remove button, we have to factor in the trailing action + // opacity as well as the disabled opacity. + .mdc-evolution-chip--disabled &.mat-mdc-chip-remove { + @include token-utils.use-tokens(tokens-mat-chip.$prefix, tokens-mat-chip.get-token-slots()) { + opacity: calc( + var(#{token-utils.get-token-variable(trailing-action-opacity)}) * + var(#{$disabled-icon-opacity}) + ); + + &:focus { opacity: calc( - var(#{token-utils.get-token-variable(trailing-action-opacity)}) * - var(#{$disabled-trailing-icon-opacity}) + var(#{token-utils.get-token-variable(trailing-action-focus-opacity)}) * + var(#{$disabled-icon-opacity}) ); - - &:focus { - opacity: calc( - var(#{token-utils.get-token-variable(trailing-action-focus-opacity)}) * - var(#{$disabled-trailing-icon-opacity}) - ); - } } } } } -// We *should* be able to include these styles through MDC's -// `theme-styles` mixin, but we can't at the time of writing. -@mixin _missing-mdc-theme-styles() { - .mat-mdc-standard-chip { - @include mdc-chip-theme.outline-style(solid); - @include mdc-chip-theme.checkmark-size(mdc-chip-theme.$checkmark-size); - @include mdc-chip-theme.trailing-action-size(mdc-chip-theme.$trailing-action-size); - @include mdc-chip-theme.horizontal-padding( - mdc-chip-theme.$leading-padding, - mdc-chip-theme.$trailing-padding - ); - @include mdc-chip-theme.with-graphic-horizontal-padding( - mdc-chip-theme.$graphic-leading-padding, - mdc-chip-theme.$graphic-trailing-padding, - mdc-chip-theme.$trailing-padding - ); - @include mdc-chip-theme.with-trailing-action-horizontal-padding( - mdc-chip-theme.$leading-padding, - mdc-chip-theme.$trailing-action-leading-padding, - mdc-chip-theme.$trailing-action-trailing-padding - ); - @include mdc-chip-theme.with-graphic-and-trailing-action-horizontal-padding( - mdc-chip-theme.$graphic-leading-padding, - mdc-chip-theme.$graphic-trailing-padding, - mdc-chip-theme.$trailing-action-leading-padding, - mdc-chip-theme.$trailing-action-trailing-padding - ); +.mat-mdc-standard-chip { + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + @include token-utils.create-token-slot(border-radius, container-shape-radius); + @include token-utils.create-token-slot(height, container-height); + + &:not(.mdc-evolution-chip--disabled) { + @include token-utils.create-token-slot(background-color, elevated-container-color); + } + + &.mdc-evolution-chip--disabled { + @include token-utils.create-token-slot(background-color, elevated-disabled-container-color); + } + + &.mdc-evolution-chip--selected:not(.mdc-evolution-chip--disabled) { + @include token-utils.create-token-slot(background-color, elevated-selected-container-color); + } + + &.mdc-evolution-chip--selected.mdc-evolution-chip--disabled { + @include token-utils.create-token-slot(background-color, + flat-disabled-selected-container-color); + } } - .mdc-evolution-chip--with-avatar { - @include mdc-chip-theme.with-graphic-horizontal-padding( - mdc-chip-theme.$avatar-leading-padding, - mdc-chip-theme.$avatar-trailing-padding, - mdc-chip-theme.$trailing-padding - ); - @include mdc-chip-theme.with-graphic-and-trailing-action-horizontal-padding( - mdc-chip-theme.$avatar-leading-padding, - mdc-chip-theme.$avatar-trailing-padding, - mdc-chip-theme.$trailing-action-leading-padding, - mdc-chip-theme.$trailing-action-trailing-padding + @include cdk.high-contrast(active, off) { + outline: solid 1px; + } +} + +.mdc-evolution-chip__icon--primary { + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + .mat-mdc-standard-chip & { + @include token-utils.create-token-slot(border-radius, with-avatar-avatar-shape-radius); + @include token-utils.create-token-slot(width, with-icon-icon-size); + @include token-utils.create-token-slot(height, with-icon-icon-size); + @include token-utils.create-token-slot(font-size, with-icon-icon-size); + } + + .mdc-evolution-chip--selected & { + opacity: 0; + } + + .mat-mdc-standard-chip:not(.mdc-evolution-chip--disabled) & { + @include token-utils.create-token-slot(color, with-icon-icon-color); + } + + .mat-mdc-standard-chip.mdc-evolution-chip--disabled & { + @include token-utils.create-token-slot(color, with-icon-disabled-icon-color); + } + } +} + +// The highlighted attribute is used to make the chip appear as selected on-demand, +// aside from showing the selected indicator. We achieve this by re-mapping the base +// tokens to the highlighted ones. Note that we only need to do this for the tokens +// that we don't re-implement ourselves below. +// TODO(crisbeto): with some future refactors we may be able to clean this up. +.mat-mdc-chip-highlighted { + @include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + $highlighted-remapped-tokens: ( + with-icon-icon-color: with-icon-selected-icon-color, + elevated-container-color: elevated-selected-container-color, + label-text-color: selected-label-text-color, + outline-width: flat-selected-outline-width, ); + + @each $selected, $base in $highlighted-remapped-tokens { + #{token-utils.get-token-variable($selected)}: var(token-utils.get-token-variable($base)); + } } } -@include mdc-helpers.disable-mdc-fallback-declarations { - @include _missing-mdc-theme-styles(); +// Add additional slots for the MDC chip tokens, needed in Angular Material. +@include token-utils.use-tokens(tokens-mdc-chip.$prefix, tokens-mdc-chip.get-token-slots()) { + .mat-mdc-chip-focus-overlay { + @include token-utils.create-token-slot(background, focus-state-layer-color); + + .mat-mdc-chip-selected &, + .mat-mdc-chip-highlighted & { + @include token-utils.create-token-slot(background, selected-focus-state-layer-color); + } + + .mat-mdc-chip:hover & { + @include token-utils.create-token-slot(background, hover-state-layer-color); + @include token-utils.create-token-slot(opacity, hover-state-layer-opacity); + } + + .mat-mdc-chip-selected:hover, + .mat-mdc-chip-highlighted:hover & { + @include token-utils.create-token-slot(background, selected-hover-state-layer-color); + @include token-utils.create-token-slot(opacity, selected-hover-state-layer-opacity); + } + + .mat-mdc-chip.cdk-focused & { + @include token-utils.create-token-slot(background, focus-state-layer-color); + @include token-utils.create-token-slot(opacity, focus-state-layer-opacity); + } + + .mat-mdc-chip-selected.cdk-focused &, + .mat-mdc-chip-highlighted.cdk-focused & { + @include token-utils.create-token-slot(background, selected-focus-state-layer-color); + @include token-utils.create-token-slot(opacity, selected-focus-state-layer-opacity); + } + } + + .mdc-evolution-chip--disabled:not(.mdc-evolution-chip--selected) .mat-mdc-chip-avatar { + @include token-utils.create-token-slot(opacity, with-avatar-disabled-avatar-opacity); + } + + .mdc-evolution-chip--disabled .mdc-evolution-chip__icon--trailing { + @include token-utils.create-token-slot( + opacity, with-trailing-icon-disabled-trailing-icon-opacity); + } + + .mdc-evolution-chip--disabled.mdc-evolution-chip--selected .mdc-evolution-chip__checkmark { + @include token-utils.create-token-slot(opacity, with-icon-disabled-icon-opacity); + } } -.mat-mdc-standard-chip { - -webkit-tap-highlight-color: transparent; +@include token-utils.use-tokens(tokens-mat-chip.$prefix, tokens-mat-chip.get-token-slots()) { + // Historically, MDC did not support disabled chips, so we needed our own disabled styles. + // Now that MDC supports disabled styles, we should switch to using theirs. + .mat-mdc-standard-chip { + &.mdc-evolution-chip--disabled { + @include token-utils.create-token-slot(opacity, disabled-container-opacity); + } - @include cdk.high-contrast(active, off) { - outline: solid 1px; + &.mdc-evolution-chip--selected, + &.mat-mdc-chip-highlighted { + .mdc-evolution-chip__icon--trailing { + @include token-utils.create-token-slot(color, selected-trailing-icon-color); + } - .mdc-evolution-chip__checkmark-path { - // SVG colors won't be changed in high contrast mode and since the checkmark is white - // by default, it'll blend in with the background in black-on-white mode. Override the - // color to ensure that it's visible. We need !important, because the theme styles are - // very specific. - stroke: CanvasText !important; + &.mdc-evolution-chip--disabled .mdc-evolution-chip__icon--trailing { + @include token-utils.create-token-slot(color, selected-disabled-trailing-icon-color); + } } } + .mat-mdc-chip-remove { + @include token-utils.create-token-slot(opacity, trailing-action-opacity); + + &:focus { + @include token-utils.create-token-slot(opacity, trailing-action-focus-opacity); + } + + &::after { + @include token-utils.create-token-slot(background, trailing-action-state-layer-color); + } + + &:hover::after { + @include token-utils.create-token-slot(opacity, trailing-action-hover-state-layer-opacity); + } + + &:focus::after { + @include token-utils.create-token-slot(opacity, trailing-action-focus-state-layer-opacity); + } + } + + .mat-mdc-chip-selected .mat-mdc-chip-remove::after, + .mat-mdc-chip-highlighted .mat-mdc-chip-remove::after { + @include token-utils.create-token-slot(background, selected-trailing-action-state-layer-color); + } +} + +.mat-mdc-standard-chip { + -webkit-tap-highlight-color: transparent; + // MDC sets `overflow: hidden` on these elements in order to truncate the text. This is // unnecessary since our chips don't truncate their text and it makes it difficult to style // the strong focus indicators so we need to override it. @@ -232,19 +568,6 @@ overflow: visible; } - // Ensures that the trailing icon is pushed to the end if the chip has a set width. - .mdc-evolution-chip__cell--primary { - flex-basis: 100%; - } - - // This element can be placed on a `button` node which usually has some user agent styles. - // Reset the font so that the typography from the root element can propagate down. - .mdc-evolution-chip__action--primary { - font: inherit; - letter-spacing: inherit; - white-space: inherit; - } - // MDC sizes and positions this element using `width`, `height` and `padding`. // This usually works, but it's common for apps to add `box-sizing: border-box` // to all elements on the page which can cause the graphic to be clipped. @@ -267,12 +590,6 @@ } } -// Keeps basic listbox chips looking consistent with the other variations. Listbox chips don't -// inherit the font size, because they wrap the label in a `button` that has user agent styles. -.mat-mdc-basic-chip .mdc-evolution-chip__action--primary { - font: inherit; -} - // MDC's focus and hover indication is handled through their ripple which we currently // don't use due to size concerns so we have to re-implement it ourselves. .mat-mdc-chip-focus-overlay { @@ -391,9 +708,9 @@ } .mat-icon { - width: mdc-chip-theme.$trailing-action-size; - height: mdc-chip-theme.$trailing-action-size; - font-size: mdc-chip-theme.$trailing-action-size; + width: $_trailing-icon-size; + height: $_trailing-icon-size; + font-size: $_trailing-icon-size; box-sizing: content-box; } } diff --git a/src/material/core/internal-form-field/internal-form-field.scss b/src/material/core/internal-form-field/internal-form-field.scss index 70170adaac36..fb2191d05b34 100644 --- a/src/material/core/internal-form-field/internal-form-field.scss +++ b/src/material/core/internal-form-field/internal-form-field.scss @@ -1,12 +1,40 @@ -@use '@material/form-field/form-field' as mdc-form-field; -@use '@material/typography/typography' as mdc-typography; -@use '@material/theme/custom-properties' as mdc-custom-properties; -@use '../mdc-helpers/mdc-helpers'; +@use '../style/vendor-prefixes'; -@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) { - @include mdc-form-field.static-styles($query: mdc-helpers.$mdc-base-styles-query); +.mat-internal-form-field { + @include vendor-prefixes.smooth-font(); + display: inline-flex; + align-items: center; + vertical-align: middle; + + & > label { + margin-left: 0; + margin-right: auto; + padding-left: 4px; + padding-right: 0; + order: 0; + } + + [dir='rtl'] & > label { + margin-left: auto; + margin-right: 0; + padding-left: 0; + padding-right: 4px; + } } -.mat-internal-form-field { - @include mdc-typography.smooth-font(); +.mdc-form-field--align-end { + & > label { + margin-left: auto; + margin-right: 0; + padding-left: 0; + padding-right: 4px; + order: -1; + } + + [dir='rtl'] .mdc-form-field--align-end & label { + margin-left: 0; + margin-right: auto; + padding-left: 4px; + padding-right: 0; + } } diff --git a/src/material/core/m2/_typography.scss b/src/material/core/m2/_typography.scss index 0d9238d5fada..2ad000cc860b 100644 --- a/src/material/core/m2/_typography.scss +++ b/src/material/core/m2/_typography.scss @@ -1,8 +1,11 @@ @use 'sass:map'; @use 'sass:math'; @use 'sass:meta'; +@use 'sass:string'; @use '@material/typography' as mdc-typography; +$_default-font-family: string.unquote('Roboto, sans-serif'); + /// Defines a typography level from the Material Design spec. /// @param {String} $font-size The font-size for this level. /// @param {String | Number} $line-height The line-height for this level. @@ -123,7 +126,7 @@ @function define-typography-config( // TODO(mmalerba): rename this function to define-typography-config, // and create a predefined px based config for people that need it. - $font-family: mdc-typography.$font-family, + $font-family: $_default-font-family, $headline-1: null, $headline-2: null, $headline-3: null, @@ -180,7 +183,7 @@ @function define-rem-typography-config( // TODO(mmalerba): rename this function to define-typography-config, // and create a predefined px based config for people that need it. - $font-family: mdc-typography.$font-family, + $font-family: $_default-font-family, $headline-1: null, $headline-2: null, $headline-3: null, diff --git a/src/material/core/mdc-helpers/_mdc-helpers.scss b/src/material/core/mdc-helpers/_mdc-helpers.scss index 28819d32b2d6..b963e10d1fbd 100644 --- a/src/material/core/mdc-helpers/_mdc-helpers.scss +++ b/src/material/core/mdc-helpers/_mdc-helpers.scss @@ -1,8 +1,8 @@ // TODO(mmalerba): this file should be split into separate cohesive partials for things like // "theming", "typography", "core". +@use 'sass:string'; @use '../typography/typography'; @use '@material/feature-targeting' as mdc-feature-targeting; -@use '@material/typography' as mdc-typography; @use '@material/theme/theme-color' as mdc-theme-color; @use '@material/theme/css' as mdc-theme-css; @@ -32,7 +32,7 @@ $mdc-typography-styles-query: typography; @function private-fallback-typography-from-mdc() { // This is very close to what we have in `define-typography-config`, but we can't use it here, // because it would cause a circular import and moving it here doesn't make sense. - $font-family: mdc-typography.$font-family; + $font-family: string.unquote('Roboto, sans-serif'); @return ( font-family: $font-family, headline-1: typography.typography-config-level-from-mdc(headline1, $font-family), diff --git a/src/material/core/style/_elevation.scss b/src/material/core/style/_elevation.scss index fa4ed60cc1a3..a8e57becb19d 100644 --- a/src/material/core/style/_elevation.scss +++ b/src/material/core/style/_elevation.scss @@ -70,6 +70,11 @@ $prefix: 'mat-elevation-z'; } } +// Gets the box shadow value for a specific elevation. +@function get-box-shadow($z-value, $shadow-color: black) { + @return mdc-elevation.elevation-box-shadow($z-value, $shadow-color); +} + // Returns a string that can be used as the value for a transition property for elevation. // Calling this function directly is useful in situations where a component needs to transition // more than one property. diff --git a/src/material/core/theming/tests/test-typography-font-family.scss b/src/material/core/theming/tests/test-typography-font-family.scss index 302a3284f79a..91b9fb824ff3 100644 --- a/src/material/core/theming/tests/test-typography-font-family.scss +++ b/src/material/core/theming/tests/test-typography-font-family.scss @@ -1,7 +1,9 @@ -@use '@material/typography' as mdc-typography; -@use '../../m2/typography' as m2-typography; +@use 'sass:string'; @use 'sass:map'; @use 'sass:meta'; +@use '../../m2/typography' as m2-typography; + +$_font-family: string.unquote('Roboto, sans-serif'); @function assert-font-family($test-name, $obj, $expected) { @each $level-name, $level in $obj { @@ -16,7 +18,7 @@ $no-font-family: assert-font-family( 'should take default MDC font family if none is specified', m2-typography.define-typography-config(), - mdc-typography.$font-family); + $_font-family); $only-top-level-font-family: assert-font-family( 'should take custom font family if specified at top level', @@ -40,7 +42,7 @@ $individual-levels-without-font-families: assert-font-family( $button: m2-typography.define-typography-level($font-size: 1px), $overline: m2-typography.define-typography-level($font-size: 1px), ), - mdc-typography.$font-family + $_font-family ); $individual-levels-without-font-families-with-top-level-family: assert-font-family( diff --git a/src/material/core/tokens/_token-utils.scss b/src/material/core/tokens/_token-utils.scss index d537d3667ce0..571c634a7ba5 100644 --- a/src/material/core/tokens/_token-utils.scss +++ b/src/material/core/tokens/_token-utils.scss @@ -1,11 +1,11 @@ @use 'sass:list'; @use 'sass:map'; @use 'sass:meta'; -@use '@material/elevation/elevation-theme' as mdc-elevation-theme; @use '@material/theme/custom-properties' as mdc-custom-properties; @use '@material/theme/theme' as mdc-theme; @use '@material/theme/keys' as mdc-keys; @use '@material/tokens/v0_161' as mdc-tokens; +@use '../style/elevation'; @use '../style/sass-utils'; @use '../m2/palette' as m2-palette; @use '../m2/theming' as m2-theming; @@ -161,7 +161,7 @@ $_component-prefix: null; $elevation: map.get($tokens, $elevation-token); $shadow-color: map.get($tokens, $shadow-color-token); @return map.merge($tokens, ( - $elevation-token: mdc-elevation-theme.elevation-box-shadow($elevation, $shadow-color), + $elevation-token: elevation.get-box-shadow($elevation, $shadow-color), $shadow-color-token: null, )); } diff --git a/src/material/core/tokens/m2/mat/_app.scss b/src/material/core/tokens/m2/mat/_app.scss index c1bde7db58fc..39a588f4f74b 100644 --- a/src/material/core/tokens/m2/mat/_app.scss +++ b/src/material/core/tokens/m2/mat/_app.scss @@ -1,4 +1,3 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; @use 'sass:map'; @use '../../token-utils'; @use '../../../theming/inspection'; @@ -23,7 +22,7 @@ $prefix: (mat, app); @for $zValue from 0 through 24 { $elevation-color: inspection.get-theme-color($theme, foreground, elevation); - $shadow: mdc-elevation.elevation-box-shadow($zValue, + $shadow: elevation.get-box-shadow($zValue, if($elevation-color == null, elevation.$color, $elevation-color)); $tokens: map.set($tokens, 'elevation-shadow-level-#{$zValue}', $shadow); } diff --git a/src/material/core/tokens/m2/mat/_autocomplete.scss b/src/material/core/tokens/m2/mat/_autocomplete.scss index a1a06caced3a..9e17ad4ec687 100644 --- a/src/material/core/tokens/m2/mat/_autocomplete.scss +++ b/src/material/core/tokens/m2/mat/_autocomplete.scss @@ -1,6 +1,6 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; @use '../../token-utils'; @use '../../../theming/inspection'; +@use '../../../style/elevation'; @use '../../../style/sass-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -11,7 +11,7 @@ $prefix: (mat, autocomplete); @function get-unthemable-tokens() { @return ( container-shape: 4px, - container-elevation-shadow: mdc-elevation.elevation-box-shadow(8), + container-elevation-shadow: elevation.get-box-shadow(8), ); } diff --git a/src/material/core/tokens/m2/mat/_datepicker.scss b/src/material/core/tokens/m2/mat/_datepicker.scss index ef00a98bf717..10731abfeeee 100644 --- a/src/material/core/tokens/m2/mat/_datepicker.scss +++ b/src/material/core/tokens/m2/mat/_datepicker.scss @@ -1,9 +1,9 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; @use 'sass:color'; @use 'sass:meta'; @use 'sass:math'; @use '../../token-utils'; @use '../../../theming/inspection'; +@use '../../../style/elevation'; @use '../../../style/sass-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -34,8 +34,8 @@ $private-default-overlap-color: #a8dab5; @return ( calendar-container-shape: 4px, calendar-container-touch-shape: 4px, - calendar-container-elevation-shadow: mdc-elevation.elevation-box-shadow(4), - calendar-container-touch-elevation-shadow: mdc-elevation.elevation-box-shadow(24), + calendar-container-elevation-shadow: elevation.get-box-shadow(4), + calendar-container-touch-elevation-shadow: elevation.get-box-shadow(24), ); } diff --git a/src/material/core/tokens/m2/mat/_dialog.scss b/src/material/core/tokens/m2/mat/_dialog.scss index ec1e888f4888..fb65d3863f4f 100644 --- a/src/material/core/tokens/m2/mat/_dialog.scss +++ b/src/material/core/tokens/m2/mat/_dialog.scss @@ -1,5 +1,5 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; @use '../../token-utils'; +@use '../../../style/elevation'; @use '../../../style/sass-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -9,7 +9,7 @@ $prefix: (mat, dialog); // but may be in a future version of the theming API. @function get-unthemable-tokens() { @return ( - container-elevation-shadow: mdc-elevation.elevation-box-shadow(24), + container-elevation-shadow: elevation.get-box-shadow(24), container-max-width: 80vw, container-small-max-width: 80vw, container-min-width: 0, diff --git a/src/material/core/tokens/m2/mat/_fab-small.scss b/src/material/core/tokens/m2/mat/_fab-small.scss index b980384d4338..f7e514cdd3e3 100644 --- a/src/material/core/tokens/m2/mat/_fab-small.scss +++ b/src/material/core/tokens/m2/mat/_fab-small.scss @@ -1,6 +1,4 @@ -@use 'sass:map'; @use 'sass:meta'; -@use '@material/ripple/ripple-theme' as mdc-ripple-theme; @use '../../token-utils'; @use '../../../theming/theming'; @use '../../../theming/inspection'; @@ -19,10 +17,6 @@ $prefix: (mat, fab-small); // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { $is-dark: inspection.get-theme-type($theme) == dark; - $ripple-opacities: if($is-dark, - mdc-ripple-theme.$light-ink-opacities, - mdc-ripple-theme.$dark-ink-opacities - ); @return ( // Color of icons and text projected into a FAB. @@ -38,13 +32,13 @@ $prefix: (mat, fab-small); ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1), // Opacity of the ripple when the button is hovered. - hover-state-layer-opacity: map.get($ripple-opacities, hover), + hover-state-layer-opacity: if($is-dark, 0.08, 0.04), // Opacity of the ripple when the button is focused. - focus-state-layer-opacity: map.get($ripple-opacities, focus), + focus-state-layer-opacity: if($is-dark, 0.24, 0.12), // Opacity of the ripple when the button is pressed. - pressed-state-layer-opacity: map.get($ripple-opacities, press), + pressed-state-layer-opacity: if($is-dark, 0.24, 0.12), // MDC doesn't have tokens for disabled FABs so we need to implemented them ourselves. // Background color of the container when the FAB is disabled. diff --git a/src/material/core/tokens/m2/mat/_fab.scss b/src/material/core/tokens/m2/mat/_fab.scss index cc8f48ed6723..687c252a0b36 100644 --- a/src/material/core/tokens/m2/mat/_fab.scss +++ b/src/material/core/tokens/m2/mat/_fab.scss @@ -1,6 +1,4 @@ -@use 'sass:map'; @use 'sass:meta'; -@use '@material/ripple/ripple-theme' as mdc-ripple-theme; @use '../../token-utils'; @use '../../../theming/theming'; @use '../../../theming/inspection'; @@ -19,10 +17,6 @@ $prefix: (mat, fab); // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { $is-dark: inspection.get-theme-type($theme) == dark; - $ripple-opacities: if($is-dark, - mdc-ripple-theme.$light-ink-opacities, - mdc-ripple-theme.$dark-ink-opacities - ); @return ( // Color of icons and text projected into a FAB. @@ -38,13 +32,13 @@ $prefix: (mat, fab); ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1), // Opacity of the ripple when the button is hovered. - hover-state-layer-opacity: map.get($ripple-opacities, hover), + hover-state-layer-opacity: if($is-dark, 0.08, 0.04), // Opacity of the ripple when the button is focused. - focus-state-layer-opacity: map.get($ripple-opacities, focus), + focus-state-layer-opacity: if($is-dark, 0.24, 0.12), // Opacity of the ripple when the button is pressed. - pressed-state-layer-opacity: map.get($ripple-opacities, press), + pressed-state-layer-opacity: if($is-dark, 0.24, 0.12), // MDC doesn't have tokens for disabled FABs so we need to implemented them ourselves. // Background color of the container when the FAB is disabled. diff --git a/src/material/core/tokens/m2/mat/_filled-button.scss b/src/material/core/tokens/m2/mat/_filled-button.scss index 19dcbf51bbbf..e89b6e76828a 100644 --- a/src/material/core/tokens/m2/mat/_filled-button.scss +++ b/src/material/core/tokens/m2/mat/_filled-button.scss @@ -1,6 +1,4 @@ -@use 'sass:map'; @use 'sass:meta'; -@use '@material/ripple/ripple-theme' as mdc-ripple-theme; @use '../../token-utils'; @use '../../../theming/theming'; @use '../../../theming/inspection'; @@ -29,10 +27,6 @@ $prefix: (mat, filled-button); // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { $is-dark: inspection.get-theme-type($theme) == dark; - $ripple-opacities: if($is-dark, - mdc-ripple-theme.$light-ink-opacities, - mdc-ripple-theme.$dark-ink-opacities - ); @return ( // Color of the element that shows the hover, focus and pressed states. @@ -45,13 +39,13 @@ $prefix: (mat, filled-button); ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1), // Opacity of the ripple when the button is hovered. - hover-state-layer-opacity: map.get($ripple-opacities, hover), + hover-state-layer-opacity: if($is-dark, 0.08, 0.04), // Opacity of the ripple when the button is focused. - focus-state-layer-opacity: map.get($ripple-opacities, focus), + focus-state-layer-opacity: if($is-dark, 0.24, 0.12), // Opacity of the ripple when the button is pressed. - pressed-state-layer-opacity: map.get($ripple-opacities, press), + pressed-state-layer-opacity: if($is-dark, 0.24, 0.12), ); } diff --git a/src/material/core/tokens/m2/mat/_form-field.scss b/src/material/core/tokens/m2/mat/_form-field.scss index 14f1d98327c3..bfee8de634c8 100644 --- a/src/material/core/tokens/m2/mat/_form-field.scss +++ b/src/material/core/tokens/m2/mat/_form-field.scss @@ -1,7 +1,6 @@ @use 'sass:math'; @use 'sass:map'; @use '@material/textfield' as mdc-textfield; -@use '@material/density' as mdc-density; @use '../../token-utils'; @use '../../../style/sass-utils'; @use '../../../theming/theming'; @@ -105,12 +104,16 @@ $prefix: (mat, form-field); // Tokens that can be configured through Angular Material's density theming API. @function get-density-tokens($theme) { - $density-scale: theming.clamp-density(inspection.get-theme-density($theme), -4); - $height: mdc-density.prop-value( - $density-config: mdc-textfield.$density-config, - $density-scale: inspection.get-theme-density($theme), - $property-name: height, + $density-scale: theming.clamp-density(inspection.get-theme-density($theme), -5); + $size-scale: ( + 0: 56px, + -1: 52px, + -2: 48px, + -3: 44px, + -4: 40px, + -5: 36px, ); + $height: map.get($size-scale, $density-scale); $hide-label: $height < mdc-textfield.$minimum-height-for-filled-label; // We computed the desired height of the form-field using the density configuration. The diff --git a/src/material/core/tokens/m2/mat/_icon-button.scss b/src/material/core/tokens/m2/mat/_icon-button.scss index 591af6611bf7..bc76f48a902a 100644 --- a/src/material/core/tokens/m2/mat/_icon-button.scss +++ b/src/material/core/tokens/m2/mat/_icon-button.scss @@ -1,6 +1,4 @@ -@use 'sass:map'; @use 'sass:meta'; -@use '@material/ripple/ripple-theme' as mdc-ripple-theme; @use '../../token-utils'; @use '../../../theming/theming'; @use '../../../theming/inspection'; @@ -18,10 +16,6 @@ $prefix: (mat, icon-button); // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { $is-dark: inspection.get-theme-type($theme) == dark; - $ripple-opacities: if($is-dark, - mdc-ripple-theme.$light-ink-opacities, - mdc-ripple-theme.$dark-ink-opacities - ); @return ( // Color of the element that shows the hover, focus and pressed states. @@ -34,13 +28,13 @@ $prefix: (mat, icon-button); ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1), // Opacity of the ripple when the button is hovered. - hover-state-layer-opacity: map.get($ripple-opacities, hover), + hover-state-layer-opacity: if($is-dark, 0.08, 0.04), // Opacity of the ripple when the button is focused. - focus-state-layer-opacity: map.get($ripple-opacities, focus), + focus-state-layer-opacity: if($is-dark, 0.24, 0.12), // Opacity of the ripple when the button is pressed. - pressed-state-layer-opacity: map.get($ripple-opacities, press), + pressed-state-layer-opacity: if($is-dark, 0.24, 0.12), ); } diff --git a/src/material/core/tokens/m2/mat/_outlined-button.scss b/src/material/core/tokens/m2/mat/_outlined-button.scss index 2626386bc1ea..2a1dccf51d1f 100644 --- a/src/material/core/tokens/m2/mat/_outlined-button.scss +++ b/src/material/core/tokens/m2/mat/_outlined-button.scss @@ -1,6 +1,4 @@ -@use 'sass:map'; @use 'sass:meta'; -@use '@material/ripple/ripple-theme' as mdc-ripple-theme; @use '../../token-utils'; @use '../../../theming/theming'; @use '../../../theming/inspection'; @@ -28,10 +26,6 @@ $prefix: (mat, outlined-button); // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { $is-dark: inspection.get-theme-type($theme) == dark; - $ripple-opacities: if($is-dark, - mdc-ripple-theme.$light-ink-opacities, - mdc-ripple-theme.$dark-ink-opacities - ); @return ( // Color of the element that shows the hover, focus and pressed states. @@ -44,13 +38,13 @@ $prefix: (mat, outlined-button); ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1), // Opacity of the ripple when the button is hovered. - hover-state-layer-opacity: map.get($ripple-opacities, hover), + hover-state-layer-opacity: if($is-dark, 0.08, 0.04), // Opacity of the ripple when the button is focused. - focus-state-layer-opacity: map.get($ripple-opacities, focus), + focus-state-layer-opacity: if($is-dark, 0.24, 0.12), // Opacity of the ripple when the button is pressed. - pressed-state-layer-opacity: map.get($ripple-opacities, press), + pressed-state-layer-opacity: if($is-dark, 0.24, 0.12), ); } diff --git a/src/material/core/tokens/m2/mat/_paginator.scss b/src/material/core/tokens/m2/mat/_paginator.scss index 8a2398484773..b0cde30bda2a 100644 --- a/src/material/core/tokens/m2/mat/_paginator.scss +++ b/src/material/core/tokens/m2/mat/_paginator.scss @@ -1,7 +1,6 @@ @use 'sass:math'; @use 'sass:map'; @use '@material/textfield' as mdc-textfield; -@use '@material/density' as mdc-density; @use '../../token-utils'; @use '../../../theming/theming'; @use '../../../theming/inspection'; @@ -51,12 +50,17 @@ $prefix: (mat, paginator); -4: 40px, -5: 40px, ); - $form-field-density-scale: if($density-scale > -4, -4, $density-scale); - $form-field-height: mdc-density.prop-value( - $density-config: mdc-textfield.$density-config, - $density-scale: $form-field-density-scale, - $property-name: height, + $form-field-size-scale: ( + 0: 56px, + -1: 52px, + -2: 48px, + -3: 44px, + -4: 40px, + -5: 36px, ); + $form-field-density-scale: if($density-scale > -4, -4, $density-scale); + $form-field-height: map.get($form-field-size-scale, $form-field-density-scale); + // We computed the desired height of the form-field using the density configuration. The // spec only describes vertical spacing/alignment in non-dense mode. This means that we // cannot update the spacing to explicit numbers based on the density scale. Instead, we diff --git a/src/material/core/tokens/m2/mat/_protected-button.scss b/src/material/core/tokens/m2/mat/_protected-button.scss index 26c621efcd1b..13c4cfa7fdaf 100644 --- a/src/material/core/tokens/m2/mat/_protected-button.scss +++ b/src/material/core/tokens/m2/mat/_protected-button.scss @@ -1,6 +1,4 @@ -@use 'sass:map'; @use 'sass:meta'; -@use '@material/ripple/ripple-theme' as mdc-ripple-theme; @use '../../token-utils'; @use '../../../theming/theming'; @use '../../../theming/inspection'; @@ -29,10 +27,6 @@ $prefix: (mat, protected-button); // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { $is-dark: inspection.get-theme-type($theme) == dark; - $ripple-opacities: if($is-dark, - mdc-ripple-theme.$light-ink-opacities, - mdc-ripple-theme.$dark-ink-opacities - ); @return ( // Color of the element that shows the hover, focus and pressed states. @@ -45,13 +39,13 @@ $prefix: (mat, protected-button); ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1), // Opacity of the ripple when the button is hovered. - hover-state-layer-opacity: map.get($ripple-opacities, hover), + hover-state-layer-opacity: if($is-dark, 0.08, 0.04), // Opacity of the ripple when the button is focused. - focus-state-layer-opacity: map.get($ripple-opacities, focus), + focus-state-layer-opacity: if($is-dark, 0.24, 0.12), // Opacity of the ripple when the button is pressed. - pressed-state-layer-opacity: map.get($ripple-opacities, press), + pressed-state-layer-opacity: if($is-dark, 0.24, 0.12), ); } diff --git a/src/material/core/tokens/m2/mat/_select.scss b/src/material/core/tokens/m2/mat/_select.scss index 5dbfa01aa2a1..d912d688b850 100644 --- a/src/material/core/tokens/m2/mat/_select.scss +++ b/src/material/core/tokens/m2/mat/_select.scss @@ -2,8 +2,8 @@ @use '../../token-utils'; @use '../../../theming/inspection'; @use '../../../theming/theming'; +@use '../../../style/elevation'; @use '../../../style/sass-utils'; -@use '@material/elevation/elevation-theme' as mdc-elevation; // The prefix used to generate the fully qualified name for tokens in this file. $prefix: (mat, select); @@ -12,7 +12,7 @@ $prefix: (mat, select); // but may be in a future version of the theming API. @function get-unthemable-tokens() { @return ( - container-elevation-shadow: mdc-elevation.elevation-box-shadow(8), + container-elevation-shadow: elevation.get-box-shadow(8), ); } diff --git a/src/material/core/tokens/m2/mat/_sidenav.scss b/src/material/core/tokens/m2/mat/_sidenav.scss index 080c3c121007..bef76fa1a250 100644 --- a/src/material/core/tokens/m2/mat/_sidenav.scss +++ b/src/material/core/tokens/m2/mat/_sidenav.scss @@ -1,8 +1,8 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; @use 'sass:color'; @use 'sass:meta'; @use '../../token-utils'; @use '../../../theming/inspection'; +@use '../../../style/elevation'; @use '../../../style/sass-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -15,7 +15,7 @@ $prefix: (mat, sidenav); // Currently zero, but it appears to be relevant for M3. // See: https://m3.material.io/components/navigation-drawer/overview container-shape: 0, - container-elevation-shadow: mdc-elevation.elevation-box-shadow(16), + container-elevation-shadow: elevation.get-box-shadow(16), container-width: auto, ); } diff --git a/src/material/core/tokens/m2/mat/_text-button.scss b/src/material/core/tokens/m2/mat/_text-button.scss index 75603f1151df..70205ee5c7e8 100644 --- a/src/material/core/tokens/m2/mat/_text-button.scss +++ b/src/material/core/tokens/m2/mat/_text-button.scss @@ -1,6 +1,4 @@ -@use 'sass:map'; @use 'sass:meta'; -@use '@material/ripple/ripple-theme' as mdc-ripple-theme; @use '../../token-utils'; @use '../../../theming/theming'; @use '../../../theming/inspection'; @@ -31,10 +29,6 @@ $prefix: (mat, text-button); // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { $is-dark: inspection.get-theme-type($theme) == dark; - $ripple-opacities: if($is-dark, - mdc-ripple-theme.$light-ink-opacities, - mdc-ripple-theme.$dark-ink-opacities - ); @return ( // Color of the element that shows the hover, focus and pressed states. @@ -47,13 +41,13 @@ $prefix: (mat, text-button); ripple-color: inspection.get-theme-color($theme, foreground, base, 0.1), // Opacity of the ripple when the button is hovered. - hover-state-layer-opacity: map.get($ripple-opacities, hover), + hover-state-layer-opacity: if($is-dark, 0.08, 0.04), // Opacity of the ripple when the button is focused. - focus-state-layer-opacity: map.get($ripple-opacities, focus), + focus-state-layer-opacity: if($is-dark, 0.24, 0.12), // Opacity of the ripple when the button is pressed. - pressed-state-layer-opacity: map.get($ripple-opacities, press), + pressed-state-layer-opacity: if($is-dark, 0.24, 0.12), ); } diff --git a/src/material/core/tokens/m2/mdc/_chip.scss b/src/material/core/tokens/m2/mdc/_chip.scss index 9a5548bef329..7ecc9451c9a2 100644 --- a/src/material/core/tokens/m2/mdc/_chip.scss +++ b/src/material/core/tokens/m2/mdc/_chip.scss @@ -19,21 +19,9 @@ $prefix: (mdc, chip); @function get-unthemable-tokens() { @return ( // The shape & radius of the chip. - container-shape: - ( - family: 'rounded', - radius: ( - 16px 16px 16px 16px, - ) - ), + container-shape-radius: 16px, // The shape & radius of the avatar. - with-avatar-avatar-shape: - ( - family: 'rounded', - radius: ( - 14px 14px 14px 14px, - ) - ), + with-avatar-avatar-shape-radius: 14px, // The width & height of the chip avatar. with-avatar-avatar-size: 28px, // The width & height of the chip icon. diff --git a/src/material/core/tokens/m2/mdc/_elevated-card.scss b/src/material/core/tokens/m2/mdc/_elevated-card.scss index fe5a649c9bf1..d5018d78f26f 100644 --- a/src/material/core/tokens/m2/mdc/_elevated-card.scss +++ b/src/material/core/tokens/m2/mdc/_elevated-card.scss @@ -1,5 +1,5 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; @use '../../../theming/inspection'; +@use '../../../style/elevation'; @use '../../../style/sass-utils'; @use '../../token-utils'; @@ -51,7 +51,7 @@ $prefix: (mdc, elevated-card); @return ( // The background color of the card. container-color: inspection.get-theme-color($theme, background, card), - container-elevation: mdc-elevation.elevation-box-shadow(1), + container-elevation: elevation.get-box-shadow(1), ); } diff --git a/src/material/core/tokens/m2/mdc/_extended-fab.scss b/src/material/core/tokens/m2/mdc/_extended-fab.scss index d4f5bcb9b938..042c01eced40 100644 --- a/src/material/core/tokens/m2/mdc/_extended-fab.scss +++ b/src/material/core/tokens/m2/mdc/_extended-fab.scss @@ -1,9 +1,8 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use 'sass:map'; @use '../../token-utils'; +@use '../../../style/elevation'; @use '../../../theming/inspection'; -@use 'sass:map'; - // The prefix used to generate the fully qualified name for tokens in this file. $prefix: (mdc, extended-fab); @@ -11,6 +10,10 @@ $prefix: (mdc, extended-fab); @return ( container-height: 48px, container-shape: 24px, + container-elevation-shadow: elevation.get-box-shadow(6), + focus-container-elevation-shadow: elevation.get-box-shadow(8), + hover-container-elevation-shadow: elevation.get-box-shadow(8), + pressed-container-elevation-shadow: elevation.get-box-shadow(12), // ============================================================================================= // = TOKENS NOT USED IN ANGULAR MATERIAL = @@ -50,12 +53,7 @@ $prefix: (mdc, extended-fab); // Tokens that can be configured through Angular Material's color theming API. @function get-color-tokens($theme) { - @return ( - container-elevation-shadow: mdc-elevation.elevation-box-shadow(6), - focus-container-elevation-shadow: mdc-elevation.elevation-box-shadow(8), - hover-container-elevation-shadow: mdc-elevation.elevation-box-shadow(8), - pressed-container-elevation-shadow: mdc-elevation.elevation-box-shadow(12), - ); + @return (); } // Tokens that can be configured through Angular Material's typography theming API. diff --git a/src/material/core/tokens/m2/mdc/_fab-small.scss b/src/material/core/tokens/m2/mdc/_fab-small.scss index b703adf98a4c..9aaf82d19229 100644 --- a/src/material/core/tokens/m2/mdc/_fab-small.scss +++ b/src/material/core/tokens/m2/mdc/_fab-small.scss @@ -1,15 +1,18 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use 'sass:map'; @use '../../../theming/inspection'; +@use '../../../style/elevation'; @use '../../token-utils'; -@use 'sass:map'; - // The prefix used to generate the fully qualified name for tokens in this file. $prefix: (mdc, fab-small); @function get-unthemable-tokens() { @return ( container-shape: 50%, + container-elevation-shadow: elevation.get-box-shadow(6), + focus-container-elevation-shadow: elevation.get-box-shadow(8), + hover-container-elevation-shadow: elevation.get-box-shadow(8), + pressed-container-elevation-shadow: elevation.get-box-shadow(12), // ============================================================================================= // = TOKENS NOT USED IN ANGULAR MATERIAL = @@ -53,10 +56,6 @@ $prefix: (mdc, fab-small); @return ( // Background color of the FAB. container-color: inspection.get-theme-color($theme, background, card), - container-elevation-shadow: mdc-elevation.elevation-box-shadow(6), - focus-container-elevation-shadow: mdc-elevation.elevation-box-shadow(8), - hover-container-elevation-shadow: mdc-elevation.elevation-box-shadow(8), - pressed-container-elevation-shadow: mdc-elevation.elevation-box-shadow(12), ); } diff --git a/src/material/core/tokens/m2/mdc/_fab.scss b/src/material/core/tokens/m2/mdc/_fab.scss index e16027f43142..b751b75ee9f1 100644 --- a/src/material/core/tokens/m2/mdc/_fab.scss +++ b/src/material/core/tokens/m2/mdc/_fab.scss @@ -1,15 +1,18 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use 'sass:map'; @use '../../../theming/inspection'; +@use '../../../style/elevation'; @use '../../token-utils'; -@use 'sass:map'; - // The prefix used to generate the fully qualified name for tokens in this file. $prefix: (mdc, fab); @function get-unthemable-tokens() { @return ( container-shape: 50%, + container-elevation-shadow: elevation.get-box-shadow(6), + focus-container-elevation-shadow: elevation.get-box-shadow(8), + hover-container-elevation-shadow: elevation.get-box-shadow(8), + pressed-container-elevation-shadow: elevation.get-box-shadow(12), // ============================================================================================= // = TOKENS NOT USED IN ANGULAR MATERIAL = @@ -54,10 +57,6 @@ $prefix: (mdc, fab); @return ( // Background color of the FAB. container-color: inspection.get-theme-color($theme, background, card), - container-elevation-shadow: mdc-elevation.elevation-box-shadow(6), - focus-container-elevation-shadow: mdc-elevation.elevation-box-shadow(8), - hover-container-elevation-shadow: mdc-elevation.elevation-box-shadow(8), - pressed-container-elevation-shadow: mdc-elevation.elevation-box-shadow(12), ); } diff --git a/src/material/core/tokens/m2/mdc/_outlined-card.scss b/src/material/core/tokens/m2/mdc/_outlined-card.scss index 38e45fbc65ca..d9dea0ec0b15 100644 --- a/src/material/core/tokens/m2/mdc/_outlined-card.scss +++ b/src/material/core/tokens/m2/mdc/_outlined-card.scss @@ -1,4 +1,3 @@ -@use '@material/elevation/elevation-theme' as mdc-elevation; @use '../../../style/elevation'; @use '../../../theming/inspection'; @use '../../../style/sass-utils'; @@ -60,7 +59,7 @@ $prefix: (mdc, outlined-card); container-color: inspection.get-theme-color($theme, background, card), // The border color of the card. outline-color: rgba(inspection.get-theme-color($theme, foreground, base), 0.12), - container-elevation: mdc-elevation.elevation-box-shadow(0), + container-elevation: elevation.get-box-shadow(0), ); } diff --git a/src/material/core/tokens/m2/mdc/_protected-button.scss b/src/material/core/tokens/m2/mdc/_protected-button.scss index c3b8eac6211e..072d82bd938a 100644 --- a/src/material/core/tokens/m2/mdc/_protected-button.scss +++ b/src/material/core/tokens/m2/mdc/_protected-button.scss @@ -1,6 +1,7 @@ @use 'sass:map'; @use '../../token-utils'; @use '../../../style/sass-utils'; +@use '../../../style/elevation'; @use '../../../theming/inspection'; @use '../../../theming/theming'; @use '../../../mdc-helpers/mdc-helpers'; @@ -17,7 +18,11 @@ $prefix: (mdc, protected-button); @function get-unthemable-tokens() { @return ( container-shape: 4px, - keep-touch-target: false, + container-elevation-shadow: elevation.get-box-shadow(2), + disabled-container-elevation-shadow: elevation.get-box-shadow(0), + focus-container-elevation-shadow: elevation.get-box-shadow(4), + hover-container-elevation-shadow: elevation.get-box-shadow(4), + pressed-container-elevation-shadow: elevation.get-box-shadow(8), // ============================================================================================= // = TOKENS NOT USED IN ANGULAR MATERIAL = @@ -39,6 +44,13 @@ $prefix: (mdc, protected-button); focus-state-layer-color: null, hover-state-layer-color: null, pressed-state-layer-color: null, + keep-touch-target: null, + container-elevation: null, + disabled-container-elevation: null, + focus-container-elevation: null, + hover-container-elevation: null, + pressed-container-elevation: null, + container-shadow-color: null, ); } @@ -53,12 +65,6 @@ $prefix: (mdc, protected-button); 0.12), disabled-label-text-color: inspection.get-theme-color($theme, foreground, disabled-button, if($is-dark, 0.5, 0.38)), - container-elevation: 2, - disabled-container-elevation: 0, - focus-container-elevation: 4, - hover-container-elevation: 4, - pressed-container-elevation: 8, - container-shadow-color: #000, ); } diff --git a/src/material/core/tokens/m3/mat/_app.scss b/src/material/core/tokens/m3/mat/_app.scss index e54826db04c5..4144c6a29846 100644 --- a/src/material/core/tokens/m3/mat/_app.scss +++ b/src/material/core/tokens/m3/mat/_app.scss @@ -1,5 +1,5 @@ @use 'sass:map'; -@use '@material/elevation' as mdc-elevation; +@use '../../../style/elevation'; @use '../../token-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -19,7 +19,7 @@ $prefix: (mat, app); @if ($shadow-color) { @for $zValue from 0 through 24 { - $shadow: mdc-elevation.elevation-box-shadow($zValue, $shadow-color); + $shadow: elevation.get-box-shadow($zValue, $shadow-color); $tokens: map.set($tokens, 'elevation-shadow-level-#{$zValue}', $shadow); } } diff --git a/src/material/core/tokens/m3/mat/_autocomplete.scss b/src/material/core/tokens/m3/mat/_autocomplete.scss index 485e452f4275..749153db51f4 100644 --- a/src/material/core/tokens/m3/mat/_autocomplete.scss +++ b/src/material/core/tokens/m3/mat/_autocomplete.scss @@ -1,5 +1,5 @@ @use 'sass:map'; -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use '../../../style/elevation'; @use '../../token-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -15,7 +15,7 @@ $prefix: (mat, autocomplete); background-color: map.get($systems, md-sys-color, surface-container), container-shape: map.get($systems, md-sys-shape, corner-extra-small), container-elevation-shadow: - token-utils.hardcode(mdc-elevation.elevation-box-shadow(2), $exclude-hardcoded), + token-utils.hardcode(elevation.get-box-shadow(2), $exclude-hardcoded), ); @return token-utils.namespace-tokens($prefix, $tokens, $token-slots); diff --git a/src/material/core/tokens/m3/mat/_datepicker.scss b/src/material/core/tokens/m3/mat/_datepicker.scss index 3f33859e3eef..25c106cc6b9b 100644 --- a/src/material/core/tokens/m3/mat/_datepicker.scss +++ b/src/material/core/tokens/m3/mat/_datepicker.scss @@ -1,7 +1,7 @@ @use 'sass:map'; +@use '../../../style/elevation'; @use '../../../style/sass-utils'; @use '../../token-utils'; -@use '@material/elevation' as mdc-elevation; // The prefix used to generate the fully qualified name for tokens in this file. $prefix: (mat, datepicker); @@ -67,10 +67,10 @@ $prefix: (mat, datepicker); ), calendar-container-background-color: map.get($systems, md-sys-color, surface-container-high), calendar-container-text-color: map.get($systems, md-sys-color, on-surface), - calendar-container-elevation-shadow: token-utils.hardcode(mdc-elevation.elevation-box-shadow(0), + calendar-container-elevation-shadow: token-utils.hardcode(elevation.get-box-shadow(0), $exclude-hardcoded), calendar-container-touch-elevation-shadow: - token-utils.hardcode(mdc-elevation.elevation-box-shadow(0), $exclude-hardcoded), + token-utils.hardcode(elevation.get-box-shadow(0), $exclude-hardcoded), calendar-container-shape: map.get($systems, md-sys-shape, corner-large), calendar-container-touch-shape: map.get($systems, md-sys-shape, corner-extra-large), calendar-text-font: map.get($systems, md-sys-typescale, body-large-font), diff --git a/src/material/core/tokens/m3/mat/_select.scss b/src/material/core/tokens/m3/mat/_select.scss index 0e928e015928..b97af9a6b7f5 100644 --- a/src/material/core/tokens/m3/mat/_select.scss +++ b/src/material/core/tokens/m3/mat/_select.scss @@ -1,7 +1,7 @@ @use 'sass:map'; @use '../../../style/sass-utils'; +@use '../../../style/elevation'; @use '../../token-utils'; -@use '@material/elevation' as mdc-elevation; // The prefix used to generate the fully qualified name for tokens in this file. $prefix: (mat, select); @@ -26,7 +26,7 @@ $prefix: (mat, select); focused-arrow-color: map.get($systems, md-sys-color, primary), invalid-arrow-color: map.get($systems, md-sys-color, error), container-elevation-shadow: - token-utils.hardcode(mdc-elevation.elevation-box-shadow(2), $exclude-hardcoded), + token-utils.hardcode(elevation.get-box-shadow(2), $exclude-hardcoded), ) ), ( // Color variants: diff --git a/src/material/core/tokens/m3/mdc/_chip.scss b/src/material/core/tokens/m3/mdc/_chip.scss index 943ede0c943e..7ba9188def22 100644 --- a/src/material/core/tokens/m3/mdc/_chip.scss +++ b/src/material/core/tokens/m3/mdc/_chip.scss @@ -16,10 +16,7 @@ $prefix: (mdc, chip); $tokens: sass-utils.merge-all( token-utils.generate-typography-tokens($systems, label-text, label-large), ( - container-shape: token-utils.hardcode(( - family: rounded, - radius: 8px, - ), $exclude-hardcoded), + container-shape-radius: token-utils.hardcode(8px, $exclude-hardcoded), with-avatar-avatar-size: token-utils.hardcode(24px, $exclude-hardcoded), label-text-color: map.get($systems, md-sys-color, on-surface-variant), disabled-label-text-color: sass-utils.safe-color-change( diff --git a/src/material/core/tokens/m3/mdc/_elevated-card.scss b/src/material/core/tokens/m3/mdc/_elevated-card.scss index 628e3a5f56e7..81cc4edf288a 100644 --- a/src/material/core/tokens/m3/mdc/_elevated-card.scss +++ b/src/material/core/tokens/m3/mdc/_elevated-card.scss @@ -1,5 +1,5 @@ @use 'sass:map'; -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use '../../../style/elevation'; @use '../../token-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -15,7 +15,7 @@ $prefix: (mdc, elevated-card); $elevation: map.get($tokens, container-elevation); @if ($elevation != null) { - $tokens: map.set($tokens, container-elevation, mdc-elevation.elevation-box-shadow($elevation)); + $tokens: map.set($tokens, container-elevation, elevation.get-box-shadow($elevation)); } @return token-utils.namespace-tokens($prefix, $tokens, $token-slots); diff --git a/src/material/core/tokens/m3/mdc/_extended-fab.scss b/src/material/core/tokens/m3/mdc/_extended-fab.scss index bfc50e5964a6..48544bb0c50c 100644 --- a/src/material/core/tokens/m3/mdc/_extended-fab.scss +++ b/src/material/core/tokens/m3/mdc/_extended-fab.scss @@ -1,5 +1,5 @@ @use 'sass:map'; -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use '../../../style/elevation'; @use '../../token-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -23,7 +23,7 @@ $prefix: (mdc, extended-fab); $elevation: map.get($tokens, $token); @if ($elevation != null) { - $tokens: map.set($tokens, $token + '-shadow', mdc-elevation.elevation-box-shadow($elevation)); + $tokens: map.set($tokens, $token + '-shadow', elevation.get-box-shadow($elevation)); } } diff --git a/src/material/core/tokens/m3/mdc/_fab-small.scss b/src/material/core/tokens/m3/mdc/_fab-small.scss index 2049f22d4551..60477725148d 100644 --- a/src/material/core/tokens/m3/mdc/_fab-small.scss +++ b/src/material/core/tokens/m3/mdc/_fab-small.scss @@ -1,5 +1,5 @@ @use 'sass:map'; -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use '../../../style/elevation'; @use '../../token-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -23,7 +23,7 @@ $prefix: (mdc, fab-small); $elevation: map.get($tokens, $token); @if ($elevation != null) { - $tokens: map.set($tokens, $token + '-shadow', mdc-elevation.elevation-box-shadow($elevation)); + $tokens: map.set($tokens, $token + '-shadow', elevation.get-box-shadow($elevation)); } } diff --git a/src/material/core/tokens/m3/mdc/_fab.scss b/src/material/core/tokens/m3/mdc/_fab.scss index 2695f6ce9207..acd7719072b7 100644 --- a/src/material/core/tokens/m3/mdc/_fab.scss +++ b/src/material/core/tokens/m3/mdc/_fab.scss @@ -1,5 +1,5 @@ @use 'sass:map'; -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use '../../../style/elevation'; @use '../../token-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -23,7 +23,7 @@ $prefix: (mdc, fab); $elevation: map.get($tokens, $token); @if ($elevation != null) { - $tokens: map.set($tokens, $token + '-shadow', mdc-elevation.elevation-box-shadow($elevation)); + $tokens: map.set($tokens, $token + '-shadow', elevation.get-box-shadow($elevation)); } } diff --git a/src/material/core/tokens/m3/mdc/_outlined-card.scss b/src/material/core/tokens/m3/mdc/_outlined-card.scss index 54ce7a762d51..dd55b5004d3a 100644 --- a/src/material/core/tokens/m3/mdc/_outlined-card.scss +++ b/src/material/core/tokens/m3/mdc/_outlined-card.scss @@ -1,5 +1,5 @@ @use 'sass:map'; -@use '@material/elevation/elevation-theme' as mdc-elevation; +@use '../../../style/elevation'; @use '../../token-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -15,7 +15,7 @@ $prefix: (mdc, outlined-card); $elevation: map.get($tokens, container-elevation); @if ($elevation != null) { - $tokens: map.set($tokens, container-elevation, mdc-elevation.elevation-box-shadow($elevation)); + $tokens: map.set($tokens, container-elevation, elevation.get-box-shadow($elevation)); } @return token-utils.namespace-tokens($prefix, $tokens, $token-slots); diff --git a/src/material/core/tokens/m3/mdc/_protected-button.scss b/src/material/core/tokens/m3/mdc/_protected-button.scss index 98b11a2b2b34..ead16381af9b 100644 --- a/src/material/core/tokens/m3/mdc/_protected-button.scss +++ b/src/material/core/tokens/m3/mdc/_protected-button.scss @@ -1,5 +1,6 @@ @use 'sass:map'; @use 'sass:meta'; +@use '../../../style/elevation'; @use '../../token-utils'; // The prefix used to generate the fully qualified name for tokens in this file. @@ -12,7 +13,7 @@ $prefix: (mdc, protected-button); /// @return {Map} A set of tokens for the MDC protected-button @function get-tokens($systems, $exclude-hardcoded, $token-slots) { // Note: in M3 the "protected" button is called "elevated". - $mdc-tokens: token-utils.get-mdc-tokens('elevated-button', $systems, $exclude-hardcoded); + $tokens: token-utils.get-mdc-tokens('elevated-button', $systems, $exclude-hardcoded); $variant-tokens: ( primary: (), // Default, no overrides needed. secondary: ( @@ -57,7 +58,7 @@ $prefix: (mdc, protected-button); ); @return token-utils.namespace-tokens($prefix, ( - _fix-tokens($mdc-tokens), + _fix-tokens($tokens), token-utils.map-values($variant-tokens, meta.get-function(_fix-tokens)), ), $token-slots); } @@ -67,11 +68,27 @@ $prefix: (mdc, protected-button); /// @param {Map} $initial-tokens Map of protected button tokens currently being generated. /// @return {Map} The given tokens, with the invalid values replaced with valid ones. @function _fix-tokens($initial-tokens) { - // Need to get the hardcoded values, because they include opacities that are used for the disabled - // state. + // Need to get the hardcoded values, because they include + // opacities that are used for the disabled state. $hardcoded-tokens: token-utils.get-mdc-tokens('elevated-button', (), false); + $tokens: $initial-tokens; + $elevation-tokens: ( + container-elevation, + disabled-container-elevation, + focus-container-elevation, + hover-container-elevation, + pressed-container-elevation, + ); + + @each $token in $elevation-tokens { + $elevation: map.get($tokens, $token); + + @if ($elevation != null) { + $tokens: map.set($tokens, $token + '-shadow', elevation.get-box-shadow($elevation)); + } + } - @return token-utils.combine-color-tokens($initial-tokens, $hardcoded-tokens, ( + @return token-utils.combine-color-tokens($tokens, $hardcoded-tokens, ( ( color: disabled-label-text-color, opacity: disabled-label-text-opacity, diff --git a/src/material/core/tokens/tests/test-validate-tokens.scss b/src/material/core/tokens/tests/test-validate-tokens.scss index 9af8a6fb23ec..42fd0bfa32d3 100644 --- a/src/material/core/tokens/tests/test-validate-tokens.scss +++ b/src/material/core/tokens/tests/test-validate-tokens.scss @@ -2,7 +2,6 @@ @use 'sass:map'; @use '@material/button/button-outlined-theme' as mdc-button-outlined-theme; -@use '@material/button/button-protected-theme' as mdc-button-protected-theme; @use '@material/button/button-filled-theme' as mdc-button-filled-theme; @use '@material/button/button-text-theme' as mdc-button-text-theme; @use '@material/card/elevated-card-theme' as mdc-elevated-card-theme; @@ -18,13 +17,11 @@ @use '@material/tab-indicator/tab-indicator-theme' as mdc-tab-indicator-theme; @use '@material/snackbar/snackbar-theme' as mdc-snackbar-theme; @use '@material/slider/slider-theme' as mdc-slider-theme; -@use '@material/chips/chip-theme' as mdc-chips-theme; @use '@material/dialog/dialog-theme' as mdc-dialog-theme; @use '@material/textfield/filled-text-field-theme' as mdc-filled-text-field-theme; @use '@material/textfield/outlined-text-field-theme' as mdc-outlined-text-field-theme; @use '@material/theme/validate' as mdc-validate; -@use '../m2/mdc/protected-button' as tokens-mdc-protected-button; @use '../m2/mdc/filled-button' as tokens-mdc-filled-button; @use '../m2/mdc/text-button' as tokens-mdc-text-button; @use '../m2/mdc/outlined-button' as tokens-mdc-outlined-button; @@ -41,7 +38,6 @@ @use '../m2/mdc/tab-indicator' as tokens-mdc-tab-indicator; @use '../m2/mdc/snack-bar' as tokens-mdc-snack-bar; @use '../m2/mdc/slider' as tokens-mdc-slider; -@use '../m2/mdc/chip' as tokens-mdc-chip; @use '../m2/mdc/dialog' as tokens-mdc-dialog; @use '../m2/mdc/filled-text-field' as tokens-mdc-filled-text-field; @use '../m2/mdc/outlined-text-field' as tokens-mdc-outlined-text-field; @@ -123,11 +119,6 @@ $slots: tokens-mdc-slider.get-token-slots(), $reference: mdc-slider-theme.$light-theme ); -@include validate-slots( - $component: 'm2.mdc.chips', - $slots: tokens-mdc-chip.get-token-slots(), - $reference: mdc-chips-theme.$light-theme -); @include validate-slots( $component: 'm2.mdc.dialog', $slots: tokens-mdc-dialog.get-token-slots(), @@ -148,11 +139,6 @@ $slots: tokens-mdc-filled-button.get-token-slots(), $reference: mdc-button-filled-theme.$light-theme ); -@include validate-slots( - $component: 'm2.mdc.protected-button', - $slots: tokens-mdc-protected-button.get-token-slots(), - $reference: mdc-button-protected-theme.$light-theme -); @include validate-slots( $component: 'm2.mdc.text-button', $slots: tokens-mdc-text-button.get-token-slots(), diff --git a/src/material/datepicker/datepicker-input-base.ts b/src/material/datepicker/datepicker-input-base.ts index 611d39a2ebf7..f0980c5a6c9e 100644 --- a/src/material/datepicker/datepicker-input-base.ts +++ b/src/material/datepicker/datepicker-input-base.ts @@ -56,7 +56,11 @@ export class MatDatepickerInputEvent { } } -/** Function that can be used to filter out dates from a calendar. */ +/** + * Function that can be used to filter out dates from a calendar. + * Datepicker can sometimes receive a null value as input for the date argument. + * This doesn't represent a "null date" but rather signifies that no date has been selected yet in the calendar. + */ export type DateFilterFn = (date: D | null) => boolean; /** diff --git a/src/material/dialog/dialog.scss b/src/material/dialog/dialog.scss index c6f4702b119c..57a831033745 100644 --- a/src/material/dialog/dialog.scss +++ b/src/material/dialog/dialog.scss @@ -1,5 +1,4 @@ @use '@angular/cdk'; -@use '@material/typography/typography' as mdc-typography; @use '../core/tokens/m2/mdc/dialog' as tokens-mdc-dialog; @use '../core/tokens/m2/mat/dialog' as tokens-mat-dialog; @use '../core/mdc-helpers/mdc-helpers'; @@ -153,12 +152,23 @@ $_emit-fallbacks: true; } .mat-mdc-dialog-title { - @include mdc-typography.text-baseline($top: 40px, $display: block, $lineHeight: null); + display: block; position: relative; flex-shrink: 0; box-sizing: border-box; margin: 0 0 1px; + // This was used by MDC to set the text baseline. We should figure out a way to + // remove it, because it can introduce unnecessary whitespace at the beginning + // of the element. + &::before { + display: inline-block; + width: 0; + height: 40px; + content: ''; + vertical-align: 0; + } + [dir='rtl'] & { text-align: right; } diff --git a/src/material/form-field/_form-field-subscript.scss b/src/material/form-field/_form-field-subscript.scss index d65aa058d89e..84edca21a142 100644 --- a/src/material/form-field/_form-field-subscript.scss +++ b/src/material/form-field/_form-field-subscript.scss @@ -1,7 +1,7 @@ -@use '@material/typography' as mdc-typography; @use '@material/textfield/variables' as mdc-textfield-variables; @use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field; +@use '../core/style/vendor-prefixes'; @use '../core/tokens/token-utils'; @mixin private-form-field-subscript() { @@ -68,7 +68,7 @@ .mat-mdc-form-field-bottom-align::before { @include token-utils.use-tokens(tokens-mat-form-field.$prefix, tokens-mat-form-field.get-token-slots()) { - @include mdc-typography.smooth-font(); + @include vendor-prefixes.smooth-font(); @include token-utils.create-token-slot(font-family, subscript-text-font); @include token-utils.create-token-slot(line-height, subscript-text-line-height); @include token-utils.create-token-slot(font-size, subscript-text-size); diff --git a/src/material/form-field/_mdc-text-field-structure-overrides.scss b/src/material/form-field/_mdc-text-field-structure-overrides.scss index 94609c8dccda..f4a85b1953fd 100644 --- a/src/material/form-field/_mdc-text-field-structure-overrides.scss +++ b/src/material/form-field/_mdc-text-field-structure-overrides.scss @@ -1,5 +1,4 @@ @use '@material/textfield/variables' as mdc-textfield-variables; -@use '@material/typography/typography' as mdc-typography; @use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field; @use '../core/tokens/token-utils'; @use '../core/style/vendor-prefixes'; @@ -25,7 +24,7 @@ $_enable-form-field-will-change-reset: true; // Note: We increase specificity here because the MDC textfield seems to override this, // depending on the CSS order, with an affix selector joint with the input. .mat-mdc-form-field-input-control.mat-mdc-form-field-input-control { - @include mdc-typography.smooth-font(); + @include vendor-prefixes.smooth-font(); font: inherit; letter-spacing: inherit; text-decoration: inherit; @@ -34,7 +33,7 @@ $_enable-form-field-will-change-reset: true; } .mat-mdc-form-field .mat-mdc-floating-label.mdc-floating-label { - @include mdc-typography.smooth-font(); + @include vendor-prefixes.smooth-font(); // In order to ensure proper alignment of the floating label, we reset its line-height. // The line-height is not important as the element is absolutely positioned and only has one diff --git a/src/material/form-field/form-field.scss b/src/material/form-field/form-field.scss index 1edabf17fecb..48d5481ee235 100644 --- a/src/material/form-field/form-field.scss +++ b/src/material/form-field/form-field.scss @@ -8,7 +8,6 @@ @use '@material/line-ripple/line-ripple' as mdc-line-ripple; @use '@material/line-ripple/line-ripple-theme' as mdc-line-ripple-theme; @use '@material/theme/custom-properties' as mdc-custom-properties; -@use '@material/typography' as mdc-typography; @use '../core/tokens/token-utils'; @use '../core/mdc-helpers/mdc-helpers'; @use '../core/style/vendor-prefixes'; @@ -91,7 +90,7 @@ $_icon-prefix-infix-padding: 4px; @include token-utils.use-tokens(tokens-mat-form-field.$prefix, tokens-mat-form-field.get-token-slots()) { - @include mdc-typography.smooth-font(); + @include vendor-prefixes.smooth-font(); @include token-utils.create-token-slot(font-family, container-text-font); @include token-utils.create-token-slot(line-height, container-text-line-height); @include token-utils.create-token-slot(font-size, container-text-size); diff --git a/src/material/list/BUILD.bazel b/src/material/list/BUILD.bazel index 5bd7a26b1467..ded4cec61b5d 100644 --- a/src/material/list/BUILD.bazel +++ b/src/material/list/BUILD.bazel @@ -42,6 +42,15 @@ sass_library( ], ) +sass_library( + name = "inherited_structure_scss_lib", + srcs = ["_list-inherited-structure.scss"], + deps = [ + "//src/cdk:sass_lib", + "//src/material/core:core_scss_lib", + ], +) + sass_library( name = "list_scss_lib", srcs = glob(["**/_*.scss"]), @@ -59,6 +68,7 @@ sass_binary( src = "list.scss", deps = [ ":hcm_indicator_scss_lib", + ":inherited_structure_scss_lib", "//:mdc_sass_lib", "//src/material/core:core_scss_lib", ], diff --git a/src/material/list/_list-inherited-structure.scss b/src/material/list/_list-inherited-structure.scss new file mode 100644 index 000000000000..f8dea90da71d --- /dev/null +++ b/src/material/list/_list-inherited-structure.scss @@ -0,0 +1,516 @@ +@use '@angular/cdk'; +@use '../core/style/vendor-prefixes'; +@use '../core/tokens/m2/mdc/list' as tokens-mdc-list; +@use '../core/tokens/token-utils'; + +// Includes the structural styles for the list that were inherited from MDC. +@mixin private-list-inherited-structural-styles { + $tokens: (tokens-mdc-list.$prefix, tokens-mdc-list.get-token-slots()); + + .mdc-list { + margin: 0; + padding: 8px 0; + list-style-type: none; + + &:focus { + outline: none; + } + } + + .mdc-list-item { + display: flex; + position: relative; + justify-content: flex-start; + overflow: hidden; + padding: 0; + align-items: stretch; + cursor: pointer; + padding-left: 16px; + padding-right: 16px; + + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(background-color, list-item-container-color); + @include token-utils.create-token-slot(border-radius, list-item-container-shape); + + &.mdc-list-item--selected { + @include token-utils.create-token-slot(background-color, + list-item-selected-container-color); + } + } + + &:focus { + outline: 0; + } + + &.mdc-list-item--disabled { + cursor: auto; + } + + &.mdc-list-item--with-one-line { + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(height, list-item-one-line-container-height); + } + + .mdc-list-item__start { + align-self: center; + margin-top: 0; + } + + .mdc-list-item__end { + align-self: center; + margin-top: 0; + } + } + + &.mdc-list-item--with-two-lines { + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(height, list-item-two-line-container-height); + } + + .mdc-list-item__start { + align-self: flex-start; + margin-top: 16px; + } + + .mdc-list-item__end { + align-self: center; + margin-top: 0; + } + } + + &.mdc-list-item--with-three-lines { + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(height, list-item-three-line-container-height); + } + + .mdc-list-item__start { + align-self: flex-start; + margin-top: 16px; + } + + .mdc-list-item__end { + align-self: flex-start; + margin-top: 16px; + } + } + + &.mdc-list-item--selected::before, + &.mdc-list-item--selected:focus::before, + &:not(.mdc-list-item--selected):focus::before { + position: absolute; + box-sizing: border-box; + width: 100%; + height: 100%; + top: 0; + left: 0; + border: 1px solid transparent; + border-radius: inherit; + content: ''; + pointer-events: none; + + @include cdk.high-contrast(active, off) { + border-color: CanvasText; + } + } + + &.mdc-list-item--selected:focus::before, + &.mdc-list-item--selected::before { + border-width: 3px; + border-style: double; + } + } + + a.mdc-list-item { + color: inherit; + text-decoration: none; + } + + .mdc-list-item__start { + fill: currentColor; + flex-shrink: 0; + pointer-events: none; + + @include token-utils.use-tokens($tokens...) { + .mdc-list-item--with-leading-icon & { + @include token-utils.create-token-slot(color, list-item-leading-icon-color); + @include token-utils.create-token-slot(width, list-item-leading-icon-size); + @include token-utils.create-token-slot(height, list-item-leading-icon-size); + margin-left: 16px; + margin-right: 32px; + } + + [dir='rtl'] .mdc-list-item--with-leading-icon & { + margin-left: 32px; + margin-right: 16px; + } + + .mdc-list-item--with-leading-icon:hover & { + @include token-utils.create-token-slot(color, list-item-hover-leading-icon-color); + } + + // This is the same in RTL, but we need the specificity. + .mdc-list-item--with-leading-avatar & { + @include token-utils.create-token-slot(width, list-item-leading-avatar-size); + @include token-utils.create-token-slot(height, list-item-leading-avatar-size); + margin-left: 16px; + margin-right: 16px; + border-radius: 50%; + } + + .mdc-list-item--with-leading-avatar &, + [dir='rtl'] .mdc-list-item--with-leading-avatar & { + margin-left: 16px; + margin-right: 16px; + border-radius: 50%; + } + } + } + + .mdc-list-item__end { + flex-shrink: 0; + pointer-events: none; + + @include token-utils.use-tokens($tokens...) { + .mdc-list-item--with-trailing-meta & { + @include token-utils.create-token-slot(font-family, + list-item-trailing-supporting-text-font); + @include token-utils.create-token-slot(line-height, + list-item-trailing-supporting-text-line-height); + @include token-utils.create-token-slot(font-size, + list-item-trailing-supporting-text-size); + @include token-utils.create-token-slot(font-weight, + list-item-trailing-supporting-text-weight); + @include token-utils.create-token-slot(letter-spacing, + list-item-trailing-supporting-text-tracking); + } + + .mdc-list-item--with-trailing-icon & { + @include token-utils.create-token-slot(color, list-item-trailing-icon-color); + @include token-utils.create-token-slot(width, list-item-trailing-icon-size); + @include token-utils.create-token-slot(height, list-item-trailing-icon-size); + } + + .mdc-list-item--with-trailing-icon:hover & { + @include token-utils.create-token-slot(color, list-item-hover-trailing-icon-color); + } + + // For some reason this has an increased specificity just for the `color`. + // Keeping it in place for now to reduce the amount of screenshot diffs. + .mdc-list-item.mdc-list-item--with-trailing-meta & { + @include token-utils.create-token-slot(color, list-item-trailing-supporting-text-color); + } + + .mdc-list-item--selected.mdc-list-item--with-trailing-icon & { + @include token-utils.create-token-slot(color, list-item-selected-trailing-icon-color); + } + } + } + + .mdc-list-item__content { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + align-self: center; + flex: 1; + pointer-events: none; + + .mdc-list-item--with-two-lines &, + .mdc-list-item--with-three-lines & { + align-self: stretch; + } + } + + .mdc-list-item__primary-text { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(color, list-item-label-text-color); + @include token-utils.create-token-slot(font-family, list-item-label-text-font); + @include token-utils.create-token-slot(line-height, list-item-label-text-line-height); + @include token-utils.create-token-slot(font-size, list-item-label-text-size); + @include token-utils.create-token-slot(font-weight, list-item-label-text-weight); + @include token-utils.create-token-slot(letter-spacing, list-item-label-text-tracking); + + .mdc-list-item:hover & { + @include token-utils.create-token-slot(color, list-item-hover-label-text-color); + } + + .mdc-list-item:focus & { + @include token-utils.create-token-slot(color, list-item-focus-label-text-color); + } + } + + .mdc-list-item--with-two-lines &, + .mdc-list-item--with-three-lines & { + display: block; + margin-top: 0; + line-height: normal; + margin-bottom: -20px; + } + + .mdc-list-item--with-two-lines &::before, + .mdc-list-item--with-three-lines &::before { + display: inline-block; + width: 0; + height: 28px; + content: ''; + vertical-align: 0; + } + + .mdc-list-item--with-two-lines &::after, + .mdc-list-item--with-three-lines &::after { + display: inline-block; + width: 0; + height: 20px; + content: ''; + vertical-align: -20px; + } + } + + .mdc-list-item__secondary-text { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: block; + margin-top: 0; + + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(color, list-item-supporting-text-color); + @include token-utils.create-token-slot(font-family, list-item-supporting-text-font); + @include token-utils.create-token-slot(line-height, list-item-supporting-text-line-height); + @include token-utils.create-token-slot(font-size, list-item-supporting-text-size); + @include token-utils.create-token-slot(font-weight, list-item-supporting-text-weight); + @include token-utils.create-token-slot(letter-spacing, list-item-supporting-text-tracking); + } + + &::before { + display: inline-block; + width: 0; + height: 20px; + content: ''; + vertical-align: 0; + } + + .mdc-list-item--with-three-lines & { + white-space: normal; + line-height: 20px; + } + + .mdc-list-item--with-overline & { + white-space: nowrap; + line-height: auto; + } + } + + .mdc-list-item--with-leading-radio, + .mdc-list-item--with-leading-checkbox, + .mdc-list-item--with-leading-icon, + .mdc-list-item--with-leading-avatar { + &.mdc-list-item { + padding-left: 0; + padding-right: 16px; + + [dir='rtl'] & { + padding-left: 16px; + padding-right: 0; + } + } + + &.mdc-list-item--with-two-lines { + .mdc-list-item__primary-text { + display: block; + margin-top: 0; + line-height: normal; + margin-bottom: -20px; + + // This was used by MDC to set the text baseline. We should figure out a way to + // remove it, because it can introduce unnecessary whitespace at the beginning + // of the element. + &::before { + display: inline-block; + width: 0; + height: 32px; + content: ''; + vertical-align: 0; + } + + &::after { + display: inline-block; + width: 0; + height: 20px; + content: ''; + vertical-align: -20px; + } + } + + &.mdc-list-item--with-trailing-meta { + .mdc-list-item__end { + display: block; + margin-top: 0; + line-height: normal; + + &::before { + display: inline-block; + width: 0; + height: 32px; + content: ''; + vertical-align: 0; + } + } + } + } + } + + .mdc-list-item--with-trailing-icon { + &.mdc-list-item { + // This is the same in RTL, but we need the specificity. + &, [dir='rtl'] & { + padding-left: 0; + padding-right: 0; + } + } + + .mdc-list-item__end { + margin-left: 16px; + margin-right: 16px; + } + } + + .mdc-list-item--with-trailing-meta { + &.mdc-list-item { + padding-left: 16px; + padding-right: 0; + + [dir='rtl'] & { + padding-left: 0; + padding-right: 16px; + } + } + + .mdc-list-item__end { + @include vendor-prefixes.user-select(none); + margin-left: 28px; + margin-right: 16px; + + [dir='rtl'] & { + margin-left: 16px; + margin-right: 28px; + } + } + + &.mdc-list-item--with-three-lines .mdc-list-item__end, + &.mdc-list-item--with-two-lines .mdc-list-item__end { + display: block; + line-height: normal; + align-self: flex-start; + margin-top: 0; + + &::before { + display: inline-block; + width: 0; + height: 28px; + content: ''; + vertical-align: 0; + } + } + } + + .mdc-list-item--with-leading-radio, + .mdc-list-item--with-leading-checkbox { + .mdc-list-item__start { + margin-left: 8px; + margin-right: 24px; + + [dir='rtl'] & { + margin-left: 24px; + margin-right: 8px; + } + } + + &.mdc-list-item--with-two-lines { + .mdc-list-item__start { + align-self: flex-start; + margin-top: 8px; + } + } + } + + .mdc-list-item--with-trailing-radio, + .mdc-list-item--with-trailing-checkbox { + &.mdc-list-item { + padding-left: 16px; + padding-right: 0; + + [dir='rtl'] & { + padding-left: 0; + padding-right: 16px; + } + } + + &.mdc-list-item--with-leading-icon, + &.mdc-list-item--with-leading-avatar { + padding-left: 0; + + [dir='rtl'] & { + padding-right: 0; + } + } + + .mdc-list-item__end { + margin-left: 24px; + margin-right: 8px; + + [dir='rtl'] & { + margin-left: 8px; + margin-right: 24px; + } + } + + &.mdc-list-item--with-three-lines .mdc-list-item__end { + align-self: flex-start; + margin-top: 8px; + } + } + + .mdc-list-group__subheader { + margin: 0.75rem 16px; + } + + .mdc-list-item--disabled { + .mdc-list-item__start, + .mdc-list-item__content, + .mdc-list-item__end { + opacity: 1; + } + + .mdc-list-item__primary-text, + .mdc-list-item__secondary-text { + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(opacity, list-item-disabled-label-text-opacity); + } + } + + &.mdc-list-item--with-leading-icon .mdc-list-item__start { + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(color, list-item-disabled-leading-icon-color); + @include token-utils.create-token-slot(opacity, list-item-disabled-leading-icon-opacity); + } + } + + &.mdc-list-item--with-trailing-icon .mdc-list-item__end { + @include token-utils.use-tokens($tokens...) { + @include token-utils.create-token-slot(color, list-item-disabled-trailing-icon-color); + @include token-utils.create-token-slot(opacity, list-item-disabled-trailing-icon-opacity); + } + } + } + + .mat-mdc-list-item.mat-mdc-list-item-both-leading-and-trailing { + &, [dir='rtl'] & { + padding-left: 0; + padding-right: 0; + } + } +} diff --git a/src/material/list/_list-item-hcm-indicator.scss b/src/material/list/_list-item-hcm-indicator.scss index 488064bb58a2..90e80eb13846 100644 --- a/src/material/list/_list-item-hcm-indicator.scss +++ b/src/material/list/_list-item-hcm-indicator.scss @@ -1,5 +1,4 @@ @use '@angular/cdk'; -@use '@material/list/evolution-variables' as mdc-list-variables; // Renders a circle indicator when Windows Hich Constrast mode (HCM) is enabled. In some // situations, such as a selected option, the list item communicates the selected state by changing @@ -12,7 +11,7 @@ content: ''; position: absolute; top: 50%; - right: mdc-list-variables.$side-padding; + right: 16px; transform: translateY(-50%); width: $size; height: 0; @@ -23,7 +22,7 @@ [dir='rtl'] { &::after { right: auto; - left: mdc-list-variables.$side-padding; + left: 16px; } } } diff --git a/src/material/list/_list-option-trailing-avatar-compat.scss b/src/material/list/_list-option-trailing-avatar-compat.scss deleted file mode 100644 index 051427222b8b..000000000000 --- a/src/material/list/_list-option-trailing-avatar-compat.scss +++ /dev/null @@ -1,58 +0,0 @@ -@use '@material/typography/typography'; -@use '@material/feature-targeting/feature-targeting'; -@use '@material/density/functions' as density-functions; -@use '@material/list/evolution-mixins' as mdc-list; -@use '@material/list/evolution-variables' as mdc-list-variables; -@use '../core/mdc-helpers/mdc-helpers'; - -// For compatibility with the non-MDC selection list, we support avatars that are -// shown at the end of the list option. This is not supported by the MDC list as the -// spec only defines avatars at the beginning of a list item. For selection list options, -// we support changing the checkbox position to `before`. This results in the avatar from -// the list start being moved to the end. Similar to MDC's `--trailing-icon` class, we -// implement a `--trailing-avatar` class that is based on the original `--leading-avatar` -// implementation. See: https://github.com/material-components/material-components-web/blob/3f342c3f4715fd3587f327ce4ea6b5dd314c5c55/packages/mdc-list/_evolution-mixins.scss#L198-L217 - -@mixin core-styles($query) { - $feat-structure: feature-targeting.create-target($query, structure); - - @include mdc-helpers.disable-mdc-fallback-declarations { - .mat-mdc-list-option-with-trailing-avatar { - @include mdc-list.item-end-spacing(16px, $query: $query); - @include mdc-list.item-end-size(40px, $query: $query); - - &.mdc-list-item--with-two-lines { - .mdc-list-item__primary-text { - @include typography.text-baseline($top: 32px, $bottom: 20px, $query: $query); - } - } - - .mdc-list-item__end { - @include feature-targeting.targets($feat-structure) { - border-radius: 50%; - } - } - } - } -} - -@mixin density-styles($density-scale) { - $one-line-tall-height: density-functions.prop-value( - $density-config: mdc-list-variables.$one-line-item-tall-density-config, - $density-scale: $density-scale, - $property-name: height, - ); - - $two-line-tall-height: density-functions.prop-value( - $density-config: mdc-list-variables.$two-line-item-tall-density-config, - $density-scale: $density-scale, - $property-name: height, - ); - - @include mdc-helpers.disable-mdc-fallback-declarations { - .mat-mdc-list-option-with-trailing-avatar { - @include mdc-list.one-line-item-height($one-line-tall-height); - @include mdc-list.two-line-item-height($two-line-tall-height); - } - } -} diff --git a/src/material/list/_list-theme.scss b/src/material/list/_list-theme.scss index af48cc9c0ee9..daa96261b4ab 100644 --- a/src/material/list/_list-theme.scss +++ b/src/material/list/_list-theme.scss @@ -1,6 +1,4 @@ @use 'sass:map'; -@use '@material/list/evolution-mixins'; -@use '@material/list/list-theme' as mdc-list-theme; @use '../core/style/sass-utils'; @use '../core/theming/theming'; @@ -20,7 +18,8 @@ } @else { @include sass-utils.current-selector-or-root() { - @include mdc-list-theme.theme(tokens-mdc-list.get-unthemable-tokens()); + @include token-utils.create-token-values( + tokens-mdc-list.$prefix, tokens-mdc-list.get-unthemable-tokens()); @include token-utils.create-token-values( tokens-mat-list.$prefix, tokens-mat-list.get-unthemable-tokens()); } @@ -32,11 +31,9 @@ @include _theme-from-tokens(inspection.get-theme-tokens($theme, color)); } @else { - $mdc-list-color-tokens: tokens-mdc-list.get-color-tokens($theme); - - // Add values for MDC list tokens. @include sass-utils.current-selector-or-root() { - @include mdc-list-theme.theme($mdc-list-color-tokens); + @include token-utils.create-token-values( + tokens-mdc-list.$prefix, tokens-mdc-list.get-color-tokens($theme)); @include token-utils.create-token-values( tokens-mat-list.$prefix, tokens-mat-list.get-color-tokens($theme)); } @@ -79,8 +76,13 @@ // There is no token for activated color on nav list. // TODO(mmalerba): Add a token to MDC or make a custom one. .mat-mdc-list-base.mat-mdc-list-base { - @include evolution-mixins.list-selected-ink-color( - inspection.get-theme-color($theme, primary)); + .mdc-list-item--selected, + .mdc-list-item--activated { + .mdc-list-item__primary-text, + .mdc-list-item__start { + color: inspection.get-theme-color($theme, primary); + } + } } // TODO(mmalerba): Leaking styles from the old MDC list mixins used in other components can @@ -102,11 +104,10 @@ } @else { $density-scale: inspection.get-theme-density($theme); - $mdc-list-density-tokens: tokens-mdc-list.get-density-tokens($theme); - // Add values for MDC list tokens. @include sass-utils.current-selector-or-root() { - @include mdc-list-theme.theme($mdc-list-density-tokens); + @include token-utils.create-token-values( + tokens-mdc-list.$prefix, tokens-mdc-list.get-density-tokens($theme)); @include token-utils.create-token-values( tokens-mat-list.$prefix, tokens-mat-list.get-density-tokens($theme)); } @@ -155,11 +156,9 @@ @include _theme-from-tokens(inspection.get-theme-tokens($theme, typography)); } @else { - $mdc-list-typography-tokens: tokens-mdc-list.get-typography-tokens($theme); - - // Add values for MDC list tokens. @include sass-utils.current-selector-or-root() { - @include mdc-list-theme.theme($mdc-list-typography-tokens); + @include token-utils.create-token-values( + tokens-mdc-list.$prefix, tokens-mdc-list.get-typography-tokens($theme)); @include token-utils.create-token-values( tokens-mat-list.$prefix, tokens-mat-list.get-typography-tokens($theme)); } @@ -204,8 +203,8 @@ @mixin _theme-from-tokens($tokens) { @include validation.selector-defined( 'Calls to Angular Material theme mixins with an M3 theme must be wrapped in a selector'); - @include mdc-list-theme.theme(token-utils.get-tokens-for($tokens, tokens-mdc-list.$prefix)); - + $mdc-list-tokens: token-utils.get-tokens-for($tokens, tokens-mdc-list.$prefix); $mat-list-tokens: token-utils.get-tokens-for($tokens, tokens-mat-list.$prefix); + @include token-utils.create-token-values(tokens-mdc-list.$prefix, $mdc-list-tokens); @include token-utils.create-token-values(tokens-mat-list.$prefix, $mat-list-tokens); } diff --git a/src/material/list/list-option.scss b/src/material/list/list-option.scss index f4ea5f27bf75..80f5b6fe7ac8 100644 --- a/src/material/list/list-option.scss +++ b/src/material/list/list-option.scss @@ -1,12 +1,63 @@ @use '../checkbox/checkbox-common'; @use '../radio/radio-common'; -@use '../core/mdc-helpers/mdc-helpers'; -@use './list-option-trailing-avatar-compat'; @use './list-item-hcm-indicator'; -// For compatibility with the non-MDC list, we support avatars that are shown at the end -// of the list option. We create a class similar to MDC's `--trailing-icon` one. -@include list-option-trailing-avatar-compat.core-styles($query: mdc-helpers.$mdc-base-styles-query); +// For compatibility with the non-MDC selection list, we support avatars that are +// shown at the end of the list option. This is not supported by the MDC list as the +// spec only defines avatars at the beginning of a list item. For selection list options, +// we support changing the checkbox position to `before`. This results in the avatar from +// the list start being moved to the end. Similar to MDC's `--trailing-icon` class, we +// implement a `--trailing-avatar` class that is based on the original `--leading-avatar` +// implementation. See: https://github.com/material-components/material-components-web/blob/3f342c3f4715fd3587f327ce4ea6b5dd314c5c55/packages/mdc-list/_evolution-mixins.scss#L198-L217 +.mat-mdc-list-option-with-trailing-avatar { + &.mdc-list-item, + [dir='rtl'] &.mdc-list-item { + padding-left: 0; + padding-right: 0; + } + + .mdc-list-item__end { + margin-left: 16px; + margin-right: 16px; + width: 40px; + height: 40px; + } + + &.mdc-list-item--with-two-lines { + $top: 32px; + $bottom: 20px; + + .mdc-list-item__primary-text { + display: block; + margin-top: 0; + line-height: normal; + margin-bottom: $bottom * -1; + + // This was used by MDC to set the text baseline. We should figure out a way to + // remove it, because it can introduce unnecessary whitespace at the beginning + // of the element. + &::before { + display: inline-block; + width: 0; + height: $top; + content: ''; + vertical-align: 0; + } + + &::after { + display: inline-block; + width: 0; + height: $bottom; + content: ''; + vertical-align: $bottom * -1; + } + } + } + + .mdc-list-item__end { + border-radius: 50%; + } +} .mat-mdc-list-option { // We can't use the MDC checkbox here directly, because this checkbox is purely diff --git a/src/material/list/list-option.ts b/src/material/list/list-option.ts index 8c4c7049fb66..7e900e6e557b 100644 --- a/src/material/list/list-option.ts +++ b/src/material/list/list-option.ts @@ -83,6 +83,10 @@ export interface SelectionList extends MatListBase { '[class.mdc-list-item--with-trailing-checkbox]': '_hasCheckboxAt("after")', '[class.mdc-list-item--with-leading-radio]': '_hasRadioAt("before")', '[class.mdc-list-item--with-trailing-radio]': '_hasRadioAt("after")', + + // Utility class that makes it easier to target the case where there's both a leading + // and a trailing icon. Avoids having to write out all the combinations. + '[class.mat-mdc-list-item-both-leading-and-trailing]': '_hasBothLeadingAndTrailing()', '[class.mat-accent]': 'color !== "primary" && color !== "warn"', '[class.mat-warn]': 'color === "warn"', '[class._mat-animation-noopable]': '_noopAnimations', @@ -337,4 +341,18 @@ export class MatListOption extends MatListItemBase implements ListOption, OnInit _setTabindex(value: number) { this._hostElement.setAttribute('tabindex', value + ''); } + + protected _hasBothLeadingAndTrailing(): boolean { + const hasLeading = + this._hasProjected('avatars', 'before') || + this._hasProjected('icons', 'before') || + this._hasCheckboxAt('before') || + this._hasRadioAt('before'); + const hasTrailing = + this._hasProjected('icons', 'after') || + this._hasProjected('avatars', 'after') || + this._hasCheckboxAt('after') || + this._hasRadioAt('after'); + return hasLeading && hasTrailing; + } } diff --git a/src/material/list/list.scss b/src/material/list/list.scss index 61e246175f42..fbbf4c4d5896 100644 --- a/src/material/list/list.scss +++ b/src/material/list/list.scss @@ -1,87 +1,61 @@ -@use 'sass:map'; -@use '@material/list/list' as mdc-list; -@use '@material/list/list-theme' as mdc-list-theme; -@use '@material/theme/custom-properties' as mdc-custom-properties; @use '../core/style/layout-common'; -@use '../core/tokens/m2/mat/list' as m2-mat-list; -@use '../core/tokens/m2/mdc/list' as m2-mdc-list; +@use '../core/tokens/m2/mat/list' as tokens-mat-list; +@use '../core/tokens/m2/mdc/list' as tokens-mdc-list; @use '../core/tokens/token-utils'; @use './list-item-hcm-indicator'; +@use './list-inherited-structure'; -// The slots for tokens that will be configured in the theme can be emitted with no fallback. -@include mdc-custom-properties.configure($emit-fallback-values: false, $emit-fallback-vars: false) { - $mdc-list-token-slots: m2-mdc-list.get-token-slots(); - - // Add the MDC list static styles. - @include mdc-list.static-styles(); - - // Add the official slots for the MDC list. - @include mdc-list-theme.theme-styles(map.merge($mdc-list-token-slots, ( - // We structure the avatar differently from how MDC expects, so we add these slots ourselves. - list-item-leading-avatar-shape: null, - list-item-leading-avatar-color: null, - // We add this slot ourselves with more specificity, so we don't need MDC to emit it. - list-item-disabled-label-text-color: null, - // We don't use MDC's state layers, so we add these slots ourselves instead. - list-item-hover-state-layer-color: null, - list-item-hover-state-layer-opacity: null, - list-item-focus-state-layer-color: null, - list-item-focus-state-layer-opacity: null, - list-item-disabled-state-layer-color: null, - list-item-disabled-state-layer-opacity: null, - ))); - - // Add additional slots for the MDC list tokens, needed in Angular Material. - @include token-utils.use-tokens(m2-mdc-list.$prefix, $mdc-list-token-slots) { - // MDC allows focus and hover colors to take precedence over disabled color. We add the disabled - // color here with higher specificity so that the disabled color takes precedence. - // TODO(mmalerba): Dicuss with MDC whether to change this in their code. - .mdc-list-item.mdc-list-item--disabled .mdc-list-item__primary-text { - @include token-utils.create-token-slot(color, list-item-disabled-label-text-color); - } +@include list-inherited-structure.private-list-inherited-structural-styles; - // We don't use MDC's state layer since it's tied in with their ripple. Instead we emit slots - // for our own state layer. - // TODO(mmalerba): Consider using MDC's ripple & state layer. - .mdc-list-item:hover::before { - @include token-utils.create-token-slot(background-color, list-item-hover-state-layer-color); - @include token-utils.create-token-slot(opacity, list-item-hover-state-layer-opacity); - } - .mdc-list-item.mdc-list-item--disabled::before { - @include token-utils.create-token-slot( - background-color, list-item-disabled-state-layer-color); - @include token-utils.create-token-slot(opacity, list-item-disabled-state-layer-opacity); - } - .mdc-list-item:focus::before { - @include token-utils.create-token-slot(background-color, list-item-focus-state-layer-color); - @include token-utils.create-token-slot(opacity, list-item-focus-state-layer-opacity); - } +// Add additional slots for the MDC list tokens, needed in Angular Material. +@include token-utils.use-tokens(tokens-mdc-list.$prefix, tokens-mdc-list.get-token-slots()) { + // MDC allows focus and hover colors to take precedence over disabled color. We add the disabled + // color here with higher specificity so that the disabled color takes precedence. + // TODO(mmalerba): Dicuss with MDC whether to change this in their code. + .mdc-list-item.mdc-list-item--disabled .mdc-list-item__primary-text { + @include token-utils.create-token-slot(color, list-item-disabled-label-text-color); + } - // Apply the disabled opacity to the checkbox/radio indicators. - // TODO(mmalerba): We should probably stop doing this and allow the checkbox/radio to decide - // what their disabled state looks like. This is done for now to avoid screenshot diffs. - .mdc-list-item--disabled { - .mdc-radio, - .mdc-checkbox { - @include token-utils.create-token-slot(opacity, list-item-disabled-label-text-opacity); - } - } + // We don't use MDC's state layer since it's tied in with their ripple. Instead we emit slots + // for our own state layer. + // TODO(mmalerba): Consider using MDC's ripple & state layer. + .mdc-list-item:hover::before { + @include token-utils.create-token-slot(background-color, list-item-hover-state-layer-color); + @include token-utils.create-token-slot(opacity, list-item-hover-state-layer-opacity); + } + .mdc-list-item.mdc-list-item--disabled::before { + @include token-utils.create-token-slot(background-color, list-item-disabled-state-layer-color); + @include token-utils.create-token-slot(opacity, list-item-disabled-state-layer-opacity); + } + .mdc-list-item:focus::before { + @include token-utils.create-token-slot(background-color, list-item-focus-state-layer-color); + @include token-utils.create-token-slot(opacity, list-item-focus-state-layer-opacity); + } - // In Angular Material we put the avatar class directly on the .mdc-list-item__start element, - // rather than nested inside it, so we need to emit avatar slots ourselves. - // TODO(mmalerba): We should try to change MDC's recommended DOM or change ours to match their - // recommendation. - .mdc-list-item--with-leading-avatar .mat-mdc-list-item-avatar { - @include token-utils.create-token-slot(border-radius, list-item-leading-avatar-shape); - @include token-utils.create-token-slot(background-color, list-item-leading-avatar-color); + // Apply the disabled opacity to the checkbox/radio indicators. + // TODO(mmalerba): We should probably stop doing this and allow the checkbox/radio to decide + // what their disabled state looks like. This is done for now to avoid screenshot diffs. + .mdc-list-item--disabled { + .mdc-radio, + .mdc-checkbox { + @include token-utils.create-token-slot(opacity, list-item-disabled-label-text-opacity); } + } - // Set font-size of leading icon to same value as its width and height. Ensure icon scales to - // "list-item-leading-icon-size" token. In Angular Material, the icon is on the same element as - // ".mdc-list-item__start", rather than a child of ".mdc-list-item__start". - .mat-mdc-list-item-icon { - @include token-utils.create-token-slot(font-size, list-item-leading-icon-size); - } + // In Angular Material we put the avatar class directly on the .mdc-list-item__start element, + // rather than nested inside it, so we need to emit avatar slots ourselves. + // TODO(mmalerba): We should try to change MDC's recommended DOM or change ours to match their + // recommendation. + .mdc-list-item--with-leading-avatar .mat-mdc-list-item-avatar { + @include token-utils.create-token-slot(border-radius, list-item-leading-avatar-shape); + @include token-utils.create-token-slot(background-color, list-item-leading-avatar-color); + } + + // Set font-size of leading icon to same value as its width and height. Ensure icon scales to + // "list-item-leading-icon-size" token. In Angular Material, the icon is on the same element as + // ".mdc-list-item__start", rather than a child of ".mdc-list-item__start". + .mat-mdc-list-item-icon { + @include token-utils.create-token-slot(font-size, list-item-leading-icon-size); } } @@ -200,7 +174,7 @@ mat-action-list button { } } -@include token-utils.use-tokens(m2-mat-list.$prefix, m2-mat-list.get-token-slots()) { +@include token-utils.use-tokens(tokens-mat-list.$prefix, tokens-mat-list.get-token-slots()) { .mdc-list-item--with-leading-icon .mdc-list-item__start { @include token-utils.create-token-slot(margin-inline-start, list-item-leading-icon-start-space); @include token-utils.create-token-slot(margin-inline-end, list-item-leading-icon-end-space); diff --git a/src/material/list/list.ts b/src/material/list/list.ts index fcfc79bbd97d..fd6662d4a468 100644 --- a/src/material/list/list.ts +++ b/src/material/list/list.ts @@ -59,6 +59,9 @@ export class MatList extends MatListBase {} '[class.mdc-list-item--with-leading-avatar]': '_avatars.length !== 0', '[class.mdc-list-item--with-leading-icon]': '_icons.length !== 0', '[class.mdc-list-item--with-trailing-meta]': '_meta.length !== 0', + // Utility class that makes it easier to target the case where there's both a leading + // and a trailing icon. Avoids having to write out all the combinations. + '[class.mat-mdc-list-item-both-leading-and-trailing]': '_hasBothLeadingAndTrailing()', '[class._mat-animation-noopable]': '_noopAnimations', '[attr.aria-current]': '_getAriaCurrent()', }, @@ -103,4 +106,8 @@ export class MatListItem extends MatListItemBase { _getAriaCurrent(): string | null { return this._hostElement.nodeName === 'A' && this._activated ? 'page' : null; } + + protected _hasBothLeadingAndTrailing(): boolean { + return this._meta.length !== 0 && (this._avatars.length !== 0 || this._icons.length !== 0); + } } diff --git a/src/material/paginator/paginator.scss b/src/material/paginator/paginator.scss index 4c66c6e456bd..f2488a36d07e 100644 --- a/src/material/paginator/paginator.scss +++ b/src/material/paginator/paginator.scss @@ -1,7 +1,7 @@ @use '@angular/cdk'; -@use '@material/typography/typography' as mdc-typography; @use '../core/tokens/m2/mat/paginator' as tokens-mat-paginator; @use '../core/tokens/token-utils'; +@use '../core/style/vendor-prefixes'; $padding: 0 8px; $page-size-margin-right: 8px; @@ -21,7 +21,7 @@ $button-icon-size: 28px; tokens-mat-paginator.$prefix, tokens-mat-paginator.get-token-slots() ) { - @include mdc-typography.smooth-font(); + @include vendor-prefixes.smooth-font(); @include token-utils.create-token-slot(color, container-text-color); @include token-utils.create-token-slot(background-color, container-background-color); @include token-utils.create-token-slot(font-family, container-text-font); diff --git a/src/material/select/select.scss b/src/material/select/select.scss index dff6e279fcf5..4654f3a06172 100644 --- a/src/material/select/select.scss +++ b/src/material/select/select.scss @@ -1,6 +1,5 @@ @use 'sass:math'; @use '@angular/cdk'; -@use '@material/typography/typography' as mdc-typography; @use '../core/style/vendor-prefixes'; @use '../core/style/variables'; @use '../core/tokens/token-utils'; @@ -22,7 +21,7 @@ $scale: 0.75 !default; @include token-utils.use-tokens( tokens-mat-select.$prefix, tokens-mat-select.get-token-slots()) { - @include mdc-typography.smooth-font(); + @include vendor-prefixes.smooth-font(); @include token-utils.create-token-slot(color, enabled-trigger-text-color); @include token-utils.create-token-slot(font-family, trigger-text-font); @include token-utils.create-token-slot(line-height, trigger-text-line-height); diff --git a/src/material/slide-toggle/slide-toggle.scss b/src/material/slide-toggle/slide-toggle.scss index 8e917b0d77b5..54cb1b956271 100644 --- a/src/material/slide-toggle/slide-toggle.scss +++ b/src/material/slide-toggle/slide-toggle.scss @@ -1,10 +1,7 @@ -@use 'sass:map'; -@use '@material/animation' as mdc-animation; @use '@material/switch/switch' as mdc-switch; @use '@material/switch/switch-theme' as mdc-switch-theme; -@use '@material/ripple' as mdc-ripple; -@use '../core/style/layout-common'; @use '@material/theme/custom-properties' as mdc-custom-properties; +@use '../core/style/layout-common'; @use '../core/tokens/m2/mat/switch' as tokens-mat-switch; @use '../core/tokens/m2/mdc/switch' as tokens-mdc-switch; @use '../core/tokens/token-utils'; @@ -86,14 +83,14 @@ } .mdc-switch:hover #{mdc-switch.$ripple-target}::after { - opacity: map.get(mdc-ripple.$dark-ink-opacities, hover); - transition: mdc-animation.enter(opacity, 75ms); + opacity: 0.04; + transition: 75ms opacity cubic-bezier(0, 0, 0.2, 1); } // Needs a little more specificity so the :hover styles don't override it. &.mat-mdc-slide-toggle-focused { .mdc-switch #{mdc-switch.$ripple-target}::after { - opacity: map.get(mdc-ripple.$dark-ink-opacities, focus); + opacity: 0.12; } // For slide-toggles render the focus indicator when we know @@ -103,10 +100,8 @@ } } - // We use an Angular Material ripple rather than an MDC ripple due to size concerns, so we need to - // style it appropriately. .mat-ripple-element { - opacity: map.get(mdc-ripple.$dark-ink-opacities, press); + opacity: 0.12; } // Slide-toggle components have to set `border-radius: 50%` in order to support density scaling diff --git a/src/material/tabs/tab-group.spec.ts b/src/material/tabs/tab-group.spec.ts index c93b7ca3cd89..bcd7ce602283 100644 --- a/src/material/tabs/tab-group.spec.ts +++ b/src/material/tabs/tab-group.spec.ts @@ -792,7 +792,7 @@ describe('MDC-based MatTabGroup', () => { ); expect(contentElements.map(element => element.style.visibility)).toEqual([ - '', + 'visible', 'hidden', 'hidden', 'hidden', @@ -805,7 +805,7 @@ describe('MDC-based MatTabGroup', () => { expect(contentElements.map(element => element.style.visibility)).toEqual([ 'hidden', 'hidden', - '', + 'visible', 'hidden', ]); @@ -815,7 +815,7 @@ describe('MDC-based MatTabGroup', () => { expect(contentElements.map(element => element.style.visibility)).toEqual([ 'hidden', - '', + 'visible', 'hidden', 'hidden', ]); diff --git a/src/material/tabs/tabs-animations.ts b/src/material/tabs/tabs-animations.ts index 5e7955d4a373..e0097556cba2 100644 --- a/src/material/tabs/tabs-animations.ts +++ b/src/material/tabs/tabs-animations.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ import { + AnimationTriggerMetadata, animate, state, style, transition, trigger, - AnimationTriggerMetadata, } from '@angular/animations'; /** @@ -24,7 +24,10 @@ export const matTabsAnimations: { /** Animation translates a tab along the X axis. */ translateTab: trigger('translateTab', [ // Transitions to `none` instead of 0, because some browsers might blur the content. - state('center, void, left-origin-center, right-origin-center', style({transform: 'none'})), + state( + 'center, void, left-origin-center, right-origin-center', + style({transform: 'none', visibility: 'visible'}), + ), // If the tab is either on the left or right, we additionally add a `min-height` of 1px // in order to ensure that the element has a height before its state changes. This is diff --git a/tools/public_api_guard/cdk/drag-drop.md b/tools/public_api_guard/cdk/drag-drop.md index 82b57b513f0d..6611f665f4f7 100644 --- a/tools/public_api_guard/cdk/drag-drop.md +++ b/tools/public_api_guard/cdk/drag-drop.md @@ -76,6 +76,8 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { // (undocumented) static ngAcceptInputType_disabled: unknown; // (undocumented) + static ngAcceptInputType_scale: unknown; + // (undocumented) ngAfterViewInit(): void; // (undocumented) ngOnChanges(changes: SimpleChanges): void; @@ -92,6 +94,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { // (undocumented) _resetPreviewTemplate(preview: CdkDragPreview): void; rootElementSelector: string; + scale: number; setFreeDragPosition(value: Point): void; // (undocumented) _setPlaceholderTemplate(placeholder: CdkDragPlaceholder): void; @@ -99,7 +102,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { _setPreviewTemplate(preview: CdkDragPreview): void; readonly started: EventEmitter; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration, "[cdkDrag]", ["cdkDrag"], { "data": { "alias": "cdkDragData"; "required": false; }; "lockAxis": { "alias": "cdkDragLockAxis"; "required": false; }; "rootElementSelector": { "alias": "cdkDragRootElement"; "required": false; }; "boundaryElement": { "alias": "cdkDragBoundary"; "required": false; }; "dragStartDelay": { "alias": "cdkDragStartDelay"; "required": false; }; "freeDragPosition": { "alias": "cdkDragFreeDragPosition"; "required": false; }; "disabled": { "alias": "cdkDragDisabled"; "required": false; }; "constrainPosition": { "alias": "cdkDragConstrainPosition"; "required": false; }; "previewClass": { "alias": "cdkDragPreviewClass"; "required": false; }; "previewContainer": { "alias": "cdkDragPreviewContainer"; "required": false; }; }, { "started": "cdkDragStarted"; "released": "cdkDragReleased"; "ended": "cdkDragEnded"; "entered": "cdkDragEntered"; "exited": "cdkDragExited"; "dropped": "cdkDragDropped"; "moved": "cdkDragMoved"; }, never, never, true, never>; + static ɵdir: i0.ɵɵDirectiveDeclaration, "[cdkDrag]", ["cdkDrag"], { "data": { "alias": "cdkDragData"; "required": false; }; "lockAxis": { "alias": "cdkDragLockAxis"; "required": false; }; "rootElementSelector": { "alias": "cdkDragRootElement"; "required": false; }; "boundaryElement": { "alias": "cdkDragBoundary"; "required": false; }; "dragStartDelay": { "alias": "cdkDragStartDelay"; "required": false; }; "freeDragPosition": { "alias": "cdkDragFreeDragPosition"; "required": false; }; "disabled": { "alias": "cdkDragDisabled"; "required": false; }; "constrainPosition": { "alias": "cdkDragConstrainPosition"; "required": false; }; "previewClass": { "alias": "cdkDragPreviewClass"; "required": false; }; "previewContainer": { "alias": "cdkDragPreviewContainer"; "required": false; }; "scale": { "alias": "cdkDragScale"; "required": false; }; }, { "started": "cdkDragStarted"; "released": "cdkDragReleased"; "ended": "cdkDragEnded"; "entered": "cdkDragEntered"; "exited": "cdkDragExited"; "dropped": "cdkDragDropped"; "moved": "cdkDragMoved"; }, never, never, true, never>; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration, [null, { optional: true; skipSelf: true; }, null, null, null, { optional: true; }, { optional: true; }, null, null, { optional: true; self: true; }, { optional: true; skipSelf: true; }]>; } @@ -440,6 +443,7 @@ export class DragRef { event: MouseEvent | TouchEvent; }>; reset(): void; + scale: number; setFreeDragPosition(value: Point): this; _sortFromLastPointerPosition(): void; readonly started: Subject<{ diff --git a/tools/public_api_guard/material/list.md b/tools/public_api_guard/material/list.md index 3a35d95306cc..da4aad970fa1 100644 --- a/tools/public_api_guard/material/list.md +++ b/tools/public_api_guard/material/list.md @@ -73,6 +73,8 @@ export class MatListItem extends MatListItemBase { _activated: boolean; _getAriaCurrent(): string | null; // (undocumented) + protected _hasBothLeadingAndTrailing(): boolean; + // (undocumented) _itemText: ElementRef; // (undocumented) _lines: QueryList; @@ -170,6 +172,8 @@ export class MatListOption extends MatListItemBase implements ListOption, OnInit _getTogglePosition(): MatListOptionTogglePosition; // (undocumented) _handleBlur(): void; + // (undocumented) + protected _hasBothLeadingAndTrailing(): boolean; _hasCheckboxAt(position: MatListOptionTogglePosition): boolean; _hasIconsOrAvatarsAt(position: 'before' | 'after'): boolean; _hasProjected(type: 'icons' | 'avatars', position: 'before' | 'after'): boolean;