feat(formfield): add nowrap class/prop to MDC/MWC

PiperOrigin-RevId: 307142349
This commit is contained in:
Elliott Marquez 2020-04-17 17:11:25 -07:00 committed by Copybara-Service
parent bade5580b8
commit dae382476b
6 changed files with 305 additions and 35 deletions

View File

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `activated` and `selected` states for ripple - Added `activated` and `selected` states for ripple
- Added documentation for ripple - Added documentation for ripple
- Prefix and suffix to mwc-textfield - Prefix and suffix to mwc-textfield
- `mwc-formfield` now has a nowrap property
### Changed ### Changed
@ -27,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Refactor `mwc-ripple` - Refactor `mwc-ripple`
- Normalized API to `start${state}` `end${state}` naming - Normalized API to `start${state}` `end${state}` naming
- **BREAKING:VISUAL:** mwc-list-item now internally uses mwc-ripple instead of styling ripple on host - **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 ### Fixed

View File

@ -36,6 +36,26 @@ npm install @material/mwc-formfield
```html ```html
<mwc-formfield label="Tomato"> <mwc-formfield label="Tomato">
<mwc-checkbox checked></mwc-checkbox>
</mwc-formfield>
<script type="module">
import '@material/mwc-checkbox';
import '@material/mwc-formfield';
</script>
```
### nowrap label with checkbox
<img src="images/nowrap.png" width="150px" height="40px">
```html
<style>
mwc-formfield[nowrap] {
width: 150px;
}
</style>
<mwc-formfield label="really really long label" nowrap>
<mwc-checkbox></mwc-checkbox> <mwc-checkbox></mwc-checkbox>
</mwc-formfield> </mwc-formfield>
@ -98,9 +118,10 @@ Name | Description
Name | Type | 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. `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. `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 ### Methods

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,31 +1,35 @@
/** /**
@license * @license
Copyright 2018 Google Inc. All Rights Reserved. * 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"); import {MDCFormFieldAdapter} from '@material/form-field/adapter';
you may not use this file except in compliance with the License. import MDCFormFieldFoundation from '@material/form-field/foundation';
You may obtain a copy of the License at import {BaseElement, EventType, SpecificEventListener} from '@material/mwc-base/base-element';
import {FormElement} from '@material/mwc-base/form-element';
http://www.apache.org/licenses/LICENSE-2.0 import {observer} from '@material/mwc-base/observer';
import {findAssignedElement} from '@material/mwc-base/utils';
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 {html, property, query} from 'lit-element'; 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 { export class FormfieldBase extends BaseElement {
@property({type: Boolean}) alignEnd = false; @property({type: Boolean}) alignEnd = false;
@property({type: Boolean}) spaceBetween = false; @property({type: Boolean}) spaceBetween = false;
@property({type: Boolean}) nowrap = false;
@property({type: String}) @property({type: String})
@observer(async function(this: FormfieldBase, label: string) { @observer(async function(this: FormfieldBase, label: string) {
@ -93,7 +97,8 @@ export class FormfieldBase extends BaseElement {
protected render() { protected render() {
const classes = { const classes = {
'mdc-form-field--align-end': this.alignEnd, '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` return html`

View File

@ -15,7 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License. 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 { :host {
display: inline-flex; display: inline-flex;
@ -26,13 +31,13 @@ limitations under the License.
} }
::slotted(*) { ::slotted(*) {
@include mdc-typography(body2); @include typography.typography(body2);
@include mdc-theme-prop(color, text-primary-on-background); @include theme.prop(color, text-primary-on-background);
} }
::slotted(mwc-switch) { ::slotted(mwc-switch) {
margin-right: 10px; margin-right: 10px;
@include mdc-rtl { @include rtl.rtl {
margin-left: 10px; margin-left: 10px;
} }
} }

View File

@ -15,21 +15,258 @@
* limitations under the License. * 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`<mwc-formfield></mwc-formfield>`;
const defaultFormfieldProps = {
alignEnd: false,
spaceBetween: false,
label: '',
content: html``,
};
type FormfieldProps = typeof defaultFormfieldProps;
const formfield = (propsInit: Partial<FormfieldProps>) => {
const props: FormfieldProps = {...defaultFormfieldProps, ...propsInit};
return html`
<mwc-formfield
.alignEnd=${props.alignEnd}
.spaceBetween=${props.spaceBetween}
.label=${props.label}>
${props.content}
</mwc-formfield>
`;
};
suite('mwc-formfield', () => { suite('mwc-formfield', () => {
let fixt: TestFixture;
let element: Formfield; let element: Formfield;
setup(() => {
element = document.createElement('mwc-formfield');
document.body.appendChild(element);
});
teardown(() => { teardown(() => {
document.body.removeChild(element); fixt.remove();
}); });
test('initializes as an mwc-formfield', () => { suite('basic', () => {
assert.instanceOf(element, Formfield); 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`<mwc-checkbox></mwc-checkbox>`}));
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`<mwc-switch></mwc-switch>`}));
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`<mwc-radio></mwc-radio>`}));
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);
});
});
}); });
}); });