diff --git a/packages/mdc-ripple/index.js b/packages/mdc-ripple/index.js index 1f051cc5480..a9b964ead82 100644 --- a/packages/mdc-ripple/index.js +++ b/packages/mdc-ripple/index.js @@ -135,4 +135,4 @@ RippleCapableSurface.prototype.unbounded; */ RippleCapableSurface.prototype.disabled; -export {MDCRipple, MDCRippleFoundation, util}; +export {MDCRipple, MDCRippleFoundation, RippleCapableSurface, util}; diff --git a/packages/mdc-textfield/README.md b/packages/mdc-textfield/README.md index 1cd1eaa71ef..fe645abdf01 100644 --- a/packages/mdc-textfield/README.md +++ b/packages/mdc-textfield/README.md @@ -217,7 +217,7 @@ Method Signature | Description ##### `MDCTextField.ripple` -The `MDCRipple` instance for the root element that `MDCTextField` initializes when given an `mdc-text-field--box` root element. Otherwise, the field is set to `null`. +`MDCRipple` instance. When given an `mdc-text-field--box` root element, this is set to the `MDCRipple` instance on the root element. When given an `mdc-text-field--outlined` root element, this is set to the `MDCRipple` instance on the `mdc-text-field__outline` element. Otherwise, the field is set to `null`. ### `MDCTextFieldAdapter` diff --git a/packages/mdc-textfield/constants.js b/packages/mdc-textfield/constants.js index 26ac5ac0548..4793d8e08f6 100644 --- a/packages/mdc-textfield/constants.js +++ b/packages/mdc-textfield/constants.js @@ -34,6 +34,7 @@ const cssClasses = { FOCUSED: 'mdc-text-field--focused', INVALID: 'mdc-text-field--invalid', BOX: 'mdc-text-field--box', + OUTLINED: 'mdc-text-field--outlined', }; export {cssClasses, strings}; diff --git a/packages/mdc-textfield/index.js b/packages/mdc-textfield/index.js index 944b217663f..036112ef535 100644 --- a/packages/mdc-textfield/index.js +++ b/packages/mdc-textfield/index.js @@ -16,7 +16,9 @@ */ import MDCComponent from '@material/base/component'; -import {MDCRipple, MDCRippleFoundation} from '@material/ripple'; +/* eslint-disable no-unused-vars */ +import {MDCRipple, MDCRippleFoundation, RippleCapableSurface} from '@material/ripple'; +/* eslint-enable no-unused-vars */ import {getMatchesProperty} from '@material/ripple/util'; @@ -91,17 +93,6 @@ class MDCTextField extends MDCComponent { if (labelElement) { this.label_ = labelFactory(labelElement); } - this.ripple = null; - if (this.root_.classList.contains(cssClasses.BOX)) { - const MATCHES = getMatchesProperty(HTMLElement.prototype); - const adapter = Object.assign(MDCRipple.createAdapter(this), { - isSurfaceActive: () => this.input_[MATCHES](':active'), - registerInteractionHandler: (type, handler) => this.input_.addEventListener(type, handler), - deregisterInteractionHandler: (type, handler) => this.input_.removeEventListener(type, handler), - }); - const foundation = new MDCRippleFoundation(adapter); - this.ripple = rippleFactory(this.root_, foundation); - } const bottomLineElement = this.root_.querySelector(strings.BOTTOM_LINE_SELECTOR); if (bottomLineElement) { this.bottomLine_ = bottomLineFactory(bottomLineElement); @@ -120,6 +111,23 @@ class MDCTextField extends MDCComponent { if (iconElement) { this.icon_ = iconFactory(iconElement); } + + this.ripple = null; + if (this.root_.classList.contains(cssClasses.BOX) || this.root_.classList.contains(cssClasses.OUTLINED)) { + // For outlined text fields, the ripple is instantiated on the outline element instead of the root element + // to clip the ripple at the outline while still allowing the label to be visible beyond the outline. + const rippleCapableSurface = outlineElement ? this.outline_ : this; + const rippleRoot = outlineElement ? outlineElement : this.root_; + const MATCHES = getMatchesProperty(HTMLElement.prototype); + const adapter = + Object.assign(MDCRipple.createAdapter(/** @type {!RippleCapableSurface} */ (rippleCapableSurface)), { + isSurfaceActive: () => this.input_[MATCHES](':active'), + registerInteractionHandler: (type, handler) => this.input_.addEventListener(type, handler), + deregisterInteractionHandler: (type, handler) => this.input_.removeEventListener(type, handler), + }); + const foundation = new MDCRippleFoundation(adapter); + this.ripple = rippleFactory(rippleRoot, foundation); + } } destroy() { diff --git a/packages/mdc-textfield/mdc-text-field.scss b/packages/mdc-textfield/mdc-text-field.scss index 15eb1296c86..2bbc377b3ac 100644 --- a/packages/mdc-textfield/mdc-text-field.scss +++ b/packages/mdc-textfield/mdc-text-field.scss @@ -167,8 +167,10 @@ opacity: 1; } - &:not(.mdc-text-field--focused) .mdc-text-field__outline-path { - stroke-width: 1px; + &.mdc-text-field--focused .mdc-text-field__outline-path { + @include mdc-theme-prop(stroke, primary); + + stroke-width: 2px; } &.mdc-text-field--disabled { diff --git a/packages/mdc-textfield/outline/README.md b/packages/mdc-textfield/outline/README.md index 63437a274eb..e002c8806dd 100644 --- a/packages/mdc-textfield/outline/README.md +++ b/packages/mdc-textfield/outline/README.md @@ -21,22 +21,56 @@ The outline is a border around all sides of the text field. This is used for the ## Usage -#### MDCTextFieldOutline API +### HTML Structure -##### MDCTextFieldOutline.foundation +```html +
+ + + +
+
+``` -MDCTextFieldOutlineFoundation. This allows the parent MDCTextField component to access the public methods on the MDCTextFieldOutlineFoundation class. +### Usage within `mdc-text-field` -### Using the foundation class +```html +
+ + +
+ + + +
+
+
+``` -Method Signature | Description +### CSS Classes + +CSS Class | Description --- | --- -getWidth() => number | Returns the width of the outline element -getHeight() => number | Returns the height of the outline element -setOutlinePathAttr(value: string) => void | Sets the "d" attribute of the outline element's SVG path +`mdc-text-field__outline` | Mandatory. Container for the SVG in the outline when the label is floating above the input. +`mdc-text-field__outline-path` | Mandatory. The SVG path in the outline when the label is floating above the input. +`mdc-text-field__idle-outline` | Mandatory. The outline when the label is resting in the input position. + +#### `MDCTextFieldOutline` + +##### `MDCTextFieldOutline.foundation` + +This allows the parent `MDCTextField` component to access the public methods on the `MDCTextFieldOutlineFoundation` class. -#### The full foundation API +### `MDCTextFieldOutlineAdapter` -##### MDCTextFieldOutlineFoundation.updateSvgPath(width: number, height: number, labelWidth: number, radius: number, isRtl: boolean) +Method Signature | Description +--- | --- +`getWidth() => number` | Returns the width of the outline element +`getHeight() => number` | Returns the height of the outline element +`setOutlinePathAttr(value: string) => void` | Sets the "d" attribute of the outline element's SVG path + +### `MDCTextFieldOutlineFoundation` -Updates the SVG path of the focus outline element based on the given width and height of the text field element, the width of the label element, the corner radius, and the RTL context. +Method Signature | Description +--- | --- +`updateSvgPath(labelWidth: number, radius: number, isRtl: boolean) => void` | Updates the SVG path of the focus outline element based on the given the width of the label element, the corner radius, and the RTL context. diff --git a/packages/mdc-textfield/outline/mdc-text-field-outline.scss b/packages/mdc-textfield/outline/mdc-text-field-outline.scss index 257d859d8b0..ec82a7fc8ba 100644 --- a/packages/mdc-textfield/outline/mdc-text-field-outline.scss +++ b/packages/mdc-textfield/outline/mdc-text-field-outline.scss @@ -17,6 +17,7 @@ @import "../mixins"; @import "../variables"; @import "@material/theme/mixins"; +@import "@material/ripple/variables"; .mdc-text-field__idle-outline { @include mdc-text-field-outlined-corner-radius($mdc-text-field-border-radius); @@ -33,6 +34,10 @@ } .mdc-text-field__outline { + @include mdc-ripple-surface; + @include mdc-ripple-radius; + @include mdc-states-base-color(text-primary-on-light); + @include mdc-states-press-opacity(map-get($mdc-ripple-dark-ink-opacities, "press")); @include mdc-theme-prop(color, primary); @include mdc-text-field-outlined-corner-radius($mdc-text-field-border-radius); @@ -44,6 +49,7 @@ transition: mdc-text-field-transition(opacity); opacity: 0; z-index: 2; + overflow: hidden; svg { position: absolute; @@ -51,10 +57,10 @@ height: 100%; .mdc-text-field__outline-path { - @include mdc-theme-prop(stroke, primary); - - stroke-width: 2px; - transition: mdc-text-field-transition(stroke-width), mdc-text-field-transition(opacity); + stroke: $mdc-text-field-outlined-idle-border; + stroke-width: 1px; + transition: mdc-text-field-transition(stroke), mdc-text-field-transition(stroke-width), + mdc-text-field-transition(opacity); fill: transparent; } } diff --git a/test/unit/mdc-textfield/mdc-text-field.test.js b/test/unit/mdc-textfield/mdc-text-field.test.js index 68b8b1172cb..a0298808068 100644 --- a/test/unit/mdc-textfield/mdc-text-field.test.js +++ b/test/unit/mdc-textfield/mdc-text-field.test.js @@ -76,6 +76,7 @@ class FakeLabel { class FakeOutline { constructor() { + this.createRipple = td.function('.createRipple'); this.destroy = td.func('.destroy'); } } @@ -87,7 +88,23 @@ test('#constructor when given a `mdc-text-field--box` element instantiates a rip assert.equal(component.ripple.root, root); }); -test('#constructor sets the ripple property to `null` when given a non `mdc-text-field--box` element', () => { +test('#constructor when given a `mdc-text-field--outlined` element instantiates a ripple on the ' + + 'outline element', () => { + const root = bel` +
+ + +
+
+
+ `; + const outline = root.querySelector('.mdc-text-field__outline'); + const component = new MDCTextField(root, undefined, (el) => new FakeRipple(el)); + assert.equal(component.ripple.root, outline); +}); + +test('#constructor sets the ripple property to `null` when not given a `mdc-text-field--box` nor ' + + 'a `mdc-text-field--outlined` subelement', () => { const component = new MDCTextField(getFixture()); assert.isNull(component.ripple); }); @@ -100,6 +117,20 @@ test('#constructor when given a `mdc-text-field--box` element, initializes a def assert.instanceOf(component.ripple, MDCRipple); }); +test('#constructor when given a `mdc-text-field--outlined` element, initializes a default ripple when no ' + + 'ripple factory given', () => { + const root = bel` +
+ + +
+
+
+ `; + const component = new MDCTextField(root); + assert.instanceOf(component.ripple, MDCRipple); +}); + test('#constructor instantiates a bottom line on the `.mdc-text-field__bottom-line` element if present', () => { const root = getFixture(); const component = new MDCTextField(root);