diff --git a/packages/mdc-select/README.md b/packages/mdc-select/README.md index e98f42c7cea..34ba672d442 100644 --- a/packages/mdc-select/README.md +++ b/packages/mdc-select/README.md @@ -49,8 +49,9 @@ The select requires that you set the `width` of the `mdc-select__anchor` element ```html
+ + -
Pick a Food Group
@@ -115,9 +116,12 @@ The following is an example of the select component with all of the necessary ar ```html
-
+
+ -
Vegetables
Pick a Food Group
@@ -155,9 +159,9 @@ same. ```html
-
+
+ -
@@ -183,8 +187,8 @@ to set the selected item. The select also needs the text from the selected eleme ```html
+ -
Vegetables
Pick a Food Group
@@ -225,13 +229,13 @@ the list with an empty `data-value` attribute. #### Required select To style a select menu as required and enable validation, add the `mdc-select--required` class to the `mdc-select` element -and set the `aria-required` attribute on the `mdc-select__selected-text` element to be `"true"`. +and set the `aria-required` attribute on the `mdc-select__anchor` element to be `"true"`. ```html
-
+
+ -
Pick a Food Group
@@ -251,9 +255,9 @@ Add the `mdc-select--disabled` class to the `mdc-select` element and and set the ```html
-
+
+ -
Pick a Food Group
@@ -323,8 +327,8 @@ structure. ```html
+ -
@@ -356,8 +360,8 @@ structure. ```html
+ -
@@ -399,13 +403,14 @@ Mixin | Description `label-color($color)` | Customizes the label color of the select in the unfocused state. `focused-label-color($color)` | Customizes the label color of the select when focused. `bottom-line-color($color)` | Customizes the color of the default bottom line of the select. +`hover-bottom-line-color($color)` | Customizes the color of the bottom line when the select is hovered. `focused-bottom-line-color($color)` | Customizes the color of the bottom line of the select when focused. `shape-radius($radius, $rtl-reflexive)` | Sets rounded shape to boxed select variant with given radius size. Set `$rtl-reflexive` to true to flip radius values in RTL context, defaults to false. -`hover-bottom-line-color($color)` | Customizes the color of the bottom line when the select is hovered. `outline-color($color)` | Customizes the color of the notched outline. `outline-shape-radius($radius, $rtl-reflexive)` | Sets the border radius of of the outlined select variant. Set `$rtl-reflexive` to true to flip radius values in RTL context, defaults to false. `focused-outline-color($color)` | Customizes the color of the outline of the select when focused. `hover-outline-color($color)` | Customizes the color of the outline when the select is hovered. +`height($height)` | Sets height of the filled select variant. > _NOTE_: To further customize the floating label, please see the [floating label documentation](./../mdc-floating-label/README.md). @@ -455,9 +460,9 @@ If you are using a JavaScript framework, such as React or Angular, you can creat | `setRippleCenter(normalizedX: number) => void` | Sets the line ripple center to the provided normalizedX value. | | `notifyChange(value: string) => void` | Emits the `MDCSelect:change` event when an element is selected. | | `setSelectedText(text: string) => void` | Sets the text content of the selectedText element to the given string. | -| `isSelectedTextFocused() => boolean` | Returns whether the selected text element is focused. | -| `getSelectedTextAttr(attr: string) => string` | Gets the given attribute on the selected text element. | -| `setSelectedTextAttr(attr: string, value: string) => void` | Sets the given attribute on the selected text element. | +| `isSelectAnchorFocused() => boolean` | Returns whether the select anchor element is focused. | +| `getSelectAnchorAttr(attr: string) => string` | Gets the given attribute on the select anchor element. | +| `setSelectAnchorAttr(attr: string, value: string) => void` | Sets the given attribute on the select anchor element. | | `openMenu() => void` | Causes the menu element in the select to open. | | `closeMenu() => void` | Causes the menu element in the select to close. | | `getAnchorElement() => Element` | Returns the select anchor element. | diff --git a/packages/mdc-select/_mixins.scss b/packages/mdc-select/_mixins.scss index fd4c7197118..67d09520db1 100644 --- a/packages/mdc-select/_mixins.scss +++ b/packages/mdc-select/_mixins.scss @@ -38,7 +38,7 @@ @use "@material/theme/variables" as theme-variables; @use "@material/typography/mixins" as typography-mixins; @use "./helper-text/mixins" as helper-text-mixins; -@use "./icon/mixins"; +@use "./icon/variables" as icon-variables; @use "./variables"; @mixin core-styles($query: feature-targeting-functions.all()) { @@ -57,7 +57,6 @@ @include label-color(variables.$label-color, $query: $query); @include bottom-line-color(variables.$bottom-line-idle-color, $query: $query); @include helper-text-mixins.helper-text-color(variables.$helper-text-color, $query: $query); - @include shape-radius(small, $query: $query); // Focused state colors @include focused-bottom-line-color(primary, $query: $query); @@ -69,6 +68,15 @@ // Floating label private mixin @include floating-label_($query: $query); + // structural + @include shape-radius(small, $query: $query); + @include padding-horizontal_( + $left: variables.$anchor-padding-left, + $left-with-leading-icon: variables.$anchor-padding-left-with-leading-icon, + $right: variables.$anchor-padding-right, + $query: $query + ); + @include feature-targeting-mixins.targets($feat-structure) { position: relative; // Menu is absolutely positioned relative to this. } @@ -77,11 +85,13 @@ @include dd-arrow-svg-bg_(variables.$dropdown-color, variables.$dropdown-opacity, $query: $query); @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive(left, auto, right, 8px); - position: absolute; - bottom: 16px; + @include rtl-mixins.reflexive-property(margin, + icon-variables.$icon-horizontal-margin, + icon-variables.$icon-horizontal-margin); width: 24px; height: 24px; + align-self: center; + flex-shrink: 0; pointer-events: none; } @@ -106,16 +116,17 @@ } .mdc-select__anchor { + @include height(variables.$height, $query: $query); @include floating-label-mixins.float-position(variables.$label-position-y, $query: $query); @include feature-targeting-mixins.targets($feat-structure) { display: inline-flex; position: relative; box-sizing: border-box; - height: variables.$height; overflow: hidden; - /* @alternate */ - will-change: opacity, transform, color; + outline: none; + cursor: pointer; + min-width: 200px; } @include focused-line-ripple_ { @@ -176,10 +187,6 @@ @include disabled_($query: $query); } - .mdc-select--no-label { - @include no-label_($query: $query); - } - .mdc-select--with-leading-icon { @include with-leading-icon_($query: $query); } @@ -206,14 +213,21 @@ @mixin ripple($query: feature-targeting-functions.all()) { .mdc-select__anchor { - @include ripple-mixins.surface($query: $query); - @include ripple-mixins.radius-bounded($query: $query); - @include ripple-mixins.states-base-color(variables.$ink-color, $query: $query); + @include ripple-mixins.surface($ripple-target: variables.$ripple-target, $query: $query); + @include ripple-mixins.radius-bounded($ripple-target: variables.$ripple-target, $query: $query); + @include ripple-mixins.states-base-color(variables.$ink-color, $ripple-target: variables.$ripple-target, $query: $query); @include ripple-mixins.states-opacities( ( hover: ripple-functions.states-opacity(variables.$ink-color, hover), focus: ripple-functions.states-opacity(variables.$ink-color, focus), - ), $query: $query); + ), + $ripple-target: variables.$ripple-target, + $query: $query + ); + + #{variables.$ripple-target} { + @include ripple-mixins.target-common($query: $query); + } } .mdc-select__menu .mdc-list .mdc-list-item--selected { @@ -314,12 +328,12 @@ } @if ($resolved-radius > notched-outline-variables.$leading-width) { - .mdc-select__selected-text { + .mdc-select__anchor { @include feature-targeting-mixins.targets($feat-structure) { @include rtl-mixins.reflexive-property( padding, $resolved-radius + notched-outline-variables.$padding, - variables.$arrow-padding + 0 ); } } @@ -423,59 +437,54 @@ } @mixin floating-label_($query: feature-targeting-functions.all()) { - $feat-structure: feature-targeting-functions.create-target($query, structure); + $feat-structure: feature-targeting-functions.create-target($query, structure); .mdc-floating-label { @include feature-targeting-mixins.targets($feat-structure) { @include rtl-mixins.reflexive-position(left, variables.$outline-label-offset); - top: 21px; + top: 50%; + transform: translateY(-50%); pointer-events: none; } } - &.mdc-select--with-leading-icon { + &.mdc-select--outlined { .mdc-floating-label { @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive-position(left, variables.$icon-padding); + @include rtl-mixins.reflexive-position(left, notched-outline-variables.$padding); } } } +} + +@mixin with-leading-icon_($query: feature-targeting-functions.all()) { + $feat-structure: feature-targeting-functions.create-target($query, structure); + + $icon-total-width: icon-variables.$icon-size + 2 * icon-variables.$icon-horizontal-margin; + + .mdc-floating-label { + @include feature-targeting-mixins.targets($feat-structure) { + @include rtl-mixins.reflexive-position(left, $icon-total-width); + } + } &.mdc-select--outlined { .mdc-floating-label { @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive-position(left, notched-outline-variables.$padding); - - top: 17px; + @include rtl-mixins.reflexive-position(left, $icon-total-width - notched-outline-variables.$leading-width); } - } - &.mdc-select--with-leading-icon { - .mdc-floating-label { + &--float-above { @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive-position(left, variables.$icon-padding - notched-outline-variables.$leading-width); - } - - &--float-above { - @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive-position(left, variables.$icon-padding - notched-outline-variables.$leading-width); - } + @include rtl-mixins.reflexive-position(left, $icon-total-width - notched-outline-variables.$leading-width); } } } - } -} -@mixin with-leading-icon_($query: feature-targeting-functions.all()) { - $feat-structure: feature-targeting-functions.create-target($query, structure); - - @include mixins.icon-horizontal-position_(16px, variables.$icon-padding, $query: $query); - - &.mdc-select--outlined { - @include notched-outline-mixins.floating-label-float-position( + @include notched-outline-mixins.floating-label-float-position-absolute( variables.$outlined-label-position-y, - variables.$outlined-with-leading-icon-label-position-x, + $icon-total-width - icon-variables.$icon-horizontal-margin - notched-outline-variables.$notch-gutter-size, $query: $query ); @include floating-label-mixins.shake-animation(select-outlined-leading-icon, $query: $query); @@ -500,8 +509,6 @@ @include typography-mixins.typography(subtitle1, $query: $query); @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive-property(padding, variables.$label-padding, variables.$arrow-padding); - &::-ms-expand { display: none; } @@ -511,23 +518,17 @@ color: inherit; } - // counteracts the extra text padding that Firefox adds by default - // stylelint-disable-next-line function-url-quotes - @-moz-document url-prefix("") { - text-indent: -2px; - } - box-sizing: border-box; - width: 100%; - min-width: 200px; - height: variables.$height; - padding-top: 20px; - padding-bottom: 4px; + width: 0; + flex-grow: 1; + height: variables.$selected-text-height; border: none; outline: none; + padding: 0; white-space: nowrap; - cursor: pointer; appearance: none; + pointer-events: none; + text-overflow: ellipsis; } @include feature-targeting-mixins.targets($feat-color) { @@ -589,18 +590,6 @@ } } -@mixin no-label_($query: feature-targeting-functions.all()) { - $feat-structure: feature-targeting-functions.create-target($query, structure); - - &:not(.mdc-select--outlined) { - .mdc-select__anchor .mdc-select__selected-text { - @include feature-targeting-mixins.targets($feat-structure) { - padding-top: 14px; - } - } - } -} - @mixin outlined_($query: feature-targeting-functions.all()) { $feat-structure: feature-targeting-functions.create-target($query, structure); $feat-color: feature-targeting-functions.create-target($query, color); @@ -618,8 +607,8 @@ .mdc-select__anchor { @include ripple-mixins.states-base-color(transparent, $query: $query); - @include floating-label-mixins.shake-animation(text-field-outlined, $query: $query); - @include notched-outline-mixins.floating-label-float-position(variables.$outlined-label-position-y, 0, $query: $query); + @include floating-label-mixins.shake-animation(select-outlined, $query: $query); + @include notched-outline-mixins.floating-label-float-position-absolute(variables.$outlined-label-position-y, 0, $query: $query); @include feature-targeting-mixins.targets($feat-structure) { overflow: visible; @@ -628,11 +617,7 @@ .mdc-select__selected-text { @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive-property(padding, variables.$label-padding, variables.$arrow-padding); - display: flex; - padding-top: 14px; - padding-bottom: 12px; border: none; z-index: 1; } @@ -662,19 +647,11 @@ .mdc-select-helper-text { // stylelint-disable plugin/selector-bem-pattern .mdc-select__anchor + & { - @include feature-targeting-mixins.targets($feat-structure) { - margin-right: 12px; - margin-left: 12px; - } - } - - .mdc-select--outlined .mdc-select__anchor + & { @include feature-targeting-mixins.targets($feat-structure) { margin-right: 16px; margin-left: 16px; } } - // stylelint-enable plugin/selector-bem-pattern } .mdc-select--focused .mdc-select__anchor + .mdc-select-helper-text:not(.mdc-select-helper-text--validation-msg) { @@ -683,3 +660,122 @@ } } } + +/// Adds horizontal padding to the selected text +/// +/// @param {Number} $left - left side padding +/// @param {Number} $left-with-leading-icon - left-side padding when a leading +/// icon is present +/// @param {Number} $right - right-side padding; note that a trailing icon is +/// always present. +@mixin padding-horizontal_( + $left, + $left-with-leading-icon, + $right, + $query: feature-targeting-functions.all() +) { + $feat-structure: feature-targeting-functions.create-target($query, structure); + + .mdc-select__anchor { + @include feature-targeting-mixins.targets($feat-structure) { + @include rtl-mixins.reflexive-property( + padding, + $left, + $right + ); + } + } + + &.mdc-select--with-leading-icon .mdc-select__anchor { + @include feature-targeting-mixins.targets($feat-structure) { + @include rtl-mixins.reflexive-property( + padding, + $left-with-leading-icon, + $right + ); + } + } +} + +/// +/// Sets height of default select variant. +/// +/// @param {Number} $height +/// @param {Number} $minimum-height-for-filled-label Sets the minimum height for +/// filled selects at which to allow floating labels. +/// @param {Number} $filled-baseline-top The baseline from the top of the anchor +/// that the input should be aligned to for a filled variant with a label +/// @access public +/// +@mixin height( + $height, + $minimum-height-for-filled-label: variables.$minimum-height-for-filled-label, + $filled-baseline-top: variables.$filled-baseline-top, + $query: feature-targeting-functions.all() +) { + $feat-structure: feature-targeting-functions.create-target($query, structure); + @include feature-targeting-mixins.targets($feat-structure) { + height: $height; + } + + // Filled variant is aligned to baseline... + @include typography-mixins.baseline($top: $filled-baseline-top, $display: inline-flex, $query: $query); + // ...unless it is too small to display a label + @if $height < $minimum-height-for-filled-label { + @include center-aligned_($query: $query); + + @include feature-targeting-mixins.targets($feat-structure) { + &:not(.mdc-select--outlined) { + .mdc-floating-label { + display: none; + } + } + } + } + + // Outlined and no-label variants are always centered + .mdc-select--outlined &, + .mdc-select--no-label & { + @include center-aligned_($query: $query); + } +} + +// Removes filled baseline alignment +@mixin center-aligned_($query: feature-targeting-functions.all()) { + $feat-structure: feature-targeting-functions.create-target($query, structure); + + @include feature-targeting-mixins.targets($feat-structure) { + // In order for a flexbox container to participate in baseline alignment, + // it follows these rules to determine where its baseline is: + // https://www.w3.org/TR/css-flexbox-1/#flex-baselines + // + // In order to avoid leading icons "controlling" the baseline (since they + // are the first child), flexbox will generate a baseline from any child + // flex items that participate in baseline alignment. + // + // Icons are set to "align-self: center", while all other children are + // aligned to baseline. The next problem is deciding which child is + // used to determine the baseline. + // + // According to spec, the item with the largest distance between its + // baseline and the edge of the cross axis is placed flush with that edge, + // making it the baseline of the container. + // https://www.w3.org/TR/css-flexbox-1/#baseline-participation + // + // For the filled variant, the pseudo ::before strut is the "largest" + // child since the input has a height of 28px and the strut is 40px. We + // can emulate center alignment and force the baseline to use the input + // text by making the input the full height of the container and removing + // the baseline strut. + + // TODO: IE11 does not respect this, and makes the leading icon (if present) + // the baseline. + .mdc-select__selected-text { + height: 100%; + } + + &::before { + display: none; + } + } +} diff --git a/packages/mdc-select/_variables.scss b/packages/mdc-select/_variables.scss index b7e350bbb4d..93dec888e8d 100644 --- a/packages/mdc-select/_variables.scss +++ b/packages/mdc-select/_variables.scss @@ -21,13 +21,22 @@ // @use "sass:color"; -@use "@material/animation/variables" as variables2; +@use "@material/notched-outline/variables" as notched-outline-variables; @use "@material/theme/variables"; +@function get-outlined-label-position-y($select-anchor-height) { + @return $select-anchor-height / 2 + notched-outline-variables.$label-box-height / 2; +} +$ripple-target: '.mdc-select__ripple'; $arrow-padding: 52px !default; $label-padding: 16px !default; $height: 56px !default; -$icon-padding: 48px !default; +$minimum-height-for-filled-label: 52px !default; +$filled-baseline-top: 40px !default; +$selected-text-height: 28px !default; +$anchor-padding-left: 16px !default; +$anchor-padding-left-with-leading-icon: 0 !default; +$anchor-padding-right: 0 !default; $ink-color: rgba(variables.prop-value(on-surface), .87) !default; $dropdown-color: variables.prop-value(on-surface) !default; @@ -55,10 +64,9 @@ $outlined-hover-border: rgba(variables.prop-value(on-surface), .87) !default; // should be .06 after mdc-select opacity is applied $outlined-disabled-border: rgba(variables.prop-value(on-surface), .16) !default; -$label-position-y: 70% !default; +$label-position-y: 106% !default; $outline-label-offset: 16px !default; -$outlined-label-position-y: 130% !default; -$outlined-dense-label-position-y: 110% !default; +$outlined-label-position-y: get-outlined-label-position-y($height) !default; $outlined-with-leading-icon-label-position-x: 32px !default; $dropdown-transition-duration: 150ms !default; diff --git a/packages/mdc-select/adapter.ts b/packages/mdc-select/adapter.ts index 6824108ba09..6ae758ba04d 100644 --- a/packages/mdc-select/adapter.ts +++ b/packages/mdc-select/adapter.ts @@ -107,19 +107,19 @@ export interface MDCSelectAdapter { setSelectedText(text: string): void; /** - * Returns whether the selected text element is focused. + * Returns whether the select anchor is focused. */ - isSelectedTextFocused(): boolean; + isSelectAnchorFocused(): boolean; /** - * Gets the given attribute on the selected text element. + * Gets the given attribute on the select anchor element. */ - getSelectedTextAttr(attr: string): string | null; + getSelectAnchorAttr(attr: string): string|null; /** - * Sets the given attribute on the selected text element. + * Sets the given attribute on the select anchor element. */ - setSelectedTextAttr(attr: string, value: string): void; + setSelectAnchorAttr(attr: string, value: string): void; // Menu-related methods ====================================================== /** diff --git a/packages/mdc-select/component.ts b/packages/mdc-select/component.ts index 80130ef58a9..aad267a7456 100644 --- a/packages/mdc-select/component.ts +++ b/packages/mdc-select/component.ts @@ -53,7 +53,7 @@ export class MDCSelect extends MDCComponent { private menu_!: MDCMenu; // assigned in menuSetup_() private selectAnchor_!: HTMLElement; // assigned in initialize() - private selectedText_!: HTMLElement; // assigned in initialize() + private selectedText_!: HTMLInputElement; // assigned in initialize() private menuElement_!: Element; // assigned in menuSetup_() private leadingIcon_?: MDCSelectIcon; // assigned in initialize() @@ -79,7 +79,9 @@ export class MDCSelect extends MDCComponent { helperTextFactory: MDCSelectHelperTextFactory = (el) => new MDCSelectHelperText(el), ) { this.selectAnchor_ = this.root_.querySelector(strings.SELECT_ANCHOR_SELECTOR) as HTMLElement; - this.selectedText_ = this.root_.querySelector(strings.SELECTED_TEXT_SELECTOR) as HTMLElement; + this.selectedText_ = + this.root_.querySelector(strings.SELECTED_TEXT_SELECTOR) as + HTMLInputElement; if (!this.selectedText_) { throw new Error( @@ -88,8 +90,9 @@ export class MDCSelect extends MDCComponent { ); } - if (this.selectedText_.hasAttribute(strings.ARIA_CONTROLS)) { - const helperTextElement = document.getElementById(this.selectedText_.getAttribute(strings.ARIA_CONTROLS)!); + if (this.selectAnchor_.hasAttribute(strings.ARIA_CONTROLS)) { + const helperTextElement = document.getElementById( + this.selectAnchor_.getAttribute(strings.ARIA_CONTROLS)!); if (helperTextElement) { this.helperText_ = helperTextFactory(helperTextElement); } @@ -125,7 +128,7 @@ export class MDCSelect extends MDCComponent { this.handleFocus_ = () => this.foundation_.handleFocus(); this.handleBlur_ = () => this.foundation_.handleBlur(); this.handleClick_ = (evt) => { - this.selectedText_.focus(); + this.selectAnchor_.focus(); this.foundation_.handleClick(this.getNormalizedXCoordinate_(evt)); }; this.handleKeydown_ = (evt) => this.foundation_.handleKeydown(evt); @@ -133,27 +136,26 @@ export class MDCSelect extends MDCComponent { this.handleMenuOpened_ = () => this.foundation_.handleMenuOpened(); this.handleMenuClosed_ = () => this.foundation_.handleMenuClosed(); - this.selectedText_.addEventListener('focus', this.handleFocus_); - this.selectedText_.addEventListener('blur', this.handleBlur_); + this.selectAnchor_.addEventListener('focus', this.handleFocus_); + this.selectAnchor_.addEventListener('blur', this.handleBlur_); - this.selectedText_.addEventListener('click', this.handleClick_ as EventListener); + this.selectAnchor_.addEventListener( + 'click', this.handleClick_ as EventListener); - this.selectedText_!.addEventListener('keydown', this.handleKeydown_); + this.selectAnchor_.addEventListener('keydown', this.handleKeydown_); this.menu_!.listen(menuSurfaceConstants.strings.CLOSED_EVENT, this.handleMenuClosed_); this.menu_!.listen(menuSurfaceConstants.strings.OPENED_EVENT, this.handleMenuOpened_); this.menu_!.listen(menuConstants.strings.SELECTED_EVENT, this.handleMenuItemAction_); this.foundation_.init(); - - // Sets disabled state in foundation - this.disabled = this.root_.classList.contains(cssClasses.DISABLED); } destroy() { - this.selectedText_.removeEventListener('change', this.handleChange_); - this.selectedText_.removeEventListener('focus', this.handleFocus_); - this.selectedText_.removeEventListener('blur', this.handleBlur_); - this.selectedText_.removeEventListener('keydown', this.handleKeydown_); - this.selectedText_.removeEventListener('click', this.handleClick_ as EventListener); + this.selectAnchor_.removeEventListener('change', this.handleChange_); + this.selectAnchor_.removeEventListener('focus', this.handleFocus_); + this.selectAnchor_.removeEventListener('blur', this.handleBlur_); + this.selectAnchor_.removeEventListener('keydown', this.handleKeydown_); + this.selectAnchor_.removeEventListener( + 'click', this.handleClick_ as EventListener); this.menu_.unlisten(menuSurfaceConstants.strings.CLOSED_EVENT, this.handleMenuClosed_); this.menu_.unlisten(menuSurfaceConstants.strings.OPENED_EVENT, this.handleMenuOpened_); @@ -279,8 +281,12 @@ export class MDCSelect extends MDCComponent { // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface. const adapter: MDCRippleAdapter = { ...MDCRipple.createAdapter({root_: this.selectAnchor_}), - registerInteractionHandler: (evtType, handler) => this.selectedText_.addEventListener(evtType, handler), - deregisterInteractionHandler: (evtType, handler) => this.selectedText_.removeEventListener(evtType, handler), + registerInteractionHandler: (evtType, handler) => { + this.selectAnchor_.addEventListener(evtType, handler); + }, + deregisterInteractionHandler: (evtType, handler) => { + this.selectAnchor_.removeEventListener(evtType, handler); + }, }; // tslint:enable:object-literal-sort-keys return new MDCRipple(this.selectAnchor_, new MDCRippleFoundation(adapter)); @@ -289,28 +295,58 @@ export class MDCSelect extends MDCComponent { private getSelectAdapterMethods_() { // tslint:disable:object-literal-sort-keys Methods should be in the same order as the adapter interface. return { - getSelectedMenuItem: () => this.menuElement_!.querySelector(strings.SELECTED_ITEM_SELECTOR), - getMenuItemAttr: (menuItem: Element, attr: string) => menuItem.getAttribute(attr), - setSelectedText: (text: string) => this.selectedText_.textContent = text, - isSelectedTextFocused: () => document.activeElement === this.selectedText_, - getSelectedTextAttr: (attr: string) => this.selectedText_.getAttribute(attr), - setSelectedTextAttr: (attr: string, value: string) => this.selectedText_.setAttribute(attr, value), - openMenu: () => this.menu_.open = true, - closeMenu: () => this.menu_.open = false, - getAnchorElement: () => this.root_.querySelector(strings.SELECT_ANCHOR_SELECTOR)!, - setMenuAnchorElement: (anchorEl: HTMLElement) => this.menu_.setAnchorElement(anchorEl), - setMenuAnchorCorner: (anchorCorner: menuSurfaceConstants.Corner) => this.menu_.setAnchorCorner(anchorCorner), - setMenuWrapFocus: (wrapFocus: boolean) => this.menu_.wrapFocus = wrapFocus, - setAttributeAtIndex: (index: number, attributeName: string, attributeValue: string) => - this.menu_.items[index].setAttribute(attributeName, attributeValue), - removeAttributeAtIndex: (index: number, attributeName: string) => - this.menu_.items[index].removeAttribute(attributeName), - focusMenuItemAtIndex: (index: number) => (this.menu_.items[index] as HTMLElement).focus(), + getSelectedMenuItem: () => + this.menuElement_!.querySelector(strings.SELECTED_ITEM_SELECTOR), + getMenuItemAttr: (menuItem: Element, attr: string) => + menuItem.getAttribute(attr), + setSelectedText: (text: string) => { + this.selectedText_.value = text; + }, + isSelectAnchorFocused: () => + document.activeElement === this.selectAnchor_, + getSelectAnchorAttr: (attr: string) => + this.selectAnchor_.getAttribute(attr), + setSelectAnchorAttr: (attr: string, value: string) => { + this.selectAnchor_.setAttribute(attr, value); + }, + openMenu: () => { + this.menu_.open = true; + }, + closeMenu: () => { + this.menu_.open = false; + }, + getAnchorElement: () => + this.root_.querySelector(strings.SELECT_ANCHOR_SELECTOR)!, + setMenuAnchorElement: (anchorEl: HTMLElement) => { + this.menu_.setAnchorElement(anchorEl); + }, + setMenuAnchorCorner: (anchorCorner: menuSurfaceConstants.Corner) => { + this.menu_.setAnchorCorner(anchorCorner); + }, + setMenuWrapFocus: (wrapFocus: boolean) => { + this.menu_.wrapFocus = wrapFocus; + }, + setAttributeAtIndex: + (index: number, attributeName: string, attributeValue: string) => { + this.menu_.items[index].setAttribute(attributeName, attributeValue); + }, + removeAttributeAtIndex: (index: number, attributeName: string) => { + this.menu_.items[index].removeAttribute(attributeName); + }, + focusMenuItemAtIndex: (index: number) => { + (this.menu_.items[index] as HTMLElement).focus(); + }, getMenuItemCount: () => this.menu_.items.length, - getMenuItemValues: () => this.menu_.items.map((el) => el.getAttribute(strings.VALUE_ATTR) || ''), - getMenuItemTextAtIndex: (index: number) => this.menu_.items[index].textContent as string, - addClassAtIndex: (index: number, className: string) => this.menu_.items[index].classList.add(className), - removeClassAtIndex: (index: number, className: string) => this.menu_.items[index].classList.remove(className), + getMenuItemValues: () => this.menu_.items.map( + (el) => el.getAttribute(strings.VALUE_ATTR) || ''), + getMenuItemTextAtIndex: (index: number) => + this.menu_.items[index].textContent as string, + addClassAtIndex: (index: number, className: string) => { + this.menu_.items[index].classList.add(className); + }, + removeClassAtIndex: (index: number, className: string) => { + this.menu_.items[index].classList.remove(className); + }, }; // tslint:enable:object-literal-sort-keys } diff --git a/packages/mdc-select/foundation.ts b/packages/mdc-select/foundation.ts index e93bce09b7f..53ab745d0d4 100644 --- a/packages/mdc-select/foundation.ts +++ b/packages/mdc-select/foundation.ts @@ -64,9 +64,9 @@ export class MDCSelectFoundation extends MDCFoundation { setRippleCenter: () => undefined, notifyChange: () => undefined, setSelectedText: () => undefined, - isSelectedTextFocused: () => false, - getSelectedTextAttr: () => '', - setSelectedTextAttr: () => undefined, + isSelectAnchorFocused: () => false, + getSelectAnchorAttr: () => '', + setSelectAnchorAttr: () => undefined, openMenu: () => undefined, closeMenu: () => undefined, getAnchorElement: () => null, @@ -112,6 +112,7 @@ export class MDCSelectFoundation extends MDCFoundation { this.helperText_ = foundationMap.helperText; this.menuItemValues_ = this.adapter_.getMenuItemValues(); + this.setDisabled(this.adapter_.hasClass(cssClasses.DISABLED)); } /** Returns the index of the currently selected menu item, or -1 if none. */ @@ -180,8 +181,9 @@ export class MDCSelectFoundation extends MDCFoundation { this.leadingIcon_.setDisabled(this.disabled_); } - this.adapter_.setSelectedTextAttr('tabindex', this.disabled_ ? '-1' : '0'); - this.adapter_.setSelectedTextAttr('aria-disabled', this.disabled_.toString()); + this.adapter_.setSelectAnchorAttr('tabindex', this.disabled_ ? '-1' : '0'); + this.adapter_.setSelectAnchorAttr( + 'aria-disabled', this.disabled_.toString()); } /** @@ -215,10 +217,10 @@ export class MDCSelectFoundation extends MDCFoundation { handleMenuClosed() { this.adapter_.removeClass(cssClasses.ACTIVATED); this.isMenuOpen_ = false; - this.adapter_.setSelectedTextAttr('aria-expanded', 'false'); + this.adapter_.setSelectAnchorAttr('aria-expanded', 'false'); // Unfocus the select if menu is closed without a selection - if (!this.adapter_.isSelectedTextFocused()) { + if (!this.adapter_.isSelectAnchorFocused()) { this.blur_(); } } @@ -278,7 +280,7 @@ export class MDCSelectFoundation extends MDCFoundation { this.adapter_.openMenu(); this.isMenuOpen_ = true; - this.adapter_.setSelectedTextAttr('aria-expanded', 'true'); + this.adapter_.setSelectAnchorAttr('aria-expanded', 'true'); } handleKeydown(event: KeyboardEvent) { @@ -294,7 +296,7 @@ export class MDCSelectFoundation extends MDCFoundation { if (this.adapter_.hasClass(cssClasses.FOCUSED) && (isEnter || isSpace || arrowUp || arrowDown)) { this.adapter_.openMenu(); this.isMenuOpen_ = true; - this.adapter_.setSelectedTextAttr('aria-expanded', 'true'); + this.adapter_.setSelectAnchorAttr('aria-expanded', 'true'); event.preventDefault(); } } @@ -336,7 +338,7 @@ export class MDCSelectFoundation extends MDCFoundation { } setValid(isValid: boolean) { - this.adapter_.setSelectedTextAttr('aria-invalid', (!isValid).toString()); + this.adapter_.setSelectAnchorAttr('aria-invalid', (!isValid).toString()); if (isValid) { this.adapter_.removeClass(cssClasses.INVALID); } else { @@ -360,11 +362,11 @@ export class MDCSelectFoundation extends MDCFoundation { } else { this.adapter_.removeClass(cssClasses.REQUIRED); } - this.adapter_.setSelectedTextAttr('aria-required', isRequired.toString()); + this.adapter_.setSelectAnchorAttr('aria-required', isRequired.toString()); } getRequired() { - return this.adapter_.getSelectedTextAttr('aria-required') === 'true'; + return this.adapter_.getSelectAnchorAttr('aria-required') === 'true'; } init() { diff --git a/packages/mdc-select/helper-text/README.md b/packages/mdc-select/helper-text/README.md index 3fede98768d..48f3f4a6d41 100644 --- a/packages/mdc-select/helper-text/README.md +++ b/packages/mdc-select/helper-text/README.md @@ -61,9 +61,7 @@ the display of the helper text is dependent on the interaction with the MDCSelec Pick a Food Group
-

- Helper text -

+

Helper text

``` When using our JS component, if the browser sees that the input element has an `aria-controls` diff --git a/packages/mdc-select/icon/_mixins.scss b/packages/mdc-select/icon/_mixins.scss index 67480a3ccab..a738c7945fa 100644 --- a/packages/mdc-select/icon/_mixins.scss +++ b/packages/mdc-select/icon/_mixins.scss @@ -32,6 +32,11 @@ .mdc-select--with-leading-icon { @include icon_($query: $query); + @include icon-horizontal-margins_( + $left: variables.$icon-horizontal-margin, + $right: variables.$icon-horizontal-margin, + $query: $query); + @include icon-color(on-surface, $query: $query); } .mdc-select__icon:not([tabindex]), @@ -55,13 +60,9 @@ $feat-structure: feature-targeting-functions.create-target($query, structure); $feat-color: feature-targeting-functions.create-target($query, color); - @include icon-color(on-surface, $query: $query); - .mdc-select__icon { @include feature-targeting-mixins.targets($feat-structure) { display: inline-block; - position: absolute; - bottom: 16px; box-sizing: border-box; width: variables.$icon-size; height: variables.$icon-size; @@ -70,6 +71,8 @@ text-decoration: none; cursor: pointer; user-select: none; + flex-shrink: 0; + align-self: center; } @include feature-targeting-mixins.targets($feat-color) { @@ -89,19 +92,15 @@ } } -@mixin icon-horizontal-position_($iconPosition, $inputPadding, $query: feature-targeting-functions.all()) { +@mixin icon-horizontal-margins_($left, $right, $query: feature-targeting-functions.all()) { $feat-structure: feature-targeting-functions.create-target($query, structure); - .mdc-select__icon { @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive-position(left, $iconPosition); - } - } - - // Move the input's position, to allow room for the icon - .mdc-select__selected-text { - @include feature-targeting-mixins.targets($feat-structure) { - @include rtl-mixins.reflexive-property(padding, $inputPadding /* left */, variables.$icon-right-padding /* right */); + @include rtl-mixins.reflexive-property( + margin, + $left, + $right + ); } } } diff --git a/packages/mdc-select/icon/_variables.scss b/packages/mdc-select/icon/_variables.scss index f708acd0839..1cdcda8f8b3 100644 --- a/packages/mdc-select/icon/_variables.scss +++ b/packages/mdc-select/icon/_variables.scss @@ -21,4 +21,4 @@ $icon-size: 24px !default; $icon-opacity: .54 !default; -$icon-right-padding: 32px !default; +$icon-horizontal-margin: 12px !default; diff --git a/packages/mdc-select/test/component.test.ts b/packages/mdc-select/test/component.test.ts index 4544c90f0b9..bd5dbc632d1 100644 --- a/packages/mdc-select/test/component.test.ts +++ b/packages/mdc-select/test/component.test.ts @@ -85,7 +85,6 @@ function getFixture() { return createFixture(`
- code
@@ -112,7 +111,6 @@ function getOutlineFixture() { return createFixture(`
- code
@@ -142,7 +140,7 @@ function getOutlineFixture() { function getHelperTextFixture(root = getFixture()) { const containerDiv = document.createElement('div'); - root.querySelector('.mdc-select__selected-text')!.setAttribute( + root.querySelector(strings.SELECT_ANCHOR_SELECTOR)!.setAttribute( 'aria-controls', 'test-helper-text'); containerDiv.appendChild(root); containerDiv.appendChild(createFixture( @@ -165,7 +163,7 @@ function setupTest( fixture.querySelector(strings.SELECT_ANCHOR_SELECTOR) as HTMLElement; const container = hasHelperText ? getHelperTextFixture(fixture) : null; const selectedText = - fixture.querySelector(strings.SELECTED_TEXT_SELECTOR) as HTMLElement; + fixture.querySelector(strings.SELECTED_TEXT_SELECTOR) as HTMLInputElement; const labelEl = fixture.querySelector(strings.LABEL_SELECTOR) as HTMLElement; const bottomLineEl = fixture.querySelector(strings.LINE_RIPPLE_SELECTOR) as HTMLElement; @@ -261,28 +259,27 @@ describe('MDCSelect', () => { component.disabled = true; checkNumTimesSpyCalledWithArgs(mockFoundation.setDisabled, [true], 1); component.disabled = false; - // Called once at initialization, once when setting to false - checkNumTimesSpyCalledWithArgs(mockFoundation.setDisabled, [false], 2); + checkNumTimesSpyCalledWithArgs(mockFoundation.setDisabled, [false], 1); }); it('#get/set required true', () => { - const {fixture, component, selectedText} = setupTest(); + const {fixture, component, anchor} = setupTest(); expect(component.required).toBe(false); component.required = true; expect(component.required).toBe(true); expect(fixture.classList.contains(cssClasses.REQUIRED)).toBe(true); - expect(selectedText.getAttribute('aria-required')).toBe('true'); + expect(anchor.getAttribute('aria-required')).toBe('true'); }); it('#get/set required false', () => { - const {fixture, component, selectedText} = setupTest(); + const {fixture, component, anchor} = setupTest(); expect(component.required).toBe(false); component.required = false; expect(component.required).toBe(false); expect(fixture.classList.contains(cssClasses.REQUIRED)).toBe(false); - expect(selectedText.getAttribute('aria-required')).toBe('false'); + expect(anchor.getAttribute('aria-required')).toBe('false'); }); it('#get value', () => { @@ -498,14 +495,12 @@ describe('MDCSelect', () => { MDCSelect.attachTo(fixture); jasmine.clock().tick(1); - const selectedText = - fixture.querySelector('.mdc-select__selected-text') as HTMLElement; + const anchor = + fixture.querySelector(strings.SELECT_ANCHOR_SELECTOR) as HTMLElement; - emitEvent(selectedText, 'focus'); + emitEvent(anchor, 'focus'); jasmine.clock().tick(1); - const anchor = - fixture.querySelector(strings.SELECT_ANCHOR_SELECTOR) as HTMLElement; expect(anchor.classList.contains(MDCRippleFoundation.cssClasses.BG_FOCUSED)) .toBe(true); }); @@ -789,61 +784,59 @@ describe('MDCSelect', () => { const adapter = (component.getDefaultFoundation() as any).adapter_; const textToSet = 'foo'; - expect(selectedText.textContent).not.toEqual(textToSet); + expect(selectedText.value).not.toEqual(textToSet); adapter.setSelectedText(textToSet); - expect(selectedText.textContent).toEqual(textToSet); + expect(selectedText.value).toEqual(textToSet); document.body.removeChild(fixture); }); - it('adapter#isSelectedTextFocused', () => { + it('adapter#isSelectAnchorFocused', () => { const hasMockFoundation = true; const hasMockMenu = true; const hasOutline = false; const hasLabel = true; - const {fixture, component, selectedText} = + const {fixture, component, anchor} = setupTest(hasOutline, hasLabel, hasMockFoundation, hasMockMenu); document.body.appendChild(fixture); - selectedText.tabIndex = 0; - selectedText.focus(); + anchor.tabIndex = 0; + anchor.focus(); const adapter = (component.getDefaultFoundation() as any).adapter_; - expect(adapter.isSelectedTextFocused()).toBe(true); + expect(adapter.isSelectAnchorFocused()).toBe(true); document.body.removeChild(fixture); }); - it('adapter#getSelectedTextAttr sets the select text content correctly', - () => { - const hasMockFoundation = true; - const hasMockMenu = true; - const hasOutline = false; - const hasLabel = true; - const {fixture, component, selectedText} = - setupTest(hasOutline, hasLabel, hasMockFoundation, hasMockMenu); - document.body.appendChild(fixture); - const adapter = (component.getDefaultFoundation() as any).adapter_; + it('adapter#getSelectAnchorAttr gets the attribute content correctly', () => { + const hasMockFoundation = true; + const hasMockMenu = true; + const hasOutline = false; + const hasLabel = true; + const {fixture, component, anchor} = + setupTest(hasOutline, hasLabel, hasMockFoundation, hasMockMenu); + document.body.appendChild(fixture); + const adapter = (component.getDefaultFoundation() as any).adapter_; - expect(selectedText.hasAttribute('foo')).toBe(false); - selectedText.setAttribute('foo', '1'); - expect(adapter.getSelectedTextAttr('foo')).toEqual('1'); - document.body.removeChild(fixture); - }); + expect(anchor.hasAttribute('foo')).toBe(false); + anchor.setAttribute('foo', '1'); + expect(adapter.getSelectAnchorAttr('foo')).toEqual('1'); + document.body.removeChild(fixture); + }); - it('adapter#setSelectedTextAttr sets the select text content correctly', - () => { - const hasMockFoundation = true; - const hasMockMenu = true; - const hasOutline = false; - const hasLabel = true; - const {fixture, component, selectedText} = - setupTest(hasOutline, hasLabel, hasMockFoundation, hasMockMenu); - document.body.appendChild(fixture); - const adapter = (component.getDefaultFoundation() as any).adapter_; + it('adapter#setSelectAnchorAttr sets the attribute content correctly', () => { + const hasMockFoundation = true; + const hasMockMenu = true; + const hasOutline = false; + const hasLabel = true; + const {fixture, component, anchor} = + setupTest(hasOutline, hasLabel, hasMockFoundation, hasMockMenu); + document.body.appendChild(fixture); + const adapter = (component.getDefaultFoundation() as any).adapter_; - expect(selectedText.hasAttribute('foo')).toBe(false); - adapter.setSelectedTextAttr('foo', '1'); - expect(selectedText.getAttribute('foo')).toEqual('1'); - document.body.removeChild(fixture); - }); + expect(anchor.hasAttribute('foo')).toBe(false); + adapter.setSelectAnchorAttr('foo', '1'); + expect(anchor.getAttribute('foo')).toEqual('1'); + document.body.removeChild(fixture); + }); it('adapter#openMenu causes the menu to open', () => { const hasMockFoundation = true; @@ -1021,42 +1014,42 @@ describe('MDCSelect', () => { }); it('focus event triggers foundation.handleFocus()', () => { - const {selectedText, mockFoundation} = setupWithMockFoundation(); - emitEvent(selectedText, 'focus'); + const {anchor, mockFoundation} = setupWithMockFoundation(); + emitEvent(anchor, 'focus'); expect(mockFoundation.handleFocus).toHaveBeenCalledTimes(1); }); it('blur event triggers foundation.handleBlur()', () => { - const {selectedText, mockFoundation} = setupWithMockFoundation(); - emitEvent(selectedText, 'blur'); + const {anchor, mockFoundation} = setupWithMockFoundation(); + emitEvent(anchor, 'blur'); expect(mockFoundation.handleBlur).toHaveBeenCalledTimes(1); }); it('#destroy removes the change handler', () => { - const {component, selectedText, mockFoundation} = setupWithMockFoundation(); + const {component, anchor, mockFoundation} = setupWithMockFoundation(); component.destroy(); - emitEvent(selectedText, 'change'); + emitEvent(anchor, 'change'); expect(mockFoundation.handleChange).not.toHaveBeenCalled(); }); it('#destroy removes the focus handler', () => { - const {component, selectedText, mockFoundation} = setupWithMockFoundation(); + const {component, anchor, mockFoundation} = setupWithMockFoundation(); component.destroy(); - emitEvent(selectedText, 'focus'); + emitEvent(anchor, 'focus'); expect(mockFoundation.handleFocus).not.toHaveBeenCalled(); }); it('#destroy removes the blur handler', () => { - const {component, selectedText, mockFoundation} = setupWithMockFoundation(); + const {component, anchor, mockFoundation} = setupWithMockFoundation(); component.destroy(); - emitEvent(selectedText, 'blur'); + emitEvent(anchor, 'blur'); expect(mockFoundation.handleBlur).not.toHaveBeenCalled(); }); it('#destroy removes the click handler', () => { - const {component, selectedText, mockFoundation} = setupWithMockFoundation(); + const {component, anchor, mockFoundation} = setupWithMockFoundation(); component.destroy(); - emitEvent(selectedText, 'click'); + emitEvent(anchor, 'click'); expect(mockFoundation.handleClick).not.toHaveBeenCalled(); }); @@ -1097,25 +1090,26 @@ describe('MDCSelect', () => { }); it('#destroy removes the click listener', () => { - const {component, selectedText} = setupTest(); - (component as any).foundation_.handleClick = jasmine.createSpy(''); + const {component, anchor} = setupTest(); + (component as any).foundation_.handleClick = + jasmine.createSpy('handleClick'); component.destroy(); - emitEvent(selectedText, 'click'); + emitEvent(anchor, 'click'); expect((component as any).foundation_.handleClick).not.toHaveBeenCalled(); }); - it('click on the selectedText calls foundation.handleClick()', () => { - const {component, selectedText} = setupTest(); + it('click on the anchor calls foundation.handleClick()', () => { + const {component, anchor} = setupTest(); (component as any).foundation_.handleClick = jasmine.createSpy(''); - emitEvent(selectedText, 'click'); + emitEvent(anchor, 'click'); expect((component as any).foundation_.handleClick).toHaveBeenCalled(); }); - it('click on the selectedText focuses on the selectedText element', () => { - const {selectedText} = setupTest(); - selectedText.focus = jasmine.createSpy(''); - emitEvent(selectedText, 'click'); - expect(selectedText.focus).toHaveBeenCalledTimes(1); + it('click on the anchor focuses on the anchor element', () => { + const {anchor} = setupTest(); + anchor.focus = jasmine.createSpy('focus'); + emitEvent(anchor, 'click'); + expect(anchor.focus).toHaveBeenCalledTimes(1); }); it('menu surface opened event causes the first element (if no element is selected) to be focused', @@ -1178,11 +1172,11 @@ describe('MDCSelect', () => { document.body.removeChild(fixture); }); - it('keydown event is added to selected-text when initialized', () => { + it('keydown event is added to select anchor when initialized', () => { const {fixture, mockFoundation} = setupWithMockFoundation(); document.body.appendChild(fixture); emitEvent( - fixture.querySelector('.mdc-select__selected-text') as HTMLElement, + fixture.querySelector(strings.SELECT_ANCHOR_SELECTOR) as HTMLElement, 'keydown'); expect(mockFoundation.handleKeydown) .toHaveBeenCalledWith(jasmine.anything()); @@ -1190,12 +1184,12 @@ describe('MDCSelect', () => { document.body.removeChild(fixture); }); - it('keydown event is removed from selected-text when destroyed', () => { + it('keydown event is removed from select anchor when destroyed', () => { const {fixture, mockFoundation, component} = setupWithMockFoundation(); document.body.appendChild(fixture); component.destroy(); emitEvent( - fixture.querySelector('.mdc-select__selected-text') as HTMLElement, + fixture.querySelector(strings.SELECT_ANCHOR_SELECTOR) as HTMLElement, 'keydown'); expect(mockFoundation.handleKeydown) .not.toHaveBeenCalledWith(jasmine.anything()); diff --git a/packages/mdc-select/test/foundation.test.ts b/packages/mdc-select/test/foundation.test.ts index 9d463620ca9..123c262ae6f 100644 --- a/packages/mdc-select/test/foundation.test.ts +++ b/packages/mdc-select/test/foundation.test.ts @@ -57,9 +57,9 @@ describe('MDCSelectFoundation', () => { 'setRippleCenter', 'notifyChange', 'setSelectedText', - 'isSelectedTextFocused', - 'getSelectedTextAttr', - 'setSelectedTextAttr', + 'isSelectAnchorFocused', + 'getSelectAnchorAttr', + 'setSelectAnchorAttr', 'openMenu', 'closeMenu', 'getAnchorElement', @@ -219,7 +219,7 @@ describe('MDCSelectFoundation', () => { it('#handleMenuClosed set aria-expanded attribute to false', () => { const {foundation, mockAdapter} = setupTest(); foundation.handleMenuClosed(); - expect(mockAdapter.setSelectedTextAttr) + expect(mockAdapter.setSelectAnchorAttr) .toHaveBeenCalledWith('aria-expanded', 'false'); }); @@ -439,7 +439,7 @@ describe('MDCSelectFoundation', () => { it('#handleClick sets the aria-expanded', () => { const {foundation, mockAdapter} = setupTest(); foundation.handleClick(0); - expect(mockAdapter.setSelectedTextAttr) + expect(mockAdapter.setSelectAnchorAttr) .toHaveBeenCalledWith('aria-expanded', 'true'); }); @@ -475,7 +475,7 @@ describe('MDCSelectFoundation', () => { foundation.handleKeydown(event); expect(mockAdapter.openMenu).toHaveBeenCalledTimes(8); checkNumTimesSpyCalledWithArgs( - mockAdapter.setSelectedTextAttr, ['aria-expanded', 'true'], 8); + mockAdapter.setSelectAnchorAttr, ['aria-expanded', 'true'], 8); expect(preventDefault).toHaveBeenCalledTimes(8); }); @@ -650,7 +650,7 @@ describe('MDCSelectFoundation', () => { () => { const {foundation, mockAdapter} = setupTest(); foundation.setValid(true); - expect(mockAdapter.setSelectedTextAttr) + expect(mockAdapter.setSelectAnchorAttr) .toHaveBeenCalledWith('aria-invalid', 'false'); expect(mockAdapter.removeClass).toHaveBeenCalledWith(cssClasses.INVALID); }); @@ -658,7 +658,7 @@ describe('MDCSelectFoundation', () => { it('#setValid false sets aria-invalid to true and adds invalid class', () => { const {foundation, mockAdapter} = setupTest(); foundation.setValid(false); - expect(mockAdapter.setSelectedTextAttr) + expect(mockAdapter.setSelectAnchorAttr) .toHaveBeenCalledWith('aria-invalid', 'true'); expect(mockAdapter.addClass).toHaveBeenCalledWith(cssClasses.INVALID); }); @@ -700,33 +700,33 @@ describe('MDCSelectFoundation', () => { it('#setRequired adds/removes ${cssClasses.REQUIRED} class name', () => { const {foundation, mockAdapter} = setupTest(); foundation.setRequired(true); - expect(mockAdapter.addClass).toHaveBeenCalledWith(cssClasses.REQUIRED); - expect(mockAdapter.addClass).toHaveBeenCalledTimes(1); + checkNumTimesSpyCalledWithArgs( + mockAdapter.addClass, [cssClasses.REQUIRED], 1); foundation.setRequired(false); - expect(mockAdapter.removeClass).toHaveBeenCalledWith(cssClasses.REQUIRED); - expect(mockAdapter.removeClass).toHaveBeenCalledTimes(1); + checkNumTimesSpyCalledWithArgs( + mockAdapter.removeClass, [cssClasses.REQUIRED], 1); }); it('#setRequired sets aria-required through adapter', () => { const {foundation, mockAdapter} = setupTest(); foundation.setRequired(true); - expect(mockAdapter.setSelectedTextAttr) + expect(mockAdapter.setSelectAnchorAttr) .toHaveBeenCalledWith('aria-required', 'true'); foundation.setRequired(false); - expect(mockAdapter.setSelectedTextAttr) + expect(mockAdapter.setSelectAnchorAttr) .toHaveBeenCalledWith('aria-required', 'false'); }); it('#getRequired returns true if aria-required is true', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getSelectedTextAttr.withArgs('aria-required') + mockAdapter.getSelectAnchorAttr.withArgs('aria-required') .and.returnValue('true'); expect(foundation.getRequired()).toBe(true); }); it('#getRequired returns false if aria-required is false', () => { const {foundation, mockAdapter} = setupTest(); - mockAdapter.getSelectedTextAttr.withArgs('aria-required') + mockAdapter.getSelectAnchorAttr.withArgs('aria-required') .and.returnValue('false'); expect(foundation.getRequired()).toBe(false); });