diff --git a/src/material-experimental/mdc-form-field/_form-field-native-select.scss b/src/material-experimental/mdc-form-field/_form-field-native-select.scss index 27bf8db4b5e6..c6073c28ed58 100644 --- a/src/material-experimental/mdc-form-field/_form-field-native-select.scss +++ b/src/material-experimental/mdc-form-field/_form-field-native-select.scss @@ -80,7 +80,7 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt $dropdown-icon-color: rgba(mdc-theme-color.prop-value(on-surface), 0.54); $disabled-dropdown-icon-color: rgba(mdc-theme-color.prop-value(on-surface), 0.38); - select.mat-mdc-form-field-input-control { + select.mat-mdc-form-field-input-control:not(.mat-mdc-native-select-inline) { // On dark themes we set the native `select` color to some shade of white, // however the color propagates to all of the `option` elements, which are // always on a white background inside the dropdown, causing them to blend in. diff --git a/src/material-experimental/mdc-input/input.spec.ts b/src/material-experimental/mdc-input/input.spec.ts index 8a1b36d8f774..db72cfd45405 100644 --- a/src/material-experimental/mdc-input/input.spec.ts +++ b/src/material-experimental/mdc-input/input.spec.ts @@ -659,6 +659,37 @@ describe('MatMdcInput without forms', () => { expect(labelEl.classList).toContain('mdc-floating-label--float-above'); })); + it('should mark a multi-select as being inline', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const select: HTMLSelectElement = fixture.nativeElement.querySelector('select'); + + expect(select.classList).not.toContain('mat-mdc-native-select-inline'); + + select.multiple = true; + fixture.detectChanges(); + + expect(select.classList).toContain('mat-mdc-native-select-inline'); + })); + + it('should mark a select with a size as being inline', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const select: HTMLSelectElement = fixture.nativeElement.querySelector('select'); + + expect(select.classList).not.toContain('mat-mdc-native-select-inline'); + + select.size = 3; + fixture.detectChanges(); + expect(select.classList).toContain('mat-mdc-native-select-inline'); + + select.size = 1; + fixture.detectChanges(); + expect(select.classList).not.toContain('mat-mdc-native-select-inline'); + })); + it('should not float the label if the selectedIndex is negative', fakeAsync(() => { const fixture = createComponent(MatInputSelect); fixture.detectChanges(); diff --git a/src/material-experimental/mdc-input/input.ts b/src/material-experimental/mdc-input/input.ts index 5a0b00956a9b..6bc4dc5248b7 100644 --- a/src/material-experimental/mdc-input/input.ts +++ b/src/material-experimental/mdc-input/input.ts @@ -26,10 +26,12 @@ import {MatInput as BaseMatInput} from '@angular/material/input'; '[class.mat-form-field-autofill-control]': 'false', '[class.mat-input-element]': 'false', '[class.mat-form-field-control]': 'false', + '[class.mat-native-select-inline]': 'false', '[class.mat-input-server]': '_isServer', '[class.mat-mdc-form-field-textarea-control]': '_isInFormField && _isTextarea', '[class.mat-mdc-form-field-input-control]': '_isInFormField', '[class.mdc-text-field__input]': '_isInFormField', + '[class.mat-mdc-native-select-inline]': '_isInlineSelect()', // Native input properties that are overwritten by Angular inputs need to be synced with // the native input element. Otherwise property bindings for those don't work. '[id]': 'id', diff --git a/src/material/input/_input-theme.scss b/src/material/input/_input-theme.scss index 5ea1e098a3ca..7a730b1d87c1 100644 --- a/src/material/input/_input-theme.scss +++ b/src/material/input/_input-theme.scss @@ -37,12 +37,14 @@ // Since we can't change background of the dropdown, we need to explicitly // reset the color of the options to something dark. @if (map.get($config, is-dark)) { - option { - color: palette.$dark-primary-text; - } - - option:disabled { - color: palette.$dark-disabled-text; + &:not(.mat-native-select-inline) { + option { + color: palette.$dark-primary-text; + } + + option:disabled { + color: palette.$dark-disabled-text; + } } } } diff --git a/src/material/input/input.spec.ts b/src/material/input/input.spec.ts index 30fcddf0dbd6..c81888a72f33 100644 --- a/src/material/input/input.spec.ts +++ b/src/material/input/input.spec.ts @@ -732,6 +732,37 @@ describe('MatInput without forms', () => { expect(formFieldEl.classList).toContain('mat-form-field-should-float'); })); + it('should mark a multi-select as being inline', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const select: HTMLSelectElement = fixture.nativeElement.querySelector('select'); + + expect(select.classList).not.toContain('mat-native-select-inline'); + + select.multiple = true; + fixture.detectChanges(); + + expect(select.classList).toContain('mat-native-select-inline'); + })); + + it('should mark a select with a size as being inline', fakeAsync(() => { + const fixture = createComponent(MatInputSelect); + fixture.detectChanges(); + + const select: HTMLSelectElement = fixture.nativeElement.querySelector('select'); + + expect(select.classList).not.toContain('mat-native-select-inline'); + + select.size = 3; + fixture.detectChanges(); + expect(select.classList).toContain('mat-native-select-inline'); + + select.size = 1; + fixture.detectChanges(); + expect(select.classList).not.toContain('mat-native-select-inline'); + })); + it('should not float the label if the selectedIndex is negative', fakeAsync(() => { const fixture = createComponent(MatInputSelect); fixture.detectChanges(); diff --git a/src/material/input/input.ts b/src/material/input/input.ts index 68b1a5494cd6..761cab242e89 100644 --- a/src/material/input/input.ts +++ b/src/material/input/input.ts @@ -80,6 +80,7 @@ const _MatInputBase = mixinErrorState( '[disabled]': 'disabled', '[required]': 'required', '[attr.readonly]': 'readonly && !_isNativeSelect || null', + '[class.mat-native-select-inline]': '_isInlineSelect()', // Only mark the input as invalid for assistive technology if it has a value since the // state usually overlaps with `aria-required` when the input is empty and can be redundant. '[attr.aria-invalid]': '(empty && required) ? null : errorState', @@ -507,6 +508,12 @@ export class MatInput } } + /** Whether the form control is a native select that is displayed inline. */ + _isInlineSelect(): boolean { + const element = this._elementRef.nativeElement as HTMLSelectElement; + return this._isNativeSelect && (element.multiple || element.size > 1); + } + static ngAcceptInputType_disabled: BooleanInput; static ngAcceptInputType_readonly: BooleanInput; static ngAcceptInputType_required: BooleanInput; diff --git a/tools/public_api_guard/material/input.md b/tools/public_api_guard/material/input.md index a08e62d92b9a..9a6d511c6eb8 100644 --- a/tools/public_api_guard/material/input.md +++ b/tools/public_api_guard/material/input.md @@ -60,6 +60,7 @@ export class MatInput extends _MatInputBase implements MatFormFieldControl, protected _id: string; protected _isBadInput(): boolean; readonly _isInFormField: boolean; + _isInlineSelect(): boolean; readonly _isNativeSelect: boolean; protected _isNeverEmpty(): boolean; readonly _isServer: boolean;