@vaadin/combo-box
Advanced tools
Comparing version 23.1.1 to 23.2.0-alpha1
{ | ||
"name": "@vaadin/combo-box", | ||
"version": "23.1.1", | ||
"version": "23.2.0-alpha1", | ||
"publishConfig": { | ||
@@ -22,6 +22,6 @@ "access": "public" | ||
"files": [ | ||
"lit.d.ts", | ||
"lit.js", | ||
"src", | ||
"theme", | ||
"lit.js", | ||
"lit.d.ts", | ||
"vaadin-*.d.ts", | ||
@@ -40,21 +40,21 @@ "vaadin-*.js" | ||
"@polymer/polymer": "^3.0.0", | ||
"@vaadin/component-base": "^23.1.1", | ||
"@vaadin/field-base": "^23.1.1", | ||
"@vaadin/input-container": "^23.1.1", | ||
"@vaadin/item": "^23.1.1", | ||
"@vaadin/lit-renderer": "^23.1.1", | ||
"@vaadin/vaadin-lumo-styles": "^23.1.1", | ||
"@vaadin/vaadin-material-styles": "^23.1.1", | ||
"@vaadin/vaadin-overlay": "^23.1.1", | ||
"@vaadin/vaadin-themable-mixin": "^23.1.1" | ||
"@vaadin/component-base": "23.2.0-alpha1", | ||
"@vaadin/field-base": "23.2.0-alpha1", | ||
"@vaadin/input-container": "23.2.0-alpha1", | ||
"@vaadin/item": "23.2.0-alpha1", | ||
"@vaadin/lit-renderer": "23.2.0-alpha1", | ||
"@vaadin/vaadin-lumo-styles": "23.2.0-alpha1", | ||
"@vaadin/vaadin-material-styles": "23.2.0-alpha1", | ||
"@vaadin/vaadin-overlay": "23.2.0-alpha1", | ||
"@vaadin/vaadin-themable-mixin": "23.2.0-alpha1" | ||
}, | ||
"devDependencies": { | ||
"@esm-bundle/chai": "^4.3.4", | ||
"@vaadin/polymer-legacy-adapter": "^23.1.1", | ||
"@vaadin/polymer-legacy-adapter": "23.2.0-alpha1", | ||
"@vaadin/testing-helpers": "^0.3.2", | ||
"@vaadin/text-field": "^23.1.1", | ||
"@vaadin/text-field": "23.2.0-alpha1", | ||
"lit": "^2.0.0", | ||
"sinon": "^13.0.2" | ||
}, | ||
"gitHead": "390458d6519433a2dd502cef90da48e84573a275" | ||
"gitHead": "f226a2976c270d3d53c824f6e0a740a5d3382d91" | ||
} |
@@ -81,3 +81,3 @@ /** | ||
this.clearCache(); | ||
this.$.dropdown.addEventListener('index-requested', (e) => { | ||
this._scroller.addEventListener('index-requested', (e) => { | ||
const index = e.detail.index; | ||
@@ -181,43 +181,40 @@ const currentScrollerPos = e.detail.currentScrollerPos; | ||
// Make sure same page isn't requested multiple times. | ||
if (!this._pendingRequests[page] && this.dataProvider) { | ||
this.loading = true; | ||
if (this._pendingRequests[page] || !this.dataProvider) { | ||
return; | ||
} | ||
const params = { | ||
page, | ||
pageSize: this.pageSize, | ||
filter: this.filter, | ||
}; | ||
const params = { | ||
page, | ||
pageSize: this.pageSize, | ||
filter: this.filter, | ||
}; | ||
const callback = (items, size) => { | ||
if (this._pendingRequests[page] === callback) { | ||
if (!this.filteredItems) { | ||
const filteredItems = []; | ||
filteredItems.splice(params.page * params.pageSize, items.length, ...items); | ||
this.filteredItems = filteredItems; | ||
} else { | ||
this.splice('filteredItems', params.page * params.pageSize, items.length, ...items); | ||
} | ||
// Update selectedItem from filteredItems if value is set | ||
if (this._isValidValue(this.value) && this._getItemValue(this.selectedItem) !== this.value) { | ||
this._selectItemForValue(this.value); | ||
} | ||
if (!this.opened && !this.hasAttribute('focused')) { | ||
this._commitValue(); | ||
} | ||
this.size = size; | ||
const callback = (items, size) => { | ||
if (this._pendingRequests[page] !== callback) { | ||
return; | ||
} | ||
delete this._pendingRequests[page]; | ||
const filteredItems = this.filteredItems ? [...this.filteredItems] : []; | ||
filteredItems.splice(params.page * params.pageSize, items.length, ...items); | ||
this.filteredItems = filteredItems; | ||
if (Object.keys(this._pendingRequests).length === 0) { | ||
this.loading = false; | ||
} | ||
} | ||
}; | ||
if (!this.opened && !this.hasAttribute('focused')) { | ||
this._commitValue(); | ||
} | ||
this.size = size; | ||
if (!this._pendingRequests[page]) { | ||
// Don't request page if it's already being requested | ||
this._pendingRequests[page] = callback; | ||
this.dataProvider(params, callback); | ||
delete this._pendingRequests[page]; | ||
if (Object.keys(this._pendingRequests).length === 0) { | ||
this.loading = false; | ||
} | ||
} | ||
}; | ||
this._pendingRequests[page] = callback; | ||
// Set the `loading` flag only after marking the request as pending | ||
// to prevent the same page from getting requested multiple times | ||
// as a result of `__loadingChanged` in the scroller which requests | ||
// a virtualizer update which in turn may trigger a data provider page request. | ||
this.loading = true; | ||
this.dataProvider(params, callback); | ||
} | ||
@@ -292,3 +289,3 @@ | ||
if (dataProvider && value !== '' && (this.selectedItem === undefined || this.selectedItem === null)) { | ||
const valueIndex = this._indexOfValue(value, this.filteredItems); | ||
const valueIndex = this.__getItemIndexByValue(this.filteredItems, value); | ||
if (valueIndex < 0 || !this._getItemLabel(this.filteredItems[valueIndex])) { | ||
@@ -295,0 +292,0 @@ console.warn( |
@@ -6,3 +6,5 @@ /** | ||
*/ | ||
import './vaadin-combo-box-dropdown.js'; | ||
import './vaadin-combo-box-item.js'; | ||
import './vaadin-combo-box-overlay.js'; | ||
import './vaadin-combo-box-scroller.js'; | ||
import { dashToCamelCase } from '@polymer/polymer/lib/utils/case-map.js'; | ||
@@ -79,15 +81,12 @@ import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
<vaadin-combo-box-dropdown | ||
id="dropdown" | ||
opened="[[opened]]" | ||
<vaadin-combo-box-overlay | ||
id="overlay" | ||
hidden$="[[_isOverlayHidden(filteredItems, loading)]]" | ||
opened="[[_overlayOpened]]" | ||
loading$="[[loading]]" | ||
theme$="[[_theme]]" | ||
position-target="[[inputElement]]" | ||
restore-focus-on-close="[[__restoreFocusOnClose]]" | ||
no-vertical-overlap | ||
restore-focus-node="[[inputElement]]" | ||
renderer="[[renderer]]" | ||
_focused-index="[[_focusedIndex]]" | ||
_item-id-path="[[itemIdPath]]" | ||
_item-label-path="[[itemLabelPath]]" | ||
loading="[[loading]]" | ||
theme="[[_theme]]" | ||
></vaadin-combo-box-dropdown> | ||
></vaadin-combo-box-overlay> | ||
`; | ||
@@ -94,0 +93,0 @@ } |
@@ -151,2 +151,7 @@ /** | ||
/** | ||
* Tag name prefix used by scroller and items. | ||
*/ | ||
protected readonly _tagNamePrefix: string; | ||
/** | ||
* Requests an update for the content of items. | ||
@@ -181,4 +186,2 @@ * While performing the update, it invokes the renderer (passed in the `renderer` property) once an item. | ||
protected _revertInputValue(): void; | ||
protected _getItemElements(): HTMLElement[]; | ||
} |
@@ -16,2 +16,30 @@ /** | ||
/** | ||
* Checks if the value is supported as an item value in this control. | ||
* | ||
* @param {unknown} value | ||
* @return {boolean} | ||
*/ | ||
function isValidValue(value) { | ||
return value !== undefined && value !== null; | ||
} | ||
/** | ||
* Returns the index of the first item that satisfies the provided testing function | ||
* ignoring placeholder items. | ||
* | ||
* @param {Array<ComboBoxItem | string>} items | ||
* @param {Function} callback | ||
* @return {number} | ||
*/ | ||
function findItemIndex(items, callback) { | ||
return items.findIndex((item) => { | ||
if (item instanceof ComboBoxPlaceholder) { | ||
return false; | ||
} | ||
return callback(item); | ||
}); | ||
} | ||
/** | ||
* @polymerMixin | ||
@@ -99,2 +127,3 @@ * @param {function(new:HTMLElement)} subclass | ||
type: Array, | ||
observer: '_filteredItemsChanged', | ||
}, | ||
@@ -116,3 +145,2 @@ | ||
reflectToAttribute: true, | ||
observer: '_loadingChanged', | ||
}, | ||
@@ -202,3 +230,9 @@ | ||
/** @private */ | ||
__restoreFocusOnClose: Boolean, | ||
_scroller: Object, | ||
/** @private */ | ||
_overlayOpened: { | ||
type: Boolean, | ||
observer: '_overlayOpenedChanged', | ||
}, | ||
}; | ||
@@ -210,5 +244,5 @@ } | ||
'_filterChanged(filter, itemValuePath, itemLabelPath)', | ||
'_itemsOrPathsChanged(items.*, itemValuePath, itemLabelPath)', | ||
'_filteredItemsChanged(filteredItems.*, itemValuePath, itemLabelPath)', | ||
'_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)', | ||
'_openedOrItemsChanged(opened, filteredItems, loading)', | ||
'_updateScroller(_scroller, filteredItems, opened, loading, selectedItem, itemIdPath, _focusedIndex, renderer, theme)', | ||
]; | ||
@@ -222,4 +256,2 @@ } | ||
this._boundOnClearButtonMouseDown = this.__onClearButtonMouseDown.bind(this); | ||
this._boundClose = this.close.bind(this); | ||
this._boundOnOpened = this._onOpened.bind(this); | ||
this._boundOnClick = this._onClick.bind(this); | ||
@@ -231,2 +263,11 @@ this._boundOnOverlayTouchAction = this._onOverlayTouchAction.bind(this); | ||
/** | ||
* Tag name prefix used by scroller and items. | ||
* @protected | ||
* @return {string} | ||
*/ | ||
get _tagNamePrefix() { | ||
return 'vaadin-combo-box'; | ||
} | ||
/** | ||
* @return {string | undefined} | ||
@@ -284,2 +325,5 @@ * @protected | ||
this._initOverlay(); | ||
this._initScroller(); | ||
this.addEventListener('focusout', this._boundOnFocusout); | ||
@@ -289,10 +333,3 @@ | ||
this.$.dropdown.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged); | ||
this.addEventListener('vaadin-combo-box-dropdown-closed', this._boundClose); | ||
this.addEventListener('vaadin-combo-box-dropdown-opened', this._boundOnOpened); | ||
this.addEventListener('click', this._boundOnClick); | ||
this.$.dropdown.addEventListener('vaadin-overlay-touch-action', this._boundOnOverlayTouchAction); | ||
this.addEventListener('touchend', this._boundOnTouchend); | ||
@@ -302,3 +339,3 @@ | ||
requestAnimationFrame(() => { | ||
this.$.dropdown.$.overlay.bringToFront(); | ||
this.$.overlay.bringToFront(); | ||
}); | ||
@@ -315,2 +352,10 @@ }; | ||
/** @protected */ | ||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
// Close the overlay on detach | ||
this.close(); | ||
} | ||
/** | ||
@@ -323,7 +368,7 @@ * Requests an update for the content of items. | ||
requestContentUpdate() { | ||
if (!this.$.dropdown._scroller) { | ||
if (!this._scroller) { | ||
return; | ||
} | ||
this.$.dropdown._scroller.requestContentUpdate(); | ||
this._scroller.requestContentUpdate(); | ||
@@ -353,2 +398,100 @@ this._getItemElements().forEach((item) => { | ||
/** @private */ | ||
_initOverlay() { | ||
const overlay = this.$.overlay; | ||
// Store instance for detecting "dir" attribute on opening | ||
overlay._comboBox = this; | ||
overlay.addEventListener('touchend', this._boundOnOverlayTouchAction); | ||
overlay.addEventListener('touchmove', this._boundOnOverlayTouchAction); | ||
// Prevent blurring the input when clicking inside the overlay | ||
overlay.addEventListener('mousedown', (e) => e.preventDefault()); | ||
// Preventing the default modal behavior of the overlay on input click | ||
overlay.addEventListener('vaadin-overlay-outside-click', (e) => { | ||
e.preventDefault(); | ||
}); | ||
// Manual two-way binding for the overlay "opened" property | ||
overlay.addEventListener('opened-changed', (e) => { | ||
this._overlayOpened = e.detail.value; | ||
}); | ||
} | ||
/** | ||
* Create and initialize the scroller element. | ||
* Override to provide custom host reference. | ||
* | ||
* @protected | ||
*/ | ||
_initScroller(host) { | ||
const scrollerTag = `${this._tagNamePrefix}-scroller`; | ||
const overlay = this.$.overlay; | ||
overlay.renderer = (root) => { | ||
if (!root.firstChild) { | ||
root.appendChild(document.createElement(scrollerTag)); | ||
} | ||
}; | ||
// Ensure the scroller is rendered | ||
if (!this.opened) { | ||
overlay.requestContentUpdate(); | ||
} | ||
const scroller = overlay.querySelector(scrollerTag); | ||
scroller.comboBox = host || this; | ||
scroller.getItemLabel = this._getItemLabel.bind(this); | ||
scroller.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged); | ||
// Trigger the observer to set properties | ||
this._scroller = scroller; | ||
} | ||
/** @private */ | ||
// eslint-disable-next-line max-params | ||
_updateScroller(scroller, items, opened, loading, selectedItem, itemIdPath, focusedIndex, renderer, theme) { | ||
if (scroller) { | ||
scroller.setProperties({ | ||
items: opened ? items : [], | ||
opened, | ||
loading, | ||
selectedItem, | ||
itemIdPath, | ||
focusedIndex, | ||
renderer, | ||
theme, | ||
}); | ||
} | ||
} | ||
/** @protected */ | ||
_isOverlayHidden(items, loading) { | ||
return !loading && !(items && items.length); | ||
} | ||
/** @private */ | ||
_openedOrItemsChanged(opened, items, loading) { | ||
// Close the overlay if there are no items to display. | ||
// See https://github.com/vaadin/vaadin-combo-box/pull/964 | ||
this._overlayOpened = !!(opened && (loading || (items && items.length))); | ||
} | ||
/** @private */ | ||
_overlayOpenedChanged(opened, wasOpened) { | ||
if (opened) { | ||
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true })); | ||
this._onOpened(); | ||
} else if (wasOpened && this.filteredItems && this.filteredItems.length) { | ||
this.close(); | ||
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true })); | ||
} | ||
} | ||
/** @private */ | ||
_focusedIndexChanged(index, oldIndex) { | ||
@@ -391,3 +534,3 @@ if (oldIndex === undefined) { | ||
this.__restoreFocusOnClose = true; | ||
this.$.overlay.restoreFocusOnClose = true; | ||
} else { | ||
@@ -405,3 +548,3 @@ this._onClosed(); | ||
if (opened) { | ||
input.setAttribute('aria-controls', this.$.dropdown.scrollerId); | ||
input.setAttribute('aria-controls', this._scroller.id); | ||
} else { | ||
@@ -496,3 +639,3 @@ input.removeAttribute('aria-controls'); | ||
if (e.key === 'Tab') { | ||
this.__restoreFocusOnClose = false; | ||
this.$.overlay.restoreFocusOnClose = false; | ||
} else if (e.key === 'ArrowDown') { | ||
@@ -517,3 +660,7 @@ this._closeOnBlurIsPrevented = true; | ||
_getItemLabel(item) { | ||
return this.$.dropdown.getItemLabel(item); | ||
let label = item && this.itemLabelPath ? this.get(this.itemLabelPath, item) : undefined; | ||
if (label === undefined || label === null) { | ||
label = item ? item.toString() : ''; | ||
} | ||
return label; | ||
} | ||
@@ -533,3 +680,3 @@ | ||
if (this.opened) { | ||
const items = this._getOverlayItems(); | ||
const items = this.filteredItems; | ||
if (items) { | ||
@@ -550,3 +697,3 @@ this._focusedIndex = Math.min(items.length - 1, this._focusedIndex + 1); | ||
} else { | ||
const items = this._getOverlayItems(); | ||
const items = this.filteredItems; | ||
if (items) { | ||
@@ -566,3 +713,4 @@ this._focusedIndex = items.length - 1; | ||
if (this._focusedIndex > -1) { | ||
this._inputElementValue = this._getItemLabel(this.$.dropdown.focusedItem); | ||
const focusedItem = this.filteredItems[this._focusedIndex]; | ||
this._inputElementValue = this._getItemLabel(focusedItem); | ||
this._markAllSelectionRange(); | ||
@@ -724,4 +872,8 @@ } | ||
requestAnimationFrame(() => { | ||
this.$.dropdown.adjustScrollPosition(); | ||
// When opened is set as attribute, this logic needs to be delayed until scroller is created. | ||
this._scroller.style.maxHeight = | ||
getComputedStyle(this).getPropertyValue(`--${this._tagNamePrefix}-overlay-max-height`) || '65vh'; | ||
this._scrollIntoView(this._focusedIndex); | ||
// Set attribute after the items are rendered when overlay is opened for the first time. | ||
@@ -744,5 +896,4 @@ this._updateActiveDescendant(this._focusedIndex); | ||
_commitValue() { | ||
const items = this._getOverlayItems(); | ||
if (items && this._focusedIndex > -1) { | ||
const focusedItem = items[this._focusedIndex]; | ||
if (this._focusedIndex > -1) { | ||
const focusedItem = this.filteredItems[this._focusedIndex]; | ||
if (this.selectedItem !== focusedItem) { | ||
@@ -760,14 +911,10 @@ this.selectedItem = focusedItem; | ||
} else { | ||
const toLowerCase = (item) => item && item.toLowerCase && item.toLowerCase(); | ||
// Try to find an item which label matches the input value. | ||
const items = [...(this.filteredItems || []), this.selectedItem]; | ||
const itemMatchingInputValue = items[this.__getItemIndexByLabel(items, this._inputElementValue)]; | ||
// Try to find an item whose label matches the input value. A matching item is searched from | ||
// the filteredItems array (if available) and the selectedItem (if available). | ||
const itemMatchingByLabel = [...(this.filteredItems || []), this.selectedItem].find((item) => { | ||
return toLowerCase(this._getItemLabel(item)) === toLowerCase(this._inputElementValue); | ||
}); | ||
if ( | ||
this.allowCustomValue && | ||
// To prevent a repetitive input value being saved after pressing ESC and Tab. | ||
!itemMatchingByLabel | ||
!itemMatchingInputValue | ||
) { | ||
@@ -789,8 +936,7 @@ const customValue = this._inputElementValue; | ||
if (!e.defaultPrevented) { | ||
this._selectItemForValue(customValue); | ||
this.value = customValue; | ||
} | ||
} else if (!this.allowCustomValue && !this.opened && itemMatchingByLabel) { | ||
} else if (!this.allowCustomValue && !this.opened && itemMatchingInputValue) { | ||
// An item matching by label was found, select it. | ||
this.value = this._getItemValue(itemMatchingByLabel); | ||
this.value = this._getItemValue(itemMatchingInputValue); | ||
} else { | ||
@@ -861,3 +1007,3 @@ // Revert the input value | ||
/** @private */ | ||
_filterChanged(filter, itemValuePath, itemLabelPath) { | ||
_filterChanged(filter, _itemValuePath, _itemLabelPath) { | ||
if (filter === undefined) { | ||
@@ -868,4 +1014,6 @@ return; | ||
// Scroll to the top of the list whenever the filter changes. | ||
this.$.dropdown._scrollIntoView(0); | ||
this._scrollIntoView(0); | ||
this._focusedIndex = -1; | ||
if (this.items) { | ||
@@ -877,13 +1025,6 @@ this.filteredItems = this._filterItems(this.items, filter); | ||
// observer should still be invoked to update focused item. | ||
this._filteredItemsChanged({ path: 'filteredItems', value: this.filteredItems }, itemValuePath, itemLabelPath); | ||
this._filteredItemsChanged(this.filteredItems); | ||
} | ||
} | ||
/** @private */ | ||
_loadingChanged(loading) { | ||
if (loading) { | ||
this._focusedIndex = -1; | ||
} | ||
} | ||
/** @protected */ | ||
@@ -934,5 +1075,3 @@ _revertInputValue() { | ||
this.$.dropdown._selectedItem = selectedItem; | ||
const items = this._getOverlayItems(); | ||
if (this.filteredItems && items) { | ||
if (this.filteredItems) { | ||
this._focusedIndex = this.filteredItems.indexOf(selectedItem); | ||
@@ -954,3 +1093,3 @@ } | ||
if (this._isValidValue(value)) { | ||
if (isValidValue(value)) { | ||
let item; | ||
@@ -988,49 +1127,49 @@ if (this._getItemValue(this.selectedItem) !== value) { | ||
}); | ||
if (items) { | ||
this.filteredItems = items.slice(0); | ||
} else if (oldItems) { | ||
// Only clear filteredItems if the component had items previously but got cleared | ||
this.filteredItems = null; | ||
} | ||
} | ||
/** @private */ | ||
_itemsOrPathsChanged(e) { | ||
if (e.path === 'items' || e.path === 'items.splices') { | ||
if (this.items) { | ||
this.filteredItems = this.items.slice(0); | ||
} else if (this.__previousItems) { | ||
// Only clear filteredItems if the component had items previously but got cleared | ||
this.filteredItems = null; | ||
} | ||
_filteredItemsChanged(filteredItems, oldFilteredItems) { | ||
// Store the currently focused item if any. The focused index preserves | ||
// in the case when more filtered items are loading but it is reset | ||
// when the user types in a filter query. | ||
const focusedItem = oldFilteredItems ? oldFilteredItems[this._focusedIndex] : null; | ||
const valueIndex = this._indexOfValue(this.value, this.items); | ||
this._focusedIndex = valueIndex; | ||
// Try to sync `selectedItem` based on `value` once a new set of `filteredItems` is available | ||
// (as a result of external filtering or when they have been loaded by the data provider). | ||
// When `value` is specified but `selectedItem` is not, it means that there was no item | ||
// matching `value` at the moment `value` was set, so `selectedItem` has remained unsynced. | ||
const valueIndex = this.__getItemIndexByValue(filteredItems, this.value); | ||
if ((this.selectedItem === null || this.selectedItem === undefined) && valueIndex >= 0) { | ||
this.selectedItem = filteredItems[valueIndex]; | ||
} | ||
const item = valueIndex > -1 && this.items[valueIndex]; | ||
if (item) { | ||
this.selectedItem = item; | ||
} | ||
// Try to first set focus on the item that had been focused before `filteredItems` were updated | ||
// if it is still present in the `filteredItems` array. Otherwise, set the focused index | ||
// depending on the selected item or the filter query. | ||
const focusedItemIndex = this.__getItemIndexByValue(filteredItems, this._getItemValue(focusedItem)); | ||
if (focusedItemIndex > -1) { | ||
this._focusedIndex = focusedItemIndex; | ||
} else { | ||
this.__setInitialFocusedIndex(); | ||
} | ||
this.__previousItems = e.value; | ||
} | ||
/** @private */ | ||
_filteredItemsChanged(e) { | ||
if (e.path === 'filteredItems' || e.path === 'filteredItems.splices') { | ||
this._setOverlayItems(this.filteredItems); | ||
// When the external filtering is used and `value` was provided before `filteredItems`, | ||
// initialize the selected item with the current value here. This will also cause | ||
// the input element value to sync. In other cases, the selected item is already initialized | ||
// in other observers such as `valueChanged`, `_itemsOrPathsChanged`. | ||
const valueIndex = this._indexOfValue(this.value, this.filteredItems); | ||
if (this.selectedItem === null && valueIndex >= 0) { | ||
this._selectItemForValue(this.value); | ||
} | ||
const inputValue = this._inputElementValue; | ||
if (inputValue === undefined || inputValue === this._getItemLabel(this.selectedItem)) { | ||
// When the input element value is the same as the current value or not defined, | ||
// set the focused index to the item that matches the value. | ||
this._focusedIndex = this.$.dropdown.indexOfLabel(this._getItemLabel(this.selectedItem)); | ||
} else { | ||
// When the user filled in something that is different from the current value = filtering is enabled, | ||
// set the focused index to the item that matches the filter query. | ||
this._focusedIndex = this.$.dropdown.indexOfLabel(this.filter); | ||
} | ||
__setInitialFocusedIndex() { | ||
const inputValue = this._inputElementValue; | ||
if (inputValue === undefined || inputValue === this._getItemLabel(this.selectedItem)) { | ||
// When the input element value is the same as the current value or not defined, | ||
// set the focused index to the item that matches the value. | ||
this._focusedIndex = this.__getItemIndexByLabel(this.filteredItems, this._getItemLabel(this.selectedItem)); | ||
} else { | ||
// When the user filled in something that is different from the current value = filtering is enabled, | ||
// set the focused index to the item that matches the filter query. | ||
this._focusedIndex = this.__getItemIndexByLabel(this.filteredItems, this.filter); | ||
} | ||
@@ -1056,3 +1195,3 @@ } | ||
_selectItemForValue(value) { | ||
const valueIndex = this._indexOfValue(value, this.filteredItems); | ||
const valueIndex = this.__getItemIndexByValue(this.filteredItems, value); | ||
const previouslySelectedItem = this.selectedItem; | ||
@@ -1073,28 +1212,26 @@ | ||
/** @protected */ | ||
/** @private */ | ||
_getItemElements() { | ||
return Array.from(this.$.dropdown._scroller.querySelectorAll('vaadin-combo-box-item')); | ||
return Array.from(this._scroller.querySelectorAll(`${this._tagNamePrefix}-item`)); | ||
} | ||
/** @private */ | ||
_getOverlayItems() { | ||
return this.$.dropdown._items; | ||
_scrollIntoView(index) { | ||
if (!this._scroller) { | ||
return; | ||
} | ||
this._scroller.scrollIntoView(index); | ||
} | ||
/** @private */ | ||
_setOverlayItems(items) { | ||
this.$.dropdown.set('_items', items); | ||
} | ||
/** @private */ | ||
_indexOfValue(value, items) { | ||
if (!items || !this._isValidValue(value)) { | ||
/** | ||
* Returns the first item that matches the provided value. | ||
* | ||
* @private | ||
*/ | ||
__getItemIndexByValue(items, value) { | ||
if (!items || !isValidValue(value)) { | ||
return -1; | ||
} | ||
return items.findIndex((item) => { | ||
if (item instanceof ComboBoxPlaceholder) { | ||
return false; | ||
} | ||
return findItemIndex(items, (item) => { | ||
return this._getItemValue(item) === value; | ||
@@ -1105,7 +1242,15 @@ }); | ||
/** | ||
* Checks if the value is supported as an item value in this control. | ||
* Returns the first item that matches the provided label. | ||
* Labels are matched against each other case insensitively. | ||
* | ||
* @private | ||
*/ | ||
_isValidValue(value) { | ||
return value !== undefined && value !== null; | ||
__getItemIndexByLabel(items, label) { | ||
if (!items || !label) { | ||
return -1; | ||
} | ||
return findItemIndex(items, (item) => { | ||
return this._getItemLabel(item).toString().toLowerCase() === label.toString().toLowerCase(); | ||
}); | ||
} | ||
@@ -1141,3 +1286,3 @@ | ||
// Fixes the problem with `focusout` happening when clicking on the scroll bar on Edge | ||
if (event.relatedTarget === this.$.dropdown.$.overlay) { | ||
if (event.relatedTarget === this.$.overlay) { | ||
event.composedPath()[0].focus(); | ||
@@ -1218,2 +1363,14 @@ return; | ||
*/ | ||
/** | ||
* Fired after the `vaadin-combo-box-overlay` opens. | ||
* | ||
* @event vaadin-combo-box-dropdown-opened | ||
*/ | ||
/** | ||
* Fired after the `vaadin-combo-box-overlay` closes. | ||
* | ||
* @event vaadin-combo-box-dropdown-closed | ||
*/ | ||
}; |
@@ -55,5 +55,3 @@ /** | ||
const dropdown = this.__dataHost; | ||
const comboBox = dropdown && dropdown.getRootNode().host; | ||
this._comboBox = comboBox; | ||
const comboBox = this._comboBox; | ||
@@ -60,0 +58,0 @@ const hostDir = comboBox && comboBox.getAttribute('dir'); |
@@ -7,2 +7,3 @@ /** | ||
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js'; | ||
import { Virtualizer } from '@vaadin/component-base/src/virtualizer.js'; | ||
@@ -94,2 +95,3 @@ import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js'; | ||
type: Object, | ||
observer: '__selectedItemChanged', | ||
}, | ||
@@ -150,2 +152,5 @@ | ||
// Ensure every instance has unique ID | ||
this.id = `${this.localName}-${generateUniqueId()}`; | ||
// Allow extensions to customize tag name for the items | ||
@@ -224,3 +229,3 @@ this.__hostTagName = this.constructor.is.replace('-scroller', ''); | ||
__isItemFocused(focusedIndex, itemIndex) { | ||
return focusedIndex === itemIndex; | ||
return !this.loading && focusedIndex === itemIndex; | ||
} | ||
@@ -250,5 +255,5 @@ | ||
/** @private */ | ||
__loadingChanged(loading) { | ||
if (this.__virtualizer && !loading) { | ||
setTimeout(() => this.requestContentUpdate()); | ||
__loadingChanged() { | ||
if (this.__virtualizer) { | ||
this.requestContentUpdate(); | ||
} | ||
@@ -258,2 +263,9 @@ } | ||
/** @private */ | ||
__selectedItemChanged() { | ||
if (this.__virtualizer) { | ||
this.requestContentUpdate(); | ||
} | ||
} | ||
/** @private */ | ||
__focusedIndexChanged(index, oldIndex) { | ||
@@ -260,0 +272,0 @@ if (!this.__virtualizer) { |
@@ -7,3 +7,5 @@ /** | ||
import '@vaadin/input-container/src/vaadin-input-container.js'; | ||
import './vaadin-combo-box-dropdown.js'; | ||
import './vaadin-combo-box-item.js'; | ||
import './vaadin-combo-box-overlay.js'; | ||
import './vaadin-combo-box-scroller.js'; | ||
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
@@ -200,15 +202,12 @@ import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; | ||
<vaadin-combo-box-dropdown | ||
id="dropdown" | ||
opened="[[opened]]" | ||
renderer="[[renderer]]" | ||
<vaadin-combo-box-overlay | ||
id="overlay" | ||
hidden$="[[_isOverlayHidden(filteredItems, loading)]]" | ||
opened="[[_overlayOpened]]" | ||
loading$="[[loading]]" | ||
theme$="[[_theme]]" | ||
position-target="[[_positionTarget]]" | ||
restore-focus-on-close="[[__restoreFocusOnClose]]" | ||
no-vertical-overlap | ||
restore-focus-node="[[inputElement]]" | ||
_focused-index="[[_focusedIndex]]" | ||
_item-id-path="[[itemIdPath]]" | ||
_item-label-path="[[itemLabelPath]]" | ||
loading="[[loading]]" | ||
theme="[[_theme]]" | ||
></vaadin-combo-box-dropdown> | ||
></vaadin-combo-box-overlay> | ||
`; | ||
@@ -278,3 +277,3 @@ } | ||
// Do not blur when focus moves to the overlay | ||
if (event.relatedTarget === this.$.dropdown.$.overlay) { | ||
if (event.relatedTarget === this.$.overlay) { | ||
event.composedPath()[0].focus(); | ||
@@ -281,0 +280,0 @@ return false; |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
136335
33
3429
1
+ Added@vaadin/component-base@23.2.0-alpha1(transitive)
+ Added@vaadin/field-base@23.2.0-alpha1(transitive)
+ Added@vaadin/icon@23.2.0-alpha1(transitive)
+ Added@vaadin/input-container@23.2.0-alpha1(transitive)
+ Added@vaadin/item@23.2.0-alpha1(transitive)
+ Added@vaadin/lit-renderer@23.2.0-alpha1(transitive)
+ Added@vaadin/vaadin-lumo-styles@23.2.0-alpha1(transitive)
+ Added@vaadin/vaadin-material-styles@23.2.0-alpha1(transitive)
+ Added@vaadin/vaadin-overlay@23.2.0-alpha1(transitive)
+ Added@vaadin/vaadin-themable-mixin@23.2.0-alpha1(transitive)
- Removed@vaadin/component-base@23.5.11(transitive)
- Removed@vaadin/field-base@23.5.11(transitive)
- Removed@vaadin/icon@23.5.11(transitive)
- Removed@vaadin/input-container@23.5.11(transitive)
- Removed@vaadin/item@23.5.11(transitive)
- Removed@vaadin/lit-renderer@23.5.11(transitive)
- Removed@vaadin/overlay@23.5.11(transitive)
- Removed@vaadin/vaadin-lumo-styles@23.5.11(transitive)
- Removed@vaadin/vaadin-material-styles@23.5.11(transitive)
- Removed@vaadin/vaadin-overlay@23.5.11(transitive)
- Removed@vaadin/vaadin-themable-mixin@23.5.11(transitive)
Updated@vaadin/item@23.2.0-alpha1