Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helper text API and slot #410

Merged
merged 21 commits into from
Oct 9, 2019
Merged
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion demo/text-field-basic-demos.html
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@ <h3>Basic text field</h3>
</template>
</vaadin-demo-snippet>


<h3>Disabled and read-only</h3>
<vaadin-demo-snippet id="text-field-basic-demos-states">
<template preserve-content>
@@ -40,6 +39,16 @@ <h3>Display the clear button</h3>
</template>
</vaadin-demo-snippet>

<h3>Hint text</h3>
<p>Use the <code>hint</code> attribute or add content to the hint slot to set helper text.</p>
<vaadin-demo-snippet id="text-field-basic-demos-helper-text">
<template preserve-content>
<vaadin-text-field label="Username">
<span slot="hint">Typically "firstname.lastname"</span>
</vaadin-text-field>
<vaadin-password-field label="Password" hint="Choose a strong password"></vaadin-password-field>
</template>
</vaadin-demo-snippet>

</template>
<script>
8 changes: 8 additions & 0 deletions demo/text-field-theme-demos.html
Original file line number Diff line number Diff line change
@@ -23,6 +23,14 @@ <h3>Small size</h3>
<vaadin-text-field theme="small" label="Label" placeholder="Text field"></vaadin-text-field>
</template>
</vaadin-demo-snippet>

<h3>Hint position</h3>
<vaadin-demo-snippet id="text-field-theme-demos-sizes">
<template preserve-content>
<vaadin-text-field hint="Hint above text field"></vaadin-text-field>
<vaadin-text-field hint="Hint below text field" theme="hint-below"></vaadin-text-field>
</template>
</vaadin-demo-snippet>


</template>
7 changes: 7 additions & 0 deletions src/vaadin-text-area.html
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@

/* The label and the error message should neither grow nor shrink. */
[part="label"],
[part="hint"],
[part="error-message"] {
flex: none;
}
@@ -51,6 +52,10 @@
<div class="vaadin-text-area-container">

<label part="label" on-click="focus" id="[[_labelId]]">[[label]]</label>

<div part="hint" on-click="focus" id="[[_hintId]]">
<slot name="hint">[[hint]]</slot>
</div>

<div part="input-field" id="[[_inputId]]">

@@ -117,6 +122,8 @@
* `disabled` | Set to a disabled text field | :host
* `has-value` | Set when the element has a value | :host
* `has-label` | Set when the element has a label | :host
* `has-hint` | Set when the element has hint text | :host
* `has-error-message` | Set when the element has an error message | :host
* `invalid` | Set when the element is invalid | :host
* `focused` | Set when the element is focused | :host
* `focus-ring` | Set when the element is keyboard focused | :host
66 changes: 59 additions & 7 deletions src/vaadin-text-field-mixin.html
Original file line number Diff line number Diff line change
@@ -189,7 +189,8 @@
*/
errorMessage: {
type: String,
value: ''
value: '',
observer: '_errorMessageChanged'
tomivirkki marked this conversation as resolved.
Show resolved Hide resolved
},

/**
@@ -221,6 +222,15 @@
observer: '_labelChanged'
},

/**
* String used for the hint text.
*/
hint: {
type: String,
value: '',
observer: '_hintChanged'
},

/**
* Maximum number of characters (in Unicode code points) that the user can enter.
*/
@@ -305,6 +315,8 @@

_labelId: String,

_hintId: String,

_errorId: String,

_inputId: String
@@ -315,9 +327,9 @@
return ['_stateChanged(disabled, readonly, clearButtonVisible, hasValue)',
'_hostPropsChanged(' + HOST_PROPS.default.join(', ') + ')',
'_hostAccessiblePropsChanged(' + HOST_PROPS.accessible.join(', ') + ')',
'_getActiveErrorId(invalid, errorMessage, _errorId)',
'_getActiveErrorId(invalid, errorMessage, _errorId, hint, _hintId)',
'_getActiveLabelId(label, _labelId, _inputId)',
'__observeOffsetHeight(errorMessage, invalid, label)'
'__observeOffsetHeight(errorMessage, invalid, label, hint)'
];
}

@@ -445,6 +457,33 @@
}
}

