diff --git a/CHANGELOG.md b/CHANGELOG.md index e02ac8ad..ce4363dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Unreleased section, uncommenting the header as necessary. --> +## Unreleased + - +### Fixed +* Type for `eventOptions` decorator now properly includes `passive` and `once` options ([#325](https://github.com/Polymer/lit-element/issues/325)) ## [0.6.5] - 2018-12-13 ### Changed: diff --git a/package-lock.json b/package-lock.json index d2184d36..6a7e1431 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2174,7 +2174,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true } @@ -2959,7 +2959,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true } @@ -3276,7 +3276,7 @@ }, "expand-range": { "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "resolved": "http://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { @@ -4093,7 +4093,7 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "dev": true }, @@ -6631,7 +6631,7 @@ }, "pretty-bytes": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "resolved": "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=", "dev": true }, @@ -7298,7 +7298,7 @@ }, "mime": { "version": "1.2.11", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", + "resolved": "http://registry.npmjs.org/mime/-/mime-1.2.11.tgz", "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=", "dev": true }, @@ -7997,7 +7997,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { diff --git a/src/lib/decorators.ts b/src/lib/decorators.ts index 58272bbe..193c2bf2 100644 --- a/src/lib/decorators.ts +++ b/src/lib/decorators.ts @@ -55,8 +55,8 @@ export const customElement = (tagName: string) => * corresponding attribute value. A `PropertyDeclaration` may optionally be * supplied to configure property features. */ -export const property = (options?: PropertyDeclaration) => (proto: Object, - name: PropertyKey) => { +export const property = (options?: PropertyDeclaration) => ( + proto: Object, name: PropertyKey) => { (proto.constructor as typeof UpdatingElement).createProperty(name, options); }; @@ -116,7 +116,7 @@ function _query(queryFn: (target: NodeSelector, selector: string) => T) { * } * } */ -export const eventOptions = (options: EventListenerOptions) => +export const eventOptions = (options: AddEventListenerOptions) => (proto: any, name: string) => { // This comment is here to fix a disagreement between formatter and linter Object.assign(proto[name], options); diff --git a/src/test/lib/decorators_test.ts b/src/test/lib/decorators_test.ts index 50b789f4..f8c8e73d 100644 --- a/src/test/lib/decorators_test.ts +++ b/src/test/lib/decorators_test.ts @@ -22,6 +22,49 @@ import { } from '../../lit-element.js'; import {generateElementName} from '../test-helpers.js'; +let hasOptions; +const supportsOptions = (function() { + if (hasOptions !== undefined) { + return hasOptions; + } + const fn = () => {}; + const event = 'foo'; + hasOptions = false; + const options = { + get capture() { + hasOptions = true; + return true; + } + }; + document.body.addEventListener(event, fn, options); + document.body.removeEventListener(event, fn, options); + return hasOptions; +})(); + +let hasPassive; +const supportsPassive = (function() { + if (hasPassive !== undefined) { + return hasPassive; + } + // Use an iframe since ShadyDOM will pass this test but doesn't actually + // enforce passive behavior. + const f = document.createElement('iframe'); + document.body.appendChild(f); + const fn = () => {}; + const event = 'foo'; + hasPassive = false; + const options = { + get passive() { + hasPassive = true; + return true; + } + }; + f.contentDocument!.addEventListener(event, fn, options); + f.contentDocument!.removeEventListener(event, fn, options as AddEventListenerOptions); + document.body.removeChild(f); + return hasPassive; +})(); + const assert = chai.assert; suite('decorators', () => { @@ -123,7 +166,10 @@ suite('decorators', () => { }); suite('@eventOptions', () => { - test('allows capturing listeners', async () => { + test('allows capturing listeners', async function() { + if (!supportsOptions) { + this.skip(); + } @customElement(generateElementName() as keyof HTMLElementTagNameMap) class C extends LitElement { eventPhase?: number; @@ -147,5 +193,69 @@ suite('decorators', () => { button.click(); assert.equal(c.eventPhase, Event.CAPTURING_PHASE); }); + + test('allows once listeners', async function() { + if (!supportsOptions) { + this.skip(); + } + @customElement(generateElementName() as keyof HTMLElementTagNameMap) + class C extends LitElement { + + clicked = 0; + + render() { + return html` +
+ `; + } + + @eventOptions({once : true}) + onClick() { + this.clicked++; + } + } + + const c = new C(); + container.appendChild(c); + await c.updateComplete; + const button = c.shadowRoot!.querySelector('button')!; + button.click(); + button.click(); + assert.equal(c.clicked, 1); + }); + + test('allows passive listeners', async function() { + if (!supportsPassive) { + this.skip(); + } + @customElement(generateElementName() as keyof HTMLElementTagNameMap) + class C extends LitElement { + + defaultPrevented?: boolean; + + render() { + return html` +
+ `; + } + + @eventOptions({passive : true}) + onClick(e: Event) { + try { + e.preventDefault(); + } catch (error) { + // no need to do anything + } + this.defaultPrevented = e.defaultPrevented; + } + } + + const c = new C(); + container.appendChild(c); + await c.updateComplete; + const button = c.shadowRoot!.querySelector('button')!; + button.click(); + assert.isFalse(c.defaultPrevented); + }); }); });