diff --git a/packages/mdc-menu/simple/adapter.js b/packages/mdc-menu/simple/adapter.js index 657aa39d040..07274eacc6d 100644 --- a/packages/mdc-menu/simple/adapter.js +++ b/packages/mdc-menu/simple/adapter.js @@ -59,6 +59,13 @@ class MDCSimpleMenuAdapter { */ getAttributeForEventTarget(target, attributeName) {} + /** + * @param {EventTarget} target + * @param {string} className + * @return {boolean} + */ + eventTargetHasClass(target, className) {} + /** @return {{ width: number, height: number }} */ getInnerDimensions() {} diff --git a/packages/mdc-menu/simple/foundation.js b/packages/mdc-menu/simple/foundation.js index 28ec0683a26..871bad05697 100644 --- a/packages/mdc-menu/simple/foundation.js +++ b/packages/mdc-menu/simple/foundation.js @@ -283,7 +283,7 @@ class MDCSimpleMenuFoundation extends MDCFoundation { while (el && el !== document.body) { if (this.adapter_.eventTargetHasClass(el, cssClasses.LIST_ITEM)) { - return true; + return; } el = el.parentNode; } diff --git a/packages/mdc-select/README.md b/packages/mdc-select/README.md index a72b96a37fc..712df5cc3b8 100644 --- a/packages/mdc-select/README.md +++ b/packages/mdc-select/README.md @@ -352,14 +352,14 @@ within `componentDidUpdate`. | `setBottomLineAttr(attr: string, value: string) => void` | Adds an attribute to the bottom line | | `setAttr(attr: string, value: string) => void` | Sets attribute `attr` to value `value` on the root element. | | `rmAttr(attr: string) => void` | Removes attribute `attr` from the root element. | -| `computeBoundingRect() => {left: number, top: number}` | Returns an object with a shape similar to a `ClientRect` object, with a `left` and `top` property specifying the element's position on the page relative to the viewport. The easiest way to achieve this is by calling `getBoundingClientRect()` on the root element. | +| `computeBoundingRect() => {left: number, top: number}` | Returns an object with a shape similar to a `ClientRect` object, with a `left` and `top` property specifying the element's position on the page relative to the viewport. The easiest way to achieve this is by calling `getBoundingClientRect()` on the surface element. | | `registerInteractionHandler(type: string, handler: EventListener) => void` | Adds an event listener `handler` for event type `type` on the root element. | | `deregisterInteractionHandler(type: string, handler: EventListener) => void` | Removes an event listener `handler` for event type `type` on the root element. | | `focus() => void` | Focuses the root element | | `makeTabbable() => void` | Allows the root element to be tab-focused via keyboard. We achieve this by setting the root element's `tabIndex` property to `0`. | | `makeUntabbable() => void` | Disallows the root element to be tab-focused via keyboard. We achieve this by setting the root element's `tabIndex` property to `-1`. | -| `getComputedStyleValue(propertyName: string) => string` | Get the root element's computed style value of the given dasherized css property `propertyName`. We achieve this via `getComputedStyle(...).getPropertyValue(propertyName). `| -| `setStyle(propertyName: string, value: string) => void` | Sets a dasherized css property `propertyName` to the value `value` on the root element. We achieve this via `root.style.setProperty(propertyName, value)`. | +| `getComputedStyleValue(propertyName: string) => string` | Get the surface element's computed style value of the given dasherized css property `propertyName`. We achieve this via `getComputedStyle(...).getPropertyValue(propertyName). `| +| `setStyle(propertyName: string, value: string) => void` | Sets a dasherized css property `propertyName` to the value `value` on the surface element. We achieve this via `root.style.setProperty(propertyName, value)`. | | `create2dRenderingContext() => {font: string, measureText: (string) => {width: number}}` | Returns an object which has the shape of a CanvasRenderingContext2d instance. Namely, it has a string property `font` which is writable, and a method `measureText` which given a string of text returns an object containing a `width` property specifying how wide that text should be rendered in the `font` specified by the font property. An easy way to achieve this is simply `document.createElement('canvas').getContext('2d');`. | | `setMenuElStyle(propertyName: string) => void` | Sets a dasherized css property `propertyName` to the value `value` on the menu element. | | `setMenuElAttr(attr: string, value: string) => void` | Sets attribute `attr` to value `value` on the menu element. | @@ -436,12 +436,15 @@ First, wrap both a custom select and a native select within a wrapper element, l - +
+ +
+
``` diff --git a/packages/mdc-select/constants.js b/packages/mdc-select/constants.js index d52f3caba2e..08d9c3b63aa 100644 --- a/packages/mdc-select/constants.js +++ b/packages/mdc-select/constants.js @@ -35,5 +35,6 @@ export const strings = { }; export const numbers = { - SURFACE_HORIZONTAL_PADDING: 26, + SURFACE_RIGHT_PADDING: 26, + SURFACE_LEFT_PADDING: 16, }; diff --git a/packages/mdc-select/foundation.js b/packages/mdc-select/foundation.js index 4024c2f252a..31ec4800f9e 100644 --- a/packages/mdc-select/foundation.js +++ b/packages/mdc-select/foundation.js @@ -45,8 +45,6 @@ export default class MDCSelectFoundation extends MDCFoundation { setAttr: (/* attr: string, value: string */) => {}, rmAttr: (/* attr: string */) => {}, computeBoundingRect: () => /* {left: number, top: number} */ ({left: 0, top: 0}), - registerPointerDownHandler: (/* evtType: string, handler: EventListener */) => {}, - deregisterPointerDownHandler: (/* evtType: string, handler: EventListener */) => {}, registerInteractionHandler: (/* type: string, handler: EventListener */) => {}, deregisterInteractionHandler: (/* type: string, handler: EventListener */) => {}, focus: () => {}, @@ -94,17 +92,15 @@ export default class MDCSelectFoundation extends MDCFoundation { this.displayViaKeyboardHandler_ = (evt) => this.handleDisplayViaKeyboard_(evt); this.selectionHandler_ = ({detail}) => { const {index} = detail; - const shouldRemoveFloatingLabelClass = false; if (index !== this.selectedIndex_) { this.setSelectedIndex(index); this.adapter_.notifyChange(); } - this.close_(shouldRemoveFloatingLabelClass); + this.close_(); }; this.cancelHandler_ = () => { - const shouldRemoveFloatingLabelClass = true; - this.close_(shouldRemoveFloatingLabelClass); + this.close_(); }; } @@ -196,9 +192,9 @@ export default class MDCSelectFoundation extends MDCFoundation { let maxTextLength = 0; for (let i = 0, l = this.adapter_.getNumberOfOptions(); i < l; i++) { - // SURFACE_HORIZONTAL_PADDING corresponds to a variable set in ./mdc-select.scss + // SURFACE_RIGHT_PADDING and SURFACE_LEFT_PADDING correspond to variables set in ./mdc-select.scss // If the UI of MDC Select changes, also change it in the style - const selectBoxAddedPadding = numbers.SURFACE_HORIZONTAL_PADDING; + const selectBoxAddedPadding = numbers.SURFACE_RIGHT_PADDING + numbers.SURFACE_LEFT_PADDING; const txt = this.adapter_.getTextForOptionAtIndex(i).trim(); const {width} = this.ctx_.measureText(txt); const addedSpace = letterSpacing * txt.length; @@ -255,11 +251,11 @@ export default class MDCSelectFoundation extends MDCFoundation { this.adapter_.setMenuElStyle('transform-origin', `center ${itemOffsetTop}px`); } - close_(shouldRemoveFloatingLabelClass) { + close_() { const {OPEN} = MDCSelectFoundation.cssClasses; this.adapter_.removeClass(OPEN); - if (shouldRemoveFloatingLabelClass && this.getSelectedIndex() === -1) { + if (this.getSelectedIndex() === -1) { this.adapter_.removeClassFromLabel(cssClasses.LABEL_FLOAT_ABOVE); } this.adapter_.removeClassFromBottomLine(cssClasses.BOTTOM_LINE_ACTIVE); diff --git a/packages/mdc-select/index.js b/packages/mdc-select/index.js index 62d189d4a0a..24bc3bae24a 100644 --- a/packages/mdc-select/index.js +++ b/packages/mdc-select/index.js @@ -56,6 +56,12 @@ export class MDCSelect extends MDCComponent { this.foundation_.setDisabled(disabled); } + constructor(...args) { + super(...args); + + this.ripple_ = MDCRipple.attachTo(this.surface_); + } + item(index) { return this.options[index] || null; } diff --git a/packages/mdc-select/mdc-select.scss b/packages/mdc-select/mdc-select.scss index b376c69ee46..d35672b47c7 100644 --- a/packages/mdc-select/mdc-select.scss +++ b/packages/mdc-select/mdc-select.scss @@ -29,7 +29,8 @@ @return #{$property} 180ms $mdc-animation-standard-curve-timing-function; } -$mdc-select-arrow-padding: 24px; +$mdc-select-arrow-padding: 26px; +$mdc-select-label-padding: 16px; // postcss-bem-linter: define select .mdc-select { @@ -72,7 +73,9 @@ $mdc-select-arrow-padding: 24px; } } - @include mdc-theme-dark(".mdc-textfield", true) { + @include mdc-theme-dark(".mdc-select") { + @include mdc-select-dd-arrow-svg-bg_("ffffff", .54); + background-color: rgba(white, .1); } @@ -89,6 +92,14 @@ $mdc-select-arrow-padding: 24px; @include mdc-typography(subheading2); @include mdc-theme-prop(color, text-primary-on-light); @include mdc-rtl-reflexive-box(padding, right, $mdc-select-arrow-padding); + @include mdc-ripple-base; + @include mdc-ripple-fg((pseudo: "::after")); + @include mdc-ripple-bg((pseudo: "::before")); + + @include mdc-theme-dark(".mdc-icon-toggle", true) { + @include mdc-ripple-bg((pseudo: "::before", base-color: white, opacity: .16)); + @include mdc-ripple-fg((pseudo: "::after", base-color: white, opacity: .16)); + } &::-ms-expand { display: none; @@ -99,7 +110,7 @@ $mdc-select-arrow-padding: 24px; flex: 1; width: 100%; height: 56px; - padding-left: 16px; + padding-left: $mdc-select-label-padding; border: none; border-radius: 4px 4px 0 0; outline: none; @@ -144,7 +155,7 @@ $mdc-select-arrow-padding: 24px; transition: mdc-select-transition(transform); color: rgba(black, .6); pointer-events: none; - // Force the label into its own layer to prevent to prevent visible layer promotion adjustments + // Force the label into its own layer to prevent visible layer promotion adjustments // when the ripple is activated behind it. will-change: transform; @@ -224,11 +235,17 @@ $mdc-select-arrow-padding: 24px; @include mdc-theme-prop(color, text-disabled-on-light); @include mdc-select-dd-arrow-svg-bg_(000000, .38); + border-bottom-width: 1px; border-bottom-style: dotted; + opacity: .38; cursor: default; pointer-events: none; // Imitate native disabled functionality user-select: none; + + .mdc-select__bottom-line { + display: none; + } } @each $sel in ("mdc-select--disabled", "mdc-select[disabled]") { diff --git a/test/unit/mdc-select/foundation.test.js b/test/unit/mdc-select/foundation.test.js index 73df8572b56..1ecbe5d8017 100644 --- a/test/unit/mdc-select/foundation.test.js +++ b/test/unit/mdc-select/foundation.test.js @@ -37,7 +37,6 @@ test('default adapter returns a complete adapter implementation', () => { verifyDefaultAdapter(MDCSelectFoundation, [ 'addClass', 'removeClass', 'addClassToLabel', 'removeClassFromLabel', 'addClassToBottomLine', 'removeClassFromBottomLine', 'setBottomLineAttr', 'setAttr', 'rmAttr', 'computeBoundingRect', - 'registerPointerDownHandler', 'deregisterPointerDownHandler', 'registerInteractionHandler', 'deregisterInteractionHandler', 'focus', 'makeTabbable', 'makeUntabbable', 'getComputedStyleValue', 'setStyle', 'create2dRenderingContext', 'setMenuElStyle', 'setMenuElAttr', 'rmMenuElAttr', 'getMenuElOffsetHeight', 'openMenu', @@ -189,7 +188,8 @@ test('#resize resizes the element to the longest-length option', () => { foundation.resize(); assert.equal(ctx.font, '16px Roboto'); // ceil(letter-spacing * 'longest'.length + longest measured width + extra padding) - const expectedWidth = Math.ceil((2.5 * 7) + Math.max(...widths) + numbers.SURFACE_HORIZONTAL_PADDING); + const expectedWidth = Math.ceil((2.5 * 7) + Math.max(...widths) + + numbers.SURFACE_RIGHT_PADDING + numbers.SURFACE_LEFT_PADDING); td.verify(mockAdapter.setStyle('width', `${expectedWidth}px`)); }); @@ -218,7 +218,8 @@ test('#resize falls back to font-{family,size} if shorthand is not supported', ( foundation.resize(); assert.equal(ctx.font, '16px Roboto'); // ceil(letter-spacing * 'longest'.length + longest measured width) - const expectedWidth = Math.ceil((2.5 * 7) + Math.max(...widths) + numbers.SURFACE_HORIZONTAL_PADDING); + const expectedWidth = Math.ceil((2.5 * 7) + Math.max(...widths) + + numbers.SURFACE_RIGHT_PADDING + numbers.SURFACE_LEFT_PADDING); td.verify(mockAdapter.setStyle('width', `${expectedWidth}px`)); }); diff --git a/test/unit/mdc-select/mdc-select.test.js b/test/unit/mdc-select/mdc-select.test.js index 80ff1c81100..c441b8e8b69 100644 --- a/test/unit/mdc-select/mdc-select.test.js +++ b/test/unit/mdc-select/mdc-select.test.js @@ -260,21 +260,21 @@ test('adapter#focus focuses on the root element', () => { document.body.removeChild(fixture); }); -test('adapter#makeTabbable sets the root element\'s tabindex to 0', () => { +test('adapter#makeTabbable sets the menu element\'s tabindex to 0', () => { const {component, menuEl} = setupTest(); menuEl.tabIndex = -1; component.getDefaultFoundation().adapter_.makeTabbable(); assert.equal(menuEl.tabIndex, 0); }); -test('adapter#makeUntabbable sets the root element\'s tabindex to -1', () => { +test('adapter#makeUntabbable sets the menu element\'s tabindex to -1', () => { const {component, menuEl} = setupTest(); menuEl.tabIndex = 0; component.getDefaultFoundation().adapter_.makeUntabbable(); assert.equal(menuEl.tabIndex, -1); }); -test('adapter#getComputedStyleValue gets the computed style value of the prop from the root element', () => { +test('adapter#getComputedStyleValue gets the computed style value of the prop from the surface element', () => { const {component, fixture, surface} = setupTest(); document.body.appendChild(fixture); surface.style.width = '500px';