@vaadin/vaadin-combo-box
Advanced tools
Comparing version 5.4.7 to 6.0.0-alpha1
{ | ||
"name": "@vaadin/vaadin-combo-box", | ||
"version": "6.0.0-alpha1", | ||
"description": "Web Component for displaying a list of items with filtering", | ||
"main": "vaadin-combo-box.js", | ||
"repository": "vaadin/vaadin-combo-box", | ||
"keywords": [ | ||
@@ -10,7 +14,2 @@ "Vaadin", | ||
], | ||
"repository": "vaadin/vaadin-combo-box", | ||
"homepage": "https://vaadin.com/components", | ||
"name": "@vaadin/vaadin-combo-box", | ||
"version": "5.4.7", | ||
"main": "vaadin-combo-box.js", | ||
"author": "Vaadin Ltd", | ||
@@ -21,19 +20,39 @@ "license": "Apache-2.0", | ||
}, | ||
"homepage": "https://vaadin.com/components", | ||
"files": [ | ||
"vaadin-*.d.ts", | ||
"vaadin-*.js", | ||
"@types", | ||
"src", | ||
"theme" | ||
], | ||
"resolutions": { | ||
"es-abstract": "1.17.6", | ||
"@types/doctrine": "0.0.3", | ||
"inherits": "2.0.3", | ||
"samsam": "1.1.3", | ||
"supports-color": "3.1.2", | ||
"type-detect": "1.0.0" | ||
"scripts": { | ||
"analyze": "polymer analyze vaadin-* > analysis.json", | ||
"check-version": "magi check-version", | ||
"debug": "web-test-runner test/*.test.js --watch", | ||
"dist": "rimraf dist && npm run analyze && rollup -c rollup.config.js && cp analysis.json dist", | ||
"lint": "npm run lint:js && npm run lint:css && npm run lint:types", | ||
"lint:js": "eslint src theme test", | ||
"lint:css": "stylelint src/*.js theme/**/*-styles.js", | ||
"lint:types": "tsc", | ||
"prestart": "npm run analyze", | ||
"preversion": "magi update-version", | ||
"screenshots": "hermione test/visual/test.js --update-refs", | ||
"serve:dist": "web-dev-server --app-index dist/index.html --open", | ||
"start": "web-dev-server --node-resolve --open", | ||
"test": "web-test-runner test/*.test.js --coverage", | ||
"test:sauce": "TEST_ENV=sauce npm test", | ||
"test:visual": "hermione test/visual/test.js" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
}, | ||
"lint-staged": { | ||
"*.js": [ | ||
"eslint --fix", | ||
"prettier --write" | ||
] | ||
}, | ||
"dependencies": { | ||
"@polymer/iron-a11y-keys-behavior": "^3.0.0", | ||
"@polymer/iron-a11y-announcer": "^3.0.0", | ||
@@ -45,22 +64,41 @@ "@polymer/iron-list": "^3.0.0", | ||
"@vaadin/vaadin-overlay": "^3.5.0", | ||
"@vaadin/vaadin-text-field": "^2.8.0", | ||
"@vaadin/vaadin-themable-mixin": "^1.6.1", | ||
"@vaadin/vaadin-lumo-styles": "^1.1.1", | ||
"@vaadin/vaadin-material-styles": "^1.1.2", | ||
"@vaadin/vaadin-item": "^2.3.0", | ||
"@vaadin/vaadin-text-field": "^3.0.0-alpha1", | ||
"@vaadin/vaadin-themable-mixin": "^1.6.2", | ||
"@vaadin/vaadin-lumo-styles": "^1.6.1", | ||
"@vaadin/vaadin-material-styles": "^1.3.2", | ||
"@vaadin/vaadin-item": "^3.0.0-alpha1", | ||
"@vaadin/vaadin-element-mixin": "^2.4.1" | ||
}, | ||
"scripts": { | ||
"generate-typings": "gen-typescript-declarations --outDir . --verify" | ||
}, | ||
"devDependencies": { | ||
"@polymer/iron-form": "^3.0.0", | ||
"@esm-bundle/chai": "^4.1.5", | ||
"@open-wc/rollup-plugin-html": "^1.2.5", | ||
"@open-wc/testing-helpers": "^1.8.0", | ||
"@polymer/iron-component-page": "^4.0.0", | ||
"@polymer/iron-input": "^3.0.1", | ||
"@polymer/iron-test-helpers": "^3.0.0", | ||
"@polymer/paper-input": "^3.0.0", | ||
"@vaadin/vaadin-button": "^2.4.0", | ||
"wct-browser-legacy": "^1.0.1", | ||
"@vaadin/vaadin-demo-helpers": "^3.1.0", | ||
"@vaadin/vaadin-dialog": "^2.5.0" | ||
"@rollup/plugin-node-resolve": "^11.0.0", | ||
"@vaadin/vaadin-dialog": "^2.5.0", | ||
"@web/dev-server": "~0.0.25", | ||
"@web/test-runner": "^0.9.13", | ||
"@web/test-runner-saucelabs": "0.1.1", | ||
"eslint": "^7.13.0", | ||
"eslint-config-prettier": "^6.15.0", | ||
"eslint-plugin-prettier": "^3.1.4", | ||
"hermione": "^3.9.0", | ||
"hermione-esm": "^0.4.0", | ||
"hermione-sauce": "^0.1.0", | ||
"husky": "^4.3.0", | ||
"lint-staged": "^10.5.1", | ||
"magi-cli": "^0.28.0", | ||
"prettier": "^2.1.2", | ||
"rimraf": "^3.0.2", | ||
"rollup": "^2.34.1", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"sinon": "^9.2.0", | ||
"stylelint": "^13.7.2", | ||
"stylelint-config-prettier": "^8.0.2", | ||
"stylelint-config-vaadin": "^0.2.6", | ||
"typescript": "^4.1.2" | ||
} | ||
} |
[![npm latest version](https://badgen.net/npm/v/@vaadin/vaadin-combo-box/latest)](https://www.npmjs.com/package/@vaadin/vaadin-combo-box) | ||
[![npm next version](https://badgen.net/npm/v/@vaadin/vaadin-combo-box/next)](https://www.npmjs.com/package/@vaadin/vaadin-combo-box) | ||
[![Bower version](https://badgen.net/github/release/vaadin/vaadin-combo-box)](https://github.com/vaadin/vaadin-combo-box/releases) | ||
[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/vaadin/vaadin-combo-box) | ||
@@ -20,20 +18,2 @@ [![Build Status](https://travis-ci.org/vaadin/vaadin-combo-box.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-combo-box) | ||
<!-- | ||
``` | ||
<custom-element-demo height="300"> | ||
<template> | ||
<script src="../webcomponentsjs/webcomponents-lite.js"></script> | ||
<link rel="import" href="vaadin-combo-box.html"> | ||
<custom-style> | ||
<style> | ||
vaadin-combo-box { | ||
width: 300px; | ||
} | ||
</style> | ||
</custom-style> | ||
<next-code-block></next-code-block> | ||
</template> | ||
</custom-element-demo> | ||
``` | ||
--> | ||
```html | ||
@@ -55,27 +35,5 @@ <vaadin-combo-box label="User" placeholder="Please select" item-value-path="email" item-label-path="email"></vaadin-combo-box> | ||
The Vaadin components are distributed as Bower and npm packages. | ||
Please note that the version range is the same, as the API has not changed. | ||
You should not mix Bower and npm versions in the same application, though. | ||
Unlike the official Polymer Elements, the converted Polymer 3 compatible Vaadin components | ||
are only published on npm, not pushed to GitHub repositories. | ||
### Polymer 2 and HTML Imports Compatible Version | ||
Install `vaadin-combo-box`: | ||
```sh | ||
bower i vaadin/vaadin-combo-box --save | ||
``` | ||
Once installed, import it in your application: | ||
```html | ||
<link rel="import" href="bower_components/vaadin-combo-box/vaadin-combo-box.html"> | ||
``` | ||
### Polymer 3 and ES Modules Compatible Version | ||
Install `vaadin-combo-box`: | ||
```sh | ||
npm i @vaadin/vaadin-combo-box --save | ||
@@ -100,31 +58,32 @@ ``` | ||
`theme/lumo/vaadin-combo-box.html` | ||
`theme/lumo/vaadin-combo-box-light.html` | ||
`theme/lumo/vaadin-combo-box.js` | ||
`theme/lumo/vaadin-combo-box-light.js` | ||
- The components with the Material theme: | ||
`theme/material/vaadin-combo-box.html` | ||
`theme/material/vaadin-combo-box-light.html` | ||
`theme/material/vaadin-combo-box.js` | ||
`theme/material/vaadin-combo-box-light.js` | ||
- Alias for `theme/lumo/vaadin-combo-box.html` | ||
`theme/lumo/vaadin-combo-box-light.html` | ||
- Alias for `theme/lumo/vaadin-combo-box.js` | ||
`theme/lumo/vaadin-combo-box-light.js` | ||
`vaadin-combo-box.html` | ||
`vaadin-combo-box-light.html` | ||
`vaadin-combo-box.js` | ||
`vaadin-combo-box-light.js` | ||
## Running demos and tests in a browser | ||
## Running API docs and tests in a browser | ||
1. Fork the `vaadin-combo-box` repository and clone it locally. | ||
1. Make sure you have [npm](https://www.npmjs.com/) and [Bower](https://bower.io) installed. | ||
1. Make sure you have [node.js](https://nodejs.org/) 12.x installed. | ||
1. When in the `vaadin-combo-box` directory, run `npm install` and then `bower install` to install dependencies. | ||
1. Make sure you have [npm](https://www.npmjs.com/) installed. | ||
1. When in the `vaadin-combo-box` directory, run `npm install` to install dependencies. | ||
1. Run `npm start`, browser will automatically open the component API documentation. | ||
1. You can also open demo or in-browser tests by adding **demo** or **test** to the URL, for example: | ||
1. You can also open visual tests, for example: | ||
- http://127.0.0.1:3000/components/vaadin-combo-box/demo | ||
- http://127.0.0.1:3000/components/vaadin-combo-box/test | ||
- http://127.0.0.1:3000/test/visual/default.html | ||
@@ -134,8 +93,11 @@ | ||
1. When in the `vaadin-combo-box` directory, run `polymer test` | ||
1. When in the `vaadin-combo-box` directory, run `npm test` | ||
## Debugging tests in the browser | ||
1. Run `npm run debug`, then choose manual mode (M) and open the link in browser. | ||
## Following the coding style | ||
We are using [ESLint](http://eslint.org/) for linting JavaScript code. You can check if your code is following our standards by running `npm run lint`, which will automatically lint all `.js` files as well as JavaScript snippets inside `.html` files. | ||
We are using [ESLint](http://eslint.org/) for linting JavaScript code. You can check if your code is following our standards by running `npm run lint`, which will automatically lint all `.js` files. | ||
@@ -142,0 +104,0 @@ |
@@ -1,27 +0,12 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file was automatically generated by | ||
* https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations | ||
* | ||
* To modify these typings, edit the source file(s): | ||
* src/vaadin-combo-box-data-provider-mixin.js | ||
*/ | ||
import { ComboBoxDataProvider } from './interfaces'; | ||
declare function ComboBoxDataProviderMixin<T extends new (...args: any[]) => {}>( | ||
base: T | ||
): T & ComboBoxDataProviderMixinConstructor; | ||
// tslint:disable:variable-name Describing an API that's defined elsewhere. | ||
// tslint:disable:no-any describes the API as best we are able today | ||
export {ComboBoxDataProviderMixin}; | ||
declare function ComboBoxDataProviderMixin<T extends new (...args: any[]) => {}>(base: T): T & ComboBoxDataProviderMixinConstructor; | ||
interface ComboBoxDataProviderMixinConstructor { | ||
new(...args: any[]): ComboBoxDataProviderMixin; | ||
new (...args: any[]): ComboBoxDataProviderMixin; | ||
} | ||
export {ComboBoxDataProviderMixinConstructor}; | ||
interface ComboBoxDataProviderMixin { | ||
/** | ||
@@ -36,3 +21,3 @@ * Number of items fetched at a time from the dataprovider. | ||
*/ | ||
size: number|undefined; | ||
size: number | undefined; | ||
@@ -52,4 +37,3 @@ /** | ||
*/ | ||
dataProvider: ComboBoxDataProvider|null|undefined; | ||
ready(): void; | ||
dataProvider: ComboBoxDataProvider | null | undefined; | ||
@@ -62,2 +46,2 @@ /** | ||
import {ComboBoxDataProvider} from '../@types/interfaces'; | ||
export { ComboBoxDataProviderMixin, ComboBoxDataProviderMixinConstructor }; |
/** | ||
@license | ||
Copyright (c) 2018 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
* @license | ||
* Copyright (c) 2020 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js'; | ||
@@ -11,318 +11,314 @@ | ||
*/ | ||
export const ComboBoxDataProviderMixin = superClass => class DataProviderMixin extends superClass { | ||
export const ComboBoxDataProviderMixin = (superClass) => | ||
class DataProviderMixin extends superClass { | ||
static get properties() { | ||
return { | ||
/** | ||
* Number of items fetched at a time from the dataprovider. | ||
* @attr {number} page-size | ||
* @type {number} | ||
*/ | ||
pageSize: { | ||
type: Number, | ||
value: 50, | ||
observer: '_pageSizeChanged' | ||
}, | ||
static get properties() { | ||
return { | ||
/** | ||
* Total number of items. | ||
* @type {number | undefined} | ||
*/ | ||
size: { | ||
type: Number, | ||
observer: '_sizeChanged' | ||
}, | ||
/** | ||
* Number of items fetched at a time from the dataprovider. | ||
* @attr {number} page-size | ||
* @type {number} | ||
*/ | ||
pageSize: { | ||
type: Number, | ||
value: 50, | ||
observer: '_pageSizeChanged' | ||
}, | ||
/** | ||
* Function that provides items lazily. Receives arguments `params`, `callback` | ||
* | ||
* `params.page` Requested page index | ||
* | ||
* `params.pageSize` Current page size | ||
* | ||
* `params.filter` Currently applied filter | ||
* | ||
* `callback(items, size)` Callback function with arguments: | ||
* - `items` Current page of items | ||
* - `size` Total number of items. | ||
* @type {ComboBoxDataProvider | undefined} | ||
*/ | ||
dataProvider: { | ||
type: Object, | ||
observer: '_dataProviderChanged' | ||
}, | ||
/** | ||
* Total number of items. | ||
* @type {number | undefined} | ||
*/ | ||
size: { | ||
type: Number, | ||
observer: '_sizeChanged' | ||
}, | ||
/** @private */ | ||
_pendingRequests: { | ||
value: () => { | ||
return {}; | ||
} | ||
}, | ||
/** | ||
* Function that provides items lazily. Receives arguments `params`, `callback` | ||
* | ||
* `params.page` Requested page index | ||
* | ||
* `params.pageSize` Current page size | ||
* | ||
* `params.filter` Currently applied filter | ||
* | ||
* `callback(items, size)` Callback function with arguments: | ||
* - `items` Current page of items | ||
* - `size` Total number of items. | ||
* @type {ComboBoxDataProvider | undefined} | ||
*/ | ||
dataProvider: { | ||
type: Object, | ||
observer: '_dataProviderChanged' | ||
}, | ||
/** @private */ | ||
_pendingRequests: { | ||
value: () => { | ||
return {}; | ||
/** @private */ | ||
__placeHolder: { | ||
value: new ComboBoxPlaceholder() | ||
} | ||
}, | ||
}; | ||
} | ||
/** @private */ | ||
__placeHolder: { | ||
value: new ComboBoxPlaceholder() | ||
static get observers() { | ||
return [ | ||
'_dataProviderFilterChanged(filter, dataProvider)', | ||
'_dataProviderClearFilter(dataProvider, opened, value)', | ||
'_warnDataProviderValue(dataProvider, value)', | ||
'_ensureFirstPage(opened)' | ||
]; | ||
} | ||
/** @private */ | ||
_dataProviderClearFilter(dataProvider) { | ||
// 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) { | ||
this.size = undefined; | ||
this._pendingRequests = {}; | ||
this.filter = ''; | ||
this.clearCache(); | ||
} | ||
} | ||
}; | ||
} | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
this.clearCache(); | ||
this.$.overlay.addEventListener('index-requested', (e) => { | ||
const index = e.detail.index; | ||
const currentScrollerPos = e.detail.currentScrollerPos; | ||
const allowedIndexRange = Math.floor(this.pageSize * 1.5); | ||
static get observers() { | ||
return [ | ||
'_dataProviderFilterChanged(filter, dataProvider)', | ||
'_dataProviderClearFilter(dataProvider, opened, value)', | ||
'_warnDataProviderValue(dataProvider, value)', | ||
'_ensureFirstPage(opened)', | ||
]; | ||
} | ||
// Ignores the indexes, which are being re-sent during scrolling reset, | ||
// if the corresponding page is around the current scroller position. | ||
// Otherwise, there might be a last pages duplicates, which cause the | ||
// loading indicator hanging and blank items | ||
if (this._shouldSkipIndex(index, allowedIndexRange, currentScrollerPos)) { | ||
return; | ||
} | ||
/** @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) { | ||
this.size = undefined; | ||
this._pendingRequests = {}; | ||
this.filter = ''; | ||
this.clearCache(); | ||
if (index !== undefined) { | ||
const page = this._getPageForIndex(index); | ||
if (this._shouldLoadPage(page)) { | ||
this._loadPage(page); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
this.clearCache(); | ||
this.$.overlay.addEventListener('index-requested', e => { | ||
const index = e.detail.index; | ||
const currentScrollerPos = e.detail.currentScrollerPos; | ||
const allowedIndexRange = Math.floor(this.pageSize * 1.5); | ||
// Ignores the indexes, which are being re-sent during scrolling reset, | ||
// if the corresponding page is around the current scroller position. | ||
// Otherwise, there might be a last pages duplicates, which cause the | ||
// loading indicator hanging and blank items | ||
if (this._shouldSkipIndex(index, allowedIndexRange, currentScrollerPos)) { | ||
/** @private */ | ||
_dataProviderFilterChanged() { | ||
if (!this._shouldFetchData()) { | ||
return; | ||
} | ||
if (index !== undefined) { | ||
const page = this._getPageForIndex(index); | ||
if (this._shouldLoadPage(page)) { | ||
this._loadPage(page); | ||
} | ||
} | ||
}); | ||
} | ||
/** @private */ | ||
_dataProviderFilterChanged() { | ||
if (!this._shouldFetchData()) { | ||
return; | ||
this.size = undefined; | ||
this._pendingRequests = {}; | ||
this.clearCache(); | ||
} | ||
this.size = undefined; | ||
this._pendingRequests = {}; | ||
this.clearCache(); | ||
} | ||
/** @private */ | ||
_shouldFetchData() { | ||
if (!this.dataProvider) { | ||
return false; | ||
} | ||
/** @private */ | ||
_shouldFetchData() { | ||
if (!this.dataProvider) { | ||
return false; | ||
return this.opened || (this.filter && this.filter.length); | ||
} | ||
return this.opened || | ||
(this.filter && this.filter.length); | ||
} | ||
/** @private */ | ||
_ensureFirstPage(opened) { | ||
if (opened && this._shouldLoadPage(0)) { | ||
this._loadPage(0); | ||
/** @private */ | ||
_ensureFirstPage(opened) { | ||
if (opened && this._shouldLoadPage(0)) { | ||
this._loadPage(0); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_shouldSkipIndex(index, allowedIndexRange, currentScrollerPos) { | ||
return currentScrollerPos !== 0 && | ||
/** @private */ | ||
_shouldSkipIndex(index, allowedIndexRange, currentScrollerPos) { | ||
return ( | ||
currentScrollerPos !== 0 && | ||
index >= currentScrollerPos - allowedIndexRange && | ||
index <= currentScrollerPos + allowedIndexRange; | ||
} | ||
/** @private */ | ||
_shouldLoadPage(page) { | ||
if (!this.filteredItems || this._forceNextRequest) { | ||
this._forceNextRequest = false; | ||
return true; | ||
index <= currentScrollerPos + allowedIndexRange | ||
); | ||
} | ||
const loadedItem = this.filteredItems[page * this.pageSize]; | ||
if (loadedItem !== undefined) { | ||
return loadedItem instanceof ComboBoxPlaceholder; | ||
} else { | ||
return this.size === undefined; | ||
/** @private */ | ||
_shouldLoadPage(page) { | ||
if (!this.filteredItems || this._forceNextRequest) { | ||
this._forceNextRequest = false; | ||
return true; | ||
} | ||
const loadedItem = this.filteredItems[page * this.pageSize]; | ||
if (loadedItem !== undefined) { | ||
return loadedItem instanceof ComboBoxPlaceholder; | ||
} else { | ||
return this.size === undefined; | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_loadPage(page) { | ||
// make sure same page isn't requested multiple times. | ||
if (!this._pendingRequests[page] && this.dataProvider) { | ||
this.loading = true; | ||
/** @private */ | ||
_loadPage(page) { | ||
// make sure same page isn't requested multiple times. | ||
if (!this._pendingRequests[page] && this.dataProvider) { | ||
this.loading = true; | ||
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) { | ||
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; | ||
delete this._pendingRequests[page]; | ||
delete this._pendingRequests[page]; | ||
if (Object.keys(this._pendingRequests).length === 0) { | ||
this.loading = false; | ||
if (Object.keys(this._pendingRequests).length === 0) { | ||
this.loading = false; | ||
} | ||
if (page === 0 && this.__repositionOverlayDebouncer && items.length > (this.__maxRenderedItems || 0)) { | ||
setTimeout(() => this.__repositionOverlayDebouncer.flush()); | ||
this.__maxRenderedItems = items.length; | ||
} | ||
} | ||
if (page === 0 && this.__repositionOverlayDebouncer && items.length > (this.__maxRenderedItems || 0)) { | ||
setTimeout(() => this.__repositionOverlayDebouncer.flush()); | ||
this.__maxRenderedItems = items.length; | ||
} | ||
}; | ||
if (!this._pendingRequests[page]) { | ||
// Don't request page if it's already being requested | ||
this._pendingRequests[page] = callback; | ||
this.dataProvider(params, callback); | ||
} | ||
}; | ||
if (!this._pendingRequests[page]) { | ||
// Don't request page if it's already being requested | ||
this._pendingRequests[page] = callback; | ||
this.dataProvider(params, callback); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_getPageForIndex(index) { | ||
return Math.floor(index / this.pageSize); | ||
} | ||
/** | ||
* Clears the cached pages and reloads data from dataprovider when needed. | ||
*/ | ||
clearCache() { | ||
if (!this.dataProvider) { | ||
return; | ||
/** @private */ | ||
_getPageForIndex(index) { | ||
return Math.floor(index / this.pageSize); | ||
} | ||
this._pendingRequests = {}; | ||
const filteredItems = []; | ||
for (let i = 0; i < (this.size || 0); i++) { | ||
filteredItems.push(this.__placeHolder); | ||
} | ||
this.filteredItems = filteredItems; | ||
if (this._shouldFetchData()) { | ||
this._loadPage(0); | ||
} else { | ||
this._forceNextRequest = true; | ||
} | ||
} | ||
/** @private */ | ||
_sizeChanged(size = 0) { | ||
const filteredItems = (this.filteredItems || []).slice(0, size); | ||
for (let i = 0; i < size; i++) { | ||
filteredItems[i] = filteredItems[i] !== undefined ? filteredItems[i] : this.__placeHolder; | ||
/** | ||
* Clears the cached pages and reloads data from dataprovider when needed. | ||
*/ | ||
clearCache() { | ||
if (!this.dataProvider) { | ||
return; | ||
} | ||
this._pendingRequests = {}; | ||
const filteredItems = []; | ||
for (let i = 0; i < (this.size || 0); i++) { | ||
filteredItems.push(this.__placeHolder); | ||
} | ||
this.filteredItems = filteredItems; | ||
if (this._shouldFetchData()) { | ||
this._loadPage(0); | ||
} else { | ||
this._forceNextRequest = true; | ||
} | ||
} | ||
this.filteredItems = filteredItems; | ||
// Cleans up the redundant pending requests for pages > size | ||
// Refers to https://github.com/vaadin/vaadin-flow-components/issues/229 | ||
this._flushPendingRequests(size); | ||
} | ||
/** @private */ | ||
_sizeChanged(size = 0) { | ||
const filteredItems = (this.filteredItems || []).slice(0, size); | ||
for (let i = 0; i < size; i++) { | ||
filteredItems[i] = filteredItems[i] !== undefined ? filteredItems[i] : this.__placeHolder; | ||
} | ||
this.filteredItems = filteredItems; | ||
/** @private */ | ||
_pageSizeChanged(pageSize, oldPageSize) { | ||
if (Math.floor(pageSize) !== pageSize || pageSize < 1) { | ||
this.pageSize = oldPageSize; | ||
throw new Error('`pageSize` value must be an integer > 0'); | ||
// Cleans up the redundant pending requests for pages > size | ||
// Refers to https://github.com/vaadin/vaadin-flow-components/issues/229 | ||
this._flushPendingRequests(size); | ||
} | ||
this.clearCache(); | ||
} | ||
/** @private */ | ||
_dataProviderChanged(dataProvider, oldDataProvider) { | ||
this._ensureItemsOrDataProvider(() => { | ||
this.dataProvider = oldDataProvider; | ||
}); | ||
} | ||
/** @private */ | ||
_pageSizeChanged(pageSize, oldPageSize) { | ||
if (Math.floor(pageSize) !== pageSize || pageSize < 1) { | ||
this.pageSize = oldPageSize; | ||
throw new Error('`pageSize` value must be an integer > 0'); | ||
} | ||
this.clearCache(); | ||
} | ||
/** @private */ | ||
_ensureItemsOrDataProvider(restoreOldValueCallback) { | ||
if (this.items !== undefined && this.dataProvider !== undefined) { | ||
restoreOldValueCallback(); | ||
throw new Error('Using `items` and `dataProvider` together is not supported'); | ||
} else if (this.dataProvider && !this.filteredItems) { | ||
this.filteredItems = []; | ||
/** @private */ | ||
_dataProviderChanged(dataProvider, oldDataProvider) { | ||
this._ensureItemsOrDataProvider(() => { | ||
this.dataProvider = oldDataProvider; | ||
}); | ||
} | ||
} | ||
/** @private */ | ||
_warnDataProviderValue(dataProvider, value) { | ||
if (dataProvider && value !== '' && (this.selectedItem === undefined || this.selectedItem === null)) { | ||
const valueIndex = this._indexOfValue(value, this.filteredItems); | ||
if (valueIndex < 0 || !this._getItemLabel(this.filteredItems[valueIndex])) { | ||
/* eslint-disable no-console */ | ||
console.warn( | ||
'Warning: unable to determine the label for the provided `value`. ' + | ||
'Nothing to display in the text field. This usually happens when ' + | ||
'setting an initial `value` before any items are returned from ' + | ||
'the `dataProvider` callback. Consider setting `selectedItem` ' + | ||
'instead of `value`' | ||
); | ||
/* eslint-enable no-console */ | ||
/** @private */ | ||
_ensureItemsOrDataProvider(restoreOldValueCallback) { | ||
if (this.items !== undefined && this.dataProvider !== undefined) { | ||
restoreOldValueCallback(); | ||
throw new Error('Using `items` and `dataProvider` together is not supported'); | ||
} else if (this.dataProvider && !this.filteredItems) { | ||
this.filteredItems = []; | ||
} | ||
} | ||
} | ||
/** | ||
* This method cleans up the page callbacks which refers to the | ||
* non-existing pages, i.e. which item indexes are greater than the | ||
* changed size. | ||
* This case is basically happens when: | ||
* 1. Users scroll fast to the bottom and combo box generates the | ||
* redundant page request/callback | ||
* 2. Server side uses undefined size lazy loading and suddenly reaches | ||
* the exact size which is on the range edge | ||
* (for default page size = 50, it will be 100, 200, 300, ...). | ||
* @param size the new size of items | ||
* @private | ||
*/ | ||
_flushPendingRequests(size) { | ||
if (this._pendingRequests) { | ||
const lastPage = Math.ceil(size / this.pageSize); | ||
const pendingRequestsKeys = Object.keys(this._pendingRequests); | ||
for (let reqIdx = 0; reqIdx < pendingRequestsKeys.length; reqIdx++) { | ||
const page = parseInt(pendingRequestsKeys[reqIdx]); | ||
if (page >= lastPage) { | ||
this._pendingRequests[page]([], size); | ||
/** @private */ | ||
_warnDataProviderValue(dataProvider, value) { | ||
if (dataProvider && value !== '' && (this.selectedItem === undefined || this.selectedItem === null)) { | ||
const valueIndex = this._indexOfValue(value, this.filteredItems); | ||
if (valueIndex < 0 || !this._getItemLabel(this.filteredItems[valueIndex])) { | ||
/* eslint-disable no-console */ | ||
console.warn( | ||
'Warning: unable to determine the label for the provided `value`. ' + | ||
'Nothing to display in the text field. This usually happens when ' + | ||
'setting an initial `value` before any items are returned from ' + | ||
'the `dataProvider` callback. Consider setting `selectedItem` ' + | ||
'instead of `value`' | ||
); | ||
/* eslint-enable no-console */ | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* This method cleans up the page callbacks which refers to the | ||
* non-existing pages, i.e. which item indexes are greater than the | ||
* changed size. | ||
* This case is basically happens when: | ||
* 1. Users scroll fast to the bottom and combo box generates the | ||
* redundant page request/callback | ||
* 2. Server side uses undefined size lazy loading and suddenly reaches | ||
* the exact size which is on the range edge | ||
* (for default page size = 50, it will be 100, 200, 300, ...). | ||
* @param size the new size of items | ||
* @private | ||
*/ | ||
_flushPendingRequests(size) { | ||
if (this._pendingRequests) { | ||
const lastPage = Math.ceil(size / this.pageSize); | ||
const pendingRequestsKeys = Object.keys(this._pendingRequests); | ||
for (let reqIdx = 0; reqIdx < pendingRequestsKeys.length; reqIdx++) { | ||
const page = parseInt(pendingRequestsKeys[reqIdx]); | ||
if (page >= lastPage) { | ||
this._pendingRequests[page]([], size); | ||
} | ||
} | ||
} | ||
} | ||
}; |
/** | ||
@license | ||
Copyright (c) 2017 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
* @license | ||
* Copyright (c) 2020 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; | ||
import '@polymer/iron-list/iron-list.js'; | ||
@@ -12,3 +11,2 @@ import './vaadin-combo-box-item.js'; | ||
import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
@@ -27,35 +25,54 @@ const TOUCH_DEVICE = (() => { | ||
* | ||
* @extends PolymerElement | ||
* @extends HTMLElement | ||
* @private | ||
*/ | ||
class ComboBoxDropdownWrapperElement extends (class extends PolymerElement {}) { | ||
class ComboBoxDropdownWrapperElement extends PolymerElement { | ||
static get template() { | ||
return html` | ||
<vaadin-combo-box-dropdown id="dropdown" hidden="[[_hidden(_items.*, loading)]]" position-target="[[positionTarget]]" on-template-changed="_templateChanged" on-position-changed="_setOverlayHeight" disable-upgrade="" theme="[[theme]]"> | ||
<template> | ||
<style> | ||
#scroller { | ||
overflow: auto; | ||
<vaadin-combo-box-dropdown | ||
id="dropdown" | ||
hidden="[[_hidden(_items.*, loading)]]" | ||
position-target="[[positionTarget]]" | ||
on-template-changed="_templateChanged" | ||
on-position-changed="_setOverlayHeight" | ||
disable-upgrade="" | ||
theme="[[theme]]" | ||
> | ||
<template> | ||
<style> | ||
#scroller { | ||
overflow: auto; | ||
/* Fixes item background from getting on top of scrollbars on Safari */ | ||
transform: translate3d(0, 0, 0); | ||
/* Fixes item background from getting on top of scrollbars on Safari */ | ||
transform: translate3d(0, 0, 0); | ||
/* Enable momentum scrolling on iOS (iron-list v1.2+ no longer does it for us) */ | ||
-webkit-overflow-scrolling: touch; | ||
/* Enable momentum scrolling on iOS (iron-list v1.2+ no longer does it for us) */ | ||
-webkit-overflow-scrolling: touch; | ||
/* Fixes scrollbar disappearing when 'Show scroll bars: Always' enabled in Safari */ | ||
box-shadow: 0 0 0 white; | ||
} | ||
</style> | ||
<div id="scroller" on-click="_stopPropagation"> | ||
<iron-list id="selector" role="listbox" items="[[_getItems(opened, _items)]]" scroll-target="[[_scroller]]"> | ||
<template> | ||
<vaadin-combo-box-item on-click="_onItemClick" index="[[__requestItemByIndex(item, index, _resetScrolling)]]" item="[[item]]" label="[[getItemLabel(item, _itemLabelPath)]]" selected="[[_isItemSelected(item, _selectedItem, _itemIdPath)]]" renderer="[[renderer]]" role\$="[[_getAriaRole(index)]]" aria-selected\$="[[_getAriaSelected(_focusedIndex,index)]]" focused="[[_isItemFocused(_focusedIndex,index)]]" tabindex="-1" theme\$="[[theme]]"> | ||
</vaadin-combo-box-item> | ||
</template> | ||
</iron-list> | ||
</div> | ||
</template> | ||
</vaadin-combo-box-dropdown> | ||
`; | ||
/* Fixes scrollbar disappearing when 'Show scroll bars: Always' enabled in Safari */ | ||
box-shadow: 0 0 0 white; | ||
} | ||
</style> | ||
<div id="scroller" on-click="_stopPropagation"> | ||
<iron-list id="selector" role="listbox" items="[[_getItems(opened, _items)]]" scroll-target="[[_scroller]]"> | ||
<template> | ||
<vaadin-combo-box-item | ||
on-click="_onItemClick" | ||
index="[[__requestItemByIndex(item, index, _resetScrolling)]]" | ||
item="[[item]]" | ||
label="[[getItemLabel(item, _itemLabelPath)]]" | ||
selected="[[_isItemSelected(item, _selectedItem, _itemIdPath)]]" | ||
renderer="[[renderer]]" | ||
role$="[[_getAriaRole(index)]]" | ||
aria-selected$="[[_getAriaSelected(_focusedIndex,index)]]" | ||
focused="[[_isItemFocused(_focusedIndex,index)]]" | ||
tabindex="-1" | ||
theme$="[[theme]]" | ||
></vaadin-combo-box-item> | ||
</template> | ||
</iron-list> | ||
</div> | ||
</template> | ||
</vaadin-combo-box-dropdown> | ||
`; | ||
} | ||
@@ -171,11 +188,16 @@ | ||
static get observers() { | ||
return ['_selectorChanged(_selector)', '_loadingChanged(loading)', | ||
return [ | ||
'_selectorChanged(_selector)', | ||
'_loadingChanged(loading)', | ||
'_openedChanged(opened, _items, loading)', | ||
'_restoreScrollerPosition(_items)']; | ||
'_restoreScrollerPosition(_items)' | ||
]; | ||
} | ||
_fireTouchAction(sourceEvent) { | ||
this.dispatchEvent(new CustomEvent('vaadin-overlay-touch-action', { | ||
detail: {sourceEvent: sourceEvent} | ||
})); | ||
this.dispatchEvent( | ||
new CustomEvent('vaadin-overlay-touch-action', { | ||
detail: { sourceEvent: sourceEvent } | ||
}) | ||
); | ||
} | ||
@@ -246,17 +268,10 @@ | ||
this.$.dropdown.$.overlay.addEventListener('touchend', e => this._fireTouchAction(e)); | ||
this.$.dropdown.$.overlay.addEventListener('touchmove', e => this._fireTouchAction(e)); | ||
this.$.dropdown.$.overlay.addEventListener('touchend', (e) => this._fireTouchAction(e)); | ||
this.$.dropdown.$.overlay.addEventListener('touchmove', (e) => this._fireTouchAction(e)); | ||
// Prevent blurring the input when clicking inside the overlay. | ||
this.$.dropdown.$.overlay.addEventListener('mousedown', e => e.preventDefault()); | ||
// IE11: when scrolling with mouse, the focus goes to the scroller. | ||
// This causes the overlay closing due to defocusing the input field. | ||
// Prevent focusing the scroller by setting `unselectable="on"`. | ||
if (/Trident/.test(navigator.userAgent)) { | ||
this._scroller.setAttribute('unselectable', 'on'); | ||
} | ||
this.$.dropdown.$.overlay.addEventListener('mousedown', (e) => e.preventDefault()); | ||
} | ||
_templateChanged(e) { | ||
_templateChanged() { | ||
if (this.$.dropdown.hasAttribute('disable-upgrade')) { | ||
@@ -282,3 +297,3 @@ return; | ||
_selectorChanged(selector) { | ||
_selectorChanged() { | ||
this._patchWheelOverScrolling(); | ||
@@ -294,5 +309,4 @@ } | ||
this._scroller.style.maxHeight = (window.ShadyCSS ? | ||
window.ShadyCSS.getComputedStyleValue(this, '--vaadin-combo-box-overlay-max-height') : | ||
getComputedStyle(this).getPropertyValue('--vaadin-combo-box-overlay-max-height')) || '65vh'; | ||
this._scroller.style.maxHeight = | ||
getComputedStyle(this).getPropertyValue('--vaadin-combo-box-overlay-max-height') || '65vh'; | ||
@@ -341,3 +355,3 @@ const maxHeight = this._maxOverlayHeight(targetRect); | ||
this.dispatchEvent(new CustomEvent('selection-changed', {detail: {item: e.model.item}})); | ||
this.dispatchEvent(new CustomEvent('selection-changed', { detail: { item: e.model.item } })); | ||
} | ||
@@ -352,4 +366,3 @@ | ||
for (let i = 0; i < this._items.length; i++) { | ||
if (this.getItemLabel(this._items[i]).toString().toLowerCase() === | ||
label.toString().toLowerCase()) { | ||
if (this.getItemLabel(this._items[i]).toString().toLowerCase() === label.toString().toLowerCase()) { | ||
return i; | ||
@@ -370,6 +383,6 @@ } | ||
__requestItemByIndex(item, index, resetScrolling) { | ||
if ((item instanceof ComboBoxPlaceholder) && index !== | ||
undefined && !resetScrolling) { | ||
this.dispatchEvent(new CustomEvent('index-requested', {detail: | ||
{index: index, currentScrollerPos: this._oldScrollerPosition}})); | ||
if (item instanceof ComboBoxPlaceholder && index !== undefined && !resetScrolling) { | ||
this.dispatchEvent( | ||
new CustomEvent('index-requested', { detail: { index, currentScrollerPos: this._oldScrollerPosition } }) | ||
); | ||
} | ||
@@ -467,6 +480,6 @@ | ||
const selector = this._selector; | ||
selector.addEventListener('wheel', e => { | ||
selector.addEventListener('wheel', (e) => { | ||
const scroller = selector._scroller || selector.scrollTarget; | ||
const scrolledToTop = scroller.scrollTop === 0; | ||
const scrolledToBottom = (scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight) <= 1; | ||
const scrolledToBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight <= 1; | ||
@@ -489,10 +502,9 @@ if (scrolledToTop && e.deltaY < 0) { | ||
const itemsStyle = window.getComputedStyle(this._selector.$.items); | ||
this._cachedViewportTotalPaddingBottom = [ | ||
itemsStyle.paddingBottom, | ||
itemsStyle.borderBottomWidth | ||
].map(v => { | ||
return parseInt(v, 10); | ||
}).reduce((sum, v) => { | ||
return sum + v; | ||
}); | ||
this._cachedViewportTotalPaddingBottom = [itemsStyle.paddingBottom, itemsStyle.borderBottomWidth] | ||
.map((v) => { | ||
return parseInt(v, 10); | ||
}) | ||
.reduce((sum, v) => { | ||
return sum + v; | ||
}); | ||
} | ||
@@ -517,15 +529,2 @@ | ||
_selectItem(item) { | ||
item = (typeof item === 'number') ? this._items[item] : item; | ||
if (this._selector.selectedItem !== item) { | ||
this._selector.selectItem(item); | ||
} | ||
} | ||
_preventDefault(e) { | ||
if (e.cancelable) { | ||
e.preventDefault(); | ||
} | ||
} | ||
_stopPropagation(e) { | ||
@@ -535,4 +534,4 @@ e.stopPropagation(); | ||
_hidden(itemsChange) { | ||
return !this.loading && (this._isEmpty(this._items)); | ||
_hidden() { | ||
return !this.loading && this._isEmpty(this._items); | ||
} | ||
@@ -539,0 +538,0 @@ } |
/** | ||
@license | ||
Copyright (c) 2017 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
* @license | ||
* Copyright (c) 2020 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; | ||
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js'; | ||
import { DisableUpgradeMixin } from '@polymer/polymer/lib/mixins/disable-upgrade-mixin.js'; | ||
import { OverlayElement } from '@vaadin/vaadin-overlay/src/vaadin-overlay.js'; | ||
import { IronResizableBehavior } from '@polymer/iron-resizable-behavior/iron-resizable-behavior.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js'; | ||
const $_documentContainer = document.createElement('template'); | ||
import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; | ||
$_documentContainer.innerHTML = `<dom-module id="vaadin-combo-box-overlay-styles" theme-for="vaadin-combo-box-overlay"> | ||
<template> | ||
<style> | ||
:host { | ||
width: var(--vaadin-combo-box-overlay-width, var(--_vaadin-combo-box-overlay-default-width, auto)); | ||
} | ||
</style> | ||
</template> | ||
</dom-module>`; | ||
registerStyles( | ||
'vaadin-combo-box-overlay', | ||
css` | ||
:host { | ||
width: var(--vaadin-combo-box-overlay-width, var(--_vaadin-combo-box-overlay-default-width, auto)); | ||
} | ||
`, | ||
{ moduleId: 'vaadin-combo-box-overlay-styles' } | ||
); | ||
document.head.appendChild($_documentContainer.content); | ||
/** | ||
@@ -36,3 +33,2 @@ * The overlay element. | ||
* | ||
* @extends PolymerElement | ||
* @private | ||
@@ -71,22 +67,28 @@ */ | ||
* | ||
* @extends PolymerElement | ||
* @extends HTMLElement | ||
* @private | ||
*/ | ||
class ComboBoxDropdownElement extends DisableUpgradeMixin( | ||
mixinBehaviors(IronResizableBehavior, PolymerElement)) { | ||
class ComboBoxDropdownElement extends DisableUpgradeMixin(mixinBehaviors(IronResizableBehavior, PolymerElement)) { | ||
static get template() { | ||
return html` | ||
<style> | ||
:host { | ||
display: block; | ||
} | ||
<style> | ||
:host { | ||
display: block; | ||
} | ||
:host > #overlay { | ||
display: none; | ||
} | ||
</style> | ||
<vaadin-combo-box-overlay id="overlay" hidden\$="[[hidden]]" opened="[[opened]]" template="{{template}}" style="align-items: stretch; margin: 0;" theme\$="[[theme]]"> | ||
<slot></slot> | ||
</vaadin-combo-box-overlay> | ||
`; | ||
:host > #overlay { | ||
display: none; | ||
} | ||
</style> | ||
<vaadin-combo-box-overlay | ||
id="overlay" | ||
hidden$="[[hidden]]" | ||
opened="[[opened]]" | ||
template="{{template}}" | ||
style="align-items: stretch; margin: 0;" | ||
theme$="[[theme]]" | ||
> | ||
<slot></slot> | ||
</vaadin-combo-box-overlay> | ||
`; | ||
} | ||
@@ -147,3 +149,3 @@ | ||
// Preventing the default modal behaviour of the overlay on input clicking | ||
this.$.overlay.addEventListener('vaadin-overlay-outside-click', e => { | ||
this.$.overlay.addEventListener('vaadin-overlay-outside-click', (e) => { | ||
e.preventDefault(); | ||
@@ -193,11 +195,10 @@ }); | ||
document.addEventListener('click', this._boundOutsideClickListener, true); | ||
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', {bubbles: true, composed: true})); | ||
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-opened', { bubbles: true, composed: true })); | ||
} else if (!this.__emptyItems) { | ||
window.removeEventListener('scroll', this._boundSetPosition, true); | ||
document.removeEventListener('click', this._boundOutsideClickListener, true); | ||
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', {bubbles: true, composed: true})); | ||
this.dispatchEvent(new CustomEvent('vaadin-combo-box-dropdown-closed', { bubbles: true, composed: true })); | ||
} | ||
} | ||
// We need to listen on 'click' event and capture it and close the overlay before | ||
@@ -216,4 +217,5 @@ // propagating the event to the listener in the button. Otherwise, if the clicked button would call | ||
return window.getComputedStyle(element).position === 'fixed' || | ||
(offsetParent && this._isPositionFixed(offsetParent)); | ||
return ( | ||
window.getComputedStyle(element).position === 'fixed' || (offsetParent && this._isPositionFixed(offsetParent)) | ||
); | ||
} | ||
@@ -240,34 +242,18 @@ | ||
_shouldAlignAbove(targetRect) { | ||
const spaceBelow = ( | ||
window.innerHeight - | ||
targetRect.bottom - | ||
Math.min(document.body.scrollTop, 0) | ||
) / window.innerHeight; | ||
const spaceBelow = | ||
(window.innerHeight - targetRect.bottom - Math.min(document.body.scrollTop, 0)) / window.innerHeight; | ||
return spaceBelow < 0.30; | ||
return spaceBelow < 0.3; | ||
} | ||
_getCustomWidth() { | ||
return window.ShadyCSS ? | ||
window.ShadyCSS.getComputedStyleValue(this, '--vaadin-combo-box-overlay-width') : | ||
getComputedStyle(this).getPropertyValue('--vaadin-combo-box-overlay-width'); | ||
} | ||
_setOverlayWidth() { | ||
const inputWidth = this.positionTarget.clientWidth + 'px'; | ||
const customWidth = this._getCustomWidth(); | ||
const customWidth = getComputedStyle(this).getPropertyValue('--vaadin-combo-box-overlay-width'); | ||
if (window.ShadyCSS && !window.ShadyCSS.nativeCss) { | ||
window.ShadyCSS.styleSubtree(this.$.overlay, { | ||
'--vaadin-combo-box-overlay-width': customWidth, | ||
'--_vaadin-combo-box-overlay-default-width': inputWidth | ||
}); | ||
this.$.overlay.style.setProperty('--_vaadin-combo-box-overlay-default-width', inputWidth); | ||
if (customWidth === '') { | ||
this.$.overlay.style.removeProperty('--vaadin-combo-box-overlay-width'); | ||
} else { | ||
this.$.overlay.style.setProperty('--_vaadin-combo-box-overlay-default-width', inputWidth); | ||
if (customWidth === '') { | ||
this.$.overlay.style.removeProperty('--vaadin-combo-box-overlay-width'); | ||
} else { | ||
this.$.overlay.style.setProperty('--vaadin-combo-box-overlay-width', customWidth); | ||
} | ||
this.$.overlay.style.setProperty('--vaadin-combo-box-overlay-width', customWidth); | ||
} | ||
@@ -293,4 +279,4 @@ } | ||
this._translateX = targetRect.left - overlayRect.left + (this._translateX || 0); | ||
this._translateY = targetRect.top - overlayRect.top + (this._translateY || 0) + | ||
this._verticalOffset(overlayRect, targetRect); | ||
this._translateY = | ||
targetRect.top - overlayRect.top + (this._translateY || 0) + this._verticalOffset(overlayRect, targetRect); | ||
@@ -297,0 +283,0 @@ const _devicePixelRatio = window.devicePixelRatio || 1; |
/** | ||
@license | ||
Copyright (c) 2017 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
* @license | ||
* Copyright (c) 2020 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { DirMixin } from '@vaadin/vaadin-element-mixin/vaadin-dir-mixin.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
/** | ||
@@ -31,3 +30,2 @@ * The default element used for items in the vaadin-combo-box. | ||
* | ||
* @extends PolymerElement | ||
* @mixes ThemableMixin | ||
@@ -39,13 +37,13 @@ * @private | ||
return html` | ||
<style> | ||
:host { | ||
display: block; | ||
} | ||
<style> | ||
:host { | ||
display: block; | ||
} | ||
:host([hidden]) { | ||
display: none; | ||
} | ||
</style> | ||
<div part="content" id="content"></div> | ||
`; | ||
:host([hidden]) { | ||
display: none; | ||
} | ||
</style> | ||
<div part="content" id="content"></div> | ||
`; | ||
} | ||
@@ -52,0 +50,0 @@ |
@@ -1,26 +0,9 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file was automatically generated by | ||
* https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations | ||
* | ||
* To modify these typings, edit the source file(s): | ||
* src/vaadin-combo-box-light.js | ||
*/ | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { ComboBoxMixin } from './vaadin-combo-box-mixin.js'; | ||
// tslint:disable:variable-name Describing an API that's defined elsewhere. | ||
import { ComboBoxDataProviderMixin } from './vaadin-combo-box-data-provider-mixin.js'; | ||
import {PolymerElement} from '@polymer/polymer/polymer-element.js'; | ||
import { ComboBoxEventMap } from './interfaces'; | ||
import {ThemableMixin} from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import {ComboBoxMixin} from './vaadin-combo-box-mixin.js'; | ||
import {ComboBoxDataProviderMixin} from './vaadin-combo-box-data-provider-mixin.js'; | ||
import {html} from '@polymer/polymer/lib/utils/html-tag.js'; | ||
import {dashToCamelCase} from '@polymer/polymer/lib/utils/case-map.js'; | ||
/** | ||
@@ -68,10 +51,14 @@ * `<vaadin-combo-box-light>` is a customizable version of the `<vaadin-combo-box>` providing | ||
* ``` | ||
* | ||
* @fires {CustomEvent<string>} filter-changed | ||
* @fires {CustomEvent<boolean>} invalid-changed | ||
* @fires {CustomEvent<boolean>} opened-change | ||
* @fires {CustomEvent<unknown>} selected-item-changed | ||
* @fires {CustomEvent<string>} value-changed | ||
*/ | ||
declare class ComboBoxLightElement extends | ||
ComboBoxDataProviderMixin( | ||
ComboBoxMixin( | ||
ThemableMixin( | ||
PolymerElement))) { | ||
declare class ComboBoxLightElement extends ComboBoxDataProviderMixin(ComboBoxMixin(ThemableMixin(HTMLElement))) { | ||
readonly _propertyForValue: string; | ||
_inputElementValue: string; | ||
readonly focused: boolean; | ||
@@ -85,15 +72,24 @@ | ||
attrForValue: string; | ||
readonly inputElement: Element|undefined; | ||
ready(): void; | ||
connectedCallback(): void; | ||
disconnectedCallback(): void; | ||
readonly inputElement: Element | undefined; | ||
addEventListener<K extends keyof ComboBoxEventMap>( | ||
type: K, | ||
listener: (this: ComboBoxLightElement, ev: ComboBoxEventMap[K]) => void, | ||
options?: boolean | AddEventListenerOptions | ||
): void; | ||
removeEventListener<K extends keyof ComboBoxEventMap>( | ||
type: K, | ||
listener: (this: ComboBoxLightElement, ev: ComboBoxEventMap[K]) => void, | ||
options?: boolean | EventListenerOptions | ||
): void; | ||
} | ||
declare global { | ||
interface HTMLElementTagNameMap { | ||
"vaadin-combo-box-light": ComboBoxLightElement; | ||
'vaadin-combo-box-light': ComboBoxLightElement; | ||
} | ||
} | ||
export {ComboBoxLightElement}; | ||
export { ComboBoxLightElement }; |
/** | ||
@license | ||
Copyright (c) 2017 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
* @license | ||
* Copyright (c) 2020 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; | ||
import { dashToCamelCase } from '@polymer/polymer/lib/utils/case-map.js'; | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
@@ -12,4 +12,3 @@ import { ComboBoxMixin } from './vaadin-combo-box-mixin.js'; | ||
import './vaadin-combo-box-dropdown-wrapper.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
import { dashToCamelCase } from '@polymer/polymer/lib/utils/case-map.js'; | ||
/** | ||
@@ -57,3 +56,10 @@ * `<vaadin-combo-box-light>` is a customizable version of the `<vaadin-combo-box>` providing | ||
* ``` | ||
* @extends PolymerElement | ||
* | ||
* @fires {CustomEvent<string>} filter-changed | ||
* @fires {CustomEvent<boolean>} invalid-changed | ||
* @fires {CustomEvent<boolean>} opened-change | ||
* @fires {CustomEvent<unknown>} selected-item-changed | ||
* @fires {CustomEvent<string>} value-changed | ||
* | ||
* @extends HTMLElement | ||
* @mixes ComboBoxDataProviderMixin | ||
@@ -63,19 +69,25 @@ * @mixes ComboBoxMixin | ||
*/ | ||
class ComboBoxLightElement extends | ||
ThemableMixin( | ||
ComboBoxDataProviderMixin( | ||
ComboBoxMixin(PolymerElement))) { | ||
class ComboBoxLightElement extends ThemableMixin(ComboBoxDataProviderMixin(ComboBoxMixin(PolymerElement))) { | ||
static get template() { | ||
return html` | ||
<style> | ||
:host([opened]) { | ||
pointer-events: auto; | ||
} | ||
</style> | ||
<style> | ||
:host([opened]) { | ||
pointer-events: auto; | ||
} | ||
</style> | ||
<slot></slot> | ||
<slot></slot> | ||
<vaadin-combo-box-dropdown-wrapper id="overlay" opened="[[opened]]" position-target="[[inputElement]]" renderer="[[renderer]]" _focused-index="[[_focusedIndex]]" _item-id-path="[[itemIdPath]]" _item-label-path="[[itemLabelPath]]" loading="[[loading]]" theme="[[theme]]"> | ||
</vaadin-combo-box-dropdown-wrapper> | ||
`; | ||
<vaadin-combo-box-dropdown-wrapper | ||
id="overlay" | ||
opened="[[opened]]" | ||
position-target="[[inputElement]]" | ||
renderer="[[renderer]]" | ||
_focused-index="[[_focusedIndex]]" | ||
_item-id-path="[[itemIdPath]]" | ||
_item-label-path="[[itemLabelPath]]" | ||
loading="[[loading]]" | ||
theme="[[theme]]" | ||
></vaadin-combo-box-dropdown-wrapper> | ||
`; | ||
} | ||
@@ -123,3 +135,3 @@ | ||
if (this._clearElement) { | ||
this._clearElement.addEventListener('mousedown', e => { | ||
this._clearElement.addEventListener('mousedown', (e) => { | ||
e.preventDefault(); // Prevent native focus changes | ||
@@ -126,0 +138,0 @@ // _focusableElement is needed for paper-input |
@@ -1,39 +0,9 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file was automatically generated by | ||
* https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations | ||
* | ||
* To modify these typings, edit the source file(s): | ||
* src/vaadin-combo-box-mixin.js | ||
*/ | ||
import { ComboBoxItem, ComboBoxRenderer } from './interfaces'; | ||
// tslint:disable:variable-name Describing an API that's defined elsewhere. | ||
// tslint:disable:no-any describes the API as best we are able today | ||
import {timeOut} from '@polymer/polymer/lib/utils/async.js'; | ||
import {Debouncer} from '@polymer/polymer/lib/utils/debounce.js'; | ||
import {flush} from '@polymer/polymer/lib/utils/flush.js'; | ||
import {templatize} from '@polymer/polymer/lib/utils/templatize.js'; | ||
import {IronA11yAnnouncer} from '@polymer/iron-a11y-announcer/iron-a11y-announcer.js'; | ||
import {IronA11yKeysBehavior} from '@polymer/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js'; | ||
import {FlattenedNodesObserver} from '@polymer/polymer/lib/utils/flattened-nodes-observer.js'; | ||
export {ComboBoxMixin}; | ||
declare function ComboBoxMixin<T extends new (...args: any[]) => {}>(base: T): T & ComboBoxMixinConstructor; | ||
interface ComboBoxMixinConstructor { | ||
new(...args: any[]): ComboBoxMixin; | ||
new (...args: any[]): ComboBoxMixin; | ||
} | ||
export {ComboBoxMixinConstructor}; | ||
interface ComboBoxMixin { | ||
@@ -51,3 +21,3 @@ readonly _propertyForValue: string; | ||
*/ | ||
autoOpenDisabled: boolean|null|undefined; | ||
autoOpenDisabled: boolean | null | undefined; | ||
@@ -75,3 +45,3 @@ /** | ||
*/ | ||
renderer: ComboBoxRenderer|null|undefined; | ||
renderer: ComboBoxRenderer | null | undefined; | ||
@@ -82,3 +52,3 @@ /** | ||
*/ | ||
items: Array<ComboBoxItem|string>|undefined; | ||
items: Array<ComboBoxItem | string> | undefined; | ||
@@ -99,7 +69,6 @@ /** | ||
*/ | ||
filteredItems: Array<ComboBoxItem|string>|undefined; | ||
filteredItems: Array<ComboBoxItem | string> | undefined; | ||
/** | ||
* The `String` value for the selected item of the combo box. Provides | ||
* the value for `iron-form`. | ||
* The `String` value for the selected item of the combo box. | ||
* | ||
@@ -117,2 +86,3 @@ * When there’s no item selected, the value is an empty string. | ||
loading: boolean; | ||
_focusedIndex: number; | ||
@@ -128,3 +98,3 @@ | ||
*/ | ||
selectedItem: ComboBoxItem|string|null|undefined; | ||
selectedItem: ComboBoxItem | string | null | undefined; | ||
@@ -163,3 +133,3 @@ /** | ||
*/ | ||
itemIdPath: string|null|undefined; | ||
itemIdPath: string | null | undefined; | ||
@@ -169,3 +139,3 @@ /** | ||
*/ | ||
name: string|null|undefined; | ||
name: string | null | undefined; | ||
@@ -176,7 +146,9 @@ /** | ||
invalid: boolean; | ||
_toggleElement: HTMLElement|undefined; | ||
_clearElement: HTMLElement|undefined; | ||
_inputElementValue: string|null|undefined; | ||
ready(): void; | ||
_toggleElement: HTMLElement | undefined; | ||
_clearElement: HTMLElement | undefined; | ||
_inputElementValue: string | null | undefined; | ||
/** | ||
@@ -196,3 +168,3 @@ * Manually invoke existing renderer. | ||
close(): void; | ||
_isEventKey(event: KeyboardEvent, key: string): boolean; | ||
_onEscape(e: KeyboardEvent): void; | ||
@@ -214,2 +186,3 @@ | ||
_inputValueChanged(e: Event): void; | ||
_revertInputValue(): void; | ||
@@ -229,11 +202,13 @@ | ||
*/ | ||
checkValidity(): boolean|undefined; | ||
checkValidity(): boolean | undefined; | ||
_ensureTemplatized(): void; | ||
_preventInputBlur(): void; | ||
_restoreInputBlur(): void; | ||
_stopPropagation(e: Event): void; | ||
} | ||
import {ComboBoxRenderer} from '../@types/interfaces'; | ||
import {ComboBoxItem} from '../@types/interfaces'; | ||
export { ComboBoxMixin, ComboBoxMixinConstructor }; |
/** | ||
@license | ||
Copyright (c) 2017 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
* @license | ||
* Copyright (c) 2020 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { timeOut } from '@polymer/polymer/lib/utils/async.js'; | ||
import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js'; | ||
@@ -12,3 +11,2 @@ import { flush } from '@polymer/polymer/lib/utils/flush.js'; | ||
import { IronA11yAnnouncer } from '@polymer/iron-a11y-announcer/iron-a11y-announcer.js'; | ||
import { IronA11yKeysBehavior } from '@polymer/iron-a11y-keys-behavior/iron-a11y-keys-behavior.js'; | ||
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js'; | ||
@@ -20,1163 +18,1163 @@ import { ComboBoxPlaceholder } from './vaadin-combo-box-placeholder.js'; | ||
*/ | ||
export const ComboBoxMixin = subclass => class VaadinComboBoxMixinElement extends subclass { | ||
export const ComboBoxMixin = (subclass) => | ||
class VaadinComboBoxMixinElement extends subclass { | ||
static get properties() { | ||
return { | ||
/** | ||
* True if the dropdown is open, false otherwise. | ||
* @type {boolean} | ||
*/ | ||
opened: { | ||
type: Boolean, | ||
notify: true, | ||
value: false, | ||
reflectToAttribute: true, | ||
observer: '_openedChanged' | ||
}, | ||
static get properties() { | ||
return { | ||
/** | ||
* True if the dropdown is open, false otherwise. | ||
* @type {boolean} | ||
*/ | ||
opened: { | ||
type: Boolean, | ||
notify: true, | ||
value: false, | ||
reflectToAttribute: true, | ||
observer: '_openedChanged' | ||
}, | ||
/** | ||
* Set true to prevent the overlay from opening automatically. | ||
* @attr {boolean} auto-open-disabled | ||
*/ | ||
autoOpenDisabled: Boolean, | ||
/** | ||
* Set true to prevent the overlay from opening automatically. | ||
* @attr {boolean} auto-open-disabled | ||
*/ | ||
autoOpenDisabled: Boolean, | ||
/** | ||
* Set to true to disable this element. | ||
* @type {boolean} | ||
*/ | ||
disabled: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* Set to true to disable this element. | ||
* @type {boolean} | ||
*/ | ||
disabled: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* When present, it specifies that the element field is read-only. | ||
* @type {boolean} | ||
*/ | ||
readonly: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* When present, it specifies that the element field is read-only. | ||
* @type {boolean} | ||
*/ | ||
readonly: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* Custom function for rendering the content of every item. | ||
* Receives three arguments: | ||
* | ||
* - `root` The `<vaadin-combo-box-item>` internal container DOM element. | ||
* - `comboBox` The reference to the `<vaadin-combo-box>` element. | ||
* - `model` The object with the properties related with the rendered | ||
* item, contains: | ||
* - `model.index` The index of the rendered item. | ||
* - `model.item` The item. | ||
* @type {ComboBoxRenderer | undefined} | ||
*/ | ||
renderer: Function, | ||
/** | ||
* Custom function for rendering the content of every item. | ||
* Receives three arguments: | ||
* | ||
* - `root` The `<vaadin-combo-box-item>` internal container DOM element. | ||
* - `comboBox` The reference to the `<vaadin-combo-box>` element. | ||
* - `model` The object with the properties related with the rendered | ||
* item, contains: | ||
* - `model.index` The index of the rendered item. | ||
* - `model.item` The item. | ||
* @type {ComboBoxRenderer | undefined} | ||
*/ | ||
renderer: Function, | ||
/** | ||
* A full set of items to filter the visible options from. | ||
* The items can be of either `String` or `Object` type. | ||
* @type {!Array<!ComboBoxItem | string> | undefined} | ||
*/ | ||
items: { | ||
type: Array, | ||
observer: '_itemsChanged' | ||
}, | ||
/** | ||
* A full set of items to filter the visible options from. | ||
* The items can be of either `String` or `Object` type. | ||
* @type {!Array<!ComboBoxItem | string> | undefined} | ||
*/ | ||
items: { | ||
type: Array, | ||
observer: '_itemsChanged' | ||
}, | ||
/** | ||
* If `true`, the user can input a value that is not present in the items list. | ||
* `value` property will be set to the input value in this case. | ||
* Also, when `value` is set programmatically, the input value will be set | ||
* to reflect that value. | ||
* @attr {boolean} allow-custom-value | ||
* @type {boolean} | ||
*/ | ||
allowCustomValue: { | ||
type: Boolean, | ||
value: false | ||
}, | ||
/** | ||
* If `true`, the user can input a value that is not present in the items list. | ||
* `value` property will be set to the input value in this case. | ||
* Also, when `value` is set programmatically, the input value will be set | ||
* to reflect that value. | ||
* @attr {boolean} allow-custom-value | ||
* @type {boolean} | ||
*/ | ||
allowCustomValue: { | ||
type: Boolean, | ||
value: false | ||
}, | ||
/** | ||
* A subset of items, filtered based on the user input. Filtered items | ||
* can be assigned directly to omit the internal filtering functionality. | ||
* The items can be of either `String` or `Object` type. | ||
* @type {!Array<!ComboBoxItem | string> | undefined} | ||
*/ | ||
filteredItems: { | ||
type: Array | ||
}, | ||
/** | ||
* A subset of items, filtered based on the user input. Filtered items | ||
* can be assigned directly to omit the internal filtering functionality. | ||
* The items can be of either `String` or `Object` type. | ||
* @type {!Array<!ComboBoxItem | string> | undefined} | ||
*/ | ||
filteredItems: { | ||
type: Array | ||
}, | ||
/** | ||
* The `String` value for the selected item of the combo box. | ||
* | ||
* When there’s no item selected, the value is an empty string. | ||
* | ||
* Use `selectedItem` property to get the raw selected item from | ||
* the `items` array. | ||
* @type {string} | ||
*/ | ||
value: { | ||
type: String, | ||
observer: '_valueChanged', | ||
notify: true, | ||
value: '' | ||
}, | ||
/** | ||
* The `String` value for the selected item of the combo box. Provides | ||
* the value for `iron-form`. | ||
* | ||
* When there’s no item selected, the value is an empty string. | ||
* | ||
* Use `selectedItem` property to get the raw selected item from | ||
* the `items` array. | ||
* @type {string} | ||
*/ | ||
value: { | ||
type: String, | ||
observer: '_valueChanged', | ||
notify: true, | ||
value: '' | ||
}, | ||
/** | ||
* Used to detect user value changes and fire `change` events. | ||
* @private | ||
*/ | ||
_lastCommittedValue: String, | ||
/** | ||
* Used to detect user value changes and fire `change` events. | ||
* @private | ||
*/ | ||
_lastCommittedValue: String, | ||
/** | ||
* When set to `true`, "loading" attribute is added to host and the overlay element. | ||
* @type {boolean} | ||
*/ | ||
loading: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* When set to `true`, "loading" attribute is added to host and the overlay element. | ||
* @type {boolean} | ||
*/ | ||
loading: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* @type {number} | ||
* @protected | ||
*/ | ||
_focusedIndex: { | ||
type: Number, | ||
value: -1 | ||
}, | ||
/** | ||
* @type {number} | ||
* @protected | ||
*/ | ||
_focusedIndex: { | ||
type: Number, | ||
value: -1 | ||
}, | ||
/** | ||
* Filtering string the user has typed into the input field. | ||
* @type {string} | ||
*/ | ||
filter: { | ||
type: String, | ||
value: '', | ||
notify: true | ||
}, | ||
/** | ||
* Filtering string the user has typed into the input field. | ||
* @type {string} | ||
*/ | ||
filter: { | ||
type: String, | ||
value: '', | ||
notify: true | ||
}, | ||
/** | ||
* The selected item from the `items` array. | ||
* @type {ComboBoxItem | string | undefined} | ||
*/ | ||
selectedItem: { | ||
type: Object, | ||
notify: true | ||
}, | ||
/** | ||
* The selected item from the `items` array. | ||
* @type {ComboBoxItem | string | undefined} | ||
*/ | ||
selectedItem: { | ||
type: Object, | ||
notify: true | ||
}, | ||
/** | ||
* Path for label of the item. If `items` is an array of objects, the | ||
* `itemLabelPath` is used to fetch the displayed string label for each | ||
* item. | ||
* | ||
* The item label is also used for matching items when processing user | ||
* input, i.e., for filtering and selecting items. | ||
* | ||
* When using item templates, the property is still needed because it is used | ||
* for filtering, and for displaying the selected item value in the input box. | ||
* @attr {string} item-label-path | ||
* @type {string} | ||
*/ | ||
itemLabelPath: { | ||
type: String, | ||
value: 'label', | ||
observer: '_itemLabelPathChanged' | ||
}, | ||
/** | ||
* Path for label of the item. If `items` is an array of objects, the | ||
* `itemLabelPath` is used to fetch the displayed string label for each | ||
* item. | ||
* | ||
* The item label is also used for matching items when processing user | ||
* input, i.e., for filtering and selecting items. | ||
* | ||
* When using item templates, the property is still needed because it is used | ||
* for filtering, and for displaying the selected item value in the input box. | ||
* @attr {string} item-label-path | ||
* @type {string} | ||
*/ | ||
itemLabelPath: { | ||
type: String, | ||
value: 'label', | ||
observer: '_itemLabelPathChanged' | ||
}, | ||
/** | ||
* Path for the value of the item. If `items` is an array of objects, the | ||
* `itemValuePath:` is used to fetch the string value for the selected | ||
* item. | ||
* | ||
* The item value is used in the `value` property of the combo box, | ||
* to provide the form value. | ||
* @attr {string} item-value-path | ||
* @type {string} | ||
*/ | ||
itemValuePath: { | ||
type: String, | ||
value: 'value' | ||
}, | ||
/** | ||
* Path for the value of the item. If `items` is an array of objects, the | ||
* `itemValuePath:` is used to fetch the string value for the selected | ||
* item. | ||
* | ||
* The item value is used in the `value` property of the combo box, | ||
* to provide the form value. | ||
* @attr {string} item-value-path | ||
* @type {string} | ||
*/ | ||
itemValuePath: { | ||
type: String, | ||
value: 'value' | ||
}, | ||
/** | ||
* Path for the id of the item. If `items` is an array of objects, | ||
* the `itemIdPath` is used to compare and identify the same item | ||
* in `selectedItem` and `filteredItems` (items given by the | ||
* `dataProvider` callback). | ||
* @attr {string} item-id-path | ||
*/ | ||
itemIdPath: String, | ||
/** | ||
* Path for the id of the item. If `items` is an array of objects, | ||
* the `itemIdPath` is used to compare and identify the same item | ||
* in `selectedItem` and `filteredItems` (items given by the | ||
* `dataProvider` callback). | ||
* @attr {string} item-id-path | ||
*/ | ||
itemIdPath: String, | ||
/** | ||
* The name of this element. | ||
*/ | ||
name: { | ||
type: String | ||
}, | ||
/** | ||
* The name of this element. | ||
*/ | ||
name: { | ||
type: String | ||
}, | ||
/** | ||
* Set to true if the value is invalid. | ||
* @type {boolean} | ||
*/ | ||
invalid: { | ||
type: Boolean, | ||
reflectToAttribute: true, | ||
notify: true, | ||
value: false | ||
}, | ||
/** | ||
* Set to true if the value is invalid. | ||
* @type {boolean} | ||
*/ | ||
invalid: { | ||
type: Boolean, | ||
reflectToAttribute: true, | ||
notify: true, | ||
value: false | ||
}, | ||
/** | ||
* @type {!HTMLElement | undefined} | ||
* @protected | ||
*/ | ||
_toggleElement: Object, | ||
/** | ||
* @type {!HTMLElement | undefined} | ||
* @protected | ||
*/ | ||
_toggleElement: Object, | ||
/** | ||
* @type {!HTMLElement | undefined} | ||
* @protected | ||
*/ | ||
_clearElement: Object, | ||
/** | ||
* @type {!HTMLElement | undefined} | ||
* @protected | ||
*/ | ||
_clearElement: Object, | ||
/** @protected */ | ||
_inputElementValue: String, | ||
/** @protected */ | ||
_inputElementValue: String, | ||
/** @private */ | ||
_closeOnBlurIsPrevented: Boolean, | ||
/** @private */ | ||
_closeOnBlurIsPrevented: Boolean, | ||
/** @private */ | ||
_previousDocumentPointerEvents: String, | ||
/** @private */ | ||
_previousDocumentPointerEvents: String, | ||
/** @private */ | ||
_itemTemplate: Object | ||
}; | ||
} | ||
/** @private */ | ||
_itemTemplate: Object | ||
}; | ||
} | ||
static get observers() { | ||
return [ | ||
'_filterChanged(filter, itemValuePath, itemLabelPath)', | ||
'_itemsOrPathsChanged(items.*, itemValuePath, itemLabelPath)', | ||
'_filteredItemsChanged(filteredItems.*, itemValuePath, itemLabelPath)', | ||
'_templateOrRendererChanged(_itemTemplate, renderer)', | ||
'_loadingChanged(loading)', | ||
'_selectedItemChanged(selectedItem, itemLabelPath)', | ||
'_toggleElementChanged(_toggleElement)' | ||
]; | ||
} | ||
static get observers() { | ||
return [ | ||
'_filterChanged(filter, itemValuePath, itemLabelPath)', | ||
'_itemsOrPathsChanged(items.*, itemValuePath, itemLabelPath)', | ||
'_filteredItemsChanged(filteredItems.*, itemValuePath, itemLabelPath)', | ||
'_templateOrRendererChanged(_itemTemplate, renderer)', | ||
'_loadingChanged(loading)', | ||
'_selectedItemChanged(selectedItem, itemLabelPath)', | ||
'_toggleElementChanged(_toggleElement)' | ||
]; | ||
} | ||
constructor() { | ||
super(); | ||
this._boundOnFocusout = this._onFocusout.bind(this); | ||
this._boundOverlaySelectedItemChanged = this._overlaySelectedItemChanged.bind(this); | ||
this._boundClose = this.close.bind(this); | ||
this._boundOnOpened = this._onOpened.bind(this); | ||
this._boundOnKeyDown = this._onKeyDown.bind(this); | ||
this._boundOnClick = this._onClick.bind(this); | ||
this._boundOnOverlayTouchAction = this._onOverlayTouchAction.bind(this); | ||
this._boundOnTouchend = this._onTouchend.bind(this); | ||
} | ||
constructor() { | ||
super(); | ||
this._boundOnFocusout = this._onFocusout.bind(this); | ||
this._boundOverlaySelectedItemChanged = this._overlaySelectedItemChanged.bind(this); | ||
this._boundClose = this.close.bind(this); | ||
this._boundOnOpened = this._onOpened.bind(this); | ||
this._boundOnKeyDown = this._onKeyDown.bind(this); | ||
this._boundOnClick = this._onClick.bind(this); | ||
this._boundOnOverlayTouchAction = this._onOverlayTouchAction.bind(this); | ||
this._boundOnTouchend = this._onTouchend.bind(this); | ||
} | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
this.addEventListener('focusout', this._boundOnFocusout); | ||
this.addEventListener('focusout', this._boundOnFocusout); | ||
this._lastCommittedValue = this.value; | ||
IronA11yAnnouncer.requestAvailability(); | ||
this._lastCommittedValue = this.value; | ||
IronA11yAnnouncer.requestAvailability(); | ||
// 2.0 does not support 'overlay.selection-changed' syntax in listeners | ||
this.$.overlay.addEventListener('selection-changed', this._boundOverlaySelectedItemChanged); | ||
// 2.0 does not support 'overlay.selection-changed' syntax in listeners | ||
this.$.overlay.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('keydown', this._boundOnKeyDown); | ||
this.addEventListener('click', this._boundOnClick); | ||
this.addEventListener('vaadin-combo-box-dropdown-closed', this._boundClose); | ||
this.addEventListener('vaadin-combo-box-dropdown-opened', this._boundOnOpened); | ||
this.addEventListener('keydown', this._boundOnKeyDown); | ||
this.addEventListener('click', this._boundOnClick); | ||
this.$.overlay.addEventListener('vaadin-overlay-touch-action', this._boundOnOverlayTouchAction); | ||
this.$.overlay.addEventListener('vaadin-overlay-touch-action', this._boundOnOverlayTouchAction); | ||
this.addEventListener('touchend', this._boundOnTouchend); | ||
this.addEventListener('touchend', this._boundOnTouchend); | ||
this._observer = new FlattenedNodesObserver(this, (info) => { | ||
this._setTemplateFromNodes(info.addedNodes); | ||
}); | ||
this._observer = new FlattenedNodesObserver(this, info => { | ||
this._setTemplateFromNodes(info.addedNodes); | ||
}); | ||
const bringToFrontListener = () => { | ||
const overlay = this.$.overlay; | ||
const dropdown = overlay && overlay.$.dropdown; | ||
// Check dropdown.$ because overlay is lazily instantiated | ||
if (dropdown && dropdown.$) { | ||
requestAnimationFrame(() => { | ||
dropdown.$.overlay.bringToFront(); | ||
}); | ||
} | ||
}; | ||
const bringToFrontListener = (e) => { | ||
const overlay = this.$.overlay; | ||
const dropdown = overlay && overlay.$.dropdown; | ||
// Check dropdown.$ because overlay is lazily instantiated | ||
if (dropdown && dropdown.$ && this.$.overlay.$.dropdown.$.overlay.bringToFront) { | ||
requestAnimationFrame(() => { | ||
dropdown.$.overlay.bringToFront(); | ||
}); | ||
} | ||
}; | ||
this.addEventListener('mousedown', bringToFrontListener); | ||
this.addEventListener('touchstart', bringToFrontListener); | ||
} | ||
/** | ||
* Manually invoke existing renderer. | ||
*/ | ||
render() { | ||
if (this.$.overlay._selector) { | ||
this.$.overlay._selector.querySelectorAll('vaadin-combo-box-item').forEach(item => item._render()); | ||
this.addEventListener('mousedown', bringToFrontListener); | ||
this.addEventListener('touchstart', bringToFrontListener); | ||
} | ||
} | ||
/** @private */ | ||
_setTemplateFromNodes(nodes) { | ||
this._itemTemplate = nodes.filter(node => node.localName && node.localName === 'template')[0] || this._itemTemplate; | ||
} | ||
/** @private */ | ||
_removeNewRendererOrTemplate(template, oldTemplate, renderer, oldRenderer) { | ||
if (template !== oldTemplate) { | ||
this._itemTemplate = undefined; | ||
} else if (renderer !== oldRenderer) { | ||
this.renderer = undefined; | ||
/** | ||
* Manually invoke existing renderer. | ||
*/ | ||
render() { | ||
if (this.$.overlay._selector) { | ||
this.$.overlay._selector.querySelectorAll('vaadin-combo-box-item').forEach((item) => item._render()); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_templateOrRendererChanged(template, renderer) { | ||
if (template && renderer) { | ||
this._removeNewRendererOrTemplate(template, this._oldTemplate, renderer, this._oldRenderer); | ||
throw new Error('You should only use either a renderer or a template for combo box items'); | ||
/** @private */ | ||
_setTemplateFromNodes(nodes) { | ||
this._itemTemplate = | ||
nodes.filter((node) => node.localName && node.localName === 'template')[0] || this._itemTemplate; | ||
} | ||
this._oldTemplate = template; | ||
this._oldRenderer = renderer; | ||
} | ||
/** | ||
* Opens the dropdown list. | ||
*/ | ||
open() { | ||
// Prevent _open() being called when input is disabled or read-only | ||
if (!this.disabled && !this.readonly) { | ||
this.opened = true; | ||
/** @private */ | ||
_removeNewRendererOrTemplate(template, oldTemplate, renderer, oldRenderer) { | ||
if (template !== oldTemplate) { | ||
this._itemTemplate = undefined; | ||
} else if (renderer !== oldRenderer) { | ||
this.renderer = undefined; | ||
} | ||
} | ||
} | ||
/** | ||
* Closes the dropdown list. | ||
*/ | ||
close() { | ||
this.opened = false; | ||
} | ||
/** @private */ | ||
_templateOrRendererChanged(template, renderer) { | ||
if (template && renderer) { | ||
this._removeNewRendererOrTemplate(template, this._oldTemplate, renderer, this._oldRenderer); | ||
throw new Error('You should only use either a renderer or a template for combo box items'); | ||
} | ||
/** @private */ | ||
_openedChanged(value, old) { | ||
// Prevent _close() being called when opened is set to its default value (false). | ||
if (old === undefined) { | ||
return; | ||
this._oldTemplate = template; | ||
this._oldRenderer = renderer; | ||
} | ||
if (this.opened) { | ||
this._openedWithFocusRing = this.hasAttribute('focus-ring') || (this.focusElement && this.focusElement.hasAttribute('focus-ring')); | ||
// For touch devices, we don't want to popup virtual keyboard unless input is explicitly focused by the user. | ||
if (!this.hasAttribute('focused') && !this.$.overlay.touchDevice) { | ||
this.focus(); | ||
/** | ||
* Opens the dropdown list. | ||
*/ | ||
open() { | ||
// Prevent _open() being called when input is disabled or read-only | ||
if (!this.disabled && !this.readonly) { | ||
this.opened = true; | ||
} | ||
} else { | ||
this._onClosed(); | ||
if (this._openedWithFocusRing && this.hasAttribute('focused')) { | ||
this.focusElement.setAttribute('focus-ring', ''); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_onOverlayTouchAction(event) { | ||
// On touch devices, blur the input on touch start inside the overlay, in order to hide | ||
// the virtual keyboard. But don't close the overlay on this blur. | ||
this._closeOnBlurIsPrevented = true; | ||
this.inputElement.blur(); | ||
this._closeOnBlurIsPrevented = false; | ||
} | ||
/** | ||
* Closes the dropdown list. | ||
*/ | ||
close() { | ||
this.opened = false; | ||
} | ||
/** @private */ | ||
_onClick(e) { | ||
this._closeOnBlurIsPrevented = true; | ||
/** @private */ | ||
_openedChanged(value, old) { | ||
// Prevent _close() being called when opened is set to its default value (false). | ||
if (old === undefined) { | ||
return; | ||
} | ||
const path = e.composedPath(); | ||
const isClearElement = (path.indexOf(this._clearElement) !== -1) || (path[0].getAttribute('part') === 'clear-button'); | ||
if (isClearElement) { | ||
this._clear(); | ||
this.focus(); | ||
} else if (path.indexOf(this.inputElement) !== -1) { | ||
if (path.indexOf(this._toggleElement) > -1 && this.opened) { | ||
this.close(); | ||
} else if (path.indexOf(this._toggleElement) > -1 || !this.autoOpenDisabled) { | ||
this.open(); | ||
if (this.opened) { | ||
this._openedWithFocusRing = | ||
this.hasAttribute('focus-ring') || (this.focusElement && this.focusElement.hasAttribute('focus-ring')); | ||
// For touch devices, we don't want to popup virtual keyboard unless input is explicitly focused by the user. | ||
if (!this.hasAttribute('focused') && !this.$.overlay.touchDevice) { | ||
this.focus(); | ||
} | ||
} else { | ||
this._onClosed(); | ||
if (this._openedWithFocusRing && this.hasAttribute('focused')) { | ||
this.focusElement.setAttribute('focus-ring', ''); | ||
} | ||
} | ||
} | ||
this._closeOnBlurIsPrevented = false; | ||
} | ||
/** | ||
* Keyboard navigation | ||
* @private | ||
*/ | ||
_onKeyDown(e) { | ||
if (this._isEventKey(e, 'down')) { | ||
/** @private */ | ||
_onOverlayTouchAction() { | ||
// On touch devices, blur the input on touch start inside the overlay, in order to hide | ||
// the virtual keyboard. But don't close the overlay on this blur. | ||
this._closeOnBlurIsPrevented = true; | ||
this._onArrowDown(); | ||
this.inputElement.blur(); | ||
this._closeOnBlurIsPrevented = false; | ||
} | ||
// prevent caret from moving | ||
e.preventDefault(); | ||
} else if (this._isEventKey(e, 'up')) { | ||
/** @private */ | ||
_onClick(e) { | ||
this._closeOnBlurIsPrevented = true; | ||
this._onArrowUp(); | ||
const path = e.composedPath(); | ||
const isClearElement = path.indexOf(this._clearElement) !== -1 || path[0].getAttribute('part') === 'clear-button'; | ||
if (isClearElement) { | ||
this._clear(); | ||
this.focus(); | ||
} else if (path.indexOf(this.inputElement) !== -1) { | ||
if (path.indexOf(this._toggleElement) > -1 && this.opened) { | ||
this.close(); | ||
} else if (path.indexOf(this._toggleElement) > -1 || !this.autoOpenDisabled) { | ||
this.open(); | ||
} | ||
} | ||
this._closeOnBlurIsPrevented = false; | ||
// prevent caret from moving | ||
e.preventDefault(); | ||
} else if (this._isEventKey(e, 'enter')) { | ||
this._onEnter(e); | ||
} else if (this._isEventKey(e, 'esc')) { | ||
this._onEscape(e); | ||
} | ||
} | ||
/** | ||
* @param {!KeyboardEvent} event | ||
* @param {string} key | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
_isEventKey(event, key) { | ||
return IronA11yKeysBehavior.keyboardEventMatchesKeys(event, key); | ||
} | ||
/** | ||
* Keyboard navigation | ||
* @private | ||
*/ | ||
_onKeyDown(e) { | ||
if (e.keyCode === 40) { | ||
this._closeOnBlurIsPrevented = true; | ||
this._onArrowDown(); | ||
this._closeOnBlurIsPrevented = false; | ||
/** @private */ | ||
_getItemLabel(item) { | ||
return this.$.overlay.getItemLabel(item); | ||
} | ||
// prevent caret from moving | ||
e.preventDefault(); | ||
} else if (e.keyCode === 38) { | ||
this._closeOnBlurIsPrevented = true; | ||
this._onArrowUp(); | ||
this._closeOnBlurIsPrevented = false; | ||
/** @private */ | ||
_getItemValue(item) { | ||
let value = item && this.itemValuePath ? this.get(this.itemValuePath, item) : undefined; | ||
if (value === undefined) { | ||
value = item ? item.toString() : ''; | ||
// prevent caret from moving | ||
e.preventDefault(); | ||
} else if (e.keyCode === 13) { | ||
this._onEnter(e); | ||
} else if (e.keyCode === 27) { | ||
this._onEscape(e); | ||
} | ||
} | ||
return value; | ||
} | ||
/** @private */ | ||
_onArrowDown() { | ||
if (this.opened) { | ||
if (this.$.overlay._items) { | ||
this._focusedIndex = Math.min(this.$.overlay._items.length - 1, this._focusedIndex + 1); | ||
this._prefillFocusedItemLabel(); | ||
/** @private */ | ||
_getItemLabel(item) { | ||
return this.$.overlay.getItemLabel(item); | ||
} | ||
/** @private */ | ||
_getItemValue(item) { | ||
let value = item && this.itemValuePath ? this.get(this.itemValuePath, item) : undefined; | ||
if (value === undefined) { | ||
value = item ? item.toString() : ''; | ||
} | ||
} else { | ||
this.open(); | ||
return value; | ||
} | ||
} | ||
/** @private */ | ||
_onArrowUp() { | ||
if (this.opened) { | ||
if (this._focusedIndex > -1) { | ||
this._focusedIndex = Math.max(0, this._focusedIndex - 1); | ||
} else { | ||
/** @private */ | ||
_onArrowDown() { | ||
if (this.opened) { | ||
if (this.$.overlay._items) { | ||
this._focusedIndex = this.$.overlay._items.length - 1; | ||
this._focusedIndex = Math.min(this.$.overlay._items.length - 1, this._focusedIndex + 1); | ||
this._prefillFocusedItemLabel(); | ||
} | ||
} else { | ||
this.open(); | ||
} | ||
} | ||
this._prefillFocusedItemLabel(); | ||
} else { | ||
this.open(); | ||
/** @private */ | ||
_onArrowUp() { | ||
if (this.opened) { | ||
if (this._focusedIndex > -1) { | ||
this._focusedIndex = Math.max(0, this._focusedIndex - 1); | ||
} else { | ||
if (this.$.overlay._items) { | ||
this._focusedIndex = this.$.overlay._items.length - 1; | ||
} | ||
} | ||
this._prefillFocusedItemLabel(); | ||
} else { | ||
this.open(); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_prefillFocusedItemLabel() { | ||
if (this._focusedIndex > -1) { | ||
// Reset the input value asyncronously to prevent partial value changes | ||
// announce. Makes OSX VoiceOver to announce the complete value instead. | ||
this._inputElementValue = ''; | ||
// 1ms delay needed for OSX VoiceOver to realise input value was reset | ||
setTimeout(() => { | ||
this._inputElementValue = this._getItemLabel(this.$.overlay._focusedItem); | ||
this._markAllSelectionRange(); | ||
}, 1); | ||
/** @private */ | ||
_prefillFocusedItemLabel() { | ||
if (this._focusedIndex > -1) { | ||
// Reset the input value asyncronously to prevent partial value changes | ||
// announce. Makes OSX VoiceOver to announce the complete value instead. | ||
this._inputElementValue = ''; | ||
// 1ms delay needed for OSX VoiceOver to realise input value was reset | ||
setTimeout(() => { | ||
this._inputElementValue = this._getItemLabel(this.$.overlay._focusedItem); | ||
this._markAllSelectionRange(); | ||
}, 1); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_setSelectionRange(start, end) { | ||
// vaadin-text-field does not implement setSelectionRange, hence we need the native input | ||
const input = this._nativeInput || this.inputElement; | ||
/** @private */ | ||
_setSelectionRange(start, end) { | ||
// vaadin-text-field does not implement setSelectionRange, hence we need the native input | ||
const input = this._nativeInput || this.inputElement; | ||
// Setting selection range focuses and/or moves the caret in some browsers, | ||
// and there's no need to modify the selection range if the input isn't focused anyway. | ||
// This affects Safari. When the overlay is open, and then hiting tab, browser should focus | ||
// 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') && input && input.setSelectionRange) { | ||
try { | ||
// Setting selection range focuses and/or moves the caret in some browsers, | ||
// and there's no need to modify the selection range if the input isn't focused anyway. | ||
// This affects Safari. When the overlay is open, and then hiting tab, browser should focus | ||
// 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') && input && input.setSelectionRange) { | ||
input.setSelectionRange(start, end); | ||
} catch (ignore) { | ||
// IE11 randomly fails when running tests in Sauce. | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_markAllSelectionRange() { | ||
if (this._inputElementValue !== undefined) { | ||
this._setSelectionRange(0, this._inputElementValue.length); | ||
/** @private */ | ||
_markAllSelectionRange() { | ||
if (this._inputElementValue !== undefined) { | ||
this._setSelectionRange(0, this._inputElementValue.length); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_clearSelectionRange() { | ||
if (this._inputElementValue !== undefined) { | ||
const pos = this._inputElementValue ? this._inputElementValue.length : 0; | ||
this._setSelectionRange(pos, pos); | ||
/** @private */ | ||
_clearSelectionRange() { | ||
if (this._inputElementValue !== undefined) { | ||
const pos = this._inputElementValue ? this._inputElementValue.length : 0; | ||
this._setSelectionRange(pos, pos); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_closeOrCommit() { | ||
if (!this.opened && !this.loading) { | ||
this._commitValue(); | ||
} else { | ||
this.close(); | ||
/** @private */ | ||
_closeOrCommit() { | ||
if (!this.opened && !this.loading) { | ||
this._commitValue(); | ||
} else { | ||
this.close(); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_onEnter(e) { | ||
// should close on enter when custom values are allowed, input field is cleared, or when an existing | ||
// item is focused with keyboard. If auto open is disabled, under the same conditions, commit value. | ||
if ((this.opened || this.autoOpenDisabled) && (this.allowCustomValue || this._inputElementValue === '' || this._focusedIndex > -1)) { | ||
this._closeOrCommit(); | ||
/** @private */ | ||
_onEnter(e) { | ||
// should close on enter when custom values are allowed, input field is cleared, or when an existing | ||
// item is focused with keyboard. If auto open is disabled, under the same conditions, commit value. | ||
if ( | ||
(this.opened || this.autoOpenDisabled) && | ||
(this.allowCustomValue || this._inputElementValue === '' || this._focusedIndex > -1) | ||
) { | ||
this._closeOrCommit(); | ||
// Do not submit the surrounding form. | ||
e.preventDefault(); | ||
// Do not submit the surrounding form. | ||
e.preventDefault(); | ||
// Do not trigger global listeners | ||
e.stopPropagation(); | ||
// Do not trigger global listeners | ||
e.stopPropagation(); | ||
} | ||
} | ||
} | ||
/** | ||
* @param {!KeyboardEvent} e | ||
* @protected | ||
*/ | ||
_onEscape(e) { | ||
if (this.autoOpenDisabled) { | ||
this._focusedIndex = -1; | ||
this.cancel(); | ||
} else if (this.opened) { | ||
this._stopPropagation(e); | ||
if (this._focusedIndex > -1) { | ||
/** | ||
* @param {!KeyboardEvent} e | ||
* @protected | ||
*/ | ||
_onEscape(e) { | ||
if (this.autoOpenDisabled) { | ||
this._focusedIndex = -1; | ||
this._revertInputValue(); | ||
} else { | ||
this.cancel(); | ||
} | ||
} | ||
} | ||
} else if (this.opened) { | ||
this._stopPropagation(e); | ||
/** @private */ | ||
_toggleElementChanged(toggleElement) { | ||
if (toggleElement) { | ||
// Don't blur the input on toggle mousedown | ||
toggleElement.addEventListener('mousedown', e => e.preventDefault()); | ||
// Unfocus previously focused element if focus is not inside combo box (on touch devices) | ||
toggleElement.addEventListener('click', e => { | ||
if (this.$.overlay.touchDevice && !this.hasAttribute('focused')) { | ||
document.activeElement.blur(); | ||
if (this._focusedIndex > -1) { | ||
this._focusedIndex = -1; | ||
this._revertInputValue(); | ||
} else { | ||
this.cancel(); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
/** | ||
* Clears the current value. | ||
* @protected | ||
*/ | ||
_clear() { | ||
this.selectedItem = null; | ||
if (this.allowCustomValue) { | ||
this.value = ''; | ||
/** @private */ | ||
_toggleElementChanged(toggleElement) { | ||
if (toggleElement) { | ||
// Don't blur the input on toggle mousedown | ||
toggleElement.addEventListener('mousedown', (e) => e.preventDefault()); | ||
// Unfocus previously focused element if focus is not inside combo box (on touch devices) | ||
toggleElement.addEventListener('click', () => { | ||
if (this.$.overlay.touchDevice && !this.hasAttribute('focused')) { | ||
document.activeElement.blur(); | ||
} | ||
}); | ||
} | ||
} | ||
this._detectAndDispatchChange(); | ||
} | ||
/** | ||
* Clears the current value. | ||
* @protected | ||
*/ | ||
_clear() { | ||
this.selectedItem = null; | ||
/** | ||
* Reverts back to original value. | ||
*/ | ||
cancel() { | ||
this._revertInputValueToValue(); | ||
// In the next _detectAndDispatchChange() call, the change detection should not pass | ||
this._lastCommittedValue = this.value; | ||
this._closeOrCommit(); | ||
} | ||
if (this.allowCustomValue) { | ||
this.value = ''; | ||
} | ||
/** @private */ | ||
_onOpened() { | ||
// Pre P2 iron-list used a debouncer to render. Now that we synchronously render items, | ||
// we need to flush the DOM to make sure it doesn't get flushed in the middle of _render call | ||
// because that will cause problems to say the least. | ||
flush(); | ||
this._detectAndDispatchChange(); | ||
} | ||
// With iron-list v1.3.9, calling `notifyResize()` no longer renders | ||
// the items synchronously. It is required to have the items rendered | ||
// before we update the overlay and the list positions and sizes. | ||
this.$.overlay.ensureItemsRendered(); | ||
this.$.overlay._selector.toggleScrollListener(true); | ||
/** | ||
* Reverts back to original value. | ||
*/ | ||
cancel() { | ||
this._revertInputValueToValue(); | ||
// In the next _detectAndDispatchChange() call, the change detection should not pass | ||
this._lastCommittedValue = this.value; | ||
this._closeOrCommit(); | ||
} | ||
// Ensure metrics are up-to-date | ||
this.$.overlay.updateViewportBoundaries(); | ||
// Force iron-list to create reusable nodes. Otherwise it will only start | ||
// doing that in scroll listener, which is especially slow in Edge. | ||
this.$.overlay._selector._increasePoolIfNeeded(); | ||
setTimeout(() => this._resizeDropdown(), 1); | ||
// Defer scroll position adjustment to prevent freeze in Edge | ||
window.requestAnimationFrame(() => this.$.overlay.adjustScrollPosition()); | ||
/** @private */ | ||
_onOpened() { | ||
// Pre P2 iron-list used a debouncer to render. Now that we synchronously render items, | ||
// we need to flush the DOM to make sure it doesn't get flushed in the middle of _render call | ||
// because that will cause problems to say the least. | ||
flush(); | ||
// With iron-list v1.3.9, calling `notifyResize()` no longer renders | ||
// the items synchronously. It is required to have the items rendered | ||
// before we update the overlay and the list positions and sizes. | ||
this.$.overlay.ensureItemsRendered(); | ||
this.$.overlay._selector.toggleScrollListener(true); | ||
// _detectAndDispatchChange() should not consider value changes done before opening | ||
this._lastCommittedValue = this.value; | ||
} | ||
// Ensure metrics are up-to-date | ||
this.$.overlay.updateViewportBoundaries(); | ||
// Force iron-list to create reusable nodes. Otherwise it will only start | ||
// doing that in scroll listener, which might affect performance. | ||
// See https://github.com/vaadin/vaadin-combo-box/pull/776 | ||
this.$.overlay._selector._increasePoolIfNeeded(); | ||
setTimeout(() => this._resizeDropdown(), 1); | ||
// Defer scroll position adjustment to improve performance. | ||
window.requestAnimationFrame(() => this.$.overlay.adjustScrollPosition()); | ||
/** @private */ | ||
_onClosed() { | ||
// Happens when the overlay is closed by clicking outside | ||
if (this.opened) { | ||
this.close(); | ||
// _detectAndDispatchChange() should not consider value changes done before opening | ||
this._lastCommittedValue = this.value; | ||
} | ||
if (!this.loading || this.allowCustomValue) { | ||
this._commitValue(); | ||
} | ||
} | ||
/** @private */ | ||
_commitValue() { | ||
if (this.$.overlay._items && this._focusedIndex > -1) { | ||
const focusedItem = this.$.overlay._items[this._focusedIndex]; | ||
if (this.selectedItem !== focusedItem) { | ||
this.selectedItem = focusedItem; | ||
/** @private */ | ||
_onClosed() { | ||
// Happens when the overlay is closed by clicking outside | ||
if (this.opened) { | ||
this.close(); | ||
} | ||
// make sure input field is updated in case value doesn't change (i.e. FOO -> foo) | ||
this._inputElementValue = this._getItemLabel(this.selectedItem); | ||
} else if (this._inputElementValue === '' || this._inputElementValue === undefined) { | ||
this.selectedItem = null; | ||
if (this.allowCustomValue) { | ||
this.value = ''; | ||
if (!this.loading || this.allowCustomValue) { | ||
this._commitValue(); | ||
} | ||
} else { | ||
const itemsMatchedByLabel = this.filteredItems | ||
&& this.filteredItems.filter(item => this._getItemLabel(item) === this._inputElementValue) | ||
|| []; | ||
if (this.allowCustomValue | ||
// to prevent a repetitive input value being saved after pressing ESC and Tab. | ||
&& !itemsMatchedByLabel.length) { | ||
} | ||
const e = new CustomEvent('custom-value-set', {detail: this._inputElementValue, composed: true, cancelable: true, bubbles: true}); | ||
this.dispatchEvent(e); | ||
if (!e.defaultPrevented) { | ||
const customValue = this._inputElementValue; | ||
this._selectItemForValue(customValue); | ||
this.value = customValue; | ||
/** @private */ | ||
_commitValue() { | ||
if (this.$.overlay._items && this._focusedIndex > -1) { | ||
const focusedItem = this.$.overlay._items[this._focusedIndex]; | ||
if (this.selectedItem !== focusedItem) { | ||
this.selectedItem = focusedItem; | ||
} | ||
} else if (!this.allowCustomValue && !this.opened && itemsMatchedByLabel.length == 1) { | ||
this.value = this._getItemValue(itemsMatchedByLabel[0]); | ||
// make sure input field is updated in case value doesn't change (i.e. FOO -> foo) | ||
this._inputElementValue = this._getItemLabel(this.selectedItem); | ||
} else if (this._inputElementValue === '' || this._inputElementValue === undefined) { | ||
this.selectedItem = null; | ||
if (this.allowCustomValue) { | ||
this.value = ''; | ||
} | ||
} else { | ||
this._inputElementValue = this.selectedItem ? this._getItemLabel(this.selectedItem) : (this.value || ''); | ||
const itemsMatchedByLabel = | ||
(this.filteredItems && | ||
this.filteredItems.filter((item) => this._getItemLabel(item) === this._inputElementValue)) || | ||
[]; | ||
if ( | ||
this.allowCustomValue && | ||
// to prevent a repetitive input value being saved after pressing ESC and Tab. | ||
!itemsMatchedByLabel.length | ||
) { | ||
const e = new CustomEvent('custom-value-set', { | ||
detail: this._inputElementValue, | ||
composed: true, | ||
cancelable: true, | ||
bubbles: true | ||
}); | ||
this.dispatchEvent(e); | ||
if (!e.defaultPrevented) { | ||
const customValue = this._inputElementValue; | ||
this._selectItemForValue(customValue); | ||
this.value = customValue; | ||
} | ||
} else if (!this.allowCustomValue && !this.opened && itemsMatchedByLabel.length == 1) { | ||
this.value = this._getItemValue(itemsMatchedByLabel[0]); | ||
} else { | ||
this._inputElementValue = this.selectedItem ? this._getItemLabel(this.selectedItem) : this.value || ''; | ||
} | ||
} | ||
} | ||
this._detectAndDispatchChange(); | ||
this._detectAndDispatchChange(); | ||
this._clearSelectionRange(); | ||
this._clearSelectionRange(); | ||
if (!this.dataProvider) { | ||
this.filter = ''; | ||
if (!this.dataProvider) { | ||
this.filter = ''; | ||
} | ||
} | ||
} | ||
/** | ||
* @return {string} | ||
* @protected | ||
*/ | ||
get _propertyForValue() { | ||
return 'value'; | ||
} | ||
/** | ||
* Filtering and items handling | ||
* @param {!Event} e | ||
* @protected | ||
*/ | ||
_inputValueChanged(e) { | ||
// Handle only input events from our inputElement. | ||
if (e.composedPath().indexOf(this.inputElement) !== -1) { | ||
this._inputElementValue = this.inputElement[this._propertyForValue]; | ||
this._filterFromInput(e); | ||
/** | ||
* @return {string} | ||
* @protected | ||
*/ | ||
get _propertyForValue() { | ||
return 'value'; | ||
} | ||
} | ||
/** @private */ | ||
_filterFromInput(e) { | ||
if (!this.opened && !e.__fromClearButton && !this.autoOpenDisabled) { | ||
this.open(); | ||
/** | ||
* Filtering and items handling | ||
* @param {!Event} e | ||
* @protected | ||
*/ | ||
_inputValueChanged(e) { | ||
// Handle only input events from our inputElement. | ||
if (e.composedPath().indexOf(this.inputElement) !== -1) { | ||
this._inputElementValue = this.inputElement[this._propertyForValue]; | ||
this._filterFromInput(e); | ||
} | ||
} | ||
if (this.filter === this._inputElementValue) { | ||
// 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); | ||
} else { | ||
this.filter = this._inputElementValue; | ||
} | ||
} | ||
/** @private */ | ||
_filterFromInput(e) { | ||
if (!this.opened && !e.__fromClearButton && !this.autoOpenDisabled) { | ||
this.open(); | ||
} | ||
/** @private */ | ||
_itemLabelPathChanged(itemLabelPath, oldItemLabelPath) { | ||
if (typeof itemLabelPath !== 'string') { | ||
console.error('You should set itemLabelPath to a valid string'); | ||
if (this.filter === this._inputElementValue) { | ||
// 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); | ||
} else { | ||
this.filter = this._inputElementValue; | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_filterChanged(filter, itemValuePath, itemLabelPath) { | ||
if (filter === undefined) { | ||
return; | ||
/** @private */ | ||
_itemLabelPathChanged(itemLabelPath) { | ||
if (typeof itemLabelPath !== 'string') { | ||
console.error('You should set itemLabelPath to a valid string'); | ||
} | ||
} | ||
// Notify the dropdown about filter changing, so to let it skip the | ||
// scrolling restore | ||
this.$.overlay.filterChanged = true; | ||
/** @private */ | ||
_filterChanged(filter, itemValuePath, itemLabelPath) { | ||
if (filter === undefined) { | ||
return; | ||
} | ||
if (this.items) { | ||
this.filteredItems = this._filterItems(this.items, filter); | ||
} else { | ||
// With certain use cases (e. g., external filtering), `items` are | ||
// undefined. Filtering is unnecessary per se, but the filteredItems | ||
// observer should still be invoked to update focused item. | ||
this._filteredItemsChanged({path: 'filteredItems', value: this.filteredItems}, itemValuePath, itemLabelPath); | ||
// Notify the dropdown about filter changing, so to let it skip the | ||
// scrolling restore | ||
this.$.overlay.filterChanged = true; | ||
if (this.items) { | ||
this.filteredItems = this._filterItems(this.items, filter); | ||
} else { | ||
// With certain use cases (e. g., external filtering), `items` are | ||
// undefined. Filtering is unnecessary per se, but the filteredItems | ||
// observer should still be invoked to update focused item. | ||
this._filteredItemsChanged({ path: 'filteredItems', value: this.filteredItems }, itemValuePath, itemLabelPath); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_loadingChanged(loading) { | ||
if (loading) { | ||
this._focusedIndex = -1; | ||
/** @private */ | ||
_loadingChanged(loading) { | ||
if (loading) { | ||
this._focusedIndex = -1; | ||
} | ||
} | ||
} | ||
/** @protected */ | ||
_revertInputValue() { | ||
if (this.filter !== '') { | ||
this._inputElementValue = this.filter; | ||
} else { | ||
this._revertInputValueToValue(); | ||
/** @protected */ | ||
_revertInputValue() { | ||
if (this.filter !== '') { | ||
this._inputElementValue = this.filter; | ||
} else { | ||
this._revertInputValueToValue(); | ||
} | ||
this._clearSelectionRange(); | ||
} | ||
this._clearSelectionRange(); | ||
} | ||
/** @private */ | ||
_revertInputValueToValue() { | ||
if (this.allowCustomValue && !this.selectedItem) { | ||
this._inputElementValue = this.value; | ||
} else { | ||
this._inputElementValue = this._getItemLabel(this.selectedItem); | ||
/** @private */ | ||
_revertInputValueToValue() { | ||
if (this.allowCustomValue && !this.selectedItem) { | ||
this._inputElementValue = this.value; | ||
} else { | ||
this._inputElementValue = this._getItemLabel(this.selectedItem); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_resizeDropdown() { | ||
this.$.overlay.$.dropdown.notifyResize(); | ||
} | ||
/** @private */ | ||
_resizeDropdown() { | ||
this.$.overlay.$.dropdown.notifyResize(); | ||
} | ||
/** @private */ | ||
_updateHasValue(hasValue) { | ||
if (hasValue) { | ||
this.setAttribute('has-value', ''); | ||
} else { | ||
this.removeAttribute('has-value'); | ||
/** @private */ | ||
_updateHasValue(hasValue) { | ||
if (hasValue) { | ||
this.setAttribute('has-value', ''); | ||
} else { | ||
this.removeAttribute('has-value'); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_selectedItemChanged(selectedItem, itemLabelPath) { | ||
if (selectedItem === null || selectedItem === undefined) { | ||
if (this.filteredItems) { | ||
if (!this.allowCustomValue) { | ||
this.value = ''; | ||
/** @private */ | ||
_selectedItemChanged(selectedItem) { | ||
if (selectedItem === null || selectedItem === undefined) { | ||
if (this.filteredItems) { | ||
if (!this.allowCustomValue) { | ||
this.value = ''; | ||
} | ||
this._updateHasValue(this.value !== ''); | ||
this._inputElementValue = this.value; | ||
} | ||
this._updateHasValue(this.value !== ''); | ||
this._inputElementValue = this.value; | ||
} | ||
} else { | ||
const value = this._getItemValue(selectedItem); | ||
if (this.value !== value) { | ||
this.value = value; | ||
} else { | ||
const value = this._getItemValue(selectedItem); | ||
if (this.value !== value) { | ||
// The value was changed to something else in value-changed listener, | ||
// so prevent from resetting it to the previous value. | ||
return; | ||
this.value = value; | ||
if (this.value !== value) { | ||
// The value was changed to something else in value-changed listener, | ||
// so prevent from resetting it to the previous value. | ||
return; | ||
} | ||
} | ||
} | ||
this._updateHasValue(true); | ||
this._inputElementValue = this._getItemLabel(selectedItem); | ||
this._updateHasValue(true); | ||
this._inputElementValue = this._getItemLabel(selectedItem); | ||
// Could not be defined in 1.x because ready is called after all prop-setters | ||
if (this.inputElement) { | ||
this.inputElement[this._propertyForValue] = this._inputElementValue; | ||
// Could not be defined in 1.x because ready is called after all prop-setters | ||
if (this.inputElement) { | ||
this.inputElement[this._propertyForValue] = this._inputElementValue; | ||
} | ||
} | ||
} | ||
this.$.overlay._selectedItem = selectedItem; | ||
if (this.filteredItems && this.$.overlay._items) { | ||
this._focusedIndex = this.filteredItems.indexOf(selectedItem); | ||
this.$.overlay._selectedItem = selectedItem; | ||
if (this.filteredItems && this.$.overlay._items) { | ||
this._focusedIndex = this.filteredItems.indexOf(selectedItem); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_valueChanged(value, oldVal) { | ||
if (value === '' && oldVal === undefined) { // initializing, no need to do anything (#554) | ||
return; | ||
} | ||
/** @private */ | ||
_valueChanged(value, oldVal) { | ||
if (value === '' && oldVal === undefined) { | ||
// initializing, no need to do anything (#554) | ||
return; | ||
} | ||
if (this._isValidValue(value)) { | ||
let item; | ||
if (this._getItemValue(this.selectedItem) !== value) { | ||
this._selectItemForValue(value); | ||
if (this._isValidValue(value)) { | ||
let item; | ||
if (this._getItemValue(this.selectedItem) !== value) { | ||
this._selectItemForValue(value); | ||
} else { | ||
item = this.selectedItem; | ||
} | ||
if (!item && this.allowCustomValue) { | ||
this._inputElementValue = value; | ||
} | ||
this._updateHasValue(this.value !== ''); | ||
} else { | ||
item = this.selectedItem; | ||
this.selectedItem = null; | ||
} | ||
// In the next _detectAndDispatchChange() call, the change detection should pass | ||
this._lastCommittedValue = undefined; | ||
} | ||
if (!item && this.allowCustomValue) { | ||
this._inputElementValue = value; | ||
/** @private */ | ||
_detectAndDispatchChange() { | ||
if (this.value !== this._lastCommittedValue) { | ||
this.dispatchEvent(new CustomEvent('change', { bubbles: true })); | ||
this._lastCommittedValue = this.value; | ||
} | ||
this._updateHasValue(this.value !== ''); | ||
} else { | ||
this.selectedItem = null; | ||
} | ||
// In the next _detectAndDispatchChange() call, the change detection should pass | ||
this._lastCommittedValue = undefined; | ||
} | ||
/** @private */ | ||
_detectAndDispatchChange() { | ||
if (this.value !== this._lastCommittedValue) { | ||
this.dispatchEvent(new CustomEvent('change', {bubbles: true})); | ||
this._lastCommittedValue = this.value; | ||
/** @private */ | ||
_itemsChanged(items, oldItems) { | ||
this._ensureItemsOrDataProvider(() => { | ||
this.items = oldItems; | ||
}); | ||
} | ||
} | ||
/** @private */ | ||
_itemsChanged(items, oldItems) { | ||
this._ensureItemsOrDataProvider(() => { | ||
this.items = oldItems; | ||
}); | ||
} | ||
/** @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; | ||
} | ||
/** @private */ | ||
_itemsOrPathsChanged(e, itemValuePath, itemLabelPath) { | ||
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; | ||
} | ||
const valueIndex = this._indexOfValue(this.value, this.items); | ||
this._focusedIndex = valueIndex; | ||
const valueIndex = this._indexOfValue(this.value, this.items); | ||
this._focusedIndex = valueIndex; | ||
const item = valueIndex > -1 && this.items[valueIndex]; | ||
if (item) { | ||
this.selectedItem = item; | ||
const item = valueIndex > -1 && this.items[valueIndex]; | ||
if (item) { | ||
this.selectedItem = item; | ||
} | ||
} | ||
this.__previousItems = e.value; | ||
} | ||
this.__previousItems = e.value; | ||
} | ||
/** @private */ | ||
_filteredItemsChanged(e, itemValuePath, itemLabelPath) { | ||
if (e.path === 'filteredItems' || e.path === 'filteredItems.splices') { | ||
this._setOverlayItems(this.filteredItems); | ||
/** @private */ | ||
_filteredItemsChanged(e) { | ||
if (e.path === 'filteredItems' || e.path === 'filteredItems.splices') { | ||
this._setOverlayItems(this.filteredItems); | ||
this._focusedIndex = this.opened || this.autoOpenDisabled ? | ||
this.$.overlay.indexOfLabel(this.filter) : | ||
this._indexOfValue(this.value, this.filteredItems); | ||
this._focusedIndex = | ||
this.opened || this.autoOpenDisabled | ||
? this.$.overlay.indexOfLabel(this.filter) | ||
: this._indexOfValue(this.value, this.filteredItems); | ||
if (this.opened) { | ||
this._repositionOverlay(); | ||
if (this.opened) { | ||
this._repositionOverlay(); | ||
} | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_filterItems(arr, filter) { | ||
if (!arr) { | ||
return arr; | ||
} | ||
/** @private */ | ||
_filterItems(arr, filter) { | ||
if (!arr) { | ||
return arr; | ||
} | ||
const filteredItems = arr.filter(item => { | ||
filter = filter ? filter.toString().toLowerCase() : ''; | ||
// Check if item contains input value. | ||
return this._getItemLabel(item).toString().toLowerCase().indexOf(filter) > -1; | ||
}); | ||
const filteredItems = arr.filter((item) => { | ||
filter = filter ? filter.toString().toLowerCase() : ''; | ||
// Check if item contains input value. | ||
return this._getItemLabel(item).toString().toLowerCase().indexOf(filter) > -1; | ||
}); | ||
return filteredItems; | ||
} | ||
return filteredItems; | ||
} | ||
/** @private */ | ||
_selectItemForValue(value) { | ||
const valueIndex = this._indexOfValue(value, this.filteredItems); | ||
const previouslySelectedItem = this.selectedItem; | ||
/** @private */ | ||
_selectItemForValue(value) { | ||
const valueIndex = this._indexOfValue(value, this.filteredItems); | ||
const previouslySelectedItem = this.selectedItem; | ||
this.selectedItem = valueIndex >= 0 | ||
? this.filteredItems[valueIndex] | ||
: (this.dataProvider && this.selectedItem === undefined) | ||
? undefined | ||
: null; | ||
this.selectedItem = | ||
valueIndex >= 0 | ||
? this.filteredItems[valueIndex] | ||
: this.dataProvider && this.selectedItem === undefined | ||
? undefined | ||
: null; | ||
if (this.selectedItem === null && previouslySelectedItem === null) { | ||
this._selectedItemChanged(this.selectedItem); | ||
if (this.selectedItem === null && previouslySelectedItem === null) { | ||
this._selectedItemChanged(this.selectedItem); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_setOverlayItems(items) { | ||
this.$.overlay.set('_items', items); | ||
} | ||
/** @private */ | ||
_setOverlayItems(items) { | ||
this.$.overlay.set('_items', items); | ||
} | ||
/** @private */ | ||
_repositionOverlay() { | ||
// async needed to reposition correctly after filtering | ||
// (especially when aligned on top of input) | ||
this.__repositionOverlayDebouncer = Debouncer.debounce( | ||
this.__repositionOverlayDebouncer, | ||
// Long debounce: sizing updates invoke multiple styling rounds, | ||
// which is very slow in Edge | ||
timeOut.after(500), | ||
() => { | ||
const selector = this.$.overlay._selector; | ||
if (!selector._isClientFull()) { | ||
// Due to the mismatch of the Y position of the item rendered | ||
// at the top of the scrolling list with some specific scroll | ||
// position values (2324, 3486, 6972, 60972, 95757 etc.) | ||
// iron-list loops the increasing of the pool and adds | ||
// too many items to the DOM. | ||
// Adjusting scroll position to equal the current scrollTop value | ||
// to avoid looping. | ||
selector._resetScrollPosition(selector._physicalTop); | ||
/** @private */ | ||
_repositionOverlay() { | ||
// async needed to reposition correctly after filtering | ||
// (especially when aligned on top of input) | ||
this.__repositionOverlayDebouncer = Debouncer.debounce( | ||
this.__repositionOverlayDebouncer, | ||
// Long debounce: sizing updates invoke multiple styling rounds, | ||
// which might affect performance, especially in old browsers. | ||
// See https://github.com/vaadin/vaadin-combo-box/pull/800 | ||
timeOut.after(500), | ||
() => { | ||
const selector = this.$.overlay._selector; | ||
if (!selector._isClientFull()) { | ||
// Due to the mismatch of the Y position of the item rendered | ||
// at the top of the scrolling list with some specific scroll | ||
// position values (2324, 3486, 6972, 60972, 95757 etc.) | ||
// iron-list loops the increasing of the pool and adds | ||
// too many items to the DOM. | ||
// Adjusting scroll position to equal the current scrollTop value | ||
// to avoid looping. | ||
selector._resetScrollPosition(selector._physicalTop); | ||
} | ||
this._resizeDropdown(); | ||
this.$.overlay.updateViewportBoundaries(); | ||
this.$.overlay.ensureItemsRendered(); | ||
selector.notifyResize(); | ||
flush(); | ||
} | ||
this._resizeDropdown(); | ||
this.$.overlay.updateViewportBoundaries(); | ||
this.$.overlay.ensureItemsRendered(); | ||
selector.notifyResize(); | ||
flush(); | ||
} | ||
); | ||
} | ||
); | ||
} | ||
/** @private */ | ||
_indexOfValue(value, items) { | ||
if (items && this._isValidValue(value)) { | ||
for (let i = 0; i < items.length; i++) { | ||
if (this._getItemValue(items[i]) === value) { | ||
return i; | ||
/** @private */ | ||
_indexOfValue(value, items) { | ||
if (items && this._isValidValue(value)) { | ||
for (let i = 0; i < items.length; i++) { | ||
if (this._getItemValue(items[i]) === value) { | ||
return i; | ||
} | ||
} | ||
} | ||
return -1; | ||
} | ||
return -1; | ||
} | ||
/** | ||
* Checks if the value is supported as an item value in this control. | ||
* @private | ||
*/ | ||
_isValidValue(value) { | ||
return value !== undefined && value !== null; | ||
} | ||
/** | ||
* Checks if the value is supported as an item value in this control. | ||
* @private | ||
*/ | ||
_isValidValue(value) { | ||
return value !== undefined && value !== null; | ||
} | ||
/** @private */ | ||
_overlaySelectedItemChanged(e) { | ||
// stop this private event from leaking outside. | ||
e.stopPropagation(); | ||
/** @private */ | ||
_overlaySelectedItemChanged(e) { | ||
// stop this private event from leaking outside. | ||
e.stopPropagation(); | ||
if (e.detail.item instanceof ComboBoxPlaceholder) { | ||
// Placeholder items should not be selectable. | ||
return; | ||
} | ||
if (e.detail.item instanceof ComboBoxPlaceholder) { | ||
// Placeholder items should not be selectable. | ||
return; | ||
if (this.opened) { | ||
this._focusedIndex = this.filteredItems.indexOf(e.detail.item); | ||
this.close(); | ||
} else if (this.selectedItem !== e.detail.item) { | ||
this.selectedItem = e.detail.item; | ||
this._detectAndDispatchChange(); | ||
} | ||
} | ||
if (this.opened) { | ||
this._focusedIndex = this.filteredItems.indexOf(e.detail.item); | ||
this.close(); | ||
} else if (this.selectedItem !== e.detail.item) { | ||
this.selectedItem = e.detail.item; | ||
this._detectAndDispatchChange(); | ||
/** @private */ | ||
_onFocusout(event) { | ||
// Fixes the problem with `focusout` happening when clicking on the scroll bar on Edge | ||
const dropdown = this.$.overlay.$.dropdown; | ||
if (dropdown && dropdown.$ && event.relatedTarget === dropdown.$.overlay) { | ||
event.composedPath()[0].focus(); | ||
return; | ||
} | ||
if (!this._closeOnBlurIsPrevented) { | ||
this._closeOrCommit(); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_onFocusout(event) { | ||
// Fixes the problem with `focusout` happening when clicking on the scroll bar on Edge | ||
const dropdown = this.$.overlay.$.dropdown; | ||
if (dropdown && dropdown.$ && event.relatedTarget === dropdown.$.overlay) { | ||
event.composedPath()[0].focus(); | ||
return; | ||
/** @private */ | ||
_onTouchend(event) { | ||
if (!this._clearElement || event.composedPath()[0] !== this._clearElement) { | ||
return; | ||
} | ||
event.preventDefault(); | ||
this._clear(); | ||
} | ||
if (!this._closeOnBlurIsPrevented) { | ||
this._closeOrCommit(); | ||
/** | ||
* 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()); | ||
} | ||
} | ||
/** @private */ | ||
_onTouchend(event) { | ||
if (!this._clearElement || event.composedPath()[0] !== this._clearElement) { | ||
return; | ||
/** | ||
* Returns true if the current input value satisfies all constraints (if any) | ||
* | ||
* You can override the `checkValidity` method for custom validations. | ||
* @return {boolean | undefined} | ||
*/ | ||
checkValidity() { | ||
if (this.inputElement.validate) { | ||
return this.inputElement.validate(); | ||
} | ||
} | ||
event.preventDefault(); | ||
this._clear(); | ||
} | ||
/** @private */ | ||
get _instanceProps() { | ||
return { | ||
item: true, | ||
index: true, | ||
selected: true, | ||
focused: true | ||
}; | ||
} | ||
/** | ||
* 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()); | ||
} | ||
/** @protected */ | ||
_ensureTemplatized() { | ||
if (!this._TemplateClass) { | ||
const tpl = this._itemTemplate || this._getRootTemplate(); | ||
if (tpl) { | ||
this._TemplateClass = templatize(tpl, this, { | ||
instanceProps: this._instanceProps, | ||
forwardHostProp: function (prop, value) { | ||
const items = this.$.overlay._selector.querySelectorAll('vaadin-combo-box-item'); | ||
Array.prototype.forEach.call(items, (item) => { | ||
if (item._itemTemplateInstance) { | ||
item._itemTemplateInstance.set(prop, value); | ||
item._itemTemplateInstance.notifyPath(prop, value, true); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
/** | ||
* Returns true if the current input value satisfies all constraints (if any) | ||
* | ||
* You can override the `checkValidity` method for custom validations. | ||
* @return {boolean | undefined} | ||
*/ | ||
checkValidity() { | ||
if (this.inputElement.validate) { | ||
return this.inputElement.validate(); | ||
/** @private */ | ||
_getRootTemplate() { | ||
return Array.prototype.filter.call(this.children, (elem) => elem.tagName === 'TEMPLATE')[0]; | ||
} | ||
} | ||
/** @private */ | ||
get _instanceProps() { | ||
return { | ||
item: true, | ||
index: true, | ||
selected: true, | ||
focused: true | ||
}; | ||
} | ||
/** @protected */ | ||
_preventInputBlur() { | ||
if (this._toggleElement) { | ||
this._toggleElement.addEventListener('click', this._preventDefault); | ||
} | ||
if (this._clearElement) { | ||
this._clearElement.addEventListener('click', this._preventDefault); | ||
} | ||
} | ||
/** @protected */ | ||
_ensureTemplatized() { | ||
if (!this._TemplateClass) { | ||
const tpl = this._itemTemplate || this._getRootTemplate(); | ||
if (tpl) { | ||
this._TemplateClass = templatize(tpl, this, { | ||
instanceProps: this._instanceProps, | ||
forwardHostProp: function(prop, value) { | ||
const items = this.$.overlay._selector.querySelectorAll('vaadin-combo-box-item'); | ||
Array.prototype.forEach.call(items, item => { | ||
if (item._itemTemplateInstance) { | ||
item._itemTemplateInstance.set(prop, value); | ||
item._itemTemplateInstance.notifyPath(prop, value, true); | ||
} | ||
}); | ||
} | ||
}); | ||
/** @protected */ | ||
_restoreInputBlur() { | ||
if (this._toggleElement) { | ||
this._toggleElement.removeEventListener('click', this._preventDefault); | ||
} | ||
if (this._clearElement) { | ||
this._clearElement.removeEventListener('click', this._preventDefault); | ||
} | ||
} | ||
} | ||
/** @private */ | ||
_getRootTemplate() { | ||
return Array.prototype.filter.call(this.children, elem => elem.tagName === 'TEMPLATE')[0]; | ||
} | ||
/** @protected */ | ||
_preventInputBlur() { | ||
if (this._toggleElement) { | ||
this._toggleElement.addEventListener('click', this._preventDefault); | ||
/** @private */ | ||
_preventDefault(e) { | ||
e.preventDefault(); | ||
} | ||
if (this._clearElement) { | ||
this._clearElement.addEventListener('click', this._preventDefault); | ||
} | ||
} | ||
/** @protected */ | ||
_restoreInputBlur() { | ||
if (this._toggleElement) { | ||
this._toggleElement.removeEventListener('click', this._preventDefault); | ||
/** | ||
* @param {!Event} e | ||
* @protected | ||
*/ | ||
_stopPropagation(e) { | ||
e.stopPropagation(); | ||
} | ||
if (this._clearElement) { | ||
this._clearElement.removeEventListener('click', this._preventDefault); | ||
} | ||
} | ||
/** @private */ | ||
_preventDefault(e) { | ||
e.preventDefault(); | ||
} | ||
/** | ||
* Fired when the value changes. | ||
* | ||
* @event value-changed | ||
* @param {Object} detail | ||
* @param {String} detail.value the combobox value | ||
*/ | ||
/** | ||
* @param {!Event} e | ||
* @protected | ||
*/ | ||
_stopPropagation(e) { | ||
e.stopPropagation(); | ||
} | ||
/** | ||
* Fired when selected item changes. | ||
* | ||
* @event selected-item-changed | ||
* @param {Object} detail | ||
* @param {Object|String} detail.value the selected item. Type is the same as the type of `items`. | ||
*/ | ||
/** | ||
* Fired when the value changes. | ||
* | ||
* @event value-changed | ||
* @param {Object} detail | ||
* @param {String} detail.value the combobox value | ||
*/ | ||
/** | ||
* Fired when the user sets a custom value. | ||
* @event custom-value-set | ||
* @param {String} detail the custom value | ||
*/ | ||
/** | ||
* Fired when selected item changes. | ||
* | ||
* @event selected-item-changed | ||
* @param {Object} detail | ||
* @param {Object|String} detail.value the selected item. Type is the same as the type of `items`. | ||
*/ | ||
/** | ||
* Fired when the user sets a custom value. | ||
* @event custom-value-set | ||
* @param {String} detail the custom value | ||
*/ | ||
/** | ||
* Fired when value changes. | ||
* To comply with https://developer.mozilla.org/en-US/docs/Web/Events/change | ||
* @event change | ||
*/ | ||
}; | ||
/** | ||
* Fired when value changes. | ||
* To comply with https://developer.mozilla.org/en-US/docs/Web/Events/change | ||
* @event change | ||
*/ | ||
}; |
/** | ||
@license | ||
Copyright (c) 2018 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
* @license | ||
* Copyright (c) 2020 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
/* | ||
@@ -7,0 +8,0 @@ * Placeholder object class representing items being loaded. |
@@ -1,28 +0,15 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file was automatically generated by | ||
* https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations | ||
* | ||
* To modify these typings, edit the source file(s): | ||
* src/vaadin-combo-box.js | ||
*/ | ||
import { TextFieldElement } from '@vaadin/vaadin-text-field/vaadin-text-field'; | ||
import { ControlStateMixin } from '@vaadin/vaadin-control-state-mixin/vaadin-control-state-mixin.js'; | ||
// tslint:disable:variable-name Describing an API that's defined elsewhere. | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import {PolymerElement} from '@polymer/polymer/polymer-element.js'; | ||
import { ComboBoxMixin } from './vaadin-combo-box-mixin.js'; | ||
import {ControlStateMixin} from '@vaadin/vaadin-control-state-mixin/vaadin-control-state-mixin.js'; | ||
import { ComboBoxDataProviderMixin } from './vaadin-combo-box-data-provider-mixin.js'; | ||
import {ThemableMixin} from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js'; | ||
import {ComboBoxMixin} from './vaadin-combo-box-mixin.js'; | ||
import { ComboBoxEventMap } from './interfaces'; | ||
import {ComboBoxDataProviderMixin} from './vaadin-combo-box-data-provider-mixin.js'; | ||
import {ElementMixin} from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js'; | ||
import {html} from '@polymer/polymer/lib/utils/html-tag.js'; | ||
/** | ||
@@ -51,4 +38,2 @@ * `<vaadin-combo-box>` is a combo box element combining a dropdown list with an | ||
* | ||
* This element can be used within an `iron-form`. | ||
* | ||
* ### Item rendering | ||
@@ -183,11 +168,12 @@ * | ||
* See [ThemableMixin – how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki) | ||
* | ||
* @fires {CustomEvent<string>} filter-changed | ||
* @fires {CustomEvent<boolean>} invalid-changed | ||
* @fires {CustomEvent<boolean>} opened-change | ||
* @fires {CustomEvent<unknown>} selected-item-changed | ||
* @fires {CustomEvent<string>} value-changed | ||
*/ | ||
declare class ComboBoxElement extends | ||
ElementMixin( | ||
ControlStateMixin( | ||
ComboBoxDataProviderMixin( | ||
ComboBoxMixin( | ||
ThemableMixin( | ||
PolymerElement))))) { | ||
declare class ComboBoxElement extends ElementMixin( | ||
ControlStateMixin(ComboBoxDataProviderMixin(ComboBoxMixin(ThemableMixin(HTMLElement)))) | ||
) { | ||
/** | ||
@@ -197,2 +183,3 @@ * Focusable element used by vaadin-control-state-mixin | ||
readonly focusElement: HTMLElement; | ||
autofocus: boolean; | ||
@@ -204,9 +191,14 @@ | ||
disabled: boolean; | ||
/** | ||
* Set to true to prevent the user from picking a value or typing in the input. | ||
*/ | ||
readonly: boolean; | ||
readonly inputElement: TextFieldElement|undefined; | ||
readonly inputElement: TextFieldElement | undefined; | ||
/** | ||
* The label for this element. | ||
*/ | ||
label: string|null|undefined; | ||
label: string | null | undefined; | ||
@@ -222,3 +214,3 @@ /** | ||
*/ | ||
preventInvalidInput: boolean|null|undefined; | ||
preventInvalidInput: boolean | null | undefined; | ||
@@ -228,3 +220,3 @@ /** | ||
*/ | ||
pattern: string|null|undefined; | ||
pattern: string | null | undefined; | ||
@@ -235,3 +227,3 @@ /** | ||
*/ | ||
errorMessage: string|null|undefined; | ||
errorMessage: string | null | undefined; | ||
@@ -247,3 +239,3 @@ /** | ||
*/ | ||
helperText: string|null|undefined; | ||
helperText: string | null | undefined; | ||
@@ -255,17 +247,22 @@ /** | ||
clearButtonVisible: boolean; | ||
ready(): void; | ||
attributeChangedCallback(name: string, oldValue: string|null, newValue: string|null): void; | ||
connectedCallback(): void; | ||
disconnectedCallback(): void; | ||
addEventListener<K extends keyof ComboBoxEventMap>( | ||
type: K, | ||
listener: (this: ComboBoxElement, ev: ComboBoxEventMap[K]) => void, | ||
options?: boolean | AddEventListenerOptions | ||
): void; | ||
removeEventListener<K extends keyof ComboBoxEventMap>( | ||
type: K, | ||
listener: (this: ComboBoxElement, ev: ComboBoxEventMap[K]) => void, | ||
options?: boolean | EventListenerOptions | ||
): void; | ||
} | ||
declare global { | ||
interface HTMLElementTagNameMap { | ||
"vaadin-combo-box": ComboBoxElement; | ||
'vaadin-combo-box': ComboBoxElement; | ||
} | ||
} | ||
export {ComboBoxElement}; | ||
import {TextFieldElement} from '@vaadin/vaadin-text-field/vaadin-text-field'; | ||
export { ComboBoxElement }; |
/** | ||
@license | ||
Copyright (c) 2017 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
import '@vaadin/vaadin-text-field/src/vaadin-text-field.js'; | ||
* @license | ||
* Copyright (c) 2020 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js'; | ||
import { ControlStateMixin } from '@vaadin/vaadin-control-state-mixin/vaadin-control-state-mixin.js'; | ||
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js'; | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
import { ComboBoxMixin } from './vaadin-combo-box-mixin.js'; | ||
import '@vaadin/vaadin-text-field/src/vaadin-text-field.js'; | ||
import './vaadin-combo-box-dropdown-wrapper.js'; | ||
import { ComboBoxDataProviderMixin } from './vaadin-combo-box-data-provider-mixin.js'; | ||
import { ElementMixin } from '@vaadin/vaadin-element-mixin/vaadin-element-mixin.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
/** | ||
@@ -39,4 +38,2 @@ * `<vaadin-combo-box>` is a combo box element combining a dropdown list with an | ||
* | ||
* This element can be used within an `iron-form`. | ||
* | ||
* ### Item rendering | ||
@@ -172,3 +169,9 @@ * | ||
* | ||
* @extends PolymerElement | ||
* @fires {CustomEvent<string>} filter-changed | ||
* @fires {CustomEvent<boolean>} invalid-changed | ||
* @fires {CustomEvent<boolean>} opened-change | ||
* @fires {CustomEvent<unknown>} selected-item-changed | ||
* @fires {CustomEvent<string>} value-changed | ||
* | ||
* @extends HTMLElement | ||
* @mixes ElementMixin | ||
@@ -179,42 +182,68 @@ * @mixes ControlStateMixin | ||
* @mixes ThemableMixin | ||
* @demo demo/index.html | ||
*/ | ||
class ComboBoxElement extends | ||
ElementMixin( | ||
ControlStateMixin( | ||
ThemableMixin( | ||
ComboBoxDataProviderMixin( | ||
ComboBoxMixin(PolymerElement))))) { | ||
class ComboBoxElement extends ElementMixin( | ||
ControlStateMixin(ThemableMixin(ComboBoxDataProviderMixin(ComboBoxMixin(PolymerElement)))) | ||
) { | ||
static get template() { | ||
return html` | ||
<style> | ||
:host { | ||
display: inline-block; | ||
} | ||
<style> | ||
:host { | ||
display: inline-block; | ||
} | ||
:host([hidden]) { | ||
display: none !important; | ||
} | ||
:host([hidden]) { | ||
display: none !important; | ||
} | ||
:host([opened]) { | ||
pointer-events: auto; | ||
} | ||
:host([opened]) { | ||
pointer-events: auto; | ||
} | ||
[part="text-field"] { | ||
width: 100%; | ||
min-width: 0; | ||
} | ||
</style> | ||
[part='text-field'] { | ||
width: 100%; | ||
min-width: 0; | ||
} | ||
</style> | ||
<vaadin-text-field part="text-field" id="input" pattern="[[pattern]]" prevent-invalid-input="[[preventInvalidInput]]" value="{{_inputElementValue}}" autocomplete="off" invalid="[[invalid]]" label="[[label]]" name="[[name]]" placeholder="[[placeholder]]" required="[[required]]" disabled="[[disabled]]" readonly="[[readonly]]" helper-text="[[helperText]]" error-message="[[errorMessage]]" autocapitalize="none" autofocus="[[autofocus]]" on-change="_stopPropagation" on-input="_inputValueChanged" clear-button-visible="[[clearButtonVisible]]" theme\$="[[theme]]"> | ||
<slot name="prefix" slot="prefix"></slot> | ||
<slot name="helper" slot="helper">[[helperText]]</slot> | ||
<vaadin-text-field | ||
part="text-field" | ||
id="input" | ||
pattern="[[pattern]]" | ||
prevent-invalid-input="[[preventInvalidInput]]" | ||
value="{{_inputElementValue}}" | ||
autocomplete="off" | ||
invalid="[[invalid]]" | ||
label="[[label]]" | ||
name="[[name]]" | ||
placeholder="[[placeholder]]" | ||
required="[[required]]" | ||
disabled="[[disabled]]" | ||
readonly="[[readonly]]" | ||
helper-text="[[helperText]]" | ||
error-message="[[errorMessage]]" | ||
autocapitalize="none" | ||
autofocus="[[autofocus]]" | ||
on-change="_stopPropagation" | ||
on-input="_inputValueChanged" | ||
clear-button-visible="[[clearButtonVisible]]" | ||
theme$="[[theme]]" | ||
> | ||
<slot name="prefix" slot="prefix"></slot> | ||
<slot name="helper" slot="helper">[[helperText]]</slot> | ||
<div part="toggle-button" id="toggleButton" slot="suffix" role="button" aria-label="Toggle"></div> | ||
<div part="toggle-button" id="toggleButton" slot="suffix" role="button" aria-label="Toggle"></div> | ||
</vaadin-text-field> | ||
</vaadin-text-field> | ||
<vaadin-combo-box-dropdown-wrapper id="overlay" opened="[[opened]]" renderer="[[renderer]]" position-target="[[_getPositionTarget()]]" _focused-index="[[_focusedIndex]]" _item-id-path="[[itemIdPath]]" _item-label-path="[[itemLabelPath]]" loading="[[loading]]" theme="[[theme]]"> | ||
</vaadin-combo-box-dropdown-wrapper> | ||
`; | ||
<vaadin-combo-box-dropdown-wrapper | ||
id="overlay" | ||
opened="[[opened]]" | ||
renderer="[[renderer]]" | ||
position-target="[[_getPositionTarget()]]" | ||
_focused-index="[[_focusedIndex]]" | ||
_item-id-path="[[itemIdPath]]" | ||
_item-label-path="[[itemLabelPath]]" | ||
loading="[[loading]]" | ||
theme="[[theme]]" | ||
></vaadin-combo-box-dropdown-wrapper> | ||
`; | ||
} | ||
@@ -235,3 +264,3 @@ | ||
static get version() { | ||
return '5.4.7'; | ||
return '6.0.0-alpha1'; | ||
} | ||
@@ -313,6 +342,10 @@ | ||
/** @type {boolean} */ | ||
/** | ||
* Set to true to prevent the user from picking a value or typing in the input. | ||
* @type {boolean} | ||
*/ | ||
readonly: { | ||
type: Boolean, | ||
value: false | ||
value: false, | ||
reflectToAttribute: true | ||
}, | ||
@@ -336,21 +369,2 @@ | ||
/** | ||
* @param {string} name | ||
* @param {?string} oldValue | ||
* @param {?string} newValue | ||
* @protected | ||
*/ | ||
attributeChangedCallback(name, oldValue, newValue) { | ||
super.attributeChangedCallback(name, oldValue, newValue); | ||
// Safari has an issue with repainting shadow root element styles when a host attribute changes. | ||
// Need this workaround (toggle any inline css property on and off) until the issue gets fixed. | ||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); | ||
if (isSafari && this.root) { | ||
Array.prototype.forEach.call(this.root.querySelectorAll('*'), el => { | ||
el.style['-webkit-backface-visibility'] = 'visible'; | ||
el.style['-webkit-backface-visibility'] = ''; | ||
}); | ||
} | ||
} | ||
/** @protected */ | ||
@@ -370,10 +384,14 @@ ready() { | ||
// 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.inputElement.addEventListener( | ||
'keydown', | ||
(e) => { | ||
if (e.keyCode === 27) { | ||
this._stopPropagation(e); | ||
// Trigger _onEscape method of vaadin-combo-box-mixin because | ||
// bubbling phase is not reached. | ||
this._onEscape(e); | ||
} | ||
}, | ||
true | ||
); | ||
@@ -380,0 +398,0 @@ this._nativeInput.setAttribute('role', 'combobox'); |
@@ -0,1 +1,2 @@ | ||
import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; | ||
import '@vaadin/vaadin-lumo-styles/color.js'; | ||
@@ -6,97 +7,82 @@ import '@vaadin/vaadin-lumo-styles/spacing.js'; | ||
import '@vaadin/vaadin-lumo-styles/mixins/menu-overlay.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
const $_documentContainer = html`<dom-module id="lumo-combo-box-overlay" theme-for="vaadin-combo-box-overlay"> | ||
<template> | ||
<style include="lumo-overlay lumo-menu-overlay-core"> | ||
[part="content"] { | ||
padding: 0; | ||
} | ||
registerStyles( | ||
'vaadin-combo-box-overlay', | ||
css` | ||
[part='content'] { | ||
padding: 0; | ||
} | ||
:host { | ||
/* TODO: using a legacy mixin (unsupported) */ | ||
--iron-list-items-container: { | ||
border-width: var(--lumo-space-xs); | ||
border-style: solid; | ||
border-color: transparent; | ||
}; | ||
:host { | ||
/* TODO: using a legacy mixin (unsupported) */ | ||
--iron-list-items-container: { | ||
border-width: var(--lumo-space-xs); | ||
border-style: solid; | ||
border-color: transparent; | ||
} | ||
} | ||
/* TODO: workaround ShadyCSS issue when using inside of the dom-if */ | ||
:host([opened]) { | ||
--iron-list-items-container_-_border-width: var(--lumo-space-xs); | ||
--iron-list-items-container_-_border-style: solid; | ||
--iron-list-items-container_-_border-color: transparent; | ||
} | ||
/* Loading state */ | ||
/* Loading state */ | ||
/* When items are empty, the spinner needs some room */ | ||
:host(:not([closing])) [part~='content'] { | ||
min-height: calc(2 * var(--lumo-space-s) + var(--lumo-icon-size-s)); | ||
} | ||
/* When items are empty, the sinner needs some room */ | ||
:host(:not([closing])) [part~="content"] { | ||
min-height: calc(2 * var(--lumo-space-s) + var(--lumo-icon-size-s)); | ||
} | ||
[part~='overlay'] { | ||
position: relative; | ||
} | ||
[part~="overlay"] { | ||
position: relative; | ||
} | ||
:host([loading]) [part~='loader'] { | ||
box-sizing: border-box; | ||
width: var(--lumo-icon-size-s); | ||
height: var(--lumo-icon-size-s); | ||
position: absolute; | ||
z-index: 1; | ||
left: var(--lumo-space-s); | ||
right: var(--lumo-space-s); | ||
top: var(--lumo-space-s); | ||
margin-left: auto; | ||
margin-inline-start: auto; | ||
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; | ||
} | ||
:host([loading]) [part~="loader"] { | ||
box-sizing: border-box; | ||
width: var(--lumo-icon-size-s); | ||
height: var(--lumo-icon-size-s); | ||
position: absolute; | ||
z-index: 1; | ||
left: var(--lumo-space-s); | ||
right: var(--lumo-space-s); | ||
top: var(--lumo-space-s); | ||
margin-left: auto; | ||
margin-inline-start: auto; | ||
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)); | ||
@keyframes lumo-combo-box-loader-fade-in { | ||
0% { | ||
opacity: 0; | ||
animation: | ||
1s linear infinite lumo-combo-box-loader-rotate, | ||
.3s .1s lumo-combo-box-loader-fade-in both; | ||
pointer-events: none; | ||
} | ||
@keyframes lumo-combo-box-loader-fade-in { | ||
0% { | ||
opacity: 0; | ||
} | ||
100% { | ||
opacity: 1; | ||
} | ||
} | ||
100% { | ||
opacity: 1; | ||
} | ||
@keyframes lumo-combo-box-loader-rotate { | ||
0% { | ||
transform: rotate(0deg); | ||
} | ||
@keyframes lumo-combo-box-loader-rotate { | ||
0% { | ||
transform: rotate(0deg); | ||
} | ||
100% { | ||
transform: rotate(360deg); | ||
} | ||
100% { | ||
transform: rotate(360deg); | ||
} | ||
} | ||
/* RTL specific styles */ | ||
/* RTL specific styles */ | ||
:host([loading][dir="rtl"]) [part~="loader"] { | ||
left: auto; | ||
margin-left: 0; | ||
margin-right: auto; | ||
margin-inline-start: 0; | ||
margin-inline-end: auto; | ||
} | ||
</style> | ||
</template> | ||
</dom-module>`; | ||
document.head.appendChild($_documentContainer.content); | ||
:host([loading][dir='rtl']) [part~='loader'] { | ||
left: auto; | ||
margin-left: 0; | ||
margin-right: auto; | ||
margin-inline-start: 0; | ||
margin-inline-end: auto; | ||
} | ||
`, | ||
{ moduleId: 'lumo-combo-box-overlay', include: ['lumo-overlay', 'lumo-menu-overlay-core'] } | ||
); |
@@ -0,1 +1,2 @@ | ||
import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; | ||
import '@vaadin/vaadin-lumo-styles/color.js'; | ||
@@ -5,52 +6,48 @@ import '@vaadin/vaadin-lumo-styles/spacing.js'; | ||
import '@vaadin/vaadin-item/theme/lumo/vaadin-item.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
const $_documentContainer = html`<dom-module id="lumo-combo-box-item" theme-for="vaadin-combo-box-item"> | ||
<template> | ||
<style include="lumo-item"> | ||
/* TODO partly duplicated from vaadin-list-box styles. Should find a way to make it DRY */ | ||
/* TODO partly duplicated from vaadin-list-box styles. Should find a way to make it DRY */ | ||
registerStyles( | ||
'vaadin-combo-box-item', | ||
css` | ||
:host { | ||
cursor: default; | ||
-webkit-tap-highlight-color: var(--lumo-primary-color-10pct); | ||
padding-left: calc(var(--lumo-border-radius) / 4); | ||
padding-right: calc(var(--lumo-space-l) + var(--lumo-border-radius) / 4); | ||
transition: background-color 100ms; | ||
border-radius: var(--lumo-border-radius); | ||
overflow: hidden; | ||
--_lumo-item-selected-icon-display: block; | ||
} | ||
:host { | ||
cursor: default; | ||
-webkit-tap-highlight-color: var(--lumo-primary-color-10pct); | ||
padding-left: calc(var(--lumo-border-radius) / 4); | ||
padding-right: calc(var(--lumo-space-l) + var(--lumo-border-radius) / 4); | ||
transition: background-color 100ms; | ||
border-radius: var(--lumo-border-radius); | ||
overflow: hidden; | ||
--_lumo-item-selected-icon-display: block; | ||
} | ||
/* ShadyCSS workaround (show the selected item checkmark) */ | ||
:host::before { | ||
display: block; | ||
} | ||
/* ShadyCSS workaround (show the selected item checkmark) */ | ||
:host::before { | ||
display: block; | ||
} | ||
:host(:hover) { | ||
background-color: var(--lumo-primary-color-10pct); | ||
} | ||
:host([focused]:not([disabled])) { | ||
box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct); | ||
} | ||
@media (pointer: coarse) { | ||
:host(:hover) { | ||
background-color: var(--lumo-primary-color-10pct); | ||
background-color: transparent; | ||
} | ||
:host([focused]:not([disabled])) { | ||
box-shadow: inset 0 0 0 2px var(--lumo-primary-color-50pct); | ||
box-shadow: none; | ||
} | ||
} | ||
@media (pointer: coarse) { | ||
:host(:hover) { | ||
background-color: transparent; | ||
} | ||
:host([focused]:not([disabled])) { | ||
box-shadow: none; | ||
} | ||
} | ||
/* RTL specific styles */ | ||
:host([dir="rtl"]) { | ||
padding-right: calc(var(--lumo-border-radius) / 4); | ||
padding-left: calc(var(--lumo-space-l) + var(--lumo-border-radius) / 4); | ||
} | ||
</style> | ||
</template> | ||
</dom-module>`; | ||
document.head.appendChild($_documentContainer.content); | ||
/* RTL specific styles */ | ||
:host([dir='rtl']) { | ||
padding-right: calc(var(--lumo-border-radius) / 4); | ||
padding-left: calc(var(--lumo-space-l) + var(--lumo-border-radius) / 4); | ||
} | ||
`, | ||
{ moduleId: 'lumo-combo-box-item', include: ['lumo-item'] } | ||
); |
@@ -0,20 +1,18 @@ | ||
import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; | ||
import '@vaadin/vaadin-lumo-styles/font-icons.js'; | ||
import '@vaadin/vaadin-lumo-styles/mixins/field-button.js'; | ||
import '@vaadin/vaadin-text-field/theme/lumo/vaadin-text-field.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
const $_documentContainer = html`<dom-module id="lumo-combo-box" theme-for="vaadin-combo-box"> | ||
<template> | ||
<style include="lumo-field-button"> | ||
:host { | ||
outline: none; | ||
} | ||
registerStyles( | ||
'vaadin-combo-box', | ||
css` | ||
:host { | ||
outline: none; | ||
} | ||
[part="toggle-button"]::before { | ||
content: var(--lumo-icons-dropdown); | ||
} | ||
</style> | ||
</template> | ||
</dom-module>`; | ||
document.head.appendChild($_documentContainer.content); | ||
[part='toggle-button']::before { | ||
content: var(--lumo-icons-dropdown); | ||
} | ||
`, | ||
{ moduleId: 'lumo-combo-box', include: ['lumo-field-button'] } | ||
); |
@@ -0,128 +1,124 @@ | ||
import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; | ||
import '@vaadin/vaadin-material-styles/color.js'; | ||
import '@vaadin/vaadin-material-styles/mixins/menu-overlay.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
const $_documentContainer = html`<dom-module id="material-combo-box-overlay" theme-for="vaadin-combo-box-overlay"> | ||
<template> | ||
<style include="material-menu-overlay"> | ||
:host { | ||
/* TODO using a legacy mixin (unsupported) */ | ||
--iron-list-items-container: { | ||
border-width: 8px 0; | ||
border-style: solid; | ||
border-color: transparent; | ||
}; | ||
registerStyles( | ||
'vaadin-combo-box-overlay', | ||
css` | ||
:host { | ||
/* TODO using a legacy mixin (unsupported) */ | ||
--iron-list-items-container: { | ||
border-width: 8px 0; | ||
border-style: solid; | ||
border-color: transparent; | ||
} | ||
} | ||
[part="overlay"] { | ||
position: relative; | ||
overflow: visible; | ||
border-top-left-radius: 0; | ||
border-top-right-radius: 0; | ||
} | ||
[part='overlay'] { | ||
position: relative; | ||
overflow: visible; | ||
border-top-left-radius: 0; | ||
border-top-right-radius: 0; | ||
} | ||
[part="content"] { | ||
padding: 0; | ||
} | ||
[part='content'] { | ||
padding: 0; | ||
} | ||
:host([loading]) [part="loader"] { | ||
height: 2px; | ||
position: absolute; | ||
z-index: 1; | ||
top: -2px; | ||
left: 0; | ||
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; | ||
:host([loading]) [part='loader'] { | ||
height: 2px; | ||
position: absolute; | ||
z-index: 1; | ||
top: -2px; | ||
left: 0; | ||
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; | ||
animation: | ||
3s linear infinite material-combo-box-loader-progress, | ||
.3s .1s both material-combo-box-loader-fade-in; | ||
} | ||
[part="loader"]::before { | ||
content: ''; | ||
display: block; | ||
height: 100%; | ||
opacity: 0.16; | ||
background: var(--material-primary-color); | ||
100% { | ||
opacity: 1; | ||
} | ||
} | ||
@keyframes material-combo-box-loader-fade-in { | ||
0% { | ||
opacity: 0; | ||
} | ||
@keyframes material-combo-box-loader-progress { | ||
0% { | ||
background-position: 0 0; | ||
background-size: 300% 100%; | ||
} | ||
100% { | ||
opacity: 1; | ||
} | ||
33% { | ||
background-position: -100% 0; | ||
background-size: 400% 100%; | ||
} | ||
@keyframes material-combo-box-loader-progress { | ||
0% { | ||
background-position: 0 0; | ||
background-size: 300% 100%; | ||
} | ||
67% { | ||
background-position: -200% 0; | ||
background-size: 250% 100%; | ||
} | ||
33% { | ||
background-position: -100% 0; | ||
background-size: 400% 100%; | ||
} | ||
100% { | ||
background-position: -300% 0; | ||
background-size: 300% 100%; | ||
} | ||
} | ||
67% { | ||
background-position: -200% 0; | ||
background-size: 250% 100%; | ||
} | ||
/* RTL specific styles */ | ||
100% { | ||
background-position: -300% 0; | ||
background-size: 300% 100%; | ||
} | ||
@keyframes material-combo-box-loader-progress-rtl { | ||
0% { | ||
background-position: 100% 0; | ||
background-size: 300% 100%; | ||
} | ||
/* RTL specific styles */ | ||
33% { | ||
background-position: 200% 0; | ||
background-size: 400% 100%; | ||
} | ||
@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%; | ||
} | ||
67% { | ||
background-position: 300% 0; | ||
background-size: 250% 100%; | ||
} | ||
:host([loading][dir="rtl"]) [part="loader"] { | ||
animation: | ||
3s linear infinite material-combo-box-loader-progress-rtl, | ||
.3s .1s both material-combo-box-loader-fade-in; | ||
100% { | ||
background-position: 400% 0; | ||
background-size: 300% 100%; | ||
} | ||
</style> | ||
</template> | ||
</dom-module>`; | ||
} | ||
document.head.appendChild($_documentContainer.content); | ||
: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; | ||
} | ||
`, | ||
{ moduleId: 'material-combo-box-overlay', include: ['material-menu-overlay'] } | ||
); |
@@ -0,1 +1,2 @@ | ||
import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; | ||
import '@vaadin/vaadin-material-styles/color.js'; | ||
@@ -5,39 +6,36 @@ import '@vaadin/vaadin-material-styles/font-icons.js'; | ||
import '@vaadin/vaadin-item/theme/material/vaadin-item.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
const $_documentContainer = html`<dom-module id="material-combo-box-item" theme-for="vaadin-combo-box-item"> | ||
<template> | ||
<style include="material-item"> | ||
:host { | ||
cursor: pointer; | ||
-webkit-tap-highlight-color: transparent; | ||
padding: 4px 10px; | ||
min-height: 36px; | ||
font-size: var(--material-small-font-size); | ||
--_material-item-selected-icon-display: block; | ||
} | ||
registerStyles( | ||
'vaadin-combo-box-item', | ||
css` | ||
:host { | ||
cursor: pointer; | ||
-webkit-tap-highlight-color: transparent; | ||
padding: 4px 10px; | ||
min-height: 36px; | ||
font-size: var(--material-small-font-size); | ||
--_material-item-selected-icon-display: block; | ||
} | ||
/* ShadyCSS workaround */ | ||
:host::before { | ||
display: block; | ||
} | ||
/* ShadyCSS workaround */ | ||
:host::before { | ||
display: block; | ||
} | ||
:host(:hover) { | ||
background-color: var(--material-secondary-background-color); | ||
} | ||
:host(:hover) { | ||
background-color: var(--material-secondary-background-color); | ||
} | ||
:host([focused]) { | ||
background-color: var(--material-divider-color); | ||
} | ||
@media (pointer: coarse) { | ||
:host(:hover), | ||
:host([focused]) { | ||
background-color: var(--material-divider-color); | ||
background-color: transparent; | ||
} | ||
@media (pointer: coarse) { | ||
:host(:hover), | ||
:host([focused]) { | ||
background-color: transparent; | ||
} | ||
} | ||
</style> | ||
</template> | ||
</dom-module>`; | ||
document.head.appendChild($_documentContainer.content); | ||
} | ||
`, | ||
{ moduleId: 'material-combo-box-item', include: ['material-item'] } | ||
); |
@@ -0,1 +1,2 @@ | ||
import { registerStyles, css } from '@vaadin/vaadin-themable-mixin/register-styles.js'; | ||
import '@vaadin/vaadin-text-field/theme/material/vaadin-text-field.js'; | ||
@@ -5,24 +6,21 @@ import '@vaadin/vaadin-material-styles/color.js'; | ||
import '@vaadin/vaadin-material-styles/mixins/field-button.js'; | ||
import { html } from '@polymer/polymer/lib/utils/html-tag.js'; | ||
const $_documentContainer = html`<dom-module id="material-combo-box" theme-for="vaadin-combo-box"> | ||
<template> | ||
<style include="material-field-button"> | ||
:host { | ||
display: inline-flex; | ||
outline: none; | ||
-webkit-tap-highlight-color: transparent; | ||
} | ||
registerStyles( | ||
'vaadin-combo-box', | ||
css` | ||
:host { | ||
display: inline-flex; | ||
outline: none; | ||
-webkit-tap-highlight-color: transparent; | ||
} | ||
[part="toggle-button"]::before { | ||
content: var(--material-icons-dropdown); | ||
} | ||
[part='toggle-button']::before { | ||
content: var(--material-icons-dropdown); | ||
} | ||
:host([opened]) [part="toggle-button"] { | ||
transform: rotate(180deg); | ||
} | ||
</style> | ||
</template> | ||
</dom-module>`; | ||
document.head.appendChild($_documentContainer.content); | ||
:host([opened]) [part='toggle-button'] { | ||
transform: rotate(180deg); | ||
} | ||
`, | ||
{ moduleId: 'material-combo-box', include: ['material-field-button'] } | ||
); |
@@ -1,14 +0,1 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file was automatically generated by | ||
* https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations | ||
* | ||
* To modify these typings, edit the source file(s): | ||
* vaadin-combo-box-light.js | ||
*/ | ||
// tslint:disable:variable-name Describing an API that's defined elsewhere. | ||
export * from './src/vaadin-combo-box-light.js'; |
@@ -1,15 +0,2 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file was automatically generated by | ||
* https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations | ||
* | ||
* To modify these typings, edit the source file(s): | ||
* vaadin-combo-box.js | ||
*/ | ||
// tslint:disable:variable-name Describing an API that's defined elsewhere. | ||
export * from './src/vaadin-combo-box.js'; | ||
export * from './@types/interfaces'; | ||
export * from './src/interfaces'; |
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
12
144010
30
3671
1
116
+ Added@vaadin/vaadin-item@3.0.0(transitive)
+ Added@vaadin/vaadin-text-field@3.0.2(transitive)
- Removed@vaadin/vaadin-item@2.3.0(transitive)
- Removed@vaadin/vaadin-text-field@2.10.0(transitive)