Skip to content

Commit

Permalink
Merge 7e80a9b into 912932b
Browse files Browse the repository at this point in the history
  • Loading branch information
Haprog committed Mar 28, 2019
2 parents 912932b + 7e80a9b commit 1de7f01
Show file tree
Hide file tree
Showing 21 changed files with 264 additions and 45 deletions.
2 changes: 1 addition & 1 deletion bower.json
Expand Up @@ -38,7 +38,7 @@
"polymer": "^2.0.0",
"vaadin-control-state-mixin": "vaadin/vaadin-control-state-mixin#^2.1.1",
"vaadin-overlay": "vaadin/vaadin-overlay#^3.2.0",
"vaadin-text-field": "vaadin/vaadin-text-field#^2.1.1",
"vaadin-text-field": "vaadin/vaadin-text-field#^2.3.0",
"vaadin-themable-mixin": "vaadin/vaadin-themable-mixin#^1.3.2",
"vaadin-lumo-styles": "vaadin/vaadin-lumo-styles#^1.1.1",
"vaadin-material-styles": "vaadin/vaadin-material-styles#^1.1.2",
Expand Down
17 changes: 17 additions & 0 deletions demo/combo-box-basic-demos.html
Expand Up @@ -40,6 +40,23 @@ <h3>Configuring the Combo Box</h3>
</template>
</vaadin-demo-snippet>

<h3>Clear Button</h3>

<p>Use the clear-button-visible attribute to display the clear button of an individual combobox.</p>

<vaadin-demo-snippet id="combo-box-basic-demos-clear-button" when-defined="vaadin-combo-box">
<template preserve-content>
<vaadin-combo-box label="Element" clear-button-visible></vaadin-combo-box>
<script>
window.addDemoReadyListener('#combo-box-basic-demos-clear-button', function(document) {
const comboBox = document.querySelector('vaadin-combo-box');
comboBox.items = ['Hydrogen', 'Helium', 'Lithium'];
comboBox.value = 'Hydrogen';
});
</script>
</template>
</vaadin-demo-snippet>


<h3>Allow Custom Values</h3>
<p>Allow the user to set any value for the field in addition to selecting a value from the dropdown menu.</p>
Expand Down
32 changes: 24 additions & 8 deletions src/vaadin-combo-box-light.html
Expand Up @@ -5,6 +5,7 @@
-->

<link rel="import" href="../../polymer/polymer-element.html">
<link rel="import" href="../../vaadin-control-state-mixin/vaadin-control-state-mixin.html">
<link rel="import" href="../../vaadin-themable-mixin/vaadin-themable-mixin.html">
<link rel="import" href="../../vaadin-themable-mixin/vaadin-theme-property-mixin.html">
<link rel="import" href="vaadin-combo-box-mixin.html">
Expand All @@ -13,6 +14,11 @@

<dom-module id="vaadin-combo-box-light">
<template>
<style>
:host([opened]) {
pointer-events: auto;
}
</style>

<slot></slot>

