@vaadin/combo-box
Advanced tools
Comparing version 23.2.0-dev.8a7678b70 to 23.2.0-rc1
{ | ||
"name": "@vaadin/combo-box", | ||
"version": "23.2.0-dev.8a7678b70", | ||
"version": "23.2.0-rc1", | ||
"publishConfig": { | ||
@@ -27,3 +27,5 @@ "access": "public" | ||
"vaadin-*.d.ts", | ||
"vaadin-*.js" | ||
"vaadin-*.js", | ||
"web-types.json", | ||
"web-types.lit.json" | ||
], | ||
@@ -40,21 +42,25 @@ "keywords": [ | ||
"@polymer/polymer": "^3.0.0", | ||
"@vaadin/component-base": "23.2.0-dev.8a7678b70", | ||
"@vaadin/field-base": "23.2.0-dev.8a7678b70", | ||
"@vaadin/input-container": "23.2.0-dev.8a7678b70", | ||
"@vaadin/item": "23.2.0-dev.8a7678b70", | ||
"@vaadin/lit-renderer": "23.2.0-dev.8a7678b70", | ||
"@vaadin/vaadin-lumo-styles": "23.2.0-dev.8a7678b70", | ||
"@vaadin/vaadin-material-styles": "23.2.0-dev.8a7678b70", | ||
"@vaadin/vaadin-overlay": "23.2.0-dev.8a7678b70", | ||
"@vaadin/vaadin-themable-mixin": "23.2.0-dev.8a7678b70" | ||
"@vaadin/component-base": "23.2.0-rc1", | ||
"@vaadin/field-base": "23.2.0-rc1", | ||
"@vaadin/input-container": "23.2.0-rc1", | ||
"@vaadin/item": "23.2.0-rc1", | ||
"@vaadin/lit-renderer": "23.2.0-rc1", | ||
"@vaadin/vaadin-lumo-styles": "23.2.0-rc1", | ||
"@vaadin/vaadin-material-styles": "23.2.0-rc1", | ||
"@vaadin/vaadin-overlay": "23.2.0-rc1", | ||
"@vaadin/vaadin-themable-mixin": "23.2.0-rc1" | ||
}, | ||
"devDependencies": { | ||
"@esm-bundle/chai": "^4.3.4", | ||
"@vaadin/polymer-legacy-adapter": "23.2.0-dev.8a7678b70", | ||
"@vaadin/polymer-legacy-adapter": "23.2.0-rc1", | ||
"@vaadin/testing-helpers": "^0.3.2", | ||
"@vaadin/text-field": "23.2.0-dev.8a7678b70", | ||
"@vaadin/text-field": "23.2.0-rc1", | ||
"lit": "^2.0.0", | ||
"sinon": "^13.0.2" | ||
}, | ||
"gitHead": "85b403f96d8282f262322b56c0ff4289f843d02a" | ||
"web-types": [ | ||
"web-types.json", | ||
"web-types.lit.json" | ||
], | ||
"gitHead": "e78a1f2fe6f42d78cefa3f48085b09a3033c9588" | ||
} |
@@ -6,6 +6,6 @@ /** | ||
*/ | ||
import { TemplateResult } from 'lit'; | ||
import { DirectiveResult } from 'lit/directive.js'; | ||
import type { TemplateResult } from 'lit'; | ||
import type { DirectiveResult } from 'lit/directive.js'; | ||
import { LitRendererDirective } from '@vaadin/lit-renderer'; | ||
import { ComboBox, ComboBoxItemModel } from '../vaadin-combo-box.js'; | ||
import type { ComboBox, ComboBoxItemModel } from '../vaadin-combo-box.js'; | ||
@@ -12,0 +12,0 @@ export type ComboBoxLitRenderer<TItem> = ( |
@@ -6,5 +6,5 @@ /** | ||
*/ | ||
import { Constructor } from '@open-wc/dedupe-mixin'; | ||
import type { Constructor } from '@open-wc/dedupe-mixin'; | ||
export type ComboBoxDataProviderCallback<TItem> = (items: TItem[], size: number) => void; | ||
export type ComboBoxDataProviderCallback<TItem> = (items: TItem[], size?: number) => void; | ||
@@ -24,3 +24,3 @@ export interface ComboBoxDataProviderParams { | ||
base: T, | ||
): T & Constructor<ComboBoxDataProviderMixinClass<TItem>>; | ||
): Constructor<ComboBoxDataProviderMixinClass<TItem>> & T; | ||
@@ -27,0 +27,0 @@ export declare class ComboBoxDataProviderMixinClass<TItem> { |
@@ -65,2 +65,7 @@ /** | ||
}, | ||
/** @private */ | ||
__previousDataProviderFilter: { | ||
type: String, | ||
}, | ||
}; | ||
@@ -71,4 +76,3 @@ } | ||
return [ | ||
'_dataProviderFilterChanged(filter, dataProvider)', | ||
'_dataProviderClearFilter(dataProvider, opened, value)', | ||
'_dataProviderFilterChanged(filter)', | ||
'_warnDataProviderValue(dataProvider, value)', | ||
@@ -82,3 +86,2 @@ '_ensureFirstPage(opened)', | ||
super.ready(); | ||
this.clearCache(); | ||
this._scroller.addEventListener('index-requested', (e) => { | ||
@@ -107,34 +110,21 @@ const index = e.detail.index; | ||
/** @private */ | ||
_dataProviderFilterChanged() { | ||
if (!this._shouldFetchData()) { | ||
_dataProviderFilterChanged(filter) { | ||
if (this.__previousDataProviderFilter === undefined && filter === '') { | ||
this.__previousDataProviderFilter = filter; | ||
return; | ||
} | ||
this._refreshData(); | ||
} | ||
if (this.__previousDataProviderFilter !== filter) { | ||
this.__previousDataProviderFilter = filter; | ||
/** @private */ | ||
_dataProviderClearFilter(dataProvider, opened, value) { | ||
// Can't depend on filter in this observer as we don't want | ||
// to clear the filter whenever it's set | ||
if (dataProvider && !this.loading && this.filter && !(opened && this.autoOpenDisabled && value === this.filter)) { | ||
this._refreshData(true); | ||
} | ||
} | ||
this._pendingRequests = {}; | ||
// Immediately mark as loading if this refresh leads to re-fetching pages | ||
// This prevents some issues with the properties below triggering | ||
// observers that also rely on the loading state | ||
this.loading = this._shouldFetchData(); | ||
// Reset size and internal loading state | ||
this.size = undefined; | ||
/** @private */ | ||
_refreshData(clearFilter) { | ||
// Immediately mark as loading if this refresh leads to re-fetching pages | ||
// This prevents some issues with the properties below triggering | ||
// observers that also rely on the loading state | ||
this.loading = this._shouldFetchData(); | ||
// Reset size and internal loading state | ||
this.size = undefined; | ||
this._pendingRequests = {}; | ||
// Clear filter if requested | ||
if (clearFilter) { | ||
this.filter = ''; | ||
this.clearCache(); | ||
} | ||
// Clear cached pages, and reload current page if we need the data | ||
this.clearCache(); | ||
} | ||
@@ -203,7 +193,10 @@ | ||
if (!this.opened && !this.hasAttribute('focused')) { | ||
if (!this.opened && !this._isInputFocused()) { | ||
this._commitValue(); | ||
} | ||
this.size = size; | ||
if (size !== undefined) { | ||
this.size = size; | ||
} | ||
delete this._pendingRequests[page]; | ||
@@ -237,2 +230,3 @@ | ||
} | ||
this._pendingRequests = {}; | ||
@@ -244,3 +238,5 @@ const filteredItems = []; | ||
this.filteredItems = filteredItems; | ||
if (this._shouldFetchData()) { | ||
this._forceNextRequest = false; | ||
this._loadPage(0); | ||
@@ -279,2 +275,4 @@ } else { | ||
}); | ||
this.clearCache(); | ||
} | ||
@@ -281,0 +279,0 @@ |
@@ -6,9 +6,10 @@ /** | ||
*/ | ||
import { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js'; | ||
import { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js'; | ||
import { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js'; | ||
import { ComboBoxMixinClass } from './vaadin-combo-box-mixin.js'; | ||
import { ComboBoxDefaultItem } from './vaadin-combo-box-mixin.js'; | ||
import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js'; | ||
import type { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js'; | ||
import type { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js'; | ||
import type { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js'; | ||
import type { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js'; | ||
import type { ComboBoxDefaultItem, ComboBoxMixinClass } from './vaadin-combo-box-mixin.js'; | ||
export { | ||
@@ -58,2 +59,7 @@ ComboBoxDataProvider, | ||
/** | ||
* Fired whenever the field is validated. | ||
*/ | ||
export type ComboBoxLightValidatedEvent = CustomEvent<{ valid: boolean }>; | ||
export interface ComboBoxLightEventMap<TItem> extends HTMLElementEventMap { | ||
@@ -73,2 +79,4 @@ change: ComboBoxLightChangeEvent<TItem>; | ||
'selected-item-changed': ComboBoxLightSelectedItemChangedEvent<TItem>; | ||
validated: ComboBoxLightValidatedEvent; | ||
} | ||
@@ -120,2 +128,3 @@ | ||
* @fires {CustomEvent} value-changed - Fired when the `value` property changes. | ||
* @fires {CustomEvent} validated - Fired whenever the field is validated. | ||
*/ | ||
@@ -133,3 +142,3 @@ declare class ComboBoxLight<TItem = ComboBoxDefaultItem> extends HTMLElement { | ||
listener: (this: ComboBoxLight<TItem>, ev: ComboBoxLightEventMap<TItem>[K]) => void, | ||
options?: boolean | AddEventListenerOptions, | ||
options?: AddEventListenerOptions | boolean, | ||
): void; | ||
@@ -140,3 +149,3 @@ | ||
listener: (this: ComboBoxLight<TItem>, ev: ComboBoxLightEventMap<TItem>[K]) => void, | ||
options?: boolean | EventListenerOptions, | ||
options?: EventListenerOptions | boolean, | ||
): void; | ||
@@ -151,3 +160,5 @@ } | ||
DisabledMixinClass, | ||
ThemableMixinClass {} | ||
ThemableMixinClass, | ||
ThemePropertyMixinClass, | ||
ValidateMixinClass {} | ||
@@ -154,0 +165,0 @@ declare global { |
@@ -10,3 +10,5 @@ /** | ||
import { dashToCamelCase } from '@polymer/polymer/lib/utils/case-map.js'; | ||
import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js'; | ||
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
import { ValidateMixin } from '@vaadin/field-base/src/validate-mixin.js'; | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
@@ -60,2 +62,3 @@ import { ComboBoxDataProviderMixin } from './vaadin-combo-box-data-provider-mixin.js'; | ||
* @fires {CustomEvent} value-changed - Fired when the `value` property changes. | ||
* @fires {CustomEvent} validated - Fired whenever the field is validated. | ||
* | ||
@@ -66,4 +69,5 @@ * @extends HTMLElement | ||
* @mixes ThemableMixin | ||
* @mixes ValidateMixin | ||
*/ | ||
class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ThemableMixin(PolymerElement))) { | ||
class ComboBoxLight extends ComboBoxDataProviderMixin(ComboBoxMixin(ValidateMixin(ThemableMixin(PolymerElement)))) { | ||
static get is() { | ||
@@ -85,3 +89,2 @@ return 'vaadin-combo-box-light'; | ||
id="overlay" | ||
hidden$="[[_isOverlayHidden(filteredItems, loading)]]" | ||
opened="[[_overlayOpened]]" | ||
@@ -124,10 +127,10 @@ loading$="[[loading]]" | ||
super.ready(); | ||
this._toggleElement = this.querySelector('.toggle-button'); | ||
} | ||
/** @protected */ | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
this._setInputElement(this.querySelector('vaadin-text-field,.input')); | ||
this._revertInputValue(); | ||
// Wait until the slotted input DOM is ready | ||
afterNextRender(this, () => { | ||
this._setInputElement(this.querySelector('vaadin-text-field,.input')); | ||
this._revertInputValue(); | ||
}); | ||
} | ||
@@ -154,2 +157,34 @@ | ||
/** | ||
* @protected | ||
* @override | ||
* @return {HTMLInputElement | undefined} | ||
*/ | ||
get _nativeInput() { | ||
const input = this.inputElement; | ||
if (input) { | ||
// Support `<input class="input">` | ||
if (input instanceof HTMLInputElement) { | ||
return input; | ||
} | ||
// Support `<input>` in light DOM (e.g. `vaadin-text-field`) | ||
const slottedInput = input.querySelector('input'); | ||
if (slottedInput) { | ||
return slottedInput; | ||
} | ||
if (input.shadowRoot) { | ||
// Support `<input>` in Shadow DOM (e.g. `mwc-textfield`) | ||
const shadowInput = input.shadowRoot.querySelector('input'); | ||
if (shadowInput) { | ||
return shadowInput; | ||
} | ||
} | ||
} | ||
return undefined; | ||
} | ||
/** @protected */ | ||
@@ -156,0 +191,0 @@ _isClearButton(event) { |
@@ -6,7 +6,7 @@ /** | ||
*/ | ||
import { Constructor } from '@open-wc/dedupe-mixin'; | ||
import { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js'; | ||
import { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js'; | ||
import { ComboBox } from './vaadin-combo-box.js'; | ||
import type { Constructor } from '@open-wc/dedupe-mixin'; | ||
import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js'; | ||
import type { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js'; | ||
import type { ComboBox } from './vaadin-combo-box.js'; | ||
@@ -28,7 +28,7 @@ export type ComboBoxDefaultItem = any; | ||
base: T, | ||
): T & | ||
Constructor<ComboBoxMixinClass<TItem>> & | ||
): Constructor<ComboBoxMixinClass<TItem>> & | ||
Constructor<DisabledMixinClass> & | ||
Constructor<InputMixinClass> & | ||
Constructor<KeyboardMixinClass>; | ||
Constructor<KeyboardMixinClass> & | ||
T; | ||
@@ -143,7 +143,2 @@ export declare class ComboBoxMixinClass<TItem> { | ||
/** | ||
* Set to true if the value is invalid. | ||
*/ | ||
invalid: boolean; | ||
protected readonly _propertyForValue: string; | ||
@@ -176,14 +171,3 @@ | ||
/** | ||
* Returns true if `value` is valid, and sets the `invalid` flag appropriately. | ||
*/ | ||
validate(): boolean; | ||
/** | ||
* Returns true if the current input value satisfies all constraints (if any). | ||
* You can override this method for custom validations. | ||
*/ | ||
checkValidity(): boolean; | ||
protected _revertInputValue(): void; | ||
} |
@@ -9,2 +9,3 @@ /** | ||
import { DisabledMixin } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import { isElementFocused } from '@vaadin/component-base/src/focus-utils.js'; | ||
import { KeyboardMixin } from '@vaadin/component-base/src/keyboard-mixin.js'; | ||
@@ -240,3 +241,2 @@ import { processTemplates } from '@vaadin/component-base/src/templates.js'; | ||
return [ | ||
'_filterChanged(filter, itemValuePath, itemLabelPath)', | ||
'_selectedItemChanged(selectedItem, itemValuePath, itemLabelPath)', | ||
@@ -286,2 +286,12 @@ '_openedOrItemsChanged(opened, filteredItems, loading)', | ||
/** | ||
* Get a reference to the native `<input>` element. | ||
* Override to provide a custom input. | ||
* @protected | ||
* @return {HTMLInputElement | undefined} | ||
*/ | ||
get _nativeInput() { | ||
return this.inputElement; | ||
} | ||
/** | ||
* Override method inherited from `InputMixin` | ||
@@ -292,5 +302,7 @@ * to customize the input element. | ||
*/ | ||
_inputElementChanged(input) { | ||
super._inputElementChanged(input); | ||
_inputElementChanged(inputElement) { | ||
super._inputElementChanged(inputElement); | ||
const input = this._nativeInput; | ||
if (input) { | ||
@@ -389,2 +401,21 @@ input.autocomplete = 'off'; | ||
/** | ||
* Override Polymer lifecycle callback to handle `filter` property change after | ||
* the observer for `opened` property is triggered. This is needed when opening | ||
* combo-box on user input to ensure the focused index is set correctly. | ||
* | ||
* @param {!Object} currentProps Current accessor values | ||
* @param {?Object} changedProps Properties changed since the last call | ||
* @param {?Object} oldProps Previous values for each changed property | ||
* @protected | ||
* @override | ||
*/ | ||
_propertiesChanged(currentProps, changedProps, oldProps) { | ||
super._propertiesChanged(currentProps, changedProps, oldProps); | ||
if (changedProps.filter !== undefined) { | ||
this._filterChanged(changedProps.filter); | ||
} | ||
} | ||
/** @private */ | ||
@@ -403,7 +434,2 @@ _initOverlay() { | ||
// 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 | ||
@@ -433,5 +459,3 @@ overlay.addEventListener('opened-changed', (e) => { | ||
// Ensure the scroller is rendered | ||
if (!this.opened) { | ||
overlay.requestContentUpdate(); | ||
} | ||
overlay.requestContentUpdate(); | ||
@@ -470,7 +494,2 @@ const scroller = overlay.querySelector(scrollerTag); | ||
/** @protected */ | ||
_isOverlayHidden(items, loading) { | ||
return !loading && !(items && items.length); | ||
} | ||
/** @private */ | ||
@@ -504,5 +523,10 @@ _openedOrItemsChanged(opened, items, loading) { | ||
/** @protected */ | ||
_isInputFocused() { | ||
return this.inputElement && isElementFocused(this.inputElement); | ||
} | ||
/** @private */ | ||
_updateActiveDescendant(index) { | ||
const input = this.inputElement; | ||
const input = this._nativeInput; | ||
if (!input) { | ||
@@ -531,3 +555,3 @@ return; | ||
// unless input element is explicitly focused by the user. | ||
if (!this.hasAttribute('focused') && !isTouch) { | ||
if (!this._isInputFocused() && !isTouch) { | ||
this.focus(); | ||
@@ -539,3 +563,3 @@ } | ||
this._onClosed(); | ||
if (this._openedWithFocusRing && this.hasAttribute('focused')) { | ||
if (this._openedWithFocusRing && this._isInputFocused()) { | ||
this.setAttribute('focus-ring', ''); | ||
@@ -545,3 +569,3 @@ } | ||
const input = this.inputElement; | ||
const input = this._nativeInput; | ||
if (input) { | ||
@@ -615,4 +639,2 @@ input.setAttribute('aria-expanded', !!opened); | ||
_onClick(e) { | ||
this._closeOnBlurIsPrevented = true; | ||
const path = e.composedPath(); | ||
@@ -627,4 +649,2 @@ | ||
} | ||
this._closeOnBlurIsPrevented = false; | ||
} | ||
@@ -645,5 +665,3 @@ | ||
} else if (e.key === 'ArrowDown') { | ||
this._closeOnBlurIsPrevented = true; | ||
this._onArrowDown(); | ||
this._closeOnBlurIsPrevented = false; | ||
@@ -653,5 +671,3 @@ // Prevent caret from moving | ||
} else if (e.key === 'ArrowUp') { | ||
this._closeOnBlurIsPrevented = true; | ||
this._onArrowUp(); | ||
this._closeOnBlurIsPrevented = false; | ||
@@ -727,4 +743,3 @@ // Prevent caret from moving | ||
// the next focusable element instead of the combo-box itself. | ||
// Checking the focused property here is enough instead of checking the activeElement. | ||
if (this.hasAttribute('focused')) { | ||
if (this._isInputFocused() && this.inputElement.setSelectionRange) { | ||
this.inputElement.setSelectionRange(start, end); | ||
@@ -839,3 +854,3 @@ } | ||
toggleElement.addEventListener('click', () => { | ||
if (isTouch && !this.hasAttribute('focused')) { | ||
if (isTouch && !this._isInputFocused()) { | ||
document.activeElement.blur(); | ||
@@ -947,5 +962,3 @@ } | ||
if (!this.dataProvider) { | ||
this.filter = ''; | ||
} | ||
this.filter = ''; | ||
} | ||
@@ -968,15 +981,23 @@ | ||
_onInput(event) { | ||
if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) { | ||
this.open(); | ||
} | ||
const filter = this._inputElementValue; | ||
const value = this._inputElementValue; | ||
if (this.filter === value) { | ||
// When opening dropdown on user input, both `opened` and `filter` properties are set. | ||
// Perform a batched property update instead of relying on sync property observers. | ||
// This is necessary to avoid an extra data-provider request for loading first page. | ||
const props = {}; | ||
if (this.filter === filter) { | ||
// Filter and input value might get out of sync, while keyboard navigating for example. | ||
// Afterwards, input value might be changed to the same value as used in filtering. | ||
// In situation like these, we need to make sure all the filter changes handlers are run. | ||
this._filterChanged(this.filter, this.itemValuePath, this.itemLabelPath); | ||
this._filterChanged(this.filter); | ||
} else { | ||
this.filter = value; | ||
props.filter = filter; | ||
} | ||
if (!this.opened && !this._isClearButton(event) && !this.autoOpenDisabled) { | ||
props.opened = true; | ||
} | ||
this.setProperties(props); | ||
} | ||
@@ -1004,7 +1025,3 @@ | ||
/** @private */ | ||
_filterChanged(filter, _itemValuePath, _itemLabelPath) { | ||
if (filter === undefined) { | ||
return; | ||
} | ||
_filterChanged(filter) { | ||
// Scroll to the top of the list whenever the filter changes. | ||
@@ -1052,3 +1069,3 @@ this._scrollIntoView(0); | ||
this._toggleHasValue(this.value !== ''); | ||
this._toggleHasValue(this._hasValue); | ||
this._inputElementValue = this.value; | ||
@@ -1089,17 +1106,17 @@ } | ||
if (isValidValue(value)) { | ||
let item; | ||
if (this._getItemValue(this.selectedItem) !== value) { | ||
this._selectItemForValue(value); | ||
} else { | ||
item = this.selectedItem; | ||
} | ||
if (!item && this.allowCustomValue) { | ||
if (!this.selectedItem && this.allowCustomValue) { | ||
this._inputElementValue = value; | ||
} | ||
this._toggleHasValue(this.value !== ''); | ||
this._toggleHasValue(this._hasValue); | ||
} else { | ||
this.selectedItem = null; | ||
} | ||
this.filter = ''; | ||
// In the next _detectAndDispatchChange() call, the change detection should pass | ||
@@ -1262,5 +1279,2 @@ this._lastCommittedValue = undefined; | ||
this.close(); | ||
} else if (this.selectedItem !== e.detail.item) { | ||
this.selectedItem = e.detail.item; | ||
this._detectAndDispatchChange(); | ||
} | ||
@@ -1277,2 +1291,8 @@ } | ||
_onFocusout(event) { | ||
// VoiceOver on iOS fires `focusout` event when moving focus to the item in the dropdown. | ||
// Do not focus the input in this case, because it would break announcement for the item. | ||
if (event.relatedTarget && event.relatedTarget.localName === `${this._tagNamePrefix}-item`) { | ||
return; | ||
} | ||
// Fixes the problem with `focusout` happening when clicking on the scroll bar on Edge | ||
@@ -1306,25 +1326,2 @@ if (event.relatedTarget === this.$.overlay) { | ||
/** | ||
* Returns true if `value` is valid, and sets the `invalid` flag appropriately. | ||
* | ||
* @return {boolean} True if the value is valid and sets the `invalid` flag appropriately | ||
*/ | ||
validate() { | ||
return !(this.invalid = !this.checkValidity()); | ||
} | ||
/** | ||
* Returns true if the current input value satisfies all constraints (if any). | ||
* You can override this method for custom validations. | ||
* | ||
* @return {boolean} | ||
*/ | ||
checkValidity() { | ||
if (super.checkValidity) { | ||
return super.checkValidity(); | ||
} | ||
return !this.required || !!this.value; | ||
} | ||
/** | ||
* Fired when the value changes. | ||
@@ -1334,3 +1331,3 @@ * | ||
* @param {Object} detail | ||
* @param {String} detail.value the combobox value | ||
* @param {String} detail.value the combobox value | ||
*/ | ||
@@ -1343,3 +1340,3 @@ | ||
* @param {Object} detail | ||
* @param {Object|String} detail.value the selected item. Type is the same as the type of `items`. | ||
* @param {Object|String} detail.value the selected item. Type is the same as the type of `items`. | ||
*/ | ||
@@ -1346,0 +1343,0 @@ |
@@ -6,20 +6,21 @@ /** | ||
*/ | ||
import { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js'; | ||
import { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js'; | ||
import { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js'; | ||
import { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js'; | ||
import { DelegateFocusMixinClass } from '@vaadin/field-base/src/delegate-focus-mixin.js'; | ||
import { DelegateStateMixinClass } from '@vaadin/field-base/src/delegate-state-mixin.js'; | ||
import { FieldMixinClass } from '@vaadin/field-base/src/field-mixin.js'; | ||
import { InputConstraintsMixinClass } from '@vaadin/field-base/src/input-constraints-mixin.js'; | ||
import { InputControlMixinClass } from '@vaadin/field-base/src/input-control-mixin.js'; | ||
import { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js'; | ||
import { LabelMixinClass } from '@vaadin/field-base/src/label-mixin.js'; | ||
import { PatternMixinClass } from '@vaadin/field-base/src/pattern-mixin.js'; | ||
import { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js'; | ||
import { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js'; | ||
import { ComboBoxMixinClass } from './vaadin-combo-box-mixin.js'; | ||
import { ComboBoxDefaultItem } from './vaadin-combo-box-mixin.js'; | ||
import type { ControllerMixinClass } from '@vaadin/component-base/src/controller-mixin.js'; | ||
import type { DisabledMixinClass } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import type { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js'; | ||
import type { FocusMixinClass } from '@vaadin/component-base/src/focus-mixin.js'; | ||
import type { KeyboardMixinClass } from '@vaadin/component-base/src/keyboard-mixin.js'; | ||
import type { DelegateFocusMixinClass } from '@vaadin/field-base/src/delegate-focus-mixin.js'; | ||
import type { DelegateStateMixinClass } from '@vaadin/field-base/src/delegate-state-mixin.js'; | ||
import type { FieldMixinClass } from '@vaadin/field-base/src/field-mixin.js'; | ||
import type { InputConstraintsMixinClass } from '@vaadin/field-base/src/input-constraints-mixin.js'; | ||
import type { InputControlMixinClass } from '@vaadin/field-base/src/input-control-mixin.js'; | ||
import type { InputMixinClass } from '@vaadin/field-base/src/input-mixin.js'; | ||
import type { LabelMixinClass } from '@vaadin/field-base/src/label-mixin.js'; | ||
import type { PatternMixinClass } from '@vaadin/field-base/src/pattern-mixin.js'; | ||
import type { ValidateMixinClass } from '@vaadin/field-base/src/validate-mixin.js'; | ||
import type { ThemableMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js'; | ||
import type { ComboBoxDataProviderMixinClass } from './vaadin-combo-box-data-provider-mixin.js'; | ||
import type { ComboBoxMixinClass } from './vaadin-combo-box-mixin.js'; | ||
import type { ComboBoxDefaultItem } from './vaadin-combo-box-mixin.js'; | ||
export { | ||
@@ -69,2 +70,7 @@ ComboBoxDataProvider, | ||
/** | ||
* Fired whenever the field is validated. | ||
*/ | ||
export type ComboBoxValidatedEvent = CustomEvent<{ valid: boolean }>; | ||
export interface ComboBoxEventMap<TItem> extends HTMLElementEventMap { | ||
@@ -84,2 +90,4 @@ change: ComboBoxChangeEvent<TItem>; | ||
'selected-item-changed': ComboBoxSelectedItemChangedEvent<TItem>; | ||
validated: ComboBoxValidatedEvent; | ||
} | ||
@@ -171,2 +179,3 @@ | ||
* `--vaadin-field-default-width` | Default width of the field | `12em` | ||
* `--vaadin-combo-box-overlay-width` | Width of the overlay | `auto` | ||
* `--vaadin-combo-box-overlay-max-height` | Max height of the overlay | `65vh` | ||
@@ -214,2 +223,3 @@ * | ||
* @fires {CustomEvent} value-changed - Fired when the `value` property changes. | ||
* @fires {CustomEvent} validated - Fired whenever the field is validated. | ||
*/ | ||
@@ -220,3 +230,3 @@ declare class ComboBox<TItem = ComboBoxDefaultItem> extends HTMLElement { | ||
listener: (this: ComboBox<TItem>, ev: ComboBoxEventMap<TItem>[K]) => void, | ||
options?: boolean | AddEventListenerOptions, | ||
options?: AddEventListenerOptions | boolean, | ||
): void; | ||
@@ -227,3 +237,3 @@ | ||
listener: (this: ComboBox<TItem>, ev: ComboBoxEventMap<TItem>[K]) => void, | ||
options?: boolean | EventListenerOptions, | ||
options?: EventListenerOptions | boolean, | ||
): void; | ||
@@ -248,2 +258,3 @@ } | ||
ThemableMixinClass, | ||
ThemePropertyMixinClass, | ||
ElementMixinClass, | ||
@@ -250,0 +261,0 @@ ControllerMixinClass {} |
@@ -107,2 +107,3 @@ /** | ||
* `--vaadin-field-default-width` | Default width of the field | `12em` | ||
* `--vaadin-combo-box-overlay-width` | Width of the overlay | `auto` | ||
* `--vaadin-combo-box-overlay-max-height` | Max height of the overlay | `65vh` | ||
@@ -150,2 +151,3 @@ * | ||
* @fires {CustomEvent} value-changed - Fired when the `value` property changes. | ||
* @fires {CustomEvent} validated - Fired whenever the field is validated. | ||
* | ||
@@ -205,3 +207,2 @@ * @extends HTMLElement | ||
id="overlay" | ||
hidden$="[[_isOverlayHidden(filteredItems, loading)]]" | ||
opened="[[_overlayOpened]]" | ||
@@ -208,0 +209,0 @@ loading$="[[loading]]" |
@@ -5,2 +5,3 @@ import '@vaadin/vaadin-lumo-styles/color.js'; | ||
import '@vaadin/vaadin-overlay/theme/lumo/vaadin-overlay.js'; | ||
import { loader } from '@vaadin/vaadin-lumo-styles/mixins/loader.js'; | ||
import { menuOverlayCore } from '@vaadin/vaadin-lumo-styles/mixins/menu-overlay.js'; | ||
@@ -40,6 +41,3 @@ import { overlay } from '@vaadin/vaadin-lumo-styles/mixins/overlay.js'; | ||
:host([loading]) [part~='loader'] { | ||
box-sizing: border-box; | ||
width: var(--lumo-icon-size-s); | ||
height: var(--lumo-icon-size-s); | ||
[part~='loader'] { | ||
position: absolute; | ||
@@ -53,34 +51,7 @@ z-index: 1; | ||
margin-inline-end: 0; | ||
border: 2px solid transparent; | ||
border-color: var(--lumo-primary-color-50pct) var(--lumo-primary-color-50pct) var(--lumo-primary-color) | ||
var(--lumo-primary-color); | ||
border-radius: calc(0.5 * var(--lumo-icon-size-s)); | ||
opacity: 0; | ||
animation: 1s linear infinite lumo-combo-box-loader-rotate, 0.3s 0.1s lumo-combo-box-loader-fade-in both; | ||
pointer-events: none; | ||
} | ||
@keyframes lumo-combo-box-loader-fade-in { | ||
0% { | ||
opacity: 0; | ||
} | ||
100% { | ||
opacity: 1; | ||
} | ||
} | ||
@keyframes lumo-combo-box-loader-rotate { | ||
0% { | ||
transform: rotate(0deg); | ||
} | ||
100% { | ||
transform: rotate(360deg); | ||
} | ||
} | ||
/* RTL specific styles */ | ||
:host([loading][dir='rtl']) [part~='loader'] { | ||
:host([dir='rtl']) [part~='loader'] { | ||
left: auto; | ||
@@ -94,4 +65,4 @@ margin-left: 0; | ||
registerStyles('vaadin-combo-box-overlay', [overlay, menuOverlayCore, comboBoxOverlay], { | ||
registerStyles('vaadin-combo-box-overlay', [overlay, menuOverlayCore, comboBoxOverlay, loader], { | ||
moduleId: 'lumo-combo-box-overlay', | ||
}); |
import '@vaadin/vaadin-material-styles/color.js'; | ||
import '@vaadin/vaadin-overlay/theme/material/vaadin-overlay.js'; | ||
import { loader } from '@vaadin/vaadin-material-styles/mixins/loader.js'; | ||
import { menuOverlay } from '@vaadin/vaadin-material-styles/mixins/menu-overlay.js'; | ||
@@ -24,4 +25,3 @@ import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
:host([loading]) [part='loader'] { | ||
height: 2px; | ||
[part~='loader'] { | ||
position: absolute; | ||
@@ -32,93 +32,7 @@ z-index: 1; | ||
right: 0; | ||
background: var(--material-background-color) | ||
linear-gradient( | ||
90deg, | ||
transparent 0%, | ||
transparent 20%, | ||
var(--material-primary-color) 20%, | ||
var(--material-primary-color) 40%, | ||
transparent 40%, | ||
transparent 60%, | ||
var(--material-primary-color) 60%, | ||
var(--material-primary-color) 80%, | ||
transparent 80%, | ||
transparent 100% | ||
) | ||
0 0 / 400% 100% repeat-x; | ||
opacity: 0; | ||
animation: 3s linear infinite material-combo-box-loader-progress, 0.3s 0.1s both material-combo-box-loader-fade-in; | ||
} | ||
[part='loader']::before { | ||
content: ''; | ||
display: block; | ||
height: 100%; | ||
opacity: 0.16; | ||
background: var(--material-primary-color); | ||
} | ||
@keyframes material-combo-box-loader-fade-in { | ||
0% { | ||
opacity: 0; | ||
} | ||
100% { | ||
opacity: 1; | ||
} | ||
} | ||
@keyframes material-combo-box-loader-progress { | ||
0% { | ||
background-position: 0 0; | ||
background-size: 300% 100%; | ||
} | ||
33% { | ||
background-position: -100% 0; | ||
background-size: 400% 100%; | ||
} | ||
67% { | ||
background-position: -200% 0; | ||
background-size: 250% 100%; | ||
} | ||
100% { | ||
background-position: -300% 0; | ||
background-size: 300% 100%; | ||
} | ||
} | ||
/* RTL specific styles */ | ||
@keyframes material-combo-box-loader-progress-rtl { | ||
0% { | ||
background-position: 100% 0; | ||
background-size: 300% 100%; | ||
} | ||
33% { | ||
background-position: 200% 0; | ||
background-size: 400% 100%; | ||
} | ||
67% { | ||
background-position: 300% 0; | ||
background-size: 250% 100%; | ||
} | ||
100% { | ||
background-position: 400% 0; | ||
background-size: 300% 100%; | ||
} | ||
} | ||
:host([loading][dir='rtl']) [part='loader'] { | ||
animation: 3s linear infinite material-combo-box-loader-progress-rtl, | ||
0.3s 0.1s both material-combo-box-loader-fade-in; | ||
} | ||
`; | ||
registerStyles('vaadin-combo-box-overlay', [menuOverlay, comboBoxOverlay], { | ||
registerStyles('vaadin-combo-box-overlay', [menuOverlay, comboBoxOverlay, loader], { | ||
moduleId: 'material-combo-box-overlay', | ||
}); |
207127
35
4959
+ Added@vaadin/component-base@23.2.0-rc1(transitive)
+ Added@vaadin/field-base@23.2.0-rc1(transitive)
+ Added@vaadin/icon@23.2.0-rc1(transitive)
+ Added@vaadin/input-container@23.2.0-rc1(transitive)
+ Added@vaadin/item@23.2.0-rc1(transitive)
+ Added@vaadin/lit-renderer@23.2.0-rc1(transitive)
+ Added@vaadin/vaadin-lumo-styles@23.2.0-rc1(transitive)
+ Added@vaadin/vaadin-material-styles@23.2.0-rc1(transitive)
+ Added@vaadin/vaadin-overlay@23.2.0-rc1(transitive)
+ Added@vaadin/vaadin-themable-mixin@23.2.0-rc1(transitive)
- Removed@vaadin/component-base@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/field-base@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/icon@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/input-container@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/item@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/lit-renderer@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/vaadin-lumo-styles@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/vaadin-material-styles@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/vaadin-overlay@23.2.0-dev.8a7678b70(transitive)
- Removed@vaadin/vaadin-themable-mixin@23.2.0-dev.8a7678b70(transitive)
Updated@vaadin/item@23.2.0-rc1