diff --git a/CHANGELOG.md b/CHANGELOG.md
index 512f21a90..5a775b009 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `activated` and `selected` states for ripple
- Added documentation for ripple
- Prefix and suffix to mwc-textfield
+- `mwc-formfield` now has a nowrap property
### Changed
@@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Refactor `mwc-ripple`
- Normalized API to `start${state}` `end${state}` naming
- **BREAKING:VISUAL:** mwc-list-item now internally uses mwc-ripple instead of styling ripple on host
+- `mwc-menu`'s `quick` variant now opens synchronously
### Fixed
diff --git a/packages/formfield/README.md b/packages/formfield/README.md
index 7aecb9418..545149543 100644
--- a/packages/formfield/README.md
+++ b/packages/formfield/README.md
@@ -36,6 +36,26 @@ npm install @material/mwc-formfield
```html
+
+
+
+
+```
+
+### nowrap label with checkbox
+
+
+
+```html
+
+
@@ -98,9 +118,10 @@ Name | Description
Name | Type | Description
------- | -------- | ----------------------------------
-`label` | `string` | The text to display for the label.
+`label` | `string` | The text to display for the label and sets a11y label on input. (visually overriden by slotted label)
`alignEnd` | `boolean` | Align the component at the end of the label.
`spaceBetween` | `boolean` | Add space between the component and the label as the formfield grows.
+`nowrap` | `boolean` | Prevents the label from wrapping and overflow text is ellipsed.
### Methods
diff --git a/packages/formfield/images/nowrap.png b/packages/formfield/images/nowrap.png
new file mode 100644
index 000000000..03c995d50
Binary files /dev/null and b/packages/formfield/images/nowrap.png differ
diff --git a/packages/formfield/src/mwc-formfield-base.ts b/packages/formfield/src/mwc-formfield-base.ts
index fad60471a..9dd50aec9 100644
--- a/packages/formfield/src/mwc-formfield-base.ts
+++ b/packages/formfield/src/mwc-formfield-base.ts
@@ -1,31 +1,35 @@
/**
-@license
-Copyright 2018 Google Inc. All Rights Reserved.
+ * @license
+ * Copyright 2018 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// tslint:disable:no-new-decorators
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-import {MDCFormFieldAdapter} from '@material/form-field/adapter.js';
-import MDCFormFieldFoundation from '@material/form-field/foundation.js';
-import {BaseElement, EventType, SpecificEventListener} from '@material/mwc-base/base-element.js';
-import {FormElement} from '@material/mwc-base/form-element.js';
-import {observer} from '@material/mwc-base/observer.js';
-import {findAssignedElement} from '@material/mwc-base/utils.js';
+import {MDCFormFieldAdapter} from '@material/form-field/adapter';
+import MDCFormFieldFoundation from '@material/form-field/foundation';
+import {BaseElement, EventType, SpecificEventListener} from '@material/mwc-base/base-element';
+import {FormElement} from '@material/mwc-base/form-element';
+import {observer} from '@material/mwc-base/observer';
+import {findAssignedElement} from '@material/mwc-base/utils';
import {html, property, query} from 'lit-element';
-import {classMap} from 'lit-html/directives/class-map.js';
+import {classMap} from 'lit-html/directives/class-map';
+
export class FormfieldBase extends BaseElement {
@property({type: Boolean}) alignEnd = false;
@property({type: Boolean}) spaceBetween = false;
+ @property({type: Boolean}) nowrap = false;
@property({type: String})
@observer(async function(this: FormfieldBase, label: string) {
@@ -93,7 +97,8 @@ export class FormfieldBase extends BaseElement {
protected render() {
const classes = {
'mdc-form-field--align-end': this.alignEnd,
- 'mdc-form-field--space-between': this.spaceBetween
+ 'mdc-form-field--space-between': this.spaceBetween,
+ 'mdc-form-field--nowrap': this.nowrap
};
return html`
diff --git a/packages/formfield/src/mwc-formfield.scss b/packages/formfield/src/mwc-formfield.scss
index 991ddc0fe..4dd851052 100644
--- a/packages/formfield/src/mwc-formfield.scss
+++ b/packages/formfield/src/mwc-formfield.scss
@@ -15,7 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-@import '@material/form-field/mdc-form-field.scss';
+@use '@material/form-field' as formfield;
+@use '@material/typography';
+@use '@material/theme';
+@use '@material/rtl';
+
+@include formfield.core-styles();
:host {
display: inline-flex;
@@ -26,13 +31,13 @@ limitations under the License.
}
::slotted(*) {
- @include mdc-typography(body2);
- @include mdc-theme-prop(color, text-primary-on-background);
+ @include typography.typography(body2);
+ @include theme.prop(color, text-primary-on-background);
}
::slotted(mwc-switch) {
margin-right: 10px;
- @include mdc-rtl {
+ @include rtl.rtl {
margin-left: 10px;
}
}
diff --git a/packages/formfield/src/test/mwc-formfield.test.ts b/packages/formfield/src/test/mwc-formfield.test.ts
index bc8161ca8..87eb061ad 100644
--- a/packages/formfield/src/test/mwc-formfield.test.ts
+++ b/packages/formfield/src/test/mwc-formfield.test.ts
@@ -15,21 +15,258 @@
* limitations under the License.
*/
-import {Formfield} from '@material/mwc-formfield';
+import '@material/mwc-formfield';
+import '@material/mwc-checkbox';
+import '@material/mwc-radio';
+import '@material/mwc-switch';
+import {Checkbox} from '@material/mwc-checkbox';
+import {Formfield} from '@material/mwc-formfield';
+import {Radio} from '@material/mwc-radio';
+import {Switch} from '@material/mwc-switch';
+import {html} from 'lit-html';
+
+import {fixture, TestFixture} from '../../../../test/src/util/helpers';
+
+const defaultEl = html``;
+
+const defaultFormfieldProps = {
+ alignEnd: false,
+ spaceBetween: false,
+ label: '',
+ content: html``,
+};
+
+type FormfieldProps = typeof defaultFormfieldProps;
+
+const formfield = (propsInit: Partial) => {
+ const props: FormfieldProps = {...defaultFormfieldProps, ...propsInit};
+
+ return html`
+
+ ${props.content}
+
+ `;
+};
+
suite('mwc-formfield', () => {
+ let fixt: TestFixture;
let element: Formfield;
- setup(() => {
- element = document.createElement('mwc-formfield');
- document.body.appendChild(element);
- });
teardown(() => {
- document.body.removeChild(element);
+ fixt.remove();
});
- test('initializes as an mwc-formfield', () => {
- assert.instanceOf(element, Formfield);
+ suite('basic', () => {
+ setup(async () => {
+ fixt = await fixture(defaultEl);
+ element = fixt.root.querySelector('mwc-formfield')!;
+ });
+
+ test('initializes as an mwc-formfield', () => {
+ assert.instanceOf(element, Formfield);
+ assert.isFalse(element.alignEnd);
+ assert.isFalse(element.spaceBetween);
+ assert.equal(element.label, '');
+ });
+ });
+
+ suite('with checkbox', () => {
+ let control: Checkbox;
+
+ suite('prop label', () => {
+ setup(async () => {
+ fixt = await fixture(formfield(
+ {label: 'label', content: html``}));
+ element = fixt.root.querySelector('mwc-formfield')!;
+ await element.updateComplete;
+ control = fixt.root.querySelector('mwc-checkbox')!;
+ await control.updateComplete;
+ });
+
+ test('sets the aria-label on the control', async () => {
+ const internalInput = control.shadowRoot!.querySelector('input')!;
+ assert.equal(internalInput.getAttribute('aria-label'), 'label');
+ });
+
+ test('label click propagates click and focus to control', async () => {
+ const labelEl = element.shadowRoot!.querySelector('label')!;
+ let numClicks = 0;
+ const origClick = control.click;
+
+ control.click = () => {
+ numClicks += 1;
+ origClick.call(control);
+ };
+
+ assert.isFalse(control.checked);
+ assert.equal(fixt.shadowRoot!.activeElement, null);
+ assert.equal(numClicks, 0);
+
+ labelEl.click();
+
+ await element.updateComplete;
+ await control.updateComplete;
+
+ assert.isTrue(control.checked);
+ assert.equal(fixt.shadowRoot!.activeElement, control);
+ assert.equal(numClicks, 1);
+ });
+
+ test('formfield will not double click control', async () => {
+ let numClicks = 0;
+ const origClick = control.click;
+
+ control.click = () => {
+ numClicks += 1;
+ origClick.call(control);
+ };
+
+ assert.isFalse(control.checked);
+ assert.equal(numClicks, 0);
+
+ control.click();
+
+ await element.updateComplete;
+ await control.updateComplete;
+
+ assert.equal(numClicks, 1);
+ assert.isTrue(control.checked);
+ });
+ });
+ });
+
+ suite('with switch', () => {
+ let control: Switch;
+
+ suite('prop label', () => {
+ setup(async () => {
+ fixt = await fixture(formfield(
+ {label: 'label', content: html``}));
+ element = fixt.root.querySelector('mwc-formfield')!;
+ await element.updateComplete;
+ control = fixt.root.querySelector('mwc-switch')!;
+ await control.updateComplete;
+ });
+
+ test('sets the aria-label on the control', async () => {
+ const internalInput = control.shadowRoot!.querySelector('input')!;
+ assert.equal(internalInput.getAttribute('aria-label'), 'label');
+ });
+
+ test('label click propagates click and focus to control', async () => {
+ const labelEl = element.shadowRoot!.querySelector('label')!;
+ let numClicks = 0;
+ const origClick = control.click;
+
+ control.click = () => {
+ numClicks += 1;
+ origClick.call(control);
+ };
+
+ assert.isFalse(control.checked);
+ assert.equal(fixt.shadowRoot!.activeElement, null);
+ assert.equal(numClicks, 0);
+
+ labelEl.click();
+
+ await element.updateComplete;
+ await control.updateComplete;
+
+ assert.isTrue(control.checked);
+ assert.equal(fixt.shadowRoot!.activeElement, control);
+ assert.equal(numClicks, 1);
+ });
+
+ test('formfield will not double click control', async () => {
+ let numClicks = 0;
+ const origClick = control.click;
+
+ control.click = () => {
+ numClicks += 1;
+ origClick.call(control);
+ };
+
+ assert.isFalse(control.checked);
+ assert.equal(numClicks, 0);
+
+ control.click();
+
+ await element.updateComplete;
+ await control.updateComplete;
+
+ assert.equal(numClicks, 1);
+ assert.isTrue(control.checked);
+ });
+ });
+ });
+
+ suite('with radio', () => {
+ let control: Radio;
+
+ suite('prop label', () => {
+ setup(async () => {
+ fixt = await fixture(formfield(
+ {label: 'label', content: html``}));
+ element = fixt.root.querySelector('mwc-formfield')!;
+ await element.updateComplete;
+ control = fixt.root.querySelector('mwc-radio')!;
+ await control.updateComplete;
+ });
+
+ test('sets the aria-label on the control', async () => {
+ const internalInput = control.shadowRoot!.querySelector('input')!;
+ assert.equal(internalInput.getAttribute('aria-label'), 'label');
+ });
+
+ test('label click propagates click and focus to control', async () => {
+ const labelEl = element.shadowRoot!.querySelector('label')!;
+ let numClicks = 0;
+ const origClick = control.click;
+
+ control.click = () => {
+ numClicks += 1;
+ origClick.call(control);
+ };
+
+ assert.isFalse(control.checked);
+ assert.equal(fixt.shadowRoot!.activeElement, null);
+ assert.equal(numClicks, 0);
+
+ labelEl.click();
+
+ await element.updateComplete;
+ await control.updateComplete;
+
+ assert.isTrue(control.checked);
+ assert.equal(fixt.shadowRoot!.activeElement, control);
+ assert.equal(numClicks, 1);
+ });
+
+ test('formfield will not double click control', async () => {
+ let numClicks = 0;
+ const origClick = control.click;
+
+ control.click = () => {
+ numClicks += 1;
+ origClick.call(control);
+ };
+
+ assert.isFalse(control.checked);
+ assert.equal(numClicks, 0);
+
+ control.click();
+
+ await element.updateComplete;
+ await control.updateComplete;
+
+ assert.equal(numClicks, 1);
+ assert.isTrue(control.checked);
+ });
+ });
});
});