_hintChanged(hint) {
if (hint !== '' && hint !== null) {
niiyeboah marked this conversation as resolved.
Show resolved Hide resolved
this.setAttribute('has-hint', '');
} else {
this.removeAttribute('has-hint');
}
}

_errorMessageChanged(errorMessage) {
if (errorMessage !== '' && errorMessage !== null) {
this.setAttribute('has-error-message', '');
} else {
this.removeAttribute('has-error-message');
}
}

_hintSlotChange() {
this._slottedHint = this.querySelector(`[slot="hint"]`);
const slottedNodes = this.root.querySelector(`slot[name="hint"]`).assignedNodes();

if (slottedNodes.length) {
this.setAttribute('has-hint', 'slotted');
} else if (this.hint === '' || this.hint === null) {
this.removeAttribute('has-hint');
}
}

_onSlotChange() {
const slotted = this.querySelector(`${this._slottedTagName}[slot="${this._slottedTagName}"]`);

@@ -571,6 +610,12 @@
this.shadowRoot.querySelector('[name="input"], [name="textarea"]')
.addEventListener('slotchange', this._onSlotChange.bind(this));

this._slottedHint = this.querySelector(`[slot="hint"]`);
niiyeboah marked this conversation as resolved.
Show resolved Hide resolved
if (this._slottedHint) {
this.setAttribute('has-hint', 'slotted');
}
this.shadowRoot.querySelector('[name="hint"]').addEventListener('slotchange', this._hintSlotChange.bind(this));

if (!(window.ShadyCSS && window.ShadyCSS.nativeCss)) {
this.updateStyles();
}
@@ -583,6 +628,7 @@
var uniqueId = Vaadin.TextFieldMixin._uniqueId = 1 + Vaadin.TextFieldMixin._uniqueId || 0;
this._errorId = `${this.constructor.is}-error-${uniqueId}`;
this._labelId = `${this.constructor.is}-label-${uniqueId}`;
this._hintId = `${this.constructor.is}-hint-${uniqueId}`;
this._inputId = `${this.constructor.is}-input-${uniqueId}`;

// Lumo theme defines a max-height transition for the "error-message"
@@ -680,10 +726,16 @@
}
}

_getActiveErrorId(invalid, errorMessage, errorId) {
this._setOrToggleAttribute('aria-describedby',
(errorMessage && invalid ? errorId : undefined),
this.focusElement);
_getActiveErrorId(invalid, errorMessage, errorId, hint, hintId) {
let ids = '';
if (hint) {
ids += hintId;
}
if (errorMessage && invalid) {
ids += hint ? ' ' : '';
ids += errorId;
}
this._setOrToggleAttribute('aria-describedby', ids, this.focusElement);
tomivirkki marked this conversation as resolved.
Show resolved Hide resolved
}

_getActiveLabelId(label, _labelId, _inputId) {
6 changes: 6 additions & 0 deletions src/vaadin-text-field.html
Original file line number Diff line number Diff line change
@@ -17,6 +17,10 @@
<div class="vaadin-text-field-container">

<label part="label" on-click="focus" id="[[_labelId]]">[[label]]</label>

<div part="hint" on-click="focus" id="[[_hintId]]">
<slot name="hint">[[hint]]</slot>
</div>

<div part="input-field" id="[[_inputId]]">

@@ -89,6 +93,8 @@
* `disabled` | Set to a disabled text field | :host
* `has-value` | Set when the element has a value | :host
* `has-label` | Set when the element has a label | :host
* `has-hint` | Set when the element has hint text | :host
* `has-error-message` | Set when the element has an error message | :host
* `invalid` | Set when the element is invalid | :host
* `input-prevented` | Temporarily set when invalid input is prevented | :host
* `focused` | Set when the element is focused | :host
18 changes: 15 additions & 3 deletions test/accessibility.html
Original file line number Diff line number Diff line change
@@ -259,12 +259,13 @@
});

