diff --git a/.gitignore b/.gitignore index 23e14c10..3495b13a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /node_modules /bower_components -/test -/poly-lit-element.js -/poly-lit-element.d.ts -/poly-lit-element.js.map +/test/lit* +/lit-element.js +/lit-element.d.ts +/lit-element.js.map diff --git a/README.md b/README.md index bc1072f9..b6bd6351 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,57 @@ -# polymer-lit +# lit-element -## This is just an example, and not event working yet. Please hold. +## Base class for creating custom elements using Polymer and lit-html. + +```javascript +import {LitElement, html} from 'node_modules/@polymer/lit-element/lit-element.js' + +class MyElement extends LitElement { + + // Public property API that triggers re-render (synched with attributes) + static get properties() { + return { + foo: String, + whales: Number + } + } + + constructor() { + super(); + this.foo = 'foo'; + } + + ready() { + this.addEventListener('click', async (e) => { + this.whales++; + await this.nextRendered; + this.dispatchEvent(new CustomEvent('whales', {detail: {whales: this.whales}})) + }); + super.ready(); + } + + // Render method should return a `TemplateResult` using the provided lit-html `html` tag function + render({foo, whales}) { + return html` + +

Foo: ${foo}

+
whales: ${'🐳'.repeat(whales)}
+ + `; + } + +} +customElements.define('my-element', MyElement); +``` + +```html + hi +``` + +## Known Issues +* This element does not yet work with the ShadyCSS polyfill. Support is coming soon! +* API is subject to minor changes. +* See [lit-html](https://github.com/Polymer/lit-html) for more info. diff --git a/demo/lit-element.html b/demo/lit-element.html new file mode 100644 index 00000000..f191ebf5 --- /dev/null +++ b/demo/lit-element.html @@ -0,0 +1,75 @@ + + + + lit-element demo + + + + Hi + diff --git a/package-lock.json b/package-lock.json index e7643921..9885048b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "poly-lit-element", + "name": "@polymer/polymer-lit-element", "version": "0.1.0", "lockfileVersion": 1, "requires": true, @@ -3681,9 +3681,9 @@ } }, "lit-html": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-0.6.0.tgz", - "integrity": "sha512-H8CmAxZ9kdhiEyrcKNBlFF4pREp5/BKzIPE3EggKISEMT10Ez6WxwR3mzGPUfwgjTX9UQHAzLMtpn4tNkqS5sg==" + "version": "0.8.0-pre.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-0.8.0-pre.1.tgz", + "integrity": "sha512-olcLRCKRlmLE0I78+bJ5GAT9ALD7A9Q6ToouRnGTtTCVJ6UoYQ0sIMieDqiF4DrmOJf1Qm4yq5eACrzIGNPYvg==" }, "load-json-file": { "version": "1.1.0", @@ -5991,12 +5991,6 @@ "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", "dev": true }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -6008,6 +6002,12 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -6419,9 +6419,9 @@ "dev": true }, "typescript": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz", - "integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz", + "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=", "dev": true }, "typical": { @@ -6721,6 +6721,74 @@ "minimalistic-assert": "1.0.0" } }, + "wct-browser-legacy": { + "version": "0.0.1-pre.10", + "resolved": "https://registry.npmjs.org/wct-browser-legacy/-/wct-browser-legacy-0.0.1-pre.10.tgz", + "integrity": "sha512-TREECc5jI43f/zkMGSLhFB4Fmn7QHV/cAPE+z+S6XU8RurQ6W4JRw72cVOfyc7oTAWF6fIzQMEQPV0HwcIYqCQ==", + "dev": true, + "requires": { + "@polymer/polymer": "3.0.0-pre.1", + "@polymer/sinonjs": "1.17.1", + "@polymer/test-fixture": "3.0.0-pre.1", + "@webcomponents/webcomponentsjs": "1.0.10", + "accessibility-developer-tools": "2.12.0", + "async": "1.5.2", + "chai": "3.5.0", + "lodash": "3.10.1", + "mocha": "3.5.0", + "sinon": "1.17.7", + "sinon-chai": "2.13.0", + "stacky": "1.3.1" + }, + "dependencies": { + "@polymer/test-fixture": { + "version": "3.0.0-pre.1", + "resolved": "https://registry.npmjs.org/@polymer/test-fixture/-/test-fixture-3.0.0-pre.1.tgz", + "integrity": "sha512-HfttRbDMz3DZezvfaFFBdZejlnnM9hT7CyPp81lTeaDWY9kCHEohuIIAEX/BNkrDp0S9+z16t60gdkPgR8JVKg==", + "dev": true + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "1.0.2", + "deep-eql": "0.1.3", + "type-detect": "1.0.0" + } + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", + "dev": true + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + } + } + }, "wct-local": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/wct-local/-/wct-local-2.0.15.tgz", diff --git a/package.json b/package.json index 336e8b6b..bfa843db 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "poly-lit-element", + "name": "@polymer/lit-element", "version": "0.1.0", - "description": "Polymer + Lit", + "description": "Polymer based lit-html custom element", "license": "BSD-3-Clause", - "repository": "PolymerLabs/poly-lit-element", - "main": "poly-lit-element.js", - "module": "poly-lit-element.js", + "repository": "PolymerLabs/lit-element", + "main": "lit-element.js", + "module": "lit-element.js", "directories": { "test": "test" }, @@ -15,7 +15,7 @@ "pretest": "npm run posttest; ln -s node_modules bower_components", "test": "npm run build && wct -l chrome && npm run lint", "posttest": "rm -f bower_components", - "checksize": "uglifyjs poly-lit-element.js -mc --toplevel | gzip -9 | wc -c", + "checksize": "uglifyjs lit-element.js -mc --toplevel | gzip -9 | wc -c", "format": "find src test | grep '\\.js$\\|\\.ts$' | xargs clang-format --style=file -i", "lint": "tslint --project ./" }, @@ -27,13 +27,17 @@ "mocha": "^3.4.2", "tslint": "^5.7.0", "typedoc": "^0.8.0", - "typescript": "^2.5.2", + "typescript": "^2.6.2", "uglify-es": "^3.0.27", + "wct-browser-legacy": "0.0.1-pre.10", "web-component-tester": "^6.0.1" }, - "typings": "poly-lit-element.d.ts", + "typings": "lit-element.d.ts", "dependencies": { - "@polymer/polymer": "^3.0.0-pre.1", - "lit-html": "^0.6.0" - } + "@polymer/polymer": "^3.0.0-pre.4", + "lit-html": "^0.8.0" + }, +"publishConfig": { +"access": "public" +} } diff --git a/src/@polymer/lit-element.ts b/src/@polymer/lit-element.ts new file mode 100644 index 00000000..30436d3a --- /dev/null +++ b/src/@polymer/lit-element.ts @@ -0,0 +1,64 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ +import { PropertiesMixin } from '../../@polymer/polymer/lib/mixins/properties-mixin.js'; +import { TemplateResult } from '../../lit-html/lit-html.js'; +import { render } from '../../lit-html/lib/lit-extended.js'; + +export { html } from '../../lit-html/lit-html.js'; +export class LitElement extends PropertiesMixin(HTMLElement) { + + _nextRendered: Promise|null = null; + _nextRenderedResolver: Function|null = null; + + ready() { + this.attachShadow({mode: 'open'}); + super.ready(); + } + + _flushProperties() { + super._flushProperties(); + // TODO(sorvell): propertiesChanged should have `_getData` + const result = this.render(this.__data); + if (result) { + render(result, this.shadowRoot!); + } + if (this._nextRenderedResolver) { + this._nextRenderedResolver(); + this._nextRenderedResolver = null; + this._nextRendered = null; + } + } + + /** + * Return a template result to render using lit-html. + */ + render(_props: object): TemplateResult { + throw new Error('render() not implemented'); + } + + invalidate() { + this._invalidateProperties(); + } + + get nextRendered() { + if (!this._nextRendered) { + // TODO(sorvell): handle rejected render. + this._nextRendered = new Promise((resolve) => { + this._nextRenderedResolver = resolve; + }); + } + return this._nextRendered; + } + +} diff --git a/src/@polymer/test/lit-element_test.ts b/src/@polymer/test/lit-element_test.ts new file mode 100644 index 00000000..699af67f --- /dev/null +++ b/src/@polymer/test/lit-element_test.ts @@ -0,0 +1,163 @@ +/** + * @license + * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at + * http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at + * http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at + * http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at + * http://polymer.github.io/PATENTS.txt + */ + +import { LitElement, html } from '../lit-element.js'; + +/// +/// + +const assert = chai.assert; + +suite('LitElement', () => { + + test('renders initial content', () => { + const rendered = `hello world`; + customElements.define('x-1', class extends LitElement { + render() { + return html`${rendered}` + } + }); + const el = document.createElement('x-1'); + document.body.appendChild(el); + assert.ok(el.shadowRoot); + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, rendered); + document.body.removeChild(el); + }); + + test('renders when created via constructor', () => { + const rendered = `hello world`; + const C = class extends LitElement { + render() { + return html`${rendered}` + } + }; + customElements.define('x-2', C); + const el = new C(); + document.body.appendChild(el); + assert.ok(el.shadowRoot); + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, rendered); + document.body.removeChild(el); + }); + + test('renders changes when properties change', (done) => { + customElements.define('x-3', class extends LitElement { + static get properties() { + return { + foo: String + } + } + + foo = 'one'; + + render(props: any) { + return html`${props.foo}` + } + }); + const el = document.createElement('x-3'); + document.body.appendChild(el); + assert.ok(el.shadowRoot); + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, 'one'); + el.foo = 'changed'; + requestAnimationFrame(() => { + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, 'changed'); + document.body.removeChild(el); + done(); + }); + }); + + test('renders changes when attributes change', (done) => { + customElements.define('x-4', class extends LitElement { + static get properties() { + return { + foo: String + } + } + + foo = 'one'; + + render(props: any) { + return html`${props.foo}` + } + }); + const el = document.createElement('x-4'); + document.body.appendChild(el); + assert.ok(el.shadowRoot); + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, 'one'); + el.setAttribute('foo', 'changed'); + requestAnimationFrame(() => { + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, 'changed'); + document.body.removeChild(el); + done(); + }); + }); + + test('renders changes made at `ready` time', () => { + customElements.define('x-5', class extends LitElement { + static get properties() { + return { + foo: String + } + } + + foo = 'one'; + + ready() { + this.foo = 'changed'; + super.ready(); + } + + render(props: any) { + return html`${props.foo}` + } + }); + const el = document.createElement('x-5'); + document.body.appendChild(el); + assert.ok(el.shadowRoot); + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, 'changed'); + document.body.removeChild(el); + }); + + test('nextRendered waits until next rendering', (done) => { + customElements.define('x-6', class extends LitElement { + static get properties() { + return { + foo: Number + } + } + + foo = 0; + + render(props: any) { + return html`${props.foo}` + } + }); + const el = document.createElement('x-6'); + document.body.appendChild(el); + el.foo++; + el.nextRendered.then(() => { + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, '1'); + el.foo++; + el.nextRendered.then(() => { + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, '2'); + el.foo++; + el.nextRendered.then(() => { + assert.equal((el.shadowRoot as ShadowRoot).innerHTML, '3'); + document.body.removeChild(el); + done(); + }); + }); + }); + }); + +}); diff --git a/src/poly-lit-element.ts b/src/poly-lit-element.ts deleted file mode 100644 index cd2df476..00000000 --- a/src/poly-lit-element.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ - -import { dedupingMixin } from '../@polymer/polymer/lib/utils/mixin.js'; -import { TemplateResult } from '../lit-html/lit-html.js'; -import { render } from '../lit-html/lib/lit-extended.js'; - -export { html } from '../lit-html/lit-html.js'; - -export type Constructor = { new(...args: any[]): T }; - -declare interface PropertyAccessors { - ready(): void; - __dataPending: object; - __dataOld: object; - _invalidateProperties(): void; - _propertiesChanged(props: any, changed: any, old: any): void; -} - -export const PolymerLit = dedupingMixin(>(base: S) => { - - return class PolymerLit extends base { - - _templateResult: TemplateResult; - - ready() { - this.attachShadow({mode: 'open'}); - super.ready(); - // TODO(sorvell): we need to trigger rendering even if no changes occurred! - // can set a dummy property, but that's maybe not good so we "invalidate" - // but need to ready private state to do so. - //this._setProperty('_readied', true); - this.__dataPending = this.__dataPending || {}; - this.__dataOld = this.__dataOld || {}; - this._invalidateProperties(); - } - - _propertiesChanged(props: any, changed: any, old: any) { - super._propertiesChanged(props, changed, old); - this._templateResult = this.render(); - if (this._templateResult) { - render(this._templateResult, this.shadowRoot!); - } - } - - /** - * Return a template result to render using lit-html. - */ - render(): TemplateResult { - throw new Error('render() not implemented'); - } - } - -}); diff --git a/src/test/poly-lit-element_test.ts b/src/test/poly-lit-element_test.ts deleted file mode 100644 index 3fbb3847..00000000 --- a/src/test/poly-lit-element_test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ - -import { html, PolymerLit } from '../poly-lit-element.js'; -import { PropertyAccessors } from '../../@polymer/polymer/lib/mixins/property-accessors.js'; - -/// -/// - -const assert = chai.assert; - -suite('PolymerList', () => { - - test('constructrs', () => { - - const C = class extends PolymerLit(PropertyAccessors(HTMLElement)) { - foo: string = 'foo'; - - render() { - return html` -

${this.foo}

- `; - } - }; - - const c = new C(); - const container = document.createElement('div'); - document.body.appendChild(container); - container.appendChild(c as any as HTMLElement); - assert.equal(c.shadowRoot!.innerHTML, '

foo

'); - }); - -}); diff --git a/test/index.html b/test/index.html new file mode 100644 index 00000000..2e8daca7 --- /dev/null +++ b/test/index.html @@ -0,0 +1,17 @@ + + + + lit-element tests + + + + + diff --git a/tsconfig.json b/tsconfig.json index 0e72c957..6d4b2a9e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,15 +5,16 @@ "lib": ["es2017", "dom"], "declaration": true, "sourceMap": true, - "outDir": "./", + "inlineSources": true, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "allowJs": true, + "outDir": "./", + "rootDir": "./src/@polymer", "rootDirs": [ - ".", + "./", "./node_modules", "./node_modules/@types" ] @@ -21,5 +22,7 @@ "include": [ "./src/**/*.ts" ], - "exclude": [] + "exclude": [ + "" + ] }