diff --git a/.eslintrc.json b/.eslintrc.json index e1ffddfed..e612fe259 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,13 +2,16 @@ "extends": "vaadin", "env": { "browser": true, + "es6": true, "node": true }, "plugins": [ "html" ], "globals": { + // TODO: remove when moving to ES6 vaadin -> Vaadin "vaadin": false, + "Vaadin": false, "Polymer": false, "CustomElements": false } diff --git a/.gitignore b/.gitignore index a13f20649..912b1b4ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -bower_components +bower_components* node_modules -test/angular2/typings +bower-*.json diff --git a/.travis.yml b/.travis.yml index 3d5008e34..6408cadd7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,13 @@ addons: firefox: latest google-chrome: latest -script: +install: + - npm install + - polymer install + +before_script: - gulp lint - - travis_retry xvfb-run -s '-screen 0 1024x768x24' wct -l chrome -l firefox - - travis_retry xvfb-run -s '-screen 0 1024x768x24' wct -l chrome -l firefox --dom=shadow + - ([ "$TRAVIS_EVENT_TYPE" = "pull_request" ] || TRAVIS_BRANCH=quick/${TRAVIS_BUILD_ID} xvfb-run -s '-screen 0 1024x768x24' polymer test) + +script: + - xvfb-run -s '-screen 0 1024x768x24' wct diff --git a/README.md b/README.md index e2f3b03d4..b907208e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -![Bower version](https://img.shields.io/bower/v/vaadin-combo-box.svg) +![Bower version](https://img.shields.io/bower/v/vaadin-combo-box.svg) [![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/vaadin/vaadin-combo-box) -[![Build Status](https://travis-ci.org/vaadin/vaadin-combo-box.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-combo-box) +[![Build Status](https://travis-ci.org/vaadin/vaadin-combo-box.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-combo-box) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/vaadin/vaadin-core-elements?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) # <vaadin-combo-box> @@ -14,6 +14,8 @@ @@ -21,12 +23,35 @@ ``` --> ```html - - +
+ + +
+ ``` -[Screenshot of vaadin-combo-box](https://vaadin.com/elements/-/element/vaadin-combo-box) +[Screenshot of vaadin-combo-box](https://vaadin.com/elements/-/element/vaadin-combo-box) ## Contributing diff --git a/bower.json b/bower.json index 506d0b4b8..8da13abfc 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "vaadin-combo-box", - "description": "Vaadin Combo Box", + "description": "Polymer element for displaying a list of items with filtering", "authors": [ "Vaadin Ltd" ], @@ -18,30 +18,67 @@ }, "license": "Apache-2.0", "dependencies": { - "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.0.3", - "iron-form": "polymerelements/iron-form#^1.0.10", - "iron-form-element-behavior": "polymerelements/iron-form-element-behavior#^1.0.4", - "iron-icon": "PolymerElements/iron-icon#^1.0.4", - "iron-iconset-svg": "polymerelements/iron-iconset-svg#^1.0.9", - "iron-input": "polymerelements/iron-input#^1.0.5", - "iron-list": "polymerelements/iron-list#^1.0.0", - "iron-validatable-behavior": "polymerelements/iron-validatable-behavior#^1.0.5", - "paper-button": "polymerelements/paper-button#^1.0.6", - "paper-input": "PolymerElements/paper-input#^1.1.3", - "polymer": "Polymer/polymer#^1.2.0", - "iron-a11y-keys-behavior": "polymerelements/iron-a11y-keys-behavior#^1.0.7", - "iron-resizable-behavior": "polymerelements/iron-resizable-behavior#^1.0.2", - "paper-styles": "polymerelements/paper-styles#^1.1.2", - "iron-a11y-announcer": "polymerelements/iron-a11y-announcer#^1.0.4", - "paper-icon-button": "polymerelements/paper-icon-button#^1.0.5" + "polymer": "^1.7.0", + "iron-flex-layout": "^1.0.0", + "iron-form": "^1.0.0", + "iron-form-element-behavior": "^1.0.0", + "iron-icon": "^1.0.0", + "iron-iconset-svg": "^1.0.0", + "iron-input": "^1.0.0", + "iron-list": "^1.0.0", + "iron-validatable-behavior": "^1.0.0", + "paper-button": "^1.0.0", + "paper-input": "^1.0.0", + "iron-a11y-keys-behavior": "^1.0.0", + "iron-resizable-behavior": "^1.0.0", + "paper-styles": "^1.0.0", + "iron-a11y-announcer": "^1.0.0", + "paper-icon-button": "^1.0.0" }, "devDependencies": { + "elements-demo-resources": "vaadin/elements-demo-resources#master", + "iron-ajax": "^1.0.0", + "iron-component-page": "^1.0.0", + "iron-demo-helpers": "^1.0.0", + "iron-form": "^1.0.0", + "iron-icons": "^1.0.0", + "iron-test-helpers": "^1.0.0", + "paper-item": "^1.0.0", "web-component-tester": "^5.0.0", - "test-fixture": "polymerelements/test-fixture#^1.0.0", - "iron-test-helpers": "polymerelements/iron-test-helpers#^1.0.0", - "iron-component-page": "polymerelements/iron-component-page#^1.1.0", - "iron-icons": "PolymerElements/iron-icons#^1.0.3", - "iron-demo-helpers": "polymerelements/iron-demo-helpers#^1.0.0", - "elements-demo-resources": "vaadin/elements-demo-resources#master" + "webcomponentsjs": "^0.7.23" + }, + "variants": { + "polymer2": { + "dependencies": { + "polymer": "^2.0.0-rc.2", + "iron-flex-layout": "#2.0-preview", + "iron-form": "#2.0-preview", + "iron-form-element-behavior": "#2.0-preview", + "iron-icon": "#2.0-preview", + "iron-iconset-svg": "#2.0-preview", + "iron-input": "#2.0-preview", + "iron-list": "#2.0-preview", + "iron-validatable-behavior": "#2.0-preview", + "paper-button": "#2.0-preview", + "paper-input": "#2.0-preview", + "iron-a11y-keys-behavior": "#2.0-preview", + "iron-resizable-behavior": "#2.0-preview", + "paper-styles": "#2.0-preview", + "iron-a11y-announcer": "#2.0-preview", + "paper-icon-button": "#2.0-preview" + }, + "devDependencies": { + "elements-demo-resources": "vaadin/elements-demo-resources#2.0-preview", + "iron-ajax": "#2.0-preview", + "iron-component-page": "#2.0-preview", + "iron-demo-helpers": "#2.0-preview", + "iron-form": "#2.0-preview", + "iron-icons": "#2.0-preview", + "iron-test-helpers": "#2.0-preview", + "paper-item": "#2.0-preview", + "web-component-tester": "6.0.0-prerelease.7", + "webcomponentsjs": "#v1" + } + } } } diff --git a/demo/.eslintrc.json b/demo/.eslintrc.json index 4931ca725..b4a37c540 100644 --- a/demo/.eslintrc.json +++ b/demo/.eslintrc.json @@ -4,7 +4,8 @@ }, "globals": { "elements": false, - "elementsJson": false + "users": false, + "elementsJson": false, + "HTMLImports": false } } - diff --git a/demo/advanced.html b/demo/advanced.html index 7142cc997..44047e403 100644 --- a/demo/advanced.html +++ b/demo/advanced.html @@ -23,14 +23,7 @@
- +

Typing Custom Values

diff --git a/demo/common.html b/demo/common.html index 8f3ce257f..610a1d2ec 100644 --- a/demo/common.html +++ b/demo/common.html @@ -2,62 +2,29 @@ - + + + - - - - + diff --git a/demo/external-filtering.html b/demo/filtering.html similarity index 86% rename from demo/external-filtering.html rename to demo/filtering.html index 784954701..f40358ea9 100644 --- a/demo/external-filtering.html +++ b/demo/filtering.html @@ -15,14 +15,7 @@
- + +

Change Event

+ + + +
diff --git a/demo/item-template.html b/demo/item-template.html new file mode 100644 index 000000000..3422b3224 --- /dev/null +++ b/demo/item-template.html @@ -0,0 +1,149 @@ + + + + + + + + + vaadin-combo-box Object Items Examples + + + + + + + + +
+ + +

Custom Item Template

+ + + + + +

Focused and Selected Flags

+ + + + + +

Styling Items with Custom Item Element

+ + + + + +

Item Template with Material Design

+ + + + + +
+ + + diff --git a/demo/objects.html b/demo/objects.html index e9cd84018..9a902325c 100644 --- a/demo/objects.html +++ b/demo/objects.html @@ -15,14 +15,7 @@
- +

Default Label and Value

diff --git a/docs/img/vaadin-combo-box-custom-item-element.png b/docs/img/vaadin-combo-box-custom-item-element.png new file mode 100755 index 000000000..37fb9cc33 Binary files /dev/null and b/docs/img/vaadin-combo-box-custom-item-element.png differ diff --git a/docs/img/vaadin-combo-box-custom-item-template.png b/docs/img/vaadin-combo-box-custom-item-template.png new file mode 100755 index 000000000..a510ded50 Binary files /dev/null and b/docs/img/vaadin-combo-box-custom-item-template.png differ diff --git a/docs/img/vaadin-combo-box-customization.png b/docs/img/vaadin-combo-box-customization.png index 658f80127..b0ebc36f3 100644 Binary files a/docs/img/vaadin-combo-box-customization.png and b/docs/img/vaadin-combo-box-customization.png differ diff --git a/docs/img/vaadin-combo-box-item-template-material.png b/docs/img/vaadin-combo-box-item-template-material.png new file mode 100644 index 000000000..21cbce861 Binary files /dev/null and b/docs/img/vaadin-combo-box-item-template-material.png differ diff --git a/docs/img/vaadin-combo-box-overview.png b/docs/img/vaadin-combo-box-overview.png index 74d4eec0f..65b5d5bee 100644 Binary files a/docs/img/vaadin-combo-box-overview.png and b/docs/img/vaadin-combo-box-overview.png differ diff --git a/docs/img/vaadin-combo-box-selected-focused-flags.png b/docs/img/vaadin-combo-box-selected-focused-flags.png new file mode 100755 index 000000000..9238fb4cd Binary files /dev/null and b/docs/img/vaadin-combo-box-selected-focused-flags.png differ diff --git a/docs/vaadin-combo-box-custom-input.adoc b/docs/vaadin-combo-box-custom-input.adoc index 5fb031d87..c34681c42 100644 --- a/docs/vaadin-combo-box-custom-input.adoc +++ b/docs/vaadin-combo-box-custom-input.adoc @@ -1,6 +1,6 @@ --- title: Custom Input Values -order: 4 +order: 5 layout: page --- diff --git a/docs/vaadin-combo-box-customization.adoc b/docs/vaadin-combo-box-customization.adoc index 69b8fb66e..f70df033b 100644 --- a/docs/vaadin-combo-box-customization.adoc +++ b/docs/vaadin-combo-box-customization.adoc @@ -1,6 +1,6 @@ --- title: Customizing the Input Field -order: 7 +order: 8 layout: page --- diff --git a/docs/vaadin-combo-box-external-filtering.adoc b/docs/vaadin-combo-box-filtering.adoc similarity index 94% rename from docs/vaadin-combo-box-external-filtering.adoc rename to docs/vaadin-combo-box-filtering.adoc index 574230b35..fe9f4bb85 100644 --- a/docs/vaadin-combo-box-external-filtering.adoc +++ b/docs/vaadin-combo-box-filtering.adoc @@ -1,11 +1,11 @@ --- title: Remote and Custom Filtering -order: 8 +order: 9 layout: page --- -[[vaadin-combo-box.external-filtering]] +[[vaadin-combo-box.filtering]] == Overview By default, [vaadinelement]#vaadin-combo-box# is used with an array of items @@ -33,9 +33,10 @@ on the [propertyname]#filter# and update [propertyname]#filteredItems# based on +---- + +[[figure.vaadin-combo-box.item-template1]] +.Using Custom Item Template +image::img/vaadin-combo-box-custom-item-template.png[alt=Using Custom Item Template,width=320] + +In the above example, the [vaadinelement]#vaadin-combo-box# element has the custom item template, that renders [propertyname]#number#, [propertyname]#symbol#, and [propertyname]#name# keys from the given object items. + +=== Item Template Variables + +The following properties are available for item template bindings: + +|=== +|Property name |Type |Description + +|[propertyname]#index# +|[classname]#Number# +|Index of the item in the [propertyname]#items# array + +|[propertyname]#item# +|[classname]#String# or [classname]#Object# +|The item reference + +|[propertyname]#selected# +|[classname]#Boolean# +|True when item is selected + +|[propertyname]#focused# +|[classname]#Boolean# +|True when item is focused +|=== + +[[vaadin-combo-box.item-template.states]] +== Selected and Focused Flags + +You can use [propertyname]#selected# and [propertyname]#focused# flags in the item template to read the item state. + +[source,html] +---- + + + + + +---- + +[[figure.vaadin-combo-box.item-template2]] +.Selected and Focused Flags +image::img/vaadin-combo-box-selected-focused-flags.png[alt=Selected and Focused Flags,width=320] + +The above example shows the item states by binding to [propertyname]#selected# and [propertyname]#focused# template properties in the item text. + +[[vaadin-combo-box.item-template.custom-element]] +== Styling Items with Custom Item Element + +If styling the custom item template contents is necessary, you apply styles by defining a custom item element for your template and item styles. + +The following example demonstrates defining a custom element. The [elementname]#my-item# element receives the [propertyname]#item# Object property and displays the specified item keys in columns using flex layout. + +[source,html] +.my-item.html +---- + + + + + +---- + +[[figure.vaadin-combo-box.item-template3]] +.Styling Items with Custom Item Element +image::img/vaadin-combo-box-custom-item-element.png[alt=Styling Items with Custom Item Element,width=320] + +Then you can use the previously defined [elementname]#my-item# element in the item template of [vaadinelement]#vaadin-combo-box#: + +[source,html] +---- + + + + + + + +---- + + +[[vaadin-combo-box.item-template.material]] +== Item Template with Material Design + +The [vaadinelement]#vaadin-combo-box#'s appearance follows Material Design guidelines. +In case you want the overlay items to follow Material Design as well, you can utilize [elementname]#paper-item# elements like in the following snippet. + +[NOTE] +If you use images in the template, make sure they have pre-defined bounds so the layout doesn't break or jump once the images are resized after loading. + +[source,html] +---- + + + +---- + +[[figure.vaadin-combo-box.item-template4]] +.Item Template with Material Design +image::img/vaadin-combo-box-item-template-material.png[alt=Item Template with Material Design,width=311] diff --git a/docs/vaadin-combo-box-styling.adoc b/docs/vaadin-combo-box-styling.adoc index 95f7d9e41..fac1e008c 100644 --- a/docs/vaadin-combo-box-styling.adoc +++ b/docs/vaadin-combo-box-styling.adoc @@ -1,6 +1,6 @@ --- title: Styling -order: 6 +order: 7 layout: page --- @@ -8,9 +8,11 @@ layout: page [[vaadin-combo-box.styling]] = Styling -You can customize certain styles of the [vaadinelement]#vaadin-combo-box# with CSS mixins and properties. +You can customize certain styles of the [vaadinelement]#vaadin-combo-box# by using CSS properties. -[propertyname]#vaadin-combo-box-overlay-max-height# property can be used to override the default maximum height of the overlay. +The [propertyname]#vaadin-combo-box-overlay-max-height# property overrides the default maximum height of the overlay. + +In addition, you can customize how items are displayed by using templates as described in the link:vaadin-combo-box-item-template.html[Custom Item Template guide]. [source,html] ---- diff --git a/gulpfile.js b/gulpfile.js index 6055c9f6a..b8706f83e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,17 +3,14 @@ var gulp = require('gulp'); var eslint = require('gulp-eslint'); var htmlExtract = require('gulp-html-extract'); -var sourcemaps = require('gulp-sourcemaps'); var stylelint = require('gulp-stylelint'); -var ts = require('gulp-typescript'); -var typings = require('gulp-typings'); gulp.task('lint', ['lint:js', 'lint:html', 'lint:css']); gulp.task('lint:js', function() { return gulp.src([ '*.js', - 'test/*.js' + 'test/**/*.js' ]) .pipe(eslint()) .pipe(eslint.format()) @@ -23,8 +20,8 @@ gulp.task('lint:js', function() { gulp.task('lint:html', function() { return gulp.src([ '*.html', - 'demo/*.html', - 'test/*.html' + 'demo/**/*.html', + 'test/**/*.html' ]) .pipe(htmlExtract({ sel: 'script, code-example code', @@ -38,8 +35,8 @@ gulp.task('lint:html', function() { gulp.task('lint:css', function() { return gulp.src([ '*.html', - 'demo/*.html', - 'test/*.html' + 'demo/**/*.html', + 'test/**/*.html' ]) .pipe(htmlExtract({ sel: 'style' @@ -50,22 +47,3 @@ gulp.task('lint:css', function() { ] })); }); - -gulp.task('typings', function() { - return gulp.src('test/angular2/typings.json') - .pipe(typings()); -}); - -gulp.task('ng2', ['typings'], function() { - ['test/angular2'].forEach(function(dir) { - gulp.src([dir + '/*.ts', 'test/angular2/typings/main/**/*.d.ts']) - .pipe(sourcemaps.init()) - .pipe(ts(ts.createProject('test/angular2/tsconfig.json'))) - .pipe(sourcemaps.write('.')) - .pipe(gulp.dest(dir)); - }); -}); - -gulp.task('ng2:watch', function() { - gulp.watch('test/angular2/*.ts', ['ng2']); -}); diff --git a/package.json b/package.json index b08f8a24d..b24a4b77f 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,36 @@ { "name": "vaadin-combo-box", - "version": "1.1.5", + "version": "1.3.3", + "description": "Polymer element for displaying a list of items with filtering", + "main": "vaadin-combo-box.html", + "repository": "vaadin/vaadin-combo-box", + "keywords": [ + "Vaadin", + "combo-box", + "web-components", + "web-component", + "polymer" + ], "author": "Vaadin Ltd", "license": "Apache-2.0", - "repository": "vaadin/vaadin-combo-box", "bugs": { "url": "https://github.com/vaadin/vaadin-combo-box/issues" }, + "homepage": "https://vaadin.com/elements", "devDependencies": { - "@angular/common": "^2.0.0", - "@angular/compiler": "^2.0.0", - "@angular/core": "^2.0.0", - "@angular/forms": "^2.0.0", - "@angular/platform-browser": "^2.0.0", - "@angular/platform-browser-dynamic": "^2.0.0", - "@vaadin/angular2-polymer": "^1.0.0-beta4", - "bower": "latest", - "es6-shim": "^0.35.0", "eslint-plugin-html": "^1.7.0", - "eslint-config-vaadin": "github:vaadin/eslint-config-vaadin", + "eslint-config-vaadin": "^0.1.0", + "bower": "latest", + "polymer-cli": "next", "gulp": "latest", "gulp-html-extract": "^0.1.0", "gulp-eslint": "^3.0.1", - "gulp-sourcemaps": "^1.6.0", "gulp-stylelint": "^3.7.0", - "gulp-typescript": "^2.13.0", - "gulp-typings": "^1.3.4", - "reflect-metadata": "0.1.3", - "rxjs": "5.0.0-beta.12", - "stylelint-config-vaadin": "github:vaadin/stylelint-config-vaadin", - "systemjs": "0.19.27", - "web-component-tester": "^5.0.0", - "yargs": "latest", - "zone.js": "^0.6.17" + "stylelint-config-vaadin": "^0.1.0", + "wct-random-output": "^0.1.1", + "web-component-tester": "^5.0.0" }, "scripts": { - "postinstall": "node_modules/.bin/bower install" + "test": "wct" } } diff --git a/test/angular2.html b/test/angular2.html deleted file mode 100644 index f3151960f..000000000 --- a/test/angular2.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/angular2/app.component.js b/test/angular2/app.component.js deleted file mode 100644 index afd675abf..000000000 --- a/test/angular2/app.component.js +++ /dev/null @@ -1,55 +0,0 @@ -System.register(['@angular/core', "@angular/forms"], function(exports_1, context_1) { - "use strict"; - var __moduleName = context_1 && context_1.id; - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); - }; - var core_1, forms_1; - var TestApp; - return { - setters:[ - function (core_1_1) { - core_1 = core_1_1; - }, - function (forms_1_1) { - forms_1 = forms_1_1; - }], - execute: function() { - TestApp = (function () { - function TestApp(e, ref) { - this.items = ['foo', 'bar', 'baz']; - this.selection = 'foo'; - this._host = e.nativeElement; - this._ref = ref; - this.form = new forms_1.FormGroup({ - selection: new forms_1.FormControl() - }); - } - TestApp.prototype.ngAfterViewInit = function () { - var event = new CustomEvent('readyForTests', { detail: this }); - this._host.dispatchEvent(event); - }; - TestApp.prototype.detectChanges = function () { - this._ref.detectChanges(); - }; - TestApp = __decorate([ - core_1.Component({ - selector: 'test-app', - template: "\n \n\n
\n \n
\n " - }), - __metadata('design:paramtypes', [core_1.ElementRef, core_1.ChangeDetectorRef]) - ], TestApp); - return TestApp; - }()); - exports_1("TestApp", TestApp); - } - } -}); - -//# sourceMappingURL=app.component.js.map diff --git a/test/angular2/app.component.js.map b/test/angular2/app.component.js.map deleted file mode 100644 index 61029b433..000000000 --- a/test/angular2/app.component.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["app.component.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;YAcA;gBAQE,iBAAY,CAAa,EAAE,GAAsB;oBAJzC,UAAK,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;oBAC9B,cAAS,GAAG,KAAK,CAAC;oBAIxB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,aAAa,CAAC;oBAC7B,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;oBAChB,IAAI,CAAC,IAAI,GAAG,IAAI,iBAAS,CAAC;wBACxB,SAAS,EAAE,IAAI,mBAAW,EAAE;qBAC7B,CAAC,CAAC;gBACL,CAAC;gBAED,iCAAe,GAAf;oBACE,IAAI,KAAK,GAAG,IAAI,WAAW,CAAC,eAAe,EAAE,EAAC,MAAM,EAAE,IAAI,EAAC,CAAC,CAAC;oBAC7D,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;gBAClC,CAAC;gBAEM,+BAAa,GAApB;oBACE,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;gBAC5B,CAAC;gBAjCH;oBAAC,gBAAS,CAAC;wBACT,QAAQ,EAAE,UAAU;wBACpB,QAAQ,EAAE,0PAMP;qBACJ,CAAC;;2BAAA;gBAyBF,cAAC;YAAD,CAxBA,AAwBC,IAAA;YAxBD,6BAwBC,CAAA","file":"app.component.js","sourcesContent":["import { Component, ElementRef, ChangeDetectorRef } from '@angular/core';\nimport { FormGroup, FormControl } from \"@angular/forms\";\nimport { PolymerElement } from '@vaadin/angular2-polymer';\n\n@Component({\n selector: 'test-app',\n template: `\n \n\n
\n \n
\n `\n})\nexport class TestApp {\n\n private _host;\n private _ref;\n private items = ['foo', 'bar', 'baz'];\n private selection = 'foo';\n public form: FormGroup;\n\n constructor(e: ElementRef, ref: ChangeDetectorRef) {\n this._host = e.nativeElement;\n this._ref = ref;\n this.form = new FormGroup({\n selection: new FormControl()\n });\n }\n\n ngAfterViewInit() {\n var event = new CustomEvent('readyForTests', {detail: this});\n this._host.dispatchEvent(event);\n }\n\n public detectChanges() {\n this._ref.detectChanges();\n }\n}\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/test/angular2/app.component.ts b/test/angular2/app.component.ts deleted file mode 100644 index 44a097b09..000000000 --- a/test/angular2/app.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Component, ElementRef, ChangeDetectorRef } from '@angular/core'; -import { FormGroup, FormControl } from "@angular/forms"; -import { PolymerElement } from '@vaadin/angular2-polymer'; - -@Component({ - selector: 'test-app', - template: ` - - -
- -
- ` -}) -export class TestApp { - - private _host; - private _ref; - private items = ['foo', 'bar', 'baz']; - private selection = 'foo'; - public form: FormGroup; - - constructor(e: ElementRef, ref: ChangeDetectorRef) { - this._host = e.nativeElement; - this._ref = ref; - this.form = new FormGroup({ - selection: new FormControl() - }); - } - - ngAfterViewInit() { - var event = new CustomEvent('readyForTests', {detail: this}); - this._host.dispatchEvent(event); - } - - public detectChanges() { - this._ref.detectChanges(); - } -} diff --git a/test/angular2/app.module.js b/test/angular2/app.module.js deleted file mode 100644 index 0a013b64f..000000000 --- a/test/angular2/app.module.js +++ /dev/null @@ -1,52 +0,0 @@ -System.register(['@angular/core', '@angular/platform-browser', "@angular/forms", '@vaadin/angular2-polymer', './app.component'], function(exports_1, context_1) { - "use strict"; - var __moduleName = context_1 && context_1.id; - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; - }; - var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); - }; - var core_1, platform_browser_1, forms_1, angular2_polymer_1, app_component_1; - var AppModule; - return { - setters:[ - function (core_1_1) { - core_1 = core_1_1; - }, - function (platform_browser_1_1) { - platform_browser_1 = platform_browser_1_1; - }, - function (forms_1_1) { - forms_1 = forms_1_1; - }, - function (angular2_polymer_1_1) { - angular2_polymer_1 = angular2_polymer_1_1; - }, - function (app_component_1_1) { - app_component_1 = app_component_1_1; - }], - execute: function() { - AppModule = (function () { - function AppModule() { - } - AppModule = __decorate([ - core_1.NgModule({ - imports: [platform_browser_1.BrowserModule, forms_1.ReactiveFormsModule], - declarations: [app_component_1.TestApp, angular2_polymer_1.PolymerElement('vaadin-combo-box')], - bootstrap: [app_component_1.TestApp], - schemas: [core_1.CUSTOM_ELEMENTS_SCHEMA] - }), - __metadata('design:paramtypes', []) - ], AppModule); - return AppModule; - }()); - exports_1("AppModule", AppModule); - } - } -}); - -//# sourceMappingURL=app.module.js.map diff --git a/test/angular2/app.module.js.map b/test/angular2/app.module.js.map deleted file mode 100644 index 522efe373..000000000 --- a/test/angular2/app.module.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["app.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAaA;gBAAA;gBAAyB,CAAC;gBAN1B;oBAAC,eAAQ,CAAC;wBACR,OAAO,EAAE,CAAE,gCAAa,EAAE,2BAAmB,CAAE;wBAC/C,YAAY,EAAE,CAAE,uBAAO,EAAE,iCAAc,CAAC,kBAAkB,CAAC,CAAE;wBAC7D,SAAS,EAAE,CAAE,uBAAO,CAAE;wBACtB,OAAO,EAAE,CAAE,6BAAsB,CAAE;qBACpC,CAAC;;6BAAA;gBACuB,gBAAC;YAAD,CAAzB,AAA0B,IAAA;YAA1B,iCAA0B,CAAA","file":"app.module.js","sourcesContent":["import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';\nimport { BrowserModule } from '@angular/platform-browser';\nimport { ReactiveFormsModule } from \"@angular/forms\";\nimport { PolymerElement } from '@vaadin/angular2-polymer';\n\nimport { TestApp } from './app.component';\n\n@NgModule({\n imports: [ BrowserModule, ReactiveFormsModule ],\n declarations: [ TestApp, PolymerElement('vaadin-combo-box') ],\n bootstrap: [ TestApp ],\n schemas: [ CUSTOM_ELEMENTS_SCHEMA ]\n})\nexport class AppModule { }\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/test/angular2/app.module.ts b/test/angular2/app.module.ts deleted file mode 100644 index 328bc0bf9..000000000 --- a/test/angular2/app.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { ReactiveFormsModule } from "@angular/forms"; -import { PolymerElement } from '@vaadin/angular2-polymer'; - -import { TestApp } from './app.component'; - -@NgModule({ - imports: [ BrowserModule, ReactiveFormsModule ], - declarations: [ TestApp, PolymerElement('vaadin-combo-box') ], - bootstrap: [ TestApp ], - schemas: [ CUSTOM_ELEMENTS_SCHEMA ] -}) -export class AppModule { } diff --git a/test/angular2/main.js b/test/angular2/main.js deleted file mode 100644 index cf6b9ec59..000000000 --- a/test/angular2/main.js +++ /dev/null @@ -1,25 +0,0 @@ -System.register(['@angular/platform-browser-dynamic', '@angular/core', './app.module'], function(exports_1, context_1) { - "use strict"; - var __moduleName = context_1 && context_1.id; - var platform_browser_dynamic_1, core_1, app_module_1; - return { - setters:[ - function (platform_browser_dynamic_1_1) { - platform_browser_dynamic_1 = platform_browser_dynamic_1_1; - }, - function (core_1_1) { - core_1 = core_1_1; - }, - function (app_module_1_1) { - app_module_1 = app_module_1_1; - }], - execute: function() { - core_1.enableProdMode(); - document.body.addEventListener('bootstrap', function () { - platform_browser_dynamic_1.platformBrowserDynamic().bootstrapModule(app_module_1.AppModule); - }); - } - } -}); - -//# sourceMappingURL=main.js.map diff --git a/test/angular2/main.js.map b/test/angular2/main.js.map deleted file mode 100644 index 393d3f3a8..000000000 --- a/test/angular2/main.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["main.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;YAIA,qBAAc,EAAE,CAAC;YAEjB,QAAQ,CAAC,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE;gBAC1C,iDAAsB,EAAE,CAAC,eAAe,CAAC,sBAAS,CAAC,CAAC;YACtD,CAAC,CAAC,CAAC","file":"main.js","sourcesContent":["import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { enableProdMode } from '@angular/core';\nimport { AppModule } from './app.module';\n\nenableProdMode();\n\ndocument.body.addEventListener('bootstrap', () => {\n platformBrowserDynamic().bootstrapModule(AppModule);\n});\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/test/angular2/main.ts b/test/angular2/main.ts deleted file mode 100644 index 22e2405e4..000000000 --- a/test/angular2/main.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { enableProdMode } from '@angular/core'; -import { AppModule } from './app.module'; - -enableProdMode(); - -document.body.addEventListener('bootstrap', () => { - platformBrowserDynamic().bootstrapModule(AppModule); -}); diff --git a/test/angular2/tsconfig.json b/test/angular2/tsconfig.json deleted file mode 100644 index c6617a9a4..000000000 --- a/test/angular2/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "system", - "moduleResolution": "node", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "removeComments": false, - "noImplicitAny": false - } -} diff --git a/test/angular2/typings.json b/test/angular2/typings.json deleted file mode 100644 index aa60b6b57..000000000 --- a/test/angular2/typings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ambientDependencies": { - "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#4de74cb527395c13ba20b438c3a7a419ad931f1c" - } -} diff --git a/test/aria.html b/test/aria.html index fbe79990d..edcb07dd5 100644 --- a/test/aria.html +++ b/test/aria.html @@ -34,7 +34,7 @@ return comboBox.$.clearIcon; } function getItemElement(i) { - return comboBox.$.overlay.$.selector.querySelectorAll('.item')[i]; + return comboBox.$.overlay.$.selector.querySelectorAll('vaadin-combo-box-item')[i]; } function arrowDown() { diff --git a/test/common.html b/test/common.html index 34ab0e502..3ad92c925 100644 --- a/test/common.html +++ b/test/common.html @@ -6,7 +6,7 @@ - + diff --git a/test/enable-shadow-dom.js b/test/enable-shadow-dom.js deleted file mode 100644 index 335d774dc..000000000 --- a/test/enable-shadow-dom.js +++ /dev/null @@ -1,3 +0,0 @@ -if (document.location.search.indexOf('dom=shadow') == -1) { - document.location.search += '&dom=shadow'; -} diff --git a/test/filtering.html b/test/filtering.html index 74302f14d..5475d062f 100644 --- a/test/filtering.html +++ b/test/filtering.html @@ -112,7 +112,7 @@ expect(comboBox._focusedIndex).to.eql(0); }); - it('should not scroll to selected value when filtering', function() { + it('should not scroll to selected value when filtering', function(done) { comboBox.value = 'baz'; var spy = sinon.spy(comboBox.$.overlay, '_scrollIntoView'); @@ -120,7 +120,11 @@ setInputValue('ba'); setInputValue('b'); - expect(spy.callCount).to.eql(1); // scrolls once on open + // first scroll after open is async + comboBox.async(function() { + expect(spy.callCount).to.eql(1); // scrolls once on open + done(); + }); }); }); diff --git a/test/index.html b/test/index.html index 4175edb1c..4e5aaa4e6 100644 --- a/test/index.html +++ b/test/index.html @@ -13,8 +13,8 @@ WCT._config.environmentScripts.push('iron-test-helpers/mock-interactions.js'); WCT._config.environmentScripts.push(location.href.replace(/[^/]+$/, '') + 'mock-touch-interactions.js'); - var shadow = document.location.search.indexOf('dom=shadow') > -1; - WCT._config.environmentScripts.push(shadow ? 'webcomponentsjs/webcomponents.js' : 'webcomponentsjs/webcomponents-lite.js'); + var edge = window.navigator.userAgent.indexOf('Edge') > -1; // webcomponentsjs/issues/575 + WCT._config.environmentScripts.push(edge ? 'webcomponentsjs/webcomponents-lite.js' : 'webcomponentsjs/webcomponents.js'); WCT.loadSuites([ 'vaadin-combo-box-properties.html', @@ -30,8 +30,10 @@ 'data-binding.html', 'late-upgrade.html', 'vaadin-combo-box-light.html', - 'angular2.html' - ]); + 'item-template.html' + ].reduce(function(suites, suite) { + return suites.concat([suite, suite + '?dom=shadow']); + }, [])); diff --git a/test/item-template.html b/test/item-template.html new file mode 100644 index 000000000..7887c4576 --- /dev/null +++ b/test/item-template.html @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/keyboard.html b/test/keyboard.html index 0bdecdab7..19a88461b 100644 --- a/test/keyboard.html +++ b/test/keyboard.html @@ -386,9 +386,9 @@ Polymer.Base.async(function() { expect(comboBox.$.overlay._visibleItemsCount()).to.eql(4); - expect(comboBox.$.overlay._lastVisibleIndex()).to.eql(3); + expect(comboBox.$.overlay.$.selector.lastVisibleIndex).to.eql(3); done(); - }); + }, 1); }); it('should calculate items correctly when some items are hidden', function(done) { @@ -402,9 +402,9 @@ comboBox.open(); Polymer.Base.async(function() { - expect(comboBox.$.overlay._lastVisibleIndex()).to.eql(comboBox.$.overlay._visibleItemsCount() - 1); + expect(comboBox.$.overlay._visibleItemsCount()).to.eql(comboBox.$.overlay.$.selector.lastVisibleIndex + 1); done(); - }); + }, 1); }); }); @@ -422,7 +422,7 @@ comboBox.open(); selector = comboBox.$.overlay.$.selector; - Polymer.Base.async(done); + Polymer.Base.async(done, 1); }); it('should scroll down after reaching the last visible item', function() { diff --git a/test/mock-touch-interactions.js b/test/mock-touch-interactions.js index b891a0407..328f1a18a 100644 --- a/test/mock-touch-interactions.js +++ b/test/mock-touch-interactions.js @@ -14,18 +14,6 @@ 'use strict'; - var HAS_NEW_TOUCH = (function() { - var has = false; - try { - has = Boolean(new TouchEvent('x')); - } catch (_) { - // continue regardless of error - } - - return has; - })(); - - var middleOfNode = global.MockInteractions.middleOfNode; @@ -40,6 +28,10 @@ function makeTouches(xyList, node) { var id = 0; + if (window.ShadowDOMPolyfill && window.ShadowDOMPolyfill.isWrapper(node)) { + node = window.ShadowDOMPolyfill.unwrap(node); + } + return xyList.map(function(xy) { var touchInit = { identifier: id++, @@ -48,7 +40,7 @@ clientY: xy.y }; - return HAS_NEW_TOUCH ? new window.Touch(touchInit) : touchInit; + return window.Touch ? new window.Touch(touchInit) : touchInit; }); } @@ -73,13 +65,9 @@ }; var event; - if (HAS_NEW_TOUCH) { - event = new TouchEvent(type, touchEventInit); - } else { - event = new CustomEvent(type, {bubbles: true, cancelable: true}); - for (var property in touchEventInit) { - event[property] = touchEventInit[property]; - } + event = new CustomEvent(type, {bubbles: true, cancelable: true}); + for (var property in touchEventInit) { + event[property] = touchEventInit[property]; } node.dispatchEvent(event); diff --git a/test/overlay-position.html b/test/overlay-position.html index 15c629508..848be589d 100644 --- a/test/overlay-position.html +++ b/test/overlay-position.html @@ -86,10 +86,15 @@ describe('overlay', function() { beforeEach(function(done) { comboBox = fixture('combobox'); + var comboBoxRect = comboBox.getBoundingClientRect(); comboBox.items = getItemArray(20); - wh = window.innerHeight; - ww = window.innerWidth; + var win = comboBox.ownerDocument.defaultView; + // Subtract the combo-box size from the coordinates range in order not to + // move it outside the viewport boundaries when changing top and left. + // Otherwise it is not nice for debugging. + wh = win.innerHeight - comboBoxRect.height; + ww = win.innerWidth - comboBoxRect.width; horiCenter = ww * 0.5; vertTop = 0; vertBottom = wh; diff --git a/test/scrolling.html b/test/scrolling.html index 0efed223f..2f3727e76 100644 --- a/test/scrolling.html +++ b/test/scrolling.html @@ -78,27 +78,33 @@ var selectedItemRect = selectedItem.getBoundingClientRect(); var overlayRect = combobox.$.overlay.getBoundingClientRect(); - expect(selectedItemRect.left).to.be.at.least(overlayRect.left); - expect(selectedItemRect.top).to.be.at.least(overlayRect.top); - expect(selectedItemRect.right).to.be.at.most(overlayRect.right); - expect(selectedItemRect.bottom).to.be.at.most(overlayRect.bottom); + expect(selectedItemRect.left).to.be.at.least(overlayRect.left - 1); + expect(selectedItemRect.top).to.be.at.least(overlayRect.top - 1); + expect(selectedItemRect.right).to.be.at.most(overlayRect.right + 1); + expect(selectedItemRect.bottom).to.be.at.most(overlayRect.bottom + 1); } - it('should make selected item visible after open', function() { + it('should make selected item visible after open', function(done) { combobox.value = combobox.items[50]; combobox.open(); - expectSelectedItemPositionToBeVisible(); + combobox.async(function() { + expectSelectedItemPositionToBeVisible(); + done(); + }, 1); }); - it('should make selected item visible after reopen', function() { + it('should make selected item visible after reopen', function(done) { combobox.open(); combobox.value = combobox.items[50]; combobox.close(); combobox.open(); - expectSelectedItemPositionToBeVisible(); + combobox.async(function() { + expectSelectedItemPositionToBeVisible(); + done(); + }, 1); }); }); }); diff --git a/test/selecting-items.html b/test/selecting-items.html index 17efeab6e..5ed9b965d 100644 --- a/test/selecting-items.html +++ b/test/selecting-items.html @@ -22,7 +22,7 @@ @@ -67,7 +67,7 @@ var spy = sinon.spy(); combobox.$.overlay.addEventListener('selection-changed', spy); - Polymer.dom(combobox.$.overlay.$.selector).querySelector('.item').dispatchEvent(new CustomEvent('tap', { + Polymer.dom(combobox.$.overlay.$.selector).querySelector('vaadin-combo-box-item').dispatchEvent(new CustomEvent('tap', { bubbles: true })); @@ -84,7 +84,7 @@ MockTouchInteractions.touchstart(combobox.$.overlay.$.scroller); combobox.async(function() { - var firstItem = Polymer.dom(combobox.$.overlay.$.selector).querySelector('.item'); + var firstItem = Polymer.dom(combobox.$.overlay.$.selector).querySelector('vaadin-combo-box-item'); dispatchTap(firstItem); expect(spy.calledOnce).to.be.true; @@ -101,7 +101,7 @@ MockTouchInteractions.touchstart(combobox.$.overlay.$.scroller); combobox.async(function() { - var firstItem = Polymer.dom(combobox.$.overlay.$.selector).querySelector('.item'); + var firstItem = Polymer.dom(combobox.$.overlay.$.selector).querySelector('vaadin-combo-box-item'); dispatchTap(firstItem); expect(spy.calledOnce).to.be.false; @@ -120,15 +120,25 @@ // open the combo box, and move the scroll combobox.opened = true; - combobox.$.overlay.$.selector.scrollToIndex(20); - combobox.async(function() { - var firstItem = Polymer.dom(combobox.$.overlay.$.selector).querySelector('.item'); - dispatchTap(firstItem); + function listener() { + // Ensure the listener is invoked only once. Otherwise, `done()` might + // be called multiple times, which produces error for the test. + combobox.$.overlay.$.scroller.removeEventListener('scroll', listener); - expect(spy.calledOnce).to.be.false; - done(); - }, 200); + combobox.async(function() { + var firstItem = Polymer.dom(combobox.$.overlay.$.selector).querySelector('vaadin-combo-box-item'); + dispatchTap(firstItem); + + expect(spy.calledOnce).to.be.false; + done(); + }, 200); + } + + // Scroll start could delay, for example, with full SD polyfill + combobox.$.overlay.$.scroller.addEventListener('scroll', listener); + + combobox.$.overlay.$.selector.scrollToIndex(20); }); it('should fire `selection-changed` after the scrolling grace period', function(done) { @@ -140,15 +150,25 @@ combobox.$.overlay.addEventListener('selection-changed', spy); combobox.opened = true; - combobox.$.overlay.$.selector.scrollToIndex(20); - combobox.async(function() { - var firstItem = Polymer.dom(combobox.$.overlay.$.selector).querySelector('.item'); - dispatchTap(firstItem); + function listener() { + // Ensure the listener is invoked only once. Otherwise, `done()` might + // be called multiple times, which produces error for the test. + combobox.$.overlay.$.scroller.removeEventListener('scroll', listener); - expect(spy.calledOnce).to.be.true; - done(); - }, 500); + combobox.async(function() { + var firstItem = Polymer.dom(combobox.$.overlay.$.selector).querySelector('vaadin-combo-box-item'); + dispatchTap(firstItem); + + expect(spy.calledOnce).to.be.true; + done(); + }, 500); + } + + // Scroll start could delay, for example, with full SD polyfill + combobox.$.overlay.$.scroller.addEventListener('scroll', listener); + + combobox.$.overlay.$.selector.scrollToIndex(20); }); it('should select by using JS api', function() { @@ -164,7 +184,7 @@ it('should close the dropdown on selection', function() { combobox.open(); - combobox.$.overlay.$.selector.querySelector('.item').click(); + combobox.$.overlay.$.selector.querySelector('vaadin-combo-box-item').click(); expect(combobox.opened).to.equal(false); }); @@ -174,32 +194,82 @@ expect(valueChangedSpy.callCount).to.equal(1); }); - it('should fire exactly one `change` event', function() { - var spy = sinon.spy(); - combobox.addEventListener('change', spy); + describe('`change` event', function() { + it('should fire `value-changed` and `selected-item-changed` before `changed`', function(done) { + combobox.addEventListener('change', function() { + expect(valueChangedSpy.called).to.be.true; + expect(selectedItemChangedSpy.called).to.be.true; + done(); + }); - combobox.value = 'foo'; + combobox.open(); + combobox.value = 'foo'; + combobox.close(); + }); - expect(spy.callCount).to.equal(1); - }); + it('should fire exactly one `change` event', function() { + var spy = sinon.spy(); + combobox.addEventListener('change', spy); - it('should fire `value-changed` and `selected-item-changed` before `changed`', function(done) { - combobox.addEventListener('change', function() { - expect(valueChangedSpy.called).to.be.true; - expect(selectedItemChangedSpy.called).to.be.true; - done(); + combobox.open(); + combobox.value = 'foo'; + combobox.close(); + + expect(spy.callCount).to.equal(1); }); - combobox.value = 'foo'; - }); + it('should fire on clear', function() { + var spy = sinon.spy(); + combobox.addEventListener('change', spy); - it('should stop input `change` event from bubbling', function() { - var spy = sinon.spy(); - combobox.addEventListener('change', spy); + combobox.value = 'foo'; + combobox.$.clearIcon.click(); + + expect(spy.callCount).to.equal(1); + }); + + it('should not fire until close', function() { + var spy = sinon.spy(); + combobox.addEventListener('change', spy); + + combobox.value = 'foo'; + combobox.open(); + combobox.value = 'bar'; + + expect(spy.callCount).to.equal(0); + }); + + it('should not fire on cancel', function() { + var spy = sinon.spy(); + combobox.addEventListener('change', spy); - combobox.$.input.fire('change'); + combobox.open(); + combobox.value = 'foo'; + combobox.cancel(); + + expect(spy.callCount).to.equal(0); + }); - expect(spy.callCount).to.equal(0); + it('should not fire for changes when closed', function() { + var spy = sinon.spy(); + combobox.addEventListener('change', spy); + + combobox.value = 'foo'; + + combobox.open(); + combobox.close(); + + expect(spy.callCount).to.equal(0); + }); + + it('should stop input `change` event from bubbling', function() { + var spy = sinon.spy(); + combobox.addEventListener('change', spy); + + combobox.$.input.fire('change'); + + expect(spy.callCount).to.equal(0); + }); }); it('should close the dropdown when reselecting the current value', function() { @@ -238,12 +308,10 @@ clearIcon = combobox._clearElement; }); - it('should show the clearing icon only when dropdown is open', function() { - expect(window.getComputedStyle(clearIcon).display).to.eql('none'); - - combobox.open(); - + it('should show the clearing icon only when combobox has value', function() { expect(window.getComputedStyle(clearIcon).display).to.contain('block'); + combobox.value = ''; + expect(window.getComputedStyle(clearIcon).display).to.contain('none'); }); it('should not show clearing icon when nothing is selected', function() { diff --git a/test/toggling-dropdown.html b/test/toggling-dropdown.html index dce232f59..82e28789a 100644 --- a/test/toggling-dropdown.html +++ b/test/toggling-dropdown.html @@ -80,6 +80,16 @@ expect(combobox.opened).to.be.true; }); + it('should set body `pointer-events: none` on open and restore initial value on close', function() { + document.body.style.pointerEvents = 'painted'; + combobox.open(); + expect(getComputedStyle(document.body).pointerEvents).to.be.equal('none'); + expect(getComputedStyle(combobox).pointerEvents).to.be.equal('auto'); + expect(getComputedStyle(combobox.$.overlay).pointerEvents).to.be.equal('auto'); + combobox.close(); + expect(getComputedStyle(document.body).pointerEvents).to.be.equal('painted'); + }); + it('should not close an open popup', function() { combobox.open(); @@ -161,7 +171,8 @@ } else { it('should focus input on dropdown open', function() { var focusedInput = Polymer.dom(combobox.root).querySelector('input'); - expect(focusedInput).to.equal(combobox.root.activeElement || document.activeElement); + var activeElement = combobox.root.activeElement || document.activeElement; + expect(focusedInput.outerHTML).to.equal(activeElement.outerHTML); expect(focusedInput).to.equal(combobox.$.input); }); @@ -169,7 +180,8 @@ tapToggleIcon(); var focusedInput = Polymer.dom(combobox.root).querySelector('input'); - expect(focusedInput).to.equal(combobox.root.activeElement || document.activeElement); + var activeElement = combobox.root.activeElement || document.activeElement; + expect(focusedInput.outerHTML).to.equal(activeElement.outerHTML); expect(focusedInput).to.eql(combobox.$.input); }); } @@ -248,6 +260,27 @@ expect(combobox.opened).to.equal(false); }); + it('should not close on blur caused by mousedown on the overlay scroller', function() { + combobox.open(); + + combobox.$.overlay.fire('mousedown', 1, {node: combobox.$.overlay.$.scroller}); + combobox.$.input.fire('blur'); + + expect(combobox.opened).to.be.true; + }); + + it('should not close asyncronously on blur caused by mousedown on the overlay scroller', function(done) { + combobox.open(); + + combobox.$.overlay.fire('mousedown', 1, {node: combobox.$.overlay.$.scroller}); + combobox.$.input.fire('blur'); + + setTimeout(function() { + expect(combobox.opened).to.be.true; + done(); + }, 100); + }); + // When using Shadow DOM polyfill, the style scope of the combobox might // not get added to the overlay after it's moved back to the combobox. // The following test ensures that the overlay doesn't keep the style @@ -277,6 +310,10 @@ expect(getComputedStyle(combobox.$.toggleIcon).display).to.equal('none'); }); + it('clearIcon should be hidden when disabled', function() { + expect(getComputedStyle(combobox.$.clearIcon).display).to.equal('none'); + }); + it('dropdown should not be shown when disabled', function() { combobox.$.input.fire('tap'); expect(combobox.opened).to.be.false; @@ -293,6 +330,10 @@ expect(getComputedStyle(combobox.$.toggleIcon).display).to.equal('none'); }); + it('clearIcon should be hidden when read-only', function() { + expect(getComputedStyle(combobox.$.clearIcon).display).to.equal('none'); + }); + it('dropdown should not be shown when read-only', function() { combobox.$.input.fire('tap'); expect(combobox.opened).to.be.false; diff --git a/test/using-object-values.html b/test/using-object-values.html index 32e840d6b..f02a7604c 100644 --- a/test/using-object-values.html +++ b/test/using-object-values.html @@ -82,10 +82,11 @@ }); it('should use the default label property in overlay items', function(done) { - Polymer.Base.async(function() { - expect(combobox.$.overlay.$.selector.querySelector('.item').textContent).to.contain('foo'); + combobox.async(function() { + var firstItem = combobox.$.overlay.$.selector.querySelector('vaadin-combo-box-item'); + expect(Polymer.dom(firstItem.root).textContent).to.contain('foo'); done(); - }); + }, 1); }); it('should use the provided label property', function() { diff --git a/test/vaadin-combo-box-light.html b/test/vaadin-combo-box-light.html index 13f92ed74..0822b6995 100644 --- a/test/vaadin-combo-box-light.html +++ b/test/vaadin-combo-box-light.html @@ -1,15 +1,13 @@ - - vaadin-combo basic tests - + @@ -22,6 +20,17 @@ + + + + - - diff --git a/test/vaadin-combo-box-properties.html b/test/vaadin-combo-box-properties.html index d1090a307..620842f84 100644 --- a/test/vaadin-combo-box-properties.html +++ b/test/vaadin-combo-box-properties.html @@ -84,6 +84,19 @@ }); }); + describe('hasValue property', function() { + it('should be readOnly', function() { + expect(comboBox.hasValue).to.be.false; + comboBox.hasValue = true; + expect(comboBox.hasValue).to.be.false; + }); + + it('should be updated when setting the value', function() { + comboBox.value = 'foo'; + expect(comboBox.hasValue).to.be.true; + }); + }); + describe('allow custom value property', function() { beforeEach(function() { comboBox.items = []; @@ -192,6 +205,60 @@ expect(comboBox.selectedItem).to.be.null; }); }); + + describe('focus API', function() { + it('should have readOnly focused property', function() { + comboBox.focused = true; + expect(comboBox.focused).to.be.false; + expect(comboBox.hasAttribute('focused')).to.be.false; + }); + + it('should not be focused by default', function() { + expect(comboBox.focused).to.be.false; + expect(comboBox.hasAttribute('focused')).to.be.false; + }); + + describe('methods', function() { + // Avoid FF window focus issues by stubing native focus behavior + var inputFocusStub, inputBlurStub; + + beforeEach(function() { + inputFocusStub = sinon.stub(comboBox.$.input, 'focus', function() { + comboBox.$.input.fire('focus'); + }); + + inputBlurStub = sinon.stub(comboBox.$.input, 'blur', function() { + comboBox.$.input.fire('blur'); + }); + }); + + it('should focus the input with focus method', function() { + var focusedChangedSpy = sinon.spy(); + comboBox.addEventListener('focused-changed', focusedChangedSpy); + + comboBox.focus(); + + expect(inputFocusStub.called).to.be.true; + expect(comboBox.focused).to.be.true; + expect(comboBox.hasAttribute('focused')).to.be.true; + expect(focusedChangedSpy.called).to.be.true; + }); + + it('should blur the input with the blur method', function() { + comboBox.focus(); + + var focusedChangedSpy = sinon.spy(); + comboBox.addEventListener('focused-changed', focusedChangedSpy); + + comboBox.blur(); + + expect(inputBlurStub.called).to.be.true; + expect(comboBox.focused).to.be.false; + expect(comboBox.hasAttribute('focused')).to.be.false; + expect(focusedChangedSpy.called).to.be.true; + }); + }); + }); }); diff --git a/vaadin-combo-box-behavior.html b/vaadin-combo-box-behavior.html index 1a7bc3c4a..45711b6c9 100644 --- a/vaadin-combo-box-behavior.html +++ b/vaadin-combo-box-behavior.html @@ -54,6 +54,11 @@ notify: false }, + /** + * Used to detect user value changes and fire `change` events. + */ + _lastCommittedValue: String, + /** * A read-only property indicating whether this combo box has a value * selected or not. It can be used for example in styling of the component. @@ -61,7 +66,7 @@ hasValue: { type: Boolean, value: false, - readonly: true, + readOnly: true, reflectToAttribute: true }, @@ -105,6 +110,9 @@ * * 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. */ itemLabelPath: { type: String, @@ -136,7 +144,12 @@ _inputElementValue: String, - _closeOnBlurIsPrevented: Boolean + _closeOnBlurIsPrevented: Boolean, + + _templatized: Boolean, + _itemTemplate: Boolean, + + _previousDocumentPointerEvents: String }, observers: [ @@ -161,16 +174,29 @@ if (this.value === undefined) { this.value = ''; } + this._lastCommittedValue = this.value; Polymer.IronA11yAnnouncer.requestAvailability(); }, _onBlur: function() { - if (!this._closeOnBlurIsPrevented) { + if (!this._closeOnBlurIsPrevented && document.activeElement !== this.$.overlay.$.scroller) { this.close(); } }, _onOverlayDown: function(event) { + if (event.detail && + event.detail.sourceEvent && + event.detail.sourceEvent.type === 'mousedown' && + event.target === this.$.overlay.$.scroller) { + // Mousedown on the scroller (e. g., when scrolling with mouse) makes + // an unintentional blur that should not close. It is hard to prevent + // the blur, so instead let us set a flag for the blur listeners. + this._closeOnBlurIsPrevented = true; + setTimeout(function() { + this._closeOnBlurIsPrevented = false; + }); + } if (this.$.overlay.touchDevice && event.target !== this.$.overlay.$.scroller) { // 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. @@ -338,7 +364,11 @@ this.value = ''; } - this.close(); + if (this.opened) { + this.close(); + } else { + this._detectAndDispatchChange(); + } }, /** @@ -346,6 +376,8 @@ */ cancel: function() { this._revertInputValueToValue(); + // In the next _detectAndDispatchChange() call, the change detection should not pass + this._lastCommittedValue = this.value; this.close(); }, @@ -357,8 +389,18 @@ // before we update the overlay and the list positions and sizes. this.$.overlay.ensureItemsRendered(); - this.$.overlay.notifyResize(); - this.$.overlay.adjustScrollPosition(); + // Ensure metrics are up-to-date + this.$.overlay.updateViewportBoundaries(); + this.$.overlay.async(this.$.overlay.adjustScrollPosition); + this.$.overlay.async(this.$.overlay.notifyResize, 1); + + // Set body pointer-events to 'none' to disable mouse interactions with + // other document nodes (combo-box is "modal") + this._previousDocumentPointerEvents = document.body.style.pointerEvents; + document.body.style.pointerEvents = 'none'; + + // _detectAndDispatchChange() should not consider value changes done before opening + this._lastCommittedValue = this.value; }, _onClosed: function() { @@ -384,9 +426,13 @@ } } + this._detectAndDispatchChange(); + this._clearSelectionRange(); this.filter = ''; + + document.body.style.pointerEvents = this._previousDocumentPointerEvents; }, /** @@ -461,7 +507,7 @@ this.value = ''; } - this.hasValue = this.value !== ''; + this._setHasValue(this.value !== ''); this._inputElementValue = this.value; } else { var value = this._getItemValue(selectedItem); @@ -469,7 +515,7 @@ this.value = value; } - this.hasValue = true; + this._setHasValue(true); this._inputElementValue = this._getItemLabel(selectedItem); } @@ -490,12 +536,19 @@ this._inputElementValue = value; } - this.hasValue = this.value !== ''; + this._setHasValue(this.value !== ''); } else { this.selectedItem = null; } + // In the next _detectAndDispatchChange() call, the change detection should pass + this._lastCommittedValue = undefined; + }, - this.fire('change', undefined, {bubbles: true}); + _detectAndDispatchChange: function() { + if (this.value !== this._lastCommittedValue) { + this.fire('change', undefined, {bubbles: true}); + this._lastCommittedValue = this.value; + } }, _itemsChanged: function(e) { @@ -602,6 +655,57 @@ } }, + get _instanceProps() { + return { + item: true, + index: true, + selected: true, + focused: true + }; + }, + + _ensureTemplatized: function() { + if (!this._templatized) { + this._templatized = true; + this._itemTemplate = Polymer.dom(this).querySelector('template'); + if (this._itemTemplate) { + this.templatize(this._itemTemplate); + } + } + }, + + _forwardParentProp: function(prop, value) { + var items = this.$.overlay.$.selector.querySelectorAll('vaadin-combo-box-item'); + Array.prototype.forEach.call(items, function(item) { + item._itemTemplateInstance[prop] = value; + }); + }, + + _forwardParentPath: function(path, value) { + var items = this.$.overlay.$.selector.querySelectorAll('vaadin-combo-box-item'); + Array.prototype.forEach.call(items, function(item) { + item._itemTemplateInstance.notifyPath(path, value, true); + }); + }, + + _preventInputBlur: function() { + if (this._toggleElement) { + this.listen(this._toggleElement, 'down', '_preventDefault'); + } + if (this._clearElement) { + this.listen(this._clearElement, 'down', '_preventDefault'); + } + }, + + _restoreInputBlur: function() { + if (this._toggleElement) { + this.unlisten(this._toggleElement, 'down', '_preventDefault'); + } + if (this._clearElement) { + this.unlisten(this._clearElement, 'down', '_preventDefault'); + } + }, + _preventDefault: function(e) { e.preventDefault(); }, @@ -614,6 +718,7 @@ /** @polymerBehavior vaadin.elements.combobox.ComboBoxBehavior */ vaadin.elements.combobox.ComboBoxBehavior = [ Polymer.IronFormElementBehavior, + Polymer.Templatizer, vaadin.elements.combobox.DropdownBehavior, vaadin.elements.combobox.ComboBoxBehaviorImpl ]; diff --git a/vaadin-combo-box-item.html b/vaadin-combo-box-item.html new file mode 100644 index 000000000..74b91d56f --- /dev/null +++ b/vaadin-combo-box-item.html @@ -0,0 +1,92 @@ + + + + + + + + + + + diff --git a/vaadin-combo-box-light.html b/vaadin-combo-box-light.html index eb3849cbe..2125d845b 100644 --- a/vaadin-combo-box-light.html +++ b/vaadin-combo-box-light.html @@ -55,10 +55,11 @@ } - + + @@ -13,11 +14,12 @@ @@ -84,13 +91,16 @@ items="[[_items]]" scroll-target="[[_getScroller()]]">
@@ -165,7 +175,7 @@ _notTapping: Boolean, - _ignoreTaps: Boolean + _ignoreTaps: Boolean, }, ready: function() { @@ -256,33 +266,40 @@ }, _scrollIntoView: function(index) { - if (this._visibleItemsCount() === undefined) { + var visibleItemsCount = this._visibleItemsCount(); + if (visibleItemsCount === undefined) { // Scroller is not visible. Moving is unnecessary. return; } var targetIndex = index; - if (index > this._lastVisibleIndex()) { + if (index > this.$.selector.lastVisibleIndex - 1) { // Index is below the bottom, scrolling down. Make the item appear at the bottom. - targetIndex = index - this._visibleItemsCount() + 1; - - // From iron-list 1.2.4, scrolling to an index guarantees that the item - // is visible into the viewport, but does not gurarantee that it is at the - // first position. Jumping first to the item we want to be at the bottom, - // fixes the problem. - this.$.selector.scrollToIndex(index); + targetIndex = index - visibleItemsCount + 1; } else if (index > this.$.selector.firstVisibleIndex) { // The item is already visible, scrolling is unnecessary per se. But we need to trigger iron-list to set // the correct scrollTop on the scrollTarget. Scrolling to firstVisibleIndex. targetIndex = this.$.selector.firstVisibleIndex; } this.$.selector.scrollToIndex(Math.max(0, targetIndex)); + + // Sometimes the item is partly below the bottom edge, detect and adjust. + var item = this._items[index]; + if (item === undefined) return; + var pidx = this.$.selector._getPhysicalIndex(item), + physicalItem = this.$.selector._physicalItems[pidx]; + if (!physicalItem) return; + var physicalItemRect = physicalItem.getBoundingClientRect(), + scrollerRect = this.$.scroller.getBoundingClientRect(), + scrollTopAdjust = physicalItemRect.bottom - scrollerRect.bottom + this._viewportTotalPaddingBottom; + if (scrollTopAdjust > 0) { + this.$.scroller.scrollTop += scrollTopAdjust; + } }, ensureItemsRendered: function() { this.$.selector.flushDebouncer('_debounceTemplate'); - this.$.selector._render && this.$.selector._render(); }, adjustScrollPosition: function() { @@ -320,17 +337,15 @@ }, updateViewportBoundaries: function() { - this._cachedViewportTotalPadding = undefined; + this._cachedViewportTotalPaddingBottom = undefined; this.$.selector.updateViewportBoundaries(); }, - get _viewportTotalPadding() { - if (this._cachedViewportTotalPadding === undefined) { + get _viewportTotalPaddingBottom() { + if (this._cachedViewportTotalPaddingBottom === undefined) { var itemsStyle = window.getComputedStyle(this._unwrapIfNeeded(this.$.selector.$.items)); - this._cachedViewportTotalPadding = [ - itemsStyle.paddingTop, + this._cachedViewportTotalPaddingBottom = [ itemsStyle.paddingBottom, - itemsStyle.borderTopWidth, itemsStyle.borderBottomWidth ].map(function(v) { return parseInt(v, 10); @@ -339,27 +354,17 @@ }); } - return this._cachedViewportTotalPadding; + return this._cachedViewportTotalPaddingBottom; }, - // TODO: PR for iron-list: https://github.com/PolymerElements/iron-list/pull/150 _visibleItemsCount: function() { - var firstItemIndex = this.$.selector._physicalStart; - var firstItemHeight = this.$.selector._physicalSizes[firstItemIndex]; - - var viewportHeight = this.$.selector._viewportHeight || this.$.selector._viewportSize; // Changed in v1.3.0. - - if (firstItemHeight && viewportHeight) { - var visibleItems = (viewportHeight - this._viewportTotalPadding) / firstItemHeight; - return Math.floor(visibleItems); - } - }, - - // TODO: PR for iron-list: https://github.com/PolymerElements/iron-list/pull/150 - _lastVisibleIndex: function() { - if (this._visibleItemsCount()) { - return this.$.selector.firstVisibleIndex + this._visibleItemsCount() - 1; - } + // Ensure items are rendered + this.$.selector.flushDebouncer('_debounceTemplate'); + // Ensure items are positioned + this.$.selector.scrollToIndex(this.$.selector.firstVisibleIndex); + // Ensure viewport boundaries are up-to-date + this.updateViewportBoundaries(); + return this.$.selector.lastVisibleIndex - this.$.selector.firstVisibleIndex + 1; }, _selectItem: function(item) { diff --git a/vaadin-combo-box-shared-styles.html b/vaadin-combo-box-shared-styles.html index 7c5f92de1..9829f2f19 100644 --- a/vaadin-combo-box-shared-styles.html +++ b/vaadin-combo-box-shared-styles.html @@ -8,6 +8,10 @@ transition: all 0.2s !important; } + :host([opened]) { + pointer-events: auto; + } + :host([opened]) .rotate-on-open, :host([opened]) ::content .rotate-on-open { -webkit-transform: rotate(180deg); @@ -16,23 +20,26 @@ paper-icon-button.small, :host ::content paper-icon-button.small { - box-sizing: content-box !important; - bottom: -6px !important; - width: 20px !important; - height: 20px !important; - padding: 4px 6px 8px !important; + padding: 6px !important; } - :host(:not([opened])) ::content .clear-button, - :host(:not([opened])) .clear-button, - :host(:not([has-value])) ::content .clear-button, + :host(:not([has-value])) ::content [slot=clear-button], + :host(:not([has-value])) ::slotted([slot=clear-button]), :host(:not([has-value])) .clear-button { display: none; } - :host([readonly]) ::content .toggle-button, + :host([readonly]) ::content [slot=clear-button], + :host([readonly]) ::slotted([slot=clear-button]), + :host([readonly]) .clear-button, + :host([disabled]) ::content [slot=clear-button], + :host([disabled]) ::slotted([slot=clear-button]), + :host([disabled]) .clear-button, + :host([readonly]) ::content [slot=toggle-button], + :host([readonly]) ::slotted([slot=toggle-button]), :host([readonly]) .toggle-button, - :host([disabled]) ::content .toggle-button, + :host([disabled]) ::content [slot=toggle-button], + :host([disabled]) ::slotted([slot=toggle-button]), :host([disabled]) .toggle-button { display: none; } diff --git a/vaadin-combo-box.html b/vaadin-combo-box.html index aa5b2b4d9..d281c4311 100644 --- a/vaadin-combo-box.html +++ b/vaadin-combo-box.html @@ -30,6 +30,30 @@ This element is also extended with the `IronFormElementBehavior` to enable usage within an `iron-form`. +### Item Template + +`` supports using custom item template provided in the light +DOM: + +```html + + + +``` + +The following properties are available for item template bindings: + +Property name | Type | Description +--------------|------|------------ +`index`| Number | Index of the item in the `items` array +`item` | String or Object | The item reference +`selected` | Boolean | True when item is selected +`focused` | Boolean | True when item is focused + +See the [Item Template Live Demos](demo/item-template.html) for more examples. + ### Styling There are custom properties and mixins you can use to style the component: @@ -74,10 +98,6 @@ paper-icon-button.toggle-button, :host ::content paper-icon-button.clear-button, :host ::content paper-icon-button.toggle-button { - position: absolute; - bottom: -4px; - right: -4px; - line-height: 18px !important; width: 32px; height: 32px; @@ -90,11 +110,6 @@ --paper-icon-button-ink-color: rgba(0, 0, 0, .54); } - paper-input-container paper-icon-button.clear-button, - paper-input-container ::content paper-icon-button.clear-button { - right: 28px; - } - paper-input-container paper-icon-button:hover, paper-input-container ::content paper-icon-button:hover, :host([opened]) paper-input-container paper-icon-button, @@ -115,17 +130,6 @@ #input::-ms-clear { display: none; } - - #input { - box-sizing: border-box; - padding-right: 28px; - } - - :host([opened][has-value]) #input { - padding-right: 60px; - margin-right: -32px; - } - - + - + - - - - - - - - - - - - + + + + + +
+ + + + +
+ +
+ + + + +