Expand Down Expand Up @@ -74,16 +80,18 @@
* </vaadin-combo-box-light>
* ```
* @memberof Vaadin
* @mixes Vaadin.ControlStateMixin
* @mixes Vaadin.ComboBoxDataProviderMixin
* @mixes Vaadin.ComboBoxMixin
* @mixes Vaadin.ThemableMixin
* @mixes Vaadin.ThemePropertyMixin
*/
class ComboBoxLightElement extends
Vaadin.ThemePropertyMixin(
Vaadin.ThemableMixin(
Vaadin.ComboBoxDataProviderMixin(
Vaadin.ComboBoxMixin(Polymer.Element)))) {
Vaadin.ControlStateMixin(
Vaadin.ThemePropertyMixin(
Vaadin.ThemableMixin(
Vaadin.ComboBoxDataProviderMixin(
Vaadin.ComboBoxMixin(Polymer.Element))))) {

static get is() {
return 'vaadin-combo-box-light';
Expand Down Expand Up @@ -118,10 +126,6 @@
this._clearElement = this.querySelector('.clear-button');
}

get focused() {
return this.getRootNode().activeElement === this.inputElement;
}

connectedCallback() {
super.connectedCallback();
const cssSelector = 'vaadin-text-field,iron-input,paper-input,.paper-input-input,.input';
Expand Down Expand Up @@ -150,6 +154,18 @@
this.inputElement[this._propertyForValue] = value;
}
}

/**
* Focusable element used by vaadin-control-state-mixin
*/
get focusElement() {
// Needed for focusing a paper-input properly
if (this.inputElement && this.inputElement._focusableElement) {
return this.inputElement._focusableElement;
}
// inputElement might not be defined on property changes before ready.
return this.inputElement || this;
}
}

customElements.define(ComboBoxLightElement.is, ComboBoxLightElement);
Expand Down
36 changes: 26 additions & 10 deletions src/vaadin-combo-box.html
Expand Up @@ -33,12 +33,6 @@
width: 100%;
min-width: 0;
}

:host([disabled]) [part="clear-button"],
:host([readonly]) [part="clear-button"],
:host(:not([has-value])) [part="clear-button"] {
display: none;
}
</style>

<vaadin-text-field part="text-field" id="input"
Expand All @@ -63,12 +57,12 @@

on-change="_stopPropagation"
on-input="_inputValueChanged"
clear-button-visible="[[clearButtonVisible]]"

theme$="[[theme]]"
>
<slot name="prefix" slot="prefix"></slot>

<div part="clear-button" id="clearButton" slot="suffix" role="button" aria-label="Clear"></div>
<div part="toggle-button" id="toggleButton" slot="suffix" role="button" aria-label="Toggle"></div>

</vaadin-text-field>
Expand Down Expand Up @@ -210,7 +204,6 @@
* Part name | Description
* ----------------|----------------
* `text-field` | The text field
* `clear-button` | The clear button
* `toggle-button` | The toggle button
*
* See [`<vaadin-overlay>` documentation](https://github.com/vaadin/vaadin-overlay/blob/master/src/vaadin-overlay.html)
Expand Down Expand Up @@ -338,6 +331,14 @@
readonly: {
type: Boolean,
value: false
},

/**
* Set to true to display the clear icon which clears the input.
*/
clearButtonVisible: {
type: Boolean,
value: false
}
};
}
Expand All @@ -361,9 +362,24 @@
ready() {
super.ready();

this._nativeInput = this.$.input.focusElement;
this._nativeInput = this.inputElement.focusElement;
this._toggleElement = this.$.toggleButton;
this._clearElement = this.$.clearButton;
this._clearElement = this.inputElement.shadowRoot.querySelector('[part="clear-button"]');

// Stop propagation of Esc in capturing phase so that
// vaadin-text-field will not handle Esc as a shortcut
// to clear the value.
// We need to set this listener for "this.inputElement"
// instead of just "this", otherwise keyboard navigation behaviour
// breaks a bit on Safari and some related tests fail.
this.inputElement.addEventListener('keydown', e => {
if (this._isEventKey(e, 'esc')) {
this._stopPropagation(e);
// Trigger _onEscape method of vaadin-combo-box-mixin because
// bubbling phase is not reached.
this._onEscape(e);
}
}, true);

this._nativeInput.setAttribute('role', 'combobox');
this._nativeInput.setAttribute('aria-autocomplete', 'list');
Expand Down
5 changes: 0 additions & 5 deletions test/aria.html
Expand Up @@ -35,9 +35,6 @@
function getToggleIcon() {
return comboBox._toggleElement;
}
function getClearIcon() {
return comboBox._clearElement;
}
function getItemElement(i) {
return comboBox.$.overlay._selector.querySelectorAll('vaadin-combo-box-item')[i];
}
Expand All @@ -63,8 +60,6 @@
expect(getNativeInput().hasAttribute('aria-labelledby')).to.be.true;
expect(getToggleIcon().getAttribute('role')).to.equal('button');
expect(getToggleIcon().getAttribute('aria-label')).to.equal('Toggle');
expect(getClearIcon().getAttribute('role')).to.equal('button');
expect(getClearIcon().getAttribute('aria-label')).to.equal('Clear');
});
});

Expand Down
14 changes: 10 additions & 4 deletions test/selecting-items.html
Expand Up @@ -20,6 +20,12 @@
</template>
</test-fixture>

<test-fixture id="combobox-clear">
<template>
<vaadin-combo-box clear-button-visible style="width: 320px;"></vaadin-combo-box>
</template>
</test-fixture>

<script>
describe('selecting a value', () => {
let combobox;
Expand Down Expand Up @@ -197,7 +203,7 @@

it('should fire on clear', () => {
combobox.value = 'foo';
fire('click', combobox._clearElement);
fire('click', combobox.$.input.$.clearButton);

expect(changeSpy.callCount).to.equal(1);
});
Expand Down Expand Up @@ -262,12 +268,12 @@
let combobox;
let clearIcon;
beforeEach(() => {
combobox = fixture('combobox');
combobox = fixture('combobox-clear');
combobox.items = ['foo', 'bar'];

combobox.value = 'foo';

clearIcon = combobox._clearElement;
clearIcon = combobox.inputElement.$.clearButton;

// needed for Edge <= 14 in Polymer 2
combobox.updateStyles();
Expand Down Expand Up @@ -307,7 +313,7 @@
expect(combobox.opened).to.eql(true);
});

it('should cancel down event to avoid input blur', () => {
it('should cancel click event to avoid input blur', () => {
combobox.open();

const event = fire('click', clearIcon);
Expand Down
8 changes: 0 additions & 8 deletions test/toggling-dropdown.html
Expand Up @@ -306,10 +306,6 @@
expect(getComputedStyle(combobox._toggleElement).display).not.to.equal('none');
});

it('clearIcon should be hidden when disabled', () => {
expect(getComputedStyle(combobox._clearElement).display).to.equal('none');
});

it('dropdown should not be shown when disabled', () => {
combobox.inputElement.dispatchEvent(new CustomEvent('click', {bubbles: true, composed: true}));
expect(combobox.opened).to.be.false;
Expand All @@ -326,10 +322,6 @@
expect(getComputedStyle(combobox._toggleElement).display).not.to.equal('none');
});

it('clearIcon should be hidden when read-only', () => {
expect(getComputedStyle(combobox._clearElement).display).to.equal('none');
});

it('dropdown should not be shown when read-only', () => {
combobox.inputElement.dispatchEvent(new CustomEvent('click', {bubbles: true, composed: true}));
expect(combobox.opened).to.be.false;
Expand Down
107 changes: 106 additions & 1 deletion test/vaadin-combo-box-light.html
Expand Up @@ -203,7 +203,7 @@
});

describe('vaadin-combo-box-light-paper-input', () => {
let comboBox, paperInput;
let comboBox;

beforeEach(() => {
comboBox = fixture('combobox-light-paper-input');
Expand Down Expand Up @@ -234,6 +234,111 @@

expect(spy.called).to.be.true;
});

describe('custom clear-button', () => {
let clearButton;

/**
* Get the most specific element at the given viewport coordinates.
* Same as "document.elementFromPoint()" but recursively digging through
* shadow roots.
*/
function elementFromPointDeep(x, y, root = document) {
const result = root.elementFromPoint(x, y);
if (!result) {
return null;
}
if (result === root.host) {
return result;
}
return result.shadowRoot ? elementFromPointDeep(x, y, result.shadowRoot) : result;
}

/**
* Simulate a click at the position of the given element.
* This can be used for more accurate testing of what would happen
* when a user tries to click on a specific element.
*
* This needs to be used when we need to consider the effect of
* possibly overlapping elements or "pointer-events: none"
*/
function clickAtPositionOf(elem) {
// Scroll the element into view if it's not already
elem.scrollIntoView();
// Get the viewport relative position of the element
const rect = elem.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) {
return;
}
const x = Math.ceil(rect.x);
const y = Math.ceil(rect.y);
// Get the element which would be targeted, when the user
// tries to click on this position
let target = elementFromPointDeep(x, y, elem.ownerDocument);
if (!target) {
return;
}

// Check if the found element contains a slot (needed for other browsers than Chrome)
const slot = target.querySelector('slot');
if (slot && slot.assignedNodes({flatten: true}).indexOf(elem) !== -1) {
target = elem;
}

fire('click', target);
}

beforeEach(() => {
clearButton = comboBox.querySelector('.clear-button');
comboBox.value = 'foo';
});

it('should be clickable when overlay is open', () => {
const clickSpy = sinon.spy();
clearButton.addEventListener('click', clickSpy);

comboBox.open();
clickAtPositionOf(clearButton);

expect(clickSpy.callCount).to.equal(1);
});

it('should fire `change` event on clear', () => {
const changeSpy = sinon.spy();
comboBox.addEventListener('change', changeSpy);

fire('click', clearButton);

expect(changeSpy.callCount).to.equal(1);
});

it('should clear the selection when clicking on the clear button', () => {
comboBox.open();

fire('click', clearButton);

expect(comboBox.value).to.eql('');
expect(comboBox.$.overlay._selectedItem).to.be.null;
expect(comboBox.selectedItem).to.be.null;
});

it('should not close the dropdown after clearing a selection', () => {
comboBox.open();

fire('click', clearButton);

expect(comboBox.opened).to.eql(true);
});

it('should cancel click event to avoid input blur', () => {
comboBox.open();

const event = fire('click', clearButton);

expect(event.defaultPrevented).to.eql(true);
});

});
});

describe('theme attribute', () => {
Expand Down

0 comments on commit 1de7f01

Please sign in to comment.