describe(`error ${condition}`, function() {
let tf, err, input;
let tf, err, input, hint;

beforeEach(function() {
tf = fixture(`${el}-error-fixture${fixtureName}`);
input = tf.inputElement;
err = tf.root.querySelector('[part=error-message]');
hint = tf.root.querySelector('[part=hint]');
});

it('should have an error element', function() {
@@ -287,15 +288,26 @@
expect(err.getAttribute('aria-hidden')).to.equal('true');
});

it('should not have aria-describedby attribute if valid', function() {
it('should not have aria-describedby attribute if valid and no hint text', function() {
expect(input.hasAttribute('aria-describedby')).to.be.false;
});

it('should have aria-describedby attribute when invalid', function() {
it('should have aria-describedby attribute with error message when invalid', function() {
tf.validate();
expect(input.getAttribute('aria-describedby')).to.equal(err.id);
});

it('should have aria-describedby attribute with hint text when hint is set', function() {
tf.hint = 'hint';
expect(input.getAttribute('aria-describedby')).to.equal(hint.id);
});

it('should have aria-describedby attribute with hint text and error message when hint is set and input invalid', function() {
tf.hint = 'hint';
tf.validate();
expect(input.getAttribute('aria-describedby')).to.equal(`${hint.id} ${err.id}`);
});

it('should have appropriate aria-live attribute', function() {
expect(err.getAttribute('aria-live')).to.equal('assertive');
});
52 changes: 52 additions & 0 deletions test/text-field.html
Original file line number Diff line number Diff line change
@@ -326,6 +326,49 @@
textField.label = 0;
expect(textField.hasAttribute('has-label')).to.be.true;
});

it('setting hint updates has-hint attribute', function() {
textField.hint = 'foo';
expect(textField.hasAttribute('has-hint')).to.be.true;
});

it('setting hint to empty string does not update has-hint attribute', function() {
textField.hint = '';
expect(textField.hasAttribute('has-hint')).to.be.false;
});

it('setting hint to null does not update has-hint attribute', function() {
textField.hint = null;
expect(textField.hasAttribute('has-hint')).to.be.false;
});

it('setting number hint updates has-hint attribute', function() {
textField.hint = 0;
expect(textField.hasAttribute('has-hint')).to.be.true;
});

it('setting hint with slot updates has-hint attribute', function(done) {
const hint = document.createElement('div');
hint.setAttribute('slot', 'hint');
hint.textContent = 'foo';
textField.appendChild(hint);
setTimeout(() => {
expect(textField.hasAttribute('has-hint')).to.be.true;
done();
}, 1);
});

it('removing slotted hint removes has-hint attribute', function(done) {
const hint = document.createElement('div');
hint.setAttribute('slot', 'hint');
hint.textContent = 'foo';
textField.appendChild(hint);
textField.removeChild(hint);
setTimeout(() => {
expect(textField.hasAttribute('has-hint')).to.be.false;
done();
}, 1);
});
});

if (!window.ShadyDOM) {
@@ -337,6 +380,15 @@
expect(textField.getAttribute('focused')).to.be.null;
});
});

describe(`hint ${condition}`, function() {
it('should not update focused property on click if disabled', function() {
textField.disabled = true;
const hint = textField.root.querySelector('[part="hint"]');
hint.click();
expect(textField.getAttribute('focused')).to.be.null;
});
});
}

describe(`autoselect ${condition}`, function() {
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions test/visual/vaadin-text-area/text-area-3.html
Original file line number Diff line number Diff line change
@@ -35,6 +35,8 @@
<fieldset id="text-area">
label and value: <vaadin-text-area label="Foo" value="Bar"></vaadin-text-area>
<p>
label, hint and value: <vaadin-text-area label="Foo" value="Bar" hint="Baz"></vaadin-text-area>
<p>
prefix and suffix:
<vaadin-text-area label="Foo" value="Bar">
<span slot="prefix">prefix</span>
7 changes: 7 additions & 0 deletions test/visual/vaadin-text-field/text-field-2.html
Original file line number Diff line number Diff line change
@@ -51,6 +51,13 @@
<p>
label and value: <vaadin-text-field label="Foo" value="Bar"></vaadin-text-field>
<p>
label, hint and value: <vaadin-text-field label="Foo" value="Bar" hint="Baz"></vaadin-text-field>
<p>
custom hint:
<vaadin-text-field>
<span slot="hint">Baz</span>
</vaadin-text-field>
<p>
prefix and suffix:
<vaadin-text-field label="Foo" value="Bar">
<span slot="prefix">prefix</span>
3 changes: 3 additions & 0 deletions test/visual/vaadin-text-field/text-field-3.html
Original file line number Diff line number Diff line change
@@ -49,6 +49,9 @@
<p>
<vaadin-text-field label="Label" invalid error-message="Error"></vaadin-text-field>
<vaadin-text-field label="Label" value="value"></vaadin-text-field>
<p>
<vaadin-text-field label="Label" hint="Hint" invalid error-message="Error"></vaadin-text-field>
<vaadin-text-field hint="Hint" value="value"></vaadin-text-field>
</fieldset>
</form>

44 changes: 43 additions & 1 deletion theme/lumo/vaadin-text-field-styles.html
Original file line number Diff line number Diff line change
@@ -32,6 +32,23 @@
color: var(--lumo-primary-text-color);
}

:host([has-hint]:not([has-hint="slotted"])) [part="hint"]::after,
:host([has-hint="slotted"]) [part="hint"] ::slotted(*)::after {
content: "";
display: block;
height: 0.4em;
}

[part="hint"],
[part="hint"] ::slotted(*) {
display: block;
color: var(--lumo-secondary-text-color);
font-size: var(--lumo-font-size-xs);
line-height: var(--lumo-line-height-xs);
margin-left: calc(var(--lumo-border-radius-m) / 4);
transition: color 0.2s;
}

[part="value"],
[part="input-field"] ::slotted(input),
[part="input-field"] ::slotted(textarea),
@@ -116,7 +133,9 @@

/* Hover */

:host(:hover:not([readonly]):not([focused])) [part="label"] {
:host(:hover:not([readonly]):not([focused])) [part="label"],
:host(:hover:not([readonly])) [part="hint"],
:host(:hover:not([readonly])) [part="hint"] ::slotted(*) {
color: var(--lumo-body-text-color);
}

@@ -197,6 +216,7 @@
}

:host([disabled]) [part="label"],
:host([disabled]) [part="hint"],
:host([disabled]) [part="value"],
:host([disabled]) [part="input-field"] ::slotted(*) {
color: var(--lumo-disabled-text-color);
@@ -255,6 +275,28 @@
}
}

/* Hint position */

:host([has-hint][theme~="hint-below"]:not([has-hint="slotted"])) [part="hint"]::after,
:host([has-hint="slotted"][theme~="hint-below"]) [part="hint"] ::slotted(*)::after {
display: none;
}

:host([has-hint][theme~="hint-below"]:not([has-hint="slotted"])) [part="hint"]::before,
:host([has-hint="slotted"][theme~="hint-below"]) [part="hint"] ::slotted(*)::before {
content: "";
display: block;
height: 0.4em;
}

:host([has-hint][theme~="hint-below"]) [part="hint"] {
order: 1;
}

:host([has-hint][theme~="hint-below"]) [part="error-message"] {
order: 2;
}

/* Slotted content */

[part="input-field"] ::slotted(:not([part]):not(iron-icon):not(input):not(textarea)) {
23 changes: 23 additions & 0 deletions theme/material/vaadin-text-field-styles.html
Original file line number Diff line number Diff line change
@@ -146,12 +146,35 @@
opacity: 0;
}

/* According to matrial theme guidelines, hint should be hidden when error message is set and input is invalid */
:host([has-hint][invalid][has-error-message]) [part="hint"] {
display: none;
}

[part="label"] {
width: 133%;
transition: transform 0.175s, color 0.175s, width 0.175s;
transition-timing-function: ease, ease, step-end;
}

:host([has-hint]:not([has-hint="slotted"])) [part="hint"]::before,
:host([has-hint="slotted"]) [part="hint"] ::slotted(*)::before {
content: "";
display: block;
height: 6px;
}

[part="hint"] {
order: 1;
}

[part="hint"],
[part="hint"] ::slotted(*) {
font-size: .75rem;
line-height: 1;
color: var(--material-secondary-text-color);
}

/* TODO: using unsupported selector to fix IE11 (even thought the label element is scaled down,
the 133% width still takes the same space as an unscaled element */
::-ms-backdrop,