From 94d4e480d253c739c29d09cc25b6876cc0140165 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Thu, 19 Jul 2018 18:57:11 -0700 Subject: [PATCH 01/65] Refactor API (wip) --- demo/lit-element.html | 32 +- package-lock.json | 1413 ++++++++++---------------- package.json | 4 +- src/demo/ts-element.ts | 20 + src/lib/microtask.ts | 80 ++ src/lib/render-helpers.ts | 71 ++ src/lib/updating-element.ts | 421 ++++++++ src/lit-element.ts | 282 +---- src/test/lit-element_styling_test.ts | 246 ----- src/test/lit-element_test.ts | 489 --------- tsconfig.json | 3 +- 11 files changed, 1188 insertions(+), 1873 deletions(-) create mode 100644 src/demo/ts-element.ts create mode 100644 src/lib/microtask.ts create mode 100644 src/lib/render-helpers.ts create mode 100644 src/lib/updating-element.ts delete mode 100644 src/test/lit-element_styling_test.ts delete mode 100644 src/test/lit-element_test.ts diff --git a/demo/lit-element.html b/demo/lit-element.html index d1c13827..3e74dcab 100644 --- a/demo/lit-element.html +++ b/demo/lit-element.html @@ -11,8 +11,13 @@ lit-element demo + + Hi +
+ + - Hi diff --git a/package-lock.json b/package-lock.json index db95284b..8b6c9632 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,37 +5,117 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", - "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.53.tgz", + "integrity": "sha1-mA0VYLhjV1v1o3eSUDfgEy71kh4=", "dev": true, "requires": { - "@babel/highlight": "7.0.0-beta.51" + "@babel/highlight": "7.0.0-beta.53" } }, "@babel/core": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-beta.51.tgz", - "integrity": "sha1-DlS9a2OHNrKuWTwxpH8JaeKyuW0=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/helpers": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-beta.53.tgz", + "integrity": "sha1-q2R8+7JyQf0i7DyhNC161Oa1T58=", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-beta.53", + "@babel/generator": "7.0.0-beta.53", + "@babel/helpers": "7.0.0-beta.53", + "@babel/parser": "7.0.0-beta.53", + "@babel/template": "7.0.0-beta.53", + "@babel/traverse": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53", "convert-source-map": "^1.1.0", "debug": "^3.1.0", "json5": "^0.5.0", "lodash": "^4.17.5", - "micromatch": "^3.1.10", + "micromatch": "^2.3.11", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -45,12 +125,12 @@ } }, "@babel/generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz", - "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.53.tgz", + "integrity": "sha1-uMrXLFcr4yNK/94ivm2sxCUOA0s=", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.51", + "@babel/types": "7.0.0-beta.53", "jsesc": "^2.5.1", "lodash": "^4.17.5", "source-map": "^0.5.0", @@ -66,33 +146,33 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.51.tgz", - "integrity": "sha1-OM95IL9fM4oif3VOKGtvut7gS1g=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.53.tgz", + "integrity": "sha1-WZYGKDdcvu+WoH7f4co4t1bwGqg=", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.51" + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-beta.51.tgz", - "integrity": "sha1-ITP//j4vcVkeQhR7lHKRyirTkjc=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-beta.53.tgz", + "integrity": "sha1-RFZwliPX2vqivulPglUD9MDs6Fs=", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" + "@babel/helper-explode-assignable-expression": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-call-delegate": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.51.tgz", - "integrity": "sha1-BO1yfJfPBbyy/WRINzMasV1jyBk=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.53.tgz", + "integrity": "sha1-ld6Lq9A/nmz08rVkoDhwjBOP/jE=", "dev": true, "requires": { - "@babel/helper-hoist-variables": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" + "@babel/helper-hoist-variables": "7.0.0-beta.53", + "@babel/traverse": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-define-map": { @@ -169,74 +249,74 @@ } }, "@babel/helper-explode-assignable-expression": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-beta.51.tgz", - "integrity": "sha1-mHUzKti11cmC+kgcuCtzFwPyzS0=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-beta.53.tgz", + "integrity": "sha1-1bytK2tH9ATAruillk3/2TEkc6g=", "dev": true, "requires": { - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" + "@babel/traverse": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-function-name": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz", - "integrity": "sha1-IbSHSiJ8+Z7K/MMKkDAtpaJkBWE=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.53.tgz", + "integrity": "sha1-USgEro6cvOVDHr6hnkdijC7WU/I=", "dev": true, "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" + "@babel/helper-get-function-arity": "7.0.0-beta.53", + "@babel/template": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz", - "integrity": "sha1-MoGy0EWvlcFyzpGyCCXYXqRnZBE=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.53.tgz", + "integrity": "sha1-3tiKsp+bHbYch9G7jTijXdp3neY=", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.51" + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-hoist-variables": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.51.tgz", - "integrity": "sha1-XX68hZZWe2RPyYmRLDo++YvgWPw=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.53.tgz", + "integrity": "sha1-TCfjuHP6CcWtbpPrQHBMIA+EE3w=", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.51" + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0-beta.51.tgz", - "integrity": "sha1-KkJTZXQXZYiAbmAusXpS0yP4KHA=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0-beta.53.tgz", + "integrity": "sha1-D7Dviy07kD0cO/Qm2kp0V14BnOQ=", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.51" + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-module-imports": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.51.tgz", - "integrity": "sha1-zgBCgEX7t9XrwOp7+DV4nxU2arI=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.53.tgz", + "integrity": "sha1-5zXmqjClBLD52Fw4ptRwqfSqgdk=", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.51", + "@babel/types": "7.0.0-beta.53", "lodash": "^4.17.5" } }, "@babel/helper-module-transforms": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.51.tgz", - "integrity": "sha1-E68MjuQfJ3dDyPxD1EQxXbIyb3M=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.53.tgz", + "integrity": "sha1-e6IUzcyPhiPy0Xl96v8f80mqzhM=", "dev": true, "requires": { - "@babel/helper-module-imports": "7.0.0-beta.51", - "@babel/helper-simple-access": "7.0.0-beta.51", - "@babel/helper-split-export-declaration": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", + "@babel/helper-module-imports": "7.0.0-beta.53", + "@babel/helper-simple-access": "7.0.0-beta.53", + "@babel/helper-split-export-declaration": "7.0.0-beta.53", + "@babel/template": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53", "lodash": "^4.17.5" } }, @@ -263,31 +343,31 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-beta.51.tgz", - "integrity": "sha1-D2pfK20cZERBP4+rYJQNebY8IDE=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-beta.53.tgz", + "integrity": "sha1-1kRYY2/8JYtCcUqd2Trrb4uM8+0=", "dev": true }, "@babel/helper-regex": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0-beta.51.tgz", - "integrity": "sha1-mXIqPAxwRZavsSMoSwqIihoAPYI=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0-beta.53.tgz", + "integrity": "sha1-bp0hl7Vid54iVWWUaumoXCFbIl4=", "dev": true, "requires": { "lodash": "^4.17.5" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.51.tgz", - "integrity": "sha1-DtxX4F3LXd4qC27m+NAmGYLe8l8=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.53.tgz", + "integrity": "sha1-uDSnVy3sF2OJ/6x+djV5WGSQySI=", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "7.0.0-beta.51", - "@babel/helper-wrap-function": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" + "@babel/helper-annotate-as-pure": "7.0.0-beta.53", + "@babel/helper-wrap-function": "7.0.0-beta.53", + "@babel/template": "7.0.0-beta.53", + "@babel/traverse": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-replace-supers": { @@ -387,52 +467,52 @@ } }, "@babel/helper-simple-access": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.51.tgz", - "integrity": "sha1-ydf+zYShgdUKOvzEIvyUqWi+MFA=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.53.tgz", + "integrity": "sha1-cvbbmr5C+GgfpvAo79WdgVRHUrM=", "dev": true, "requires": { - "@babel/template": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", + "@babel/template": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53", "lodash": "^4.17.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz", - "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.53.tgz", + "integrity": "sha1-rvVLix+ZYW6jfJhHhxajeAJjMls=", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.51" + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-wrap-function": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.51.tgz", - "integrity": "sha1-bFFvsEQQmWTuAxwiUAqDAxOGL7E=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.53.tgz", + "integrity": "sha1-q/sr+pQBBCurJXwBkPWtbbjfFdU=", "dev": true, "requires": { - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/template": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" + "@babel/helper-function-name": "7.0.0-beta.53", + "@babel/template": "7.0.0-beta.53", + "@babel/traverse": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53" } }, "@babel/helpers": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-beta.51.tgz", - "integrity": "sha1-lScr4qtGNNaCBCX4klAxqSiRg5c=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-beta.53.tgz", + "integrity": "sha1-xDb/uOCTAU2olba7d5f/RuPRzM8=", "dev": true, "requires": { - "@babel/template": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" + "@babel/template": "7.0.0-beta.53", + "@babel/traverse": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53" } }, "@babel/highlight": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", - "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.53.tgz", + "integrity": "sha1-9OlS2tF4fSBeGI0+OEzc5JyjaPs=", "dev": true, "requires": { "chalk": "^2.0.0", @@ -441,113 +521,113 @@ } }, "@babel/parser": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz", - "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.53.tgz", + "integrity": "sha1-H0XrYXv5Rj1IKywE00nZ5O2/SJI=", "dev": true }, "@babel/plugin-external-helpers": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.0.0-beta.51.tgz", - "integrity": "sha1-tHg7z5FS0VlCy+DwvKJhuEnTXJg=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.0.0-beta.53.tgz", + "integrity": "sha1-WMMIYn4cBXN34ehxpqJ904CNLbc=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-beta.51.tgz", - "integrity": "sha1-99aS+Uakp/ynjkM2QHoAvq+KTeo=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-beta.53.tgz", + "integrity": "sha1-XFnvZm0Xwn3LVoa3XsMr622MUNY=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-remap-async-to-generator": "7.0.0-beta.51", - "@babel/plugin-syntax-async-generators": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53", + "@babel/helper-remap-async-to-generator": "7.0.0-beta.53", + "@babel/plugin-syntax-async-generators": "7.0.0-beta.53" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-beta.51.tgz", - "integrity": "sha1-W8Rp5ebRuEpdYEa1npDKAWwghtY=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-beta.53.tgz", + "integrity": "sha1-5rXwusUBg48W6PPG00sAs+pANdk=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53", + "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.53" } }, "@babel/plugin-syntax-async-generators": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-beta.51.tgz", - "integrity": "sha1-aSGvHcPaD87d4KYQc+7Hl7jKpwc=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-beta.53.tgz", + "integrity": "sha1-gpvvbxUBeentC7lDM58qMSM6qSE=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-syntax-dynamic-import": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0-beta.51.tgz", - "integrity": "sha1-nAru9X0GeONybbFxqnPkdKJd5/I=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0-beta.53.tgz", + "integrity": "sha1-s+lA+H7oeq8UORsXHGNHb9Aa8yM=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-syntax-import-meta": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.0.0-beta.51.tgz", - "integrity": "sha1-EfleSTZJIxliJxuokXdvouxJmCM=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.0.0-beta.53.tgz", + "integrity": "sha1-4ZKYmcLqr1NKbbJFjlOnI0wB0mg=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-beta.51.tgz", - "integrity": "sha1-bVehGcHwZMRY5FutRb7wqD7RDAA=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-beta.53.tgz", + "integrity": "sha1-nb12jD8QnwKyT7oXNllp+iXrRYw=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-beta.51.tgz", - "integrity": "sha1-KbnbbjhoigbsXCVjmZbYml6/2+M=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-beta.53.tgz", + "integrity": "sha1-p19fqEl6rBcp0DO/QcJQQWudHgQ=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-beta.51.tgz", - "integrity": "sha1-lFOFBVoubTVmv1WvEnyNclzToXM=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-beta.53.tgz", + "integrity": "sha1-REx2HMQhXJeptVb/WMp7p99dQVM=", "dev": true, "requires": { - "@babel/helper-module-imports": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-remap-async-to-generator": "7.0.0-beta.51" + "@babel/helper-module-imports": "7.0.0-beta.53", + "@babel/helper-plugin-utils": "7.0.0-beta.53", + "@babel/helper-remap-async-to-generator": "7.0.0-beta.53" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-beta.51.tgz", - "integrity": "sha1-IxKbr4FEcfOeqU7shKsf/nbJ/pY=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-beta.53.tgz", + "integrity": "sha1-CkMiGhsMkM1NCfG0a5Wd0khlf3M=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.51.tgz", - "integrity": "sha1-vlVcefDaTrFop/4W14eppxc3AeA=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.53.tgz", + "integrity": "sha1-nv1uUMofo5jcqnEZYh2j8fu4IbY=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", + "@babel/helper-plugin-utils": "7.0.0-beta.53", "lodash": "^4.17.5" } }, @@ -636,224 +716,224 @@ } }, "@babel/plugin-transform-computed-properties": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.51.tgz", - "integrity": "sha1-jHKhqz4HZwNP+eZzLSWBwjwDLv4=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.53.tgz", + "integrity": "sha1-l0fiYIKulO2lMPmNLCBZ6NLbwAU=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-destructuring": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.51.tgz", - "integrity": "sha1-1dRU5XTH7zPuSekYsEivspvpNfY=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.53.tgz", + "integrity": "sha1-DwrbDhptzTWjZkEBYJ7AYv8SenY=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-beta.51.tgz", - "integrity": "sha1-VB6vipfRSpgJs1nY9UgAHwhbm38=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-beta.53.tgz", + "integrity": "sha1-D1WZE6v6GCOcpOCPc+7DbF5XuB8=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-beta.51.tgz", - "integrity": "sha1-BLTj5As3AREt1u2jliUTJ1eIH9Q=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-beta.53.tgz", + "integrity": "sha1-PiZxeSBMd1GdhBepsZnyUiIejZU=", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-builder-binary-assignment-operator-visitor": "7.0.0-beta.53", + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-for-of": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.51.tgz", - "integrity": "sha1-RPR2sGxANVF6hAOiYk+xZMQ3FFU=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.53.tgz", + "integrity": "sha1-+gZSFeGFacj3TdUktXIeEdzKlzs=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-function-name": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.51.tgz", - "integrity": "sha1-cGU8NgtTJUJG9GWexFCwwKVthqo=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.53.tgz", + "integrity": "sha1-Kzpbs2TB4cV+zL/iXGv1XygEET4=", "dev": true, "requires": { - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-function-name": "7.0.0-beta.53", + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-instanceof": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-instanceof/-/plugin-transform-instanceof-7.0.0-beta.51.tgz", - "integrity": "sha1-ft1hag3njWuvU0NgpHWGWQbt6Zk=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-instanceof/-/plugin-transform-instanceof-7.0.0-beta.53.tgz", + "integrity": "sha1-WC2CtyUYggGtDiIx8fzpTHRaLAY=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-literals": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.51.tgz", - "integrity": "sha1-RbB6lCI8+iJnAaeUYLQrMt8d7AU=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.53.tgz", + "integrity": "sha1-vsTxROmpbvUSHRQwx+vl/QiGV8k=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-beta.51.tgz", - "integrity": "sha1-9oqL5/ZRd9JGUGo5FNrk1m5nWh8=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-beta.53.tgz", + "integrity": "sha1-WFTXOeZ5IzqId8C0GCaca+t6Miw=", "dev": true, "requires": { - "@babel/helper-module-transforms": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-module-transforms": "7.0.0-beta.53", + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-object-super": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-beta.51.tgz", - "integrity": "sha1-rBjoi8HXm3GL2vSKdWgzzfW9zr8=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-beta.53.tgz", + "integrity": "sha1-4sTwbts0s9eksnV7oYgp0N8gKcs=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-replace-supers": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53", + "@babel/helper-replace-supers": "7.0.0-beta.53" }, "dependencies": { "@babel/helper-optimise-call-expression": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.51.tgz", - "integrity": "sha1-IfIVjvCDoSPOHgRmW1u4TzcAgNc=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.53.tgz", + "integrity": "sha1-j8eO9MD2n4uzu980zSMsIBIEFMg=", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.51" + "@babel/types": "7.0.0-beta.53" } }, "@babel/helper-replace-supers": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.51.tgz", - "integrity": "sha1-J5phr7hJR2xsxw1VGfg99KdP+m8=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.53.tgz", + "integrity": "sha1-M5tb3BAilElbGifFWBMjBuG3vKc=", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "7.0.0-beta.51", - "@babel/helper-optimise-call-expression": "7.0.0-beta.51", - "@babel/traverse": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51" + "@babel/helper-member-expression-to-functions": "7.0.0-beta.53", + "@babel/helper-optimise-call-expression": "7.0.0-beta.53", + "@babel/traverse": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53" } } } }, "@babel/plugin-transform-parameters": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.51.tgz", - "integrity": "sha1-mQGVsd/bG8yUkG8wNJUQie0e3U4=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.53.tgz", + "integrity": "sha1-7+YM7IzsoNGdXG+hrnm8TjMnnVY=", "dev": true, "requires": { - "@babel/helper-call-delegate": "7.0.0-beta.51", - "@babel/helper-get-function-arity": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-call-delegate": "7.0.0-beta.53", + "@babel/helper-get-function-arity": "7.0.0-beta.53", + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-regenerator": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.51.tgz", - "integrity": "sha1-U28NWZ0nU9ygor6KZeLCRKe1YSs=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.53.tgz", + "integrity": "sha1-T+u/YISvoMHJ7ISX3mjAaV/p2gs=", "dev": true, "requires": { - "regenerator-transform": "^0.12.4" + "regenerator-transform": "^0.13.3" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-beta.51.tgz", - "integrity": "sha1-3bwLGuHds7z+aWnyyWgQPxHjK9k=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-beta.53.tgz", + "integrity": "sha1-38SIG2vXZYoAMew7gWPliPCJjUs=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-spread": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.51.tgz", - "integrity": "sha1-EAEpvI19z0vHmtzWEppCFCWdilA=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.53.tgz", + "integrity": "sha1-g+j2Rsok8cmCKPnxREz2DL1JOLw=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.51.tgz", - "integrity": "sha1-SMvqzTG9Be6AC1+svLCcV4G9lhk=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.53.tgz", + "integrity": "sha1-D888mUq92Lq1m6l4L+TZ+KVF1uc=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-regex": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53", + "@babel/helper-regex": "7.0.0-beta.53" } }, "@babel/plugin-transform-template-literals": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.51.tgz", - "integrity": "sha1-LQWV9WRh1DRbo1w41zAz+H7Lu8g=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.53.tgz", + "integrity": "sha1-+msLQXEA0j4tsUwd9HorGzl48dk=", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "7.0.0-beta.51", - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-annotate-as-pure": "7.0.0-beta.53", + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-beta.51.tgz", - "integrity": "sha1-SVDAyOPJ4eFB5Fzrq15hSCYyBMM=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-beta.53.tgz", + "integrity": "sha1-ZarocamqQPYRSDZlcxIJrr1cKis=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51" + "@babel/helper-plugin-utils": "7.0.0-beta.53" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-beta.51.tgz", - "integrity": "sha1-kBn5FQj0C1CmRDUEMijEFCws2GQ=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-beta.53.tgz", + "integrity": "sha1-CvdOyAGefVnji+ZNt/YikZQv7SU=", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.51", - "@babel/helper-regex": "7.0.0-beta.51", + "@babel/helper-plugin-utils": "7.0.0-beta.53", + "@babel/helper-regex": "7.0.0-beta.53", "regexpu-core": "^4.1.3" } }, "@babel/template": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz", - "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.53.tgz", + "integrity": "sha1-MyIpCQDQsYewpxdDgeHzu3EFDS4=", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", + "@babel/code-frame": "7.0.0-beta.53", + "@babel/parser": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53", "lodash": "^4.17.5" } }, "@babel/traverse": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.51.tgz", - "integrity": "sha1-mB2vLOw0emIx06odnhgDsDqqpKg=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.53.tgz", + "integrity": "sha1-ANMs2NC1j0wB0xFXvmIsZigm00Q=", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.51", - "@babel/generator": "7.0.0-beta.51", - "@babel/helper-function-name": "7.0.0-beta.51", - "@babel/helper-split-export-declaration": "7.0.0-beta.51", - "@babel/parser": "7.0.0-beta.51", - "@babel/types": "7.0.0-beta.51", + "@babel/code-frame": "7.0.0-beta.53", + "@babel/generator": "7.0.0-beta.53", + "@babel/helper-function-name": "7.0.0-beta.53", + "@babel/helper-split-export-declaration": "7.0.0-beta.53", + "@babel/parser": "7.0.0-beta.53", + "@babel/types": "7.0.0-beta.53", "debug": "^3.1.0", "globals": "^11.1.0", "invariant": "^2.2.0", @@ -861,9 +941,9 @@ } }, "@babel/types": { - "version": "7.0.0-beta.51", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz", - "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=", + "version": "7.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.53.tgz", + "integrity": "sha1-GaRhwNpRVZXftnQLS0Xce7Dms3U=", "dev": true, "requires": { "esutils": "^2.0.2", @@ -872,9 +952,9 @@ } }, "@polymer/esm-amd-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@polymer/esm-amd-loader/-/esm-amd-loader-1.0.1.tgz", - "integrity": "sha512-yqa8Go1CJ07sTdjYXzl4cBa9W4MoTdwF1We94P/nENWCCVWMItWQmUKmKEpQBJOYWd6DZSgEOdVEXa4HS2QAkA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@polymer/esm-amd-loader/-/esm-amd-loader-1.0.2.tgz", + "integrity": "sha512-n45zYqDfZUKBiM+Nj0jU6An2xEP5avKKdsl8ecgh2PbA0I0lamEExs0BmHfD4Br+lJDNbbDEVsUMDlrqNqcceg==", "dev": true }, "@polymer/polymer": { @@ -940,9 +1020,9 @@ } }, "@types/bluebird": { - "version": "3.5.20", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.20.tgz", - "integrity": "sha512-Wk41MVdF+cHBfVXj/ufUHJeO3BlIQr1McbHZANErMykaCWeDSZbH5erGjNBw2/3UlRdSxZbLfSuQTzFmPOYFsA==", + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.21.tgz", + "integrity": "sha512-6UNEwyw+6SGMC/WMI0ld0PS4st7Qq51qgguFrFizOSpGvZiqe9iswztFSdZvwJBEhLOy2JaxNE6VC7yMAlbfyQ==", "dev": true }, "@types/body-parser": { @@ -1160,9 +1240,9 @@ "dev": true }, "@types/mime": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-0.0.29.tgz", - "integrity": "sha1-+8/TMFc7kS71nu7hRgK/rOYwdUs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", + "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==", "dev": true }, "@types/minimatch": { @@ -1172,9 +1252,9 @@ "dev": true }, "@types/mocha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.2.tgz", - "integrity": "sha512-tfg9rh2qQhBW6SBqpvfqTgU6lHWGhQURoTrn7NeDF+CEkO9JGYbkzU23EXu6p3bnvDxLeeSX8ohAA23urvWeNw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.4.tgz", + "integrity": "sha512-XMHApnKWI0jvXU5gLcSTsRjJBpSzP0BG+2oGv98JFyS4a5R0tRy0oshHBRndb3BuHb9AwDKaUL8Ja7GfUvsG4g==", "dev": true }, "@types/mz": { @@ -1378,9 +1458,9 @@ "dev": true }, "acorn": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.6.2.tgz", - "integrity": "sha512-zUzo1E5dI2Ey8+82egfnttyMlMZ2y0D8xOCO3PNPPlYXpl8NZvF6Qk9L9BEtJs+43FqEmfBViDqc5d1ckRDguw==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", "dev": true }, "acorn-import-meta": { @@ -1423,9 +1503,9 @@ "dev": true }, "agent-base": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", - "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", + "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "dev": true, "optional": true, "requires": { @@ -1470,15 +1550,6 @@ "string-width": "^2.0.0" } }, - "ansi-escape-sequences": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-3.0.0.tgz", - "integrity": "sha1-HBg5S2r5t2/5pjUJ+kl2af0s5T4=", - "dev": true, - "requires": { - "array-back": "^1.0.3" - } - }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -1611,37 +1682,6 @@ "requires": { "array-back": "^2.0.0", "find-replace": "^2.0.1" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - }, - "find-replace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-2.0.1.tgz", - "integrity": "sha512-LzDo3Fpa30FLIBsh6DCDnMN1KW2g4QKkqKmejlImgWY67dDFPX/x9Kh/op/GK522DchQXEvDi/wD48HKW49XOQ==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "test-value": "^3.0.0" - } - }, - "test-value": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz", - "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "typical": "^2.6.1" - } - } } }, "arr-diff": { @@ -1663,12 +1703,12 @@ "dev": true }, "array-back": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-1.0.4.tgz", - "integrity": "sha1-ZEun8JX3/898Q7Xw3DnTwfA8Bjs=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", + "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", "dev": true, "requires": { - "typical": "^2.6.0" + "typical": "^2.6.1" } }, "array-find-index": { @@ -1695,12 +1735,6 @@ "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", "dev": true }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", - "dev": true - }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -2241,9 +2275,9 @@ "dev": true }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "optional": true, "requires": { @@ -2415,9 +2449,9 @@ } }, "browser-capabilities": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/browser-capabilities/-/browser-capabilities-1.1.0.tgz", - "integrity": "sha512-D0AhTybfR0KbVxy1DShQut4eCeluMyJhbTgVTIxvItJKzEGG9pNvOBFZfpeCASo2z0XdfczuvSfNZe/vmNlqwQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/browser-capabilities/-/browser-capabilities-1.1.1.tgz", + "integrity": "sha512-b+zF28HRpaKhdvLGqirkvn8XO+WEpLxAWg+dqa3OAoriVMS2UucVc1xis4Et9vMnQGLSipWks8bDeCeUvuZ0EQ==", "dev": true, "requires": { "@types/ua-parser-js": "^0.7.31", @@ -2780,28 +2814,28 @@ } }, "command-line-args": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-3.0.5.tgz", - "integrity": "sha1-W9StReeYPlwTRJGOQCgO4mk8WsA=", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.0.2.tgz", + "integrity": "sha512-/qPcbL8zpqg53x4rAaqMFlRV4opN3pbla7I7k9x8kyOBMQoGT6WltjN6sXZuxOXw6DgdK7Ad+ijYS5gjcr7vlA==", "dev": true, "requires": { - "array-back": "^1.0.4", - "feature-detect-es6": "^1.3.1", - "find-replace": "^1.0.2", - "typical": "^2.6.0" + "argv-tools": "^0.1.1", + "array-back": "^2.0.0", + "find-replace": "^2.0.1", + "lodash.camelcase": "^4.3.0", + "typical": "^2.6.1" } }, "command-line-usage": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-3.0.8.tgz", - "integrity": "sha1-tqIJeMGzg0d/XBGlKUKLiAv+D00=", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-5.0.5.tgz", + "integrity": "sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==", "dev": true, "requires": { - "ansi-escape-sequences": "^3.0.0", - "array-back": "^1.0.3", - "feature-detect-es6": "^1.3.1", - "table-layout": "^0.3.0", - "typical": "^2.6.0" + "array-back": "^2.0.0", + "chalk": "^2.4.1", + "table-layout": "^0.4.3", + "typical": "^2.6.1" } }, "commander": { @@ -3126,91 +3160,6 @@ "dom5": "^3.0.0", "parse5": "^4.0.0", "shady-css-parser": "^0.1.0" - }, - "dependencies": { - "array-back": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", - "integrity": "sha512-eJv4pLLufP3g5kcZry0j6WXpIbzYw9GUB4mVJZno9wfwiBxbizTnHCw3VJb07cBihbFX48Y7oSrW9y+gt4glyw==", - "dev": true, - "requires": { - "typical": "^2.6.1" - } - }, - "command-line-args": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.0.2.tgz", - "integrity": "sha512-/qPcbL8zpqg53x4rAaqMFlRV4opN3pbla7I7k9x8kyOBMQoGT6WltjN6sXZuxOXw6DgdK7Ad+ijYS5gjcr7vlA==", - "dev": true, - "requires": { - "argv-tools": "^0.1.1", - "array-back": "^2.0.0", - "find-replace": "^2.0.1", - "lodash.camelcase": "^4.3.0", - "typical": "^2.6.1" - } - }, - "command-line-usage": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-5.0.5.tgz", - "integrity": "sha512-d8NrGylA5oCXSbGoKz05FkehDAzSmIm4K03S5VDh4d5lZAtTWfc3D1RuETtuQCn8129nYfJfDdF7P/lwcz1BlA==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "chalk": "^2.4.1", - "table-layout": "^0.4.3", - "typical": "^2.6.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "find-replace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-2.0.1.tgz", - "integrity": "sha512-LzDo3Fpa30FLIBsh6DCDnMN1KW2g4QKkqKmejlImgWY67dDFPX/x9Kh/op/GK522DchQXEvDi/wD48HKW49XOQ==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "test-value": "^3.0.0" - } - }, - "table-layout": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.4.tgz", - "integrity": "sha512-uNaR3SRMJwfdp9OUr36eyEi6LLsbcTqTO/hfTsNviKsNeyMBPICJCC7QXRF3+07bAP6FRwA8rczJPBqXDc0CkQ==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "deep-extend": "~0.6.0", - "lodash.padend": "^4.6.1", - "typical": "^2.6.1", - "wordwrapjs": "^3.0.0" - } - }, - "test-value": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz", - "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==", - "dev": true, - "requires": { - "array-back": "^2.0.0", - "typical": "^2.6.1" - } - }, - "wordwrapjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", - "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", - "dev": true, - "requires": { - "reduce-flatten": "^1.0.1", - "typical": "^2.6.1" - } - } } }, "cssbeautify": { @@ -3274,9 +3223,9 @@ } }, "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, "define-property": { @@ -3400,12 +3349,12 @@ } }, "dom5": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dom5/-/dom5-3.0.0.tgz", - "integrity": "sha512-PbE+7C4Sh1dHDTLNuSDaMUGD1ivDiSZw0L+a9xVUzUKeQ8w3vdzfKHRA07CxcrFZZOa1SGl2nIJ9T49j63q+bg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dom5/-/dom5-3.0.1.tgz", + "integrity": "sha512-JPFiouQIr16VQ4dX6i0+Hpbg3H2bMKPmZ+WZgBOSSvOPx9QHwwY8sPzeM2baUtViESYto6wC2nuZOMC/6gulcA==", "dev": true, "requires": { - "@types/parse5": "^2.2.32", + "@types/parse5": "^2.2.34", "clone": "^2.1.0", "parse5": "^4.0.0" } @@ -3582,9 +3531,9 @@ } }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" @@ -4038,15 +3987,6 @@ "pend": "~1.2.0" } }, - "feature-detect-es6": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/feature-detect-es6/-/feature-detect-es6-1.4.1.tgz", - "integrity": "sha512-iMxKpKdIBgcWiBPtz2qnEsNjCE2dBQvDyUqgrXLJboiaHwJa+2vDIZ8XbgNZGh1Rx1PUfZmI7uhG6Z4iQYWVjg==", - "dev": true, - "requires": { - "array-back": "^1.0.4" - } - }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -4126,13 +4066,13 @@ } }, "find-replace": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-1.0.3.tgz", - "integrity": "sha1-uI5zZNLZyVlVnziMZmcNYTBEH6A=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-2.0.1.tgz", + "integrity": "sha512-LzDo3Fpa30FLIBsh6DCDnMN1KW2g4QKkqKmejlImgWY67dDFPX/x9Kh/op/GK522DchQXEvDi/wD48HKW49XOQ==", "dev": true, "requires": { - "array-back": "^1.0.4", - "test-value": "^2.1.0" + "array-back": "^2.0.0", + "test-value": "^3.0.0" } }, "find-up": { @@ -4164,9 +4104,9 @@ "dev": true }, "follow-redirects": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz", - "integrity": "sha512-fdrt472/9qQ6Kgjvb935ig6vJCuofpBUD14f9Vb+SLlm7xIe4Qva5gey8EKtv8lp7ahE1wilg3xL1znpVGtZIA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", + "integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==", "dev": true, "requires": { "debug": "^3.1.0" @@ -4555,9 +4495,9 @@ } }, "globals": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", - "integrity": "sha512-hYyf+kI8dm3nORsiiXUQigOU62hDLfJ9G01uyGMxhc6BKsircrUhC4uJPQPUSuq2GrTmiiEt7ewxlMdBewfmKQ==", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", + "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", "dev": true }, "got": { @@ -4803,9 +4743,9 @@ } }, "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "hpack.js": { @@ -4847,20 +4787,26 @@ } }, "html-minifier": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.16.tgz", - "integrity": "sha512-zP5EfLSpiLRp0aAgud4CQXPQZm9kXwWjR/cF0PfdOj+jjWnOaCgeZcll4kYXSvIBPeUMmyaSc7mM4IDtA+kboA==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.18.tgz", + "integrity": "sha512-sczoq/9zeXiKZMj8tsQzHJE7EyjrpMHvblTLuh9o8h5923a6Ts5uQ/3YdY+xIqJYRjzHQPlrHjfjh0BtwPJG0g==", "dev": true, "requires": { "camel-case": "3.0.x", "clean-css": "4.1.x", - "commander": "2.15.x", + "commander": "2.16.x", "he": "1.1.x", "param-case": "2.1.x", "relateurl": "0.2.x", - "uglify-js": "3.3.x" + "uglify-js": "3.4.x" }, "dependencies": { + "commander": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4868,12 +4814,12 @@ "dev": true }, "uglify-js": { - "version": "3.3.28", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.28.tgz", - "integrity": "sha512-68Rc/aA6cswiaQ5SrE979UJcXX+ADA1z33/ZsPd+fbAiVdjZ16OXdbtGO+rJUUBgK6qdf3SOPhQf3K/ybF5Miw==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.4.tgz", + "integrity": "sha512-RiB1kNcC9RMyqwRrjXC+EjgLoXULoDnCaOnEDzUCHkBN0bHwmtF5rzDMiDWU29gu0kXCRRWwtcTAVFWRECmU2Q==", "dev": true, "requires": { - "commander": "~2.15.0", + "commander": "~2.16.0", "source-map": "~0.6.1" } } @@ -5260,23 +5206,6 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, "is-path-inside": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", @@ -5750,12 +5679,12 @@ "dev": true }, "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "requires": { - "js-tokens": "^3.0.0" + "js-tokens": "^3.0.0 || ^4.0.0" } }, "loud-rejection": { @@ -6096,14 +6025,14 @@ "dev": true }, "multer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.0.tgz", - "integrity": "sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.3.1.tgz", + "integrity": "sha512-JHdEoxkA/5NgZRo91RNn4UT+HdcJV9XUo01DTkKC7vo1erNIngtuaw9Y0WI8RdTlyi+wMIbunflhghzVLuGJyw==", "dev": true, "requires": { "append-field": "^0.1.0", "busboy": "^0.2.11", - "concat-stream": "^1.5.0", + "concat-stream": "^1.5.2", "mkdirp": "^0.5.1", "object-assign": "^3.0.0", "on-finished": "^2.3.0", @@ -6149,9 +6078,9 @@ } }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -6159,7 +6088,6 @@ "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", "is-windows": "^1.0.2", "kind-of": "^6.0.2", "object.pick": "^1.3.0", @@ -6204,15 +6132,6 @@ "lower-case": "^1.1.1" } }, - "nodegit-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/nodegit-promise/-/nodegit-promise-4.0.0.tgz", - "integrity": "sha1-VyKxhPLfcycWEGSnkdLoQskWezQ=", - "dev": true, - "requires": { - "asap": "~2.0.3" - } - }, "nomnom": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", @@ -6714,9 +6633,9 @@ } }, "polymer-analyzer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/polymer-analyzer/-/polymer-analyzer-3.0.1.tgz", - "integrity": "sha512-s1fEMUeUHs7EWZQ5cxL/RL6qzDcYnkJcLeQbNvVD1qOPtxRejGeonq5xsxmb8o7mstzSYb1x0Iba2Bdbfr+PAQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/polymer-analyzer/-/polymer-analyzer-3.0.2.tgz", + "integrity": "sha512-a8g+60VagUqmzWttG+/7BBGJiQLdLTIpFzhHp8UVcAitq/Z3ZywWaEGP5C9VEtALRCruVYue+dAsGvxHhNBdJw==", "dev": true, "requires": { "@babel/generator": "^7.0.0-beta.42", @@ -6766,9 +6685,9 @@ "dev": true }, "@types/node": { - "version": "9.6.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.21.tgz", - "integrity": "sha512-zQS6mHzxEstR8Vvnpc3JDUCDGWnHFzMTcBu9UCZoVLuj1Uvkkk0qFKJQEhlvbsX34m3xt12ejV09eO/ljZcn7A==", + "version": "9.6.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", + "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", "dev": true }, "chalk": { @@ -6793,9 +6712,9 @@ } }, "polymer-build": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/polymer-build/-/polymer-build-3.0.1.tgz", - "integrity": "sha512-XnNP9M/fUbwYYOPijMK6t+o9DOjO8kpj2rm6irefA00TGfaQXHZhgPpT0zdd5xjAyAcZ4lpaLGcjIcCLY5mkgg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/polymer-build/-/polymer-build-3.0.4.tgz", + "integrity": "sha512-YSppvctpcO2do3XHXNo2WnD4mxpzTpjgLlByPXE0Jfz9N+Ez6EGmge7Xwd6NsFH9ch6IMyV1P9H238I/C/KZRw==", "dev": true, "requires": { "@babel/core": "^7.0.0-beta.46", @@ -6846,7 +6765,7 @@ "babel-plugin-minify-guarded-expressions": "=0.4.1", "babel-preset-minify": "=0.4.0-alpha.caaefb4c", "babylon": "^7.0.0-beta.42", - "css-slam": "^2.1.1", + "css-slam": "^2.1.2", "dom5": "^3.0.0", "gulp-if": "^2.0.2", "html-minifier": "^3.5.10", @@ -6876,9 +6795,9 @@ } }, "@types/node": { - "version": "9.6.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.21.tgz", - "integrity": "sha512-zQS6mHzxEstR8Vvnpc3JDUCDGWnHFzMTcBu9UCZoVLuj1Uvkkk0qFKJQEhlvbsX34m3xt12ejV09eO/ljZcn7A==", + "version": "9.6.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", + "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", "dev": true }, "@types/resolve": { @@ -6893,9 +6812,9 @@ } }, "polymer-bundler": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/polymer-bundler/-/polymer-bundler-4.0.1.tgz", - "integrity": "sha512-nG+mpWn5h6nflOqZwtcpIKlYxXMznKFODa5XMQsUgD2126BT5cdznbMXCkH9vCIpbV5iBm5lCunmTyoYF+3BqA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/polymer-bundler/-/polymer-bundler-4.0.2.tgz", + "integrity": "sha512-eH+MNSVb/bCqchxYE1gVtdLP9eq1pLsr9NdcHhiJGEgSoZOYq7lGm2M/L7DHGJqa1/OqC7ZC9Sz3eQKAB8FaJQ==", "dev": true, "requires": { "@types/acorn": "^4.0.3", @@ -6906,44 +6825,19 @@ "babel-traverse": "^6.26.0", "babel-types": "^6.26.0", "clone": "^2.1.0", - "command-line-args": "^3.0.1", - "command-line-usage": "^3.0.3", - "dom5": "^2.2.0", + "command-line-args": "^5.0.2", + "command-line-usage": "^5.0.5", + "dom5": "^3.0.0", "espree": "^3.5.2", "magic-string": "^0.22.4", "mkdirp": "^0.5.1", - "parse5": "^2.2.2", + "parse5": "^4.0.0", "polymer-analyzer": "^3.0.1", "rollup": "^0.58.2", "source-map": "^0.5.6", "vscode-uri": "^1.0.1" }, "dependencies": { - "@types/node": { - "version": "6.0.113", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.113.tgz", - "integrity": "sha512-f9XXUWFqryzjkZA1EqFvJHSFyqyasV17fq8zCDIzbRV4ctL7RrJGKvG+lcex86Rjbzd1GrER9h9VmF5sSjV0BQ==", - "dev": true - }, - "dom5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/dom5/-/dom5-2.3.0.tgz", - "integrity": "sha1-+CBJdb0NrLvltYqKk//B/tD/zSo=", - "dev": true, - "requires": { - "@types/clone": "^0.1.29", - "@types/node": "^6.0.0", - "@types/parse5": "^2.2.32", - "clone": "^2.1.0", - "parse5": "^2.2.2" - } - }, - "parse5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-2.2.3.tgz", - "integrity": "sha1-DE/EHBAAxea5PUiwP4CDg3g06fY=", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -6953,9 +6847,9 @@ } }, "polymer-project-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/polymer-project-config/-/polymer-project-config-4.0.1.tgz", - "integrity": "sha512-NJjP5gf6tOQ5YY8u0UM3hzrXPF2hpNIIyXCtd5VNCYoRGJdT//UFubyWFDd9Aje09yNWjS1SAfjZIhMgZ5DESg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/polymer-project-config/-/polymer-project-config-4.0.2.tgz", + "integrity": "sha512-nnxLkbpYYPIVBYooeercovQIWqq4coHzQ5PwK2+NxNpVWUJ5tW3OCDt46dqw3CtJNe4r/qIOkPgBJdVwXAAEmw==", "dev": true, "requires": { "@types/node": "^9.6.4", @@ -6966,24 +6860,24 @@ }, "dependencies": { "@types/node": { - "version": "9.6.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.21.tgz", - "integrity": "sha512-zQS6mHzxEstR8Vvnpc3JDUCDGWnHFzMTcBu9UCZoVLuj1Uvkkk0qFKJQEhlvbsX34m3xt12ejV09eO/ljZcn7A==", + "version": "9.6.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", + "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", "dev": true } } }, "polyserve": { - "version": "0.27.11", - "resolved": "https://registry.npmjs.org/polyserve/-/polyserve-0.27.11.tgz", - "integrity": "sha512-C6laEBzDawtKzJEojv2wjUbuu66fNFEOfHsbHztrw0jn0CZsaV1okWEuFczgMNgW5ZL+Eg+QC4hzsHNA1dZZaw==", + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/polyserve/-/polyserve-0.27.12.tgz", + "integrity": "sha512-P4lb0fNqkSSRHrKTp9/bUnTjZOmnNnLWJ5zMBiWjkkJe3vzNcRpdL0vMQO6RxZ8MUvBI2Iv9mqGPVPY+Dk+Z1w==", "dev": true, "requires": { "@types/compression": "^0.0.33", "@types/content-type": "^1.1.0", "@types/escape-html": "0.0.20", "@types/express": "^4.0.36", - "@types/mime": "0.0.29", + "@types/mime": "^2.0.0", "@types/mz": "0.0.29", "@types/node": "^9.6.4", "@types/opn": "^3.0.28", @@ -6994,8 +6888,8 @@ "@types/spdy": "^3.4.1", "bower-config": "^1.4.1", "browser-capabilities": "^1.0.0", - "command-line-args": "^3.0.1", - "command-line-usage": "^3.0.3", + "command-line-args": "^5.0.2", + "command-line-usage": "^5.0.5", "compression": "^1.6.2", "content-type": "^1.0.2", "escape-html": "^1.0.3", @@ -7003,109 +6897,72 @@ "find-port": "^1.0.1", "http-proxy-middleware": "^0.17.2", "lru-cache": "^4.0.2", - "mime": "^1.3.4", + "mime": "^2.3.1", "mz": "^2.4.0", "opn": "^3.0.2", "pem": "^1.8.3", - "polymer-build": "^3.0.0", + "polymer-build": "^3.0.3", "polymer-project-config": "^4.0.0", "requirejs": "^2.3.4", "resolve": "^1.5.0", - "send": "^0.14.1", + "send": "^0.16.2", "spdy": "^3.3.3" }, "dependencies": { "@types/node": { - "version": "9.6.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.21.tgz", - "integrity": "sha512-zQS6mHzxEstR8Vvnpc3JDUCDGWnHFzMTcBu9UCZoVLuj1Uvkkk0qFKJQEhlvbsX34m3xt12ejV09eO/ljZcn7A==", + "version": "9.6.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", + "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", "dev": true }, "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", - "dev": true, - "requires": { - "ms": "0.7.1" - }, - "dependencies": { - "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", - "dev": true - } - } - }, - "etag": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz", - "integrity": "sha1-A9MLX2fdbmMtKUXTDWZScxo01dg=", - "dev": true - }, - "fresh": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz", - "integrity": "sha1-ZR+DjiJCTnVm3hYdg1jKoZn4PU8=", - "dev": true - }, - "http-errors": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.1.tgz", - "integrity": "sha1-eIwNLB3iyBuebowBhDtrl+uSB1A=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { - "inherits": "2.0.3", - "setprototypeof": "1.0.2", - "statuses": ">= 1.3.1 < 2" + "ms": "2.0.0" } }, - "ms": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", - "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", "dev": true }, "send": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.14.2.tgz", - "integrity": "sha1-ObBDiz9RC+Xcb2Z6EfcWiTaM3u8=", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", "dev": true, "requires": { - "debug": "~2.2.0", - "depd": "~1.1.0", + "debug": "2.6.9", + "depd": "~1.1.2", "destroy": "~1.0.4", - "encodeurl": "~1.0.1", + "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "etag": "~1.7.0", - "fresh": "0.3.0", - "http-errors": "~1.5.1", - "mime": "1.3.4", - "ms": "0.7.2", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", "on-finished": "~2.3.0", "range-parser": "~1.2.0", - "statuses": "~1.3.1" + "statuses": "~1.4.0" }, "dependencies": { "mime": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", - "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", "dev": true } } }, - "setprototypeof": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz", - "integrity": "sha1-gaVSFB7BBLiOic44MQOtXGZWTQg=", - "dev": true - }, "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", "dev": true } } @@ -7152,24 +7009,6 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, - "promisify-node": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/promisify-node/-/promisify-node-0.4.0.tgz", - "integrity": "sha1-MoA4dOxBF4TkeGwzmQKoeheaRpw=", - "dev": true, - "requires": { - "nodegit-promise": "~4.0.0", - "object-assign": "^4.0.1" - }, - "dependencies": { - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - } - } - }, "proxy-addr": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", @@ -7260,12 +7099,6 @@ "strip-json-comments": "~2.0.1" }, "dependencies": { - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", @@ -7373,9 +7206,9 @@ "dev": true }, "regenerator-transform": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.12.4.tgz", - "integrity": "sha512-p2I0fY+TbSLD2/VFTFb/ypEHxs3e3AjU0DzttdPqk2bSmDhfSh5E54b86Yc6XhUa5KykK1tgbvZ4Nr82oCJWkQ==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz", + "integrity": "sha512-5ipTrZFSq5vU2YoGoww4uaRVAK4wyYC4TSICibbfEPOruUu8FFP7ErV0BjmbIOEpn3O/k9na9UEdYR/3m7N6uA==", "dev": true, "requires": { "private": "^0.1.6" @@ -7665,9 +7498,9 @@ "dev": true }, "selenium-standalone": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/selenium-standalone/-/selenium-standalone-6.15.0.tgz", - "integrity": "sha512-SUEbbxo/IK2RsuPQ1QFgdyKXvxDYJUen6nR40zWL9P0FrqeuAXHNCWdtqnwbgGeoCxCVbPVzUsXfSKtjp2+j0g==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/selenium-standalone/-/selenium-standalone-6.15.1.tgz", + "integrity": "sha512-2VXqkcpd+RNJZwCp8UMmqubeSkLvscraRZtg2qdkXwoNNmx5Hu6uaOBy45VJNG6PiUJNZtBZQpnOUfNN2aD1EA==", "dev": true, "optional": true, "requires": { @@ -8567,17 +8400,16 @@ } }, "table-layout": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.3.0.tgz", - "integrity": "sha1-buINxIPbNxs+XIf3BO0vfHmdLJo=", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-0.4.4.tgz", + "integrity": "sha512-uNaR3SRMJwfdp9OUr36eyEi6LLsbcTqTO/hfTsNviKsNeyMBPICJCC7QXRF3+07bAP6FRwA8rczJPBqXDc0CkQ==", "dev": true, "requires": { - "array-back": "^1.0.3", - "core-js": "^2.4.1", - "deep-extend": "~0.4.1", - "feature-detect-es6": "^1.3.1", - "typical": "^2.6.0", - "wordwrapjs": "^2.0.0-0" + "array-back": "^2.0.0", + "deep-extend": "~0.6.0", + "lodash.padend": "^4.6.1", + "typical": "^2.6.1", + "wordwrapjs": "^3.0.0" } }, "tar-stream": { @@ -8663,13 +8495,13 @@ } }, "test-value": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/test-value/-/test-value-2.1.0.tgz", - "integrity": "sha1-Edpv9nDzRxpztiXKTz/c97t0gpE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/test-value/-/test-value-3.0.0.tgz", + "integrity": "sha512-sVACdAWcZkSU9x7AOmJo5TqE+GyNJknHaHsMrR6ZnhjVlVN9Yx6FjHrsKZ3BjIpPCT68zYesPWkakrNupwfOTQ==", "dev": true, "requires": { - "array-back": "^1.0.3", - "typical": "^2.6.0" + "array-back": "^2.0.0", + "typical": "^2.6.1" } }, "text-encoding": { @@ -9271,9 +9103,9 @@ "dev": true }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true }, "vali-date": { @@ -9560,9 +9392,9 @@ } }, "wct-local": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wct-local/-/wct-local-2.1.0.tgz", - "integrity": "sha512-OiMSbxp6e5tyvTbUA4VAQbw4we53EXEmekx9BbIgS/HryoQISzap5DSAE/kvpRpJ2Axt0z12d8ChxqwNflApfA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/wct-local/-/wct-local-2.1.1.tgz", + "integrity": "sha512-U0qcIzsjl0vJ2KR5K766WVzJlmqfMRo8VqgRVQmrePGBsE40vj9hD+XiFw8yusamibZEWRU+DtVP3GKSwJz2EQ==", "dev": true, "optional": true, "requires": { @@ -9575,77 +9407,39 @@ "cleankill": "^2.0.0", "freeport": "^1.0.4", "launchpad": "^0.7.0", - "promisify-node": "^0.4.0", "selenium-standalone": "^6.7.0", "which": "^1.0.8" }, "dependencies": { "@types/node": { - "version": "9.6.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.21.tgz", - "integrity": "sha512-zQS6mHzxEstR8Vvnpc3JDUCDGWnHFzMTcBu9UCZoVLuj1Uvkkk0qFKJQEhlvbsX34m3xt12ejV09eO/ljZcn7A==", + "version": "9.6.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", + "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", "dev": true, "optional": true } } }, "wct-sauce": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wct-sauce/-/wct-sauce-2.0.1.tgz", - "integrity": "sha512-nk6cPKdACVt3R+n9XyCTQTeRKOhhajyh0WaF5U1BsxPlVS8tNOUGleXQDUUjB+q1DPS7+j1F3UJqhnmrUMDVJA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/wct-sauce/-/wct-sauce-2.0.3.tgz", + "integrity": "sha512-vR+gdd1RJjK6+UaiduNYxxNneIFLAwkpO7FlJR045q0Hguavvax2NvSLw+XibQdE0khQxmjsXSM/rq1bk2tYmg==", "dev": true, "optional": true, "requires": { - "chalk": "^1.1.1", + "chalk": "^2.4.1", "cleankill": "^2.0.0", - "lodash": "^3.0.1", + "lodash": "^4.17.10", "request": "^2.85.0", "sauce-connect-launcher": "^1.0.0", "temp": "^0.8.1", - "uuid": "^2.0.1" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true, - "optional": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true, - "optional": true - }, - "uuid": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", - "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", - "dev": true, - "optional": true - } + "uuid": "^3.2.1" } }, "wd": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/wd/-/wd-1.9.0.tgz", - "integrity": "sha512-etqaVS5YFPuwaBRXNFnG0UIoCJHIPoWbVgdbK0v8MQRPQx2sNJc/wu0cRzLEuQjhGt/b0NGxcO1CvA5IAqoWrg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/wd/-/wd-1.10.1.tgz", + "integrity": "sha512-5qkDXM8+oRGu0LovGM6iw2Fo6YJfZBJHOGVC0eDi7DK0BVzbXODCUqonHGmOxsBV9BvaSWWQJtnrcjo8Bq6WjQ==", "dev": true, "requires": { "archiver": "2.1.1", @@ -9706,9 +9500,9 @@ } }, "web-component-tester": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/web-component-tester/-/web-component-tester-6.6.0.tgz", - "integrity": "sha512-f+LqR4rHUtVIg6T79uo6BYuyrIOzInPWzOJo69sGxX3tRTDeTwQzJSrfX4/MKA4qa3ldGPODJHizRYgZbjYjbQ==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/web-component-tester/-/web-component-tester-6.7.1.tgz", + "integrity": "sha512-i/N0MYLBh9fjzI4pcKvfYiTx4JEr+Zbt2m1/ANovpvT74El55WaiFyiwCdUGhnlX1xy1URGUB2CJgl6gdTBumg==", "dev": true, "requires": { "@polymer/sinonjs": "^1.14.1", @@ -9725,11 +9519,9 @@ "findup-sync": "^2.0.0", "glob": "^7.1.2", "lodash": "^3.10.1", - "mocha": "^3.4.2", "multer": "^1.3.0", "nomnom": "^1.8.1", "polyserve": "^0.27.11", - "promisify-node": "^0.4.0", "resolve": "^1.5.0", "semver": "^5.3.0", "send": "^0.11.1", @@ -9739,8 +9531,8 @@ "socket.io": "^2.0.3", "stacky": "^1.3.1", "update-notifier": "^2.2.0", - "wct-local": "^2.1.0", - "wct-sauce": "^2.0.0", + "wct-local": "^2.1.1", + "wct-sauce": "^2.0.2", "wd": "^1.2.0" }, "dependencies": { @@ -9773,12 +9565,6 @@ } } }, - "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", - "dev": true - }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -9792,30 +9578,6 @@ "supports-color": "^2.0.0" } }, - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, - "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", - "dev": true - }, "formatio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", @@ -9825,18 +9587,6 @@ "samsam": "1.x" } }, - "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", - "dev": true - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -9855,51 +9605,6 @@ "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", "dev": true }, - "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", - "dev": true, - "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", - "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", - "mkdirp": "0.5.1", - "supports-color": "3.1.2" - }, - "dependencies": { - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.2", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", @@ -10010,15 +9715,13 @@ "dev": true }, "wordwrapjs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-2.0.0.tgz", - "integrity": "sha1-q1X2leYRjak4WP3XDAU9HF4BrCA=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-3.0.0.tgz", + "integrity": "sha512-mO8XtqyPvykVCsrwj5MlOVWvSnCdT+C+QVbm6blradR7JExAhbkZ7hZ9A+9NUtwzSqrlUo9a67ws0EiILrvRpw==", "dev": true, "requires": { - "array-back": "^1.0.3", - "feature-detect-es6": "^1.3.1", "reduce-flatten": "^1.0.1", - "typical": "^2.6.0" + "typical": "^2.6.1" } }, "wrappy": { @@ -10101,9 +9804,9 @@ } }, "yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha1-T7G8euH8L1cDe1SvasyP4QMcW3c=", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, "optional": true, "requires": { diff --git a/package.json b/package.json index 98fdf50e..e96c25c0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "author": "The Polymer Authors", "devDependencies": { "@types/chai": "^4.0.1", - "@types/mocha": "^5.0.0", + "@types/mocha": "^5.2.4", "@webcomponents/webcomponentsjs": "^2.0.2", "chai": "^4.0.2", "mocha": "^5.0.5", @@ -31,7 +31,7 @@ "typescript": "^2.7.2", "uglify-es": "^3.3.9", "wct-browser-legacy": "^1.0.1", - "web-component-tester": "^6.6.0" + "web-component-tester": "^6.7.1" }, "typings": "lit-element.d.ts", "dependencies": { diff --git a/src/demo/ts-element.ts b/src/demo/ts-element.ts new file mode 100644 index 00000000..9490baf9 --- /dev/null +++ b/src/demo/ts-element.ts @@ -0,0 +1,20 @@ +import { LitElement, html, property } from '../lit-element.js'; + +class TSElement extends LitElement { + + @property({attribute: true, fromAttribute: String}) + message = 'Hi'; + + render() { + const {message} = this; + return html` + TSElement says: ${message} + `; + } + +} +customElements.define('ts-element', TSElement); \ No newline at end of file diff --git a/src/lib/microtask.ts b/src/lib/microtask.ts new file mode 100644 index 00000000..d46e6350 --- /dev/null +++ b/src/lib/microtask.ts @@ -0,0 +1,80 @@ +/** +@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 +*/ + +// Microtask implemented using Mutation Observer +let microtaskCurrHandle = 0; +let microtaskLastHandle = 0; +const microtaskCallbacks: Array = []; +let microtaskNodeContent = 0; +const microtaskNode = document.createTextNode(''); +new MutationObserver(microtaskFlush).observe(microtaskNode, {characterData: true}); + +function microtaskFlush() { + const len = microtaskCallbacks.length; + for (let i = 0; i < len; i++) { + const cb = microtaskCallbacks[i]; + if (cb) { + try { + cb(); + } catch (e) { + setTimeout(() => { throw e; }); + } + } + } + microtaskCallbacks.splice(0, len); + microtaskLastHandle += len; +} + +/** + * Async interface for enqueuing callbacks that run at microtask timing. + * + * Note that microtask timing is achieved via a single `MutationObserver`, + * and thus callbacks enqueued with this API will all run in a single + * batch, and not interleaved with other microtasks such as promises. + * Promises are avoided as an implementation choice for the time being + * due to Safari bugs that cause Promises to lack microtask guarantees. + * + * @namespace + * @summary Async interface for enqueuing callbacks that run at microtask + * timing. + */ +export const microTask = { + + /** + * Enqueues a function called at microtask timing. + * + * @memberof microTask + * @param {!Function=} callback Callback to run + * @return {number} Handle used for canceling task + */ + run(callback: Function) { + microtaskNode.textContent = String(microtaskNodeContent++); + microtaskCallbacks.push(callback); + return microtaskCurrHandle++; + }, + + /** + * Cancels a previously enqueued `microTask` callback. + * + * @memberof microTask + * @param {number} handle Handle returned from `run` of callback to cancel + * @return {void} + */ + cancel(handle: number) { + const idx = handle - microtaskLastHandle; + if (idx >= 0) { + if (!microtaskCallbacks[idx]) { + throw new Error('invalid async handle: ' + handle); + } + microtaskCallbacks[idx] = null; + } + } + +}; diff --git a/src/lib/render-helpers.ts b/src/lib/render-helpers.ts new file mode 100644 index 00000000..e89cdafa --- /dev/null +++ b/src/lib/render-helpers.ts @@ -0,0 +1,71 @@ +/** + * @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 {camelToDashCase} from '@polymer/polymer/lib/utils/case-map.js'; + +/** + * Renders attributes to the given element based on the `attrInfo` object where + * boolean values are added/removed as attributes. + * @param element Element on which to set attributes. + * @param attrInfo Object describing attributes. + */ +export function renderAttributes( + element: HTMLElement, attrInfo: {[name: string]: string|boolean|number}) { + for (const a in attrInfo) { + const v = attrInfo[a] === true ? '' : attrInfo[a]; + if (v || v === '' || v === 0) { + if (element.getAttribute(a) !== v) { + element.setAttribute(a, String(v)); + } + } else if (element.hasAttribute(a)) { + element.removeAttribute(a); + } + } +} + +/** + * Returns a string of css class names formed by taking the properties + * in the `classInfo` object and appending the property name to the string of + * class names if the property value is truthy. + * @param classInfo + */ +export function classString( + classInfo: {[name: string]: string|boolean|number}) { + const o = []; + for (const name in classInfo) { + const v = classInfo[name]; + if (v) { + o.push(name); + } + } + return o.join(' '); +} + +/** + * Returns a css style string formed by taking the properties in the `styleInfo` + * object and appending the property name (dash-cased) colon the + * property value. Properties are separated by a semi-colon. + * @param styleInfo + */ +export function styleString( + styleInfo: {[name: string]: string|boolean|number}) { + const o = []; + for (const name in styleInfo) { + const v = styleInfo[name]; + if (v || v === 0) { + o.push(`${camelToDashCase(name)}: ${v}`); + } + } + return o.join('; '); +} diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts new file mode 100644 index 00000000..4819dcf3 --- /dev/null +++ b/src/lib/updating-element.ts @@ -0,0 +1,421 @@ +/** + * @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 {microTask} from './microtask.js'; + +export {microTask}; + +// TODO(sorvell): consider `noAttribute` instead of `attribute` +interface PropertyOptions { + // true to put property into observedAttributes + attribute?: boolean; + // defaults to lowercase + attributeName?: string; + // custom deserializer + fromAttribute?: Function; + // custom serializer + toAttribute?: Function; + // custom change function + shouldChange?: Function; +} + +interface Properties { + [key: string]: PropertyOptions; +} + +interface AttributeMap { + [key: string]: string; +} + +interface AnyObject { + [key: string]: {}; +} + +/** + * Creates a property accessor on the given prototype if one does not exist. + * Uses `getProperty` and `setProperty` to manage the property's value. + */ +function makeProperty(name: string, proto: Object) { + if (proto.hasOwnProperty(name) || (name in proto)) { + return; + } + Object.defineProperty(proto, name, { + get() { + return this.getProperty(name); + }, + set(value) { + this.setProperty(name, value); + }, + configurable: true, + enumerable: true + }); +} + +/** + * Decorator which creates a property. Optionally a `PropertyOptions` object + * can be supplied with keys: + * * attribute: a boolean that should be true if the property should be an observedAttribute + * * fromAttribute: function used to deserialize an attribute value + * * toAttribute: function used to serialize a property to an attribute value + * * attributeName: string that is a custom attribute name (defaults to lowercased property name) + */ +export const property = (options?: PropertyOptions) => (proto: Object, name: string) => { + const ctor = proto.constructor as typeof UpdatingElement; + ctor._ensurePropertyStorage(); + ctor._classProperties[name] = options || {}; + makeProperty(name, proto); +}; + +/** + * Change function that returns true if `value` is different from `oldValue`. + * This method is used as the default for a property's `shouldChange` function. + */ +// tslint:disable-next-line no-any +export function identity(value: any, old: any) { + // This ensures (old==NaN, value==NaN) always returns false + return old !== value && (old === old || value === value); +} + +/** + * Base element class which manages element properties and attributes. When + * properties change, the `update` method is asynchronously called. This method + * should be supplied by subclassers to render updates as desired. + */ +export class UpdatingElement extends HTMLElement { + + private _updatePromise: Promise|null = null; + private _isEnabled = false; + private _isValidating = false; + private _hasUpdated = false; + private _isUpdating = false; + private _serializingProperty = false; + private _instanceProperties: AnyObject = {}; + private _updateCompleteResolver: Function|null = null; + + /** + * Node or ShadowRoot into which element DOM should be renderd. Defaults + * to an open shadowRoot. + */ + root?: Element|DocumentFragment; + + /** + * Object with keys for all properties with their current values. + */ + props: AnyObject = {}; + /** + * Object with keys for any properties that have changed since the last + * update cycle. + */ + changedProps: AnyObject = {}; + /** + * Object with keys for all properties with their value from the previous + * update cycle. + */ + prevProps: AnyObject = {}; + + private static _attributeToPropertyMap: AttributeMap = {}; + private static _finalized = false; + private static _observedAttributes: string[]|undefined; + // TODO(sorvell): intended to be private but called by decorator. + static _classProperties: Properties = {}; + + /** + * Implement to return an object with keys that become registered properties, + * changes to which will trigger `update`. A set of options may be supplied + * for a property: + * * attribute: a boolean that should be true if the property should be an observedAttribute + * * fromAttribute: function used to deserialize an attribute value + * * toAttribute: function used to serialize a property to an attribute value + * * attributeName: string that is a custom attribute name (defaults to lowercased property name) + */ + static properties: Properties = {}; + + /** + * Returns a list of attributes corresponding to the registered properties + * that have listed themselves as attributes. + */ + static get observedAttributes() { + if (!this.hasOwnProperty('_observedAttributes')) { + // note: piggy backing on this to ensure we're _finalized. + this._finalize(); + this._observedAttributes = []; + for (const p in this._classProperties) { + const info = this._classProperties[p]; + if (info.attribute) { + const attr = this.propertyToAttributeName(p); + this._attributeToPropertyMap[attr] = p; + this._observedAttributes.push(attr); + } + } + } + return this._observedAttributes; + } + + // TODO(sorvell): intended to be private but called by decorator + static _ensurePropertyStorage() { + if (!this.hasOwnProperty('_classProperties')) { + this._classProperties = Object.create(Object.getPrototypeOf(this)._classProperties); + } + } + + /** + * Creates property accessors for registered properties and ensures + * any superclasses are also finalized. + */ + private static _finalize() { + if (!(this.hasOwnProperty('_finalized') && this._finalized)) { + const superCtor = Object.getPrototypeOf(this); + if (superCtor.prototype instanceof UpdatingElement) { + superCtor._finalize(); + } + this._finalized = true; + const props = this.properties; + for (const p in props) { + makeProperty(p, this.prototype); + } + this._attributeToPropertyMap = Object.create(superCtor._attributeToPropertyMap); + this._ensurePropertyStorage(); + Object.assign(this._classProperties, props); + } + } + + /** + * Returns the property name for the given attribute `name`. + */ + protected static propertyToAttributeName(name: string) { + const info = this._classProperties[name]; + return info && info.attributeName || name.toLowerCase(); + } + + /** + * Called when a property value is set to determine if the value should change. + * This uses the `shouldChange` property option for the given property `name`. + */ + // tslint:disable-next-line no-any + protected static propertyShouldChange(name: string, value: any, old: any) { + const info = this._classProperties[name]; + const fn = info && info.shouldChange || identity; + return fn(value, old); + } + + /** + * Called to deserialize an attribute value to a property value. + * This uses the `fromAttribute` property option for the given property `name`. + */ + // tslint:disable-next-line no-any + protected static propertyValueFromAttribute(name: string, value: string) { + const info = this._classProperties[name]; + const fromAttribute = info && info.fromAttribute; + return fromAttribute ? fromAttribute(value) : value; + } + + /** + * Called to serialize an property value to an attribute value. If this + * returns undefined, the property will *not* be reflected to an attribute. + * This uses the `toAttribute` property option for the given property `name`. + */ + // tslint:disable-next-line no-any + protected static propertyValueToAttribute(name: string, value: any) { + const info = this._classProperties[name]; + const toAttribute = info && info.toAttribute; + if (toAttribute !== undefined) { + return toAttribute(value); + } + } + + constructor() { + super(); + this.initialize(); + } + + /** + * Performs element initialization. By default this calls `createRoot` to + * create the element `root` node and captures any pre-set values for + * registered properties. + */ + initialize() { + this.createRoot(); + // save instance properties. + for (const p in (this.constructor as typeof UpdatingElement)._classProperties) { + if (this.hasOwnProperty(p)) { + // tslint:disable-next-line no-any + const me = (this as any); + const value = me[p]; + delete me[p]; + this._instanceProperties[p] = value; + } + } + } + + /** + * Implement to customize where the element's template is rendered by + * returning an element into which to render. By default this creates + * a shadowRoot for the element. To render into the element's childNodes, + * return `this`. + * @returns {Element|DocumentFragment} Returns a node into which to render. + */ + protected createRoot() { + this.root = this.attachShadow({mode : 'open'}); + } + + /** + * Calls `enableUpdate` and uses ShadyCSS to keep element DOM updated. + */ + connectedCallback() { + if (window.ShadyCSS && this._hasUpdated) { + window.ShadyCSS.styleElement(this); + } + this.enableUpdate(); + } + + /** + * Call to enable element updating. This is called in `connectedCallback` + * but can be called sooner if desired. + */ + enableUpdate() { + if (!this._isEnabled) { + // replay instance properties + for (const p in this._instanceProperties) { + // tslint:disable-next-line no-any + (this as any)[p] = this._instanceProperties[p]; + } + this._isEnabled = true; + this.invalidate(); + } + } + + /** + * Synchronizes property values when attributes change. + */ + attributeChangedCallback(name: string, old: string, value: string) { + if (old !== value) { + const ctor = (this.constructor as typeof UpdatingElement); + const propName = ctor._attributeToPropertyMap[name]; + // TODO(sorvell): currently not used. + if (!this._serializingProperty) { + // tslint:disable-next-line no-any + (this as any)[propName] = ctor.propertyValueFromAttribute(propName, value); + } + } + } + + /** + * Returns the value of the property with `name`. This method is used + * to get property values of registered properties. + */ + protected getProperty(name: string) { + return this.props[name]; + } + + /** + * Sets the property `name` to the given `value`. This method is used + * to set property values of registered properties. + */ + // tslint:disable-next-line no-any + protected setProperty(name: string, value: any) { + const old = this.props[name]; + const ctor = (this.constructor as typeof UpdatingElement); + if (ctor.propertyShouldChange(name, value, old)) { + this.props[name] = value; + this.changedProps[name] = value; + const attrValue = ctor.propertyValueToAttribute(name, value); + if (attrValue !== undefined) { + // TODO(sorvell): doing this here means we need to allow round tripping + // through property setting to not interfere with the reaction stack. + // However, we could move this to update (as in Polymer) to avoid this + // with a flag, but this is undesirable since we want to keep update + // unimplemented here. + this.setAttribute(name, attrValue); + } + this.invalidate(); + } + } + + /** + * Call to request the element to asynchronously update regardless + * of whether or not any property changes are pending. This method is + * automatically called when any registered property changes. + */ + protected invalidate() { + if (this._isEnabled) { + if (this._isUpdating) { + console.warn('Requested an update while updating. This is not supported.'); + } else if (!this._isValidating) { + this._isValidating = true; + microTask.run(() => this._validate()); + } + } + } + + /** + * Validates the element. If `shouldUpdate` returns true then `update` is + * called. When the update is complete `changeProps` and `prevProps` are + * updated and any `updateComplete` promise is resolved. + */ + private _validate() { + this._isValidating = false; + if (!this.shouldUpdate()) { + return; + } + this._isUpdating = true; + this.update(); + this._hasUpdated = true; + this.changedProps = {}; + Object.assign(this.prevProps, this.props); + this._isUpdating = false; + if (this._updateCompleteResolver) { + this._updateCompleteResolver(true); + } + } + + /** + * Implement to control if updating should occur when property values + * change or `invalidate` is called. By default, this method always + * returns true, but this can be customized as an optimization to avoid + * rendering work when changes occur which should not be rendered. + */ + protected shouldUpdate(): boolean { + return true; + } + + /** + * Implement to update the element. Typically renders and keeps updated DOM + * in the element's root. + */ + protected update() { + } + + /** + * Returns a promise which resolves after the element next updates. + * The promise resolves to `true` if the element updated and `false` if the + * element did not. + * This is useful when users (e.g. tests) need to react to the updated state + * of the element after a change is made. + * This can also be useful in event handlers if it is desireable to wait + * to send an event until after updating. + */ + get updateComplete() { + if (!this._updatePromise) { + this._updatePromise = new Promise((resolve) => { + this._updateCompleteResolver = (value: boolean) => { + this._updateCompleteResolver = this._updatePromise = null; + resolve(value); + }; + }); + if (!this._isUpdating) { + Promise.resolve().then(() => this._updateCompleteResolver!(false)); + } + } + return this._updatePromise; + } +} \ No newline at end of file diff --git a/src/lit-element.ts b/src/lit-element.ts index fbf44fc2..41c66a7e 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -11,286 +11,32 @@ * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ -import { - PropertiesChangedConstructor -} from '@polymer/polymer/lib/mixins/properties-changed.js'; -import {PropertiesMixin} from '@polymer/polymer/lib/mixins/properties-mixin.js'; -import { - PropertiesMixinConstructor -} from '@polymer/polymer/lib/mixins/properties-mixin.js'; -import {camelToDashCase} from '@polymer/polymer/lib/utils/case-map.js'; -import {render} from 'lit-html/lib/shady-render.js'; -import {TemplateResult} from 'lit-html/lit-html.js'; +import {render} from 'lit-html/lib/shady-render'; +import {TemplateResult} from 'lit-html'; +import {UpdatingElement} from './lib/updating-element.js'; -export { - PropertiesChangedConstructor -} from '@polymer/polymer/lib/mixins/properties-changed.js'; -export { - PropertiesMixinConstructor -} from '@polymer/polymer/lib/mixins/properties-mixin.js'; -export {html, svg} from 'lit-html/lib/lit-extended.js'; +export {microTask, property} from './lib/updating-element.js'; +export {html, svg} from 'lit-html/lib/lit-extended'; -// This is a hack to get tsc to not complain about unused interfaces and -// still generate the type declarations properly -export type __unused = PropertiesChangedConstructor&PropertiesMixinConstructor; -/** - * Renders attributes to the given element based on the `attrInfo` object where - * boolean values are added/removed as attributes. - * @param element Element on which to set attributes. - * @param attrInfo Object describing attributes. - */ -export function renderAttributes( - element: HTMLElement, attrInfo: {[name: string]: string|boolean|number}) { - for (const a in attrInfo) { - const v = attrInfo[a] === true ? '' : attrInfo[a]; - if (v || v === '' || v === 0) { - if (element.getAttribute(a) !== v) { - element.setAttribute(a, String(v)); - } - } else if (element.hasAttribute(a)) { - element.removeAttribute(a); - } - } -} - -/** - * Returns a string of css class names formed by taking the properties - * in the `classInfo` object and appending the property name to the string of - * class names if the property value is truthy. - * @param classInfo - */ -export function classString( - classInfo: {[name: string]: string|boolean|number}) { - const o = []; - for (const name in classInfo) { - const v = classInfo[name]; - if (v) { - o.push(name); - } - } - return o.join(' '); -} - -/** - * Returns a css style string formed by taking the properties in the `styleInfo` - * object and appending the property name (dash-cased) colon the - * property value. Properties are separated by a semi-colon. - * @param styleInfo - */ -export function styleString( - styleInfo: {[name: string]: string|boolean|number}) { - const o = []; - for (const name in styleInfo) { - const v = styleInfo[name]; - if (v || v === 0) { - o.push(`${camelToDashCase(name)}: ${v}`); - } - } - return o.join('; '); -} - -export class LitElement extends PropertiesMixin -(HTMLElement) { - - private __renderComplete: Promise|null = null; - private __resolveRenderComplete: Function|null = null; - private __isInvalid: Boolean = false; - private __isChanging: Boolean = false; - private _root?: Element|DocumentFragment; +export class LitElement extends UpdatingElement { /** - * Override which sets up element rendering by calling* `_createRoot` - * and `_firstRendered`. + * Override which performs element rendering by calling the `render` method. + * Override to perform tasks before and/or after updating. */ - ready() { - this._root = this._createRoot(); - super.ready(); - this._firstRendered(); - } - - connectedCallback() { - if (window.ShadyCSS && this._root) { - window.ShadyCSS.styleElement(this); - } - super.connectedCallback(); - } - - /** - * Called after the element DOM is rendered for the first time. - * Implement to perform tasks after first rendering like capturing a - * reference to a static node which must be directly manipulated. - * This should not be commonly needed. For tasks which should be performed - * before first render, use the element constructor. - */ - _firstRendered() {} - - /** - * Implement to customize where the element's template is rendered by - * returning an element into which to render. By default this creates - * a shadowRoot for the element. To render into the element's childNodes, - * return `this`. - * @returns {Element|DocumentFragment} Returns a node into which to render. - */ - protected _createRoot(): Element|DocumentFragment { - return this.attachShadow({mode : 'open'}); - } - - /** - * Override which returns the value of `_shouldRender` which users - * should implement to control rendering. If this method returns false, - * _propertiesChanged will not be called and no rendering will occur even - * if property values change or `requestRender` is called. - * @param _props Current element properties - * @param _changedProps Changing element properties - * @param _prevProps Previous element properties - * @returns {boolean} Default implementation always returns true. - */ - _shouldPropertiesChange(_props: object, _changedProps: object, - _prevProps: object): boolean { - const shouldRender = this._shouldRender(_props, _changedProps, _prevProps); - if (!shouldRender && this.__resolveRenderComplete) { - this.__resolveRenderComplete(false); - } - return shouldRender; - } - - /** - * Implement to control if rendering should occur when property values - * change or `requestRender` is called. By default, this method always - * returns true, but this can be customized as an optimization to avoid - * rendering work when changes occur which should not be rendered. - * @param _props Current element properties - * @param _changedProps Changing element properties - * @param _prevProps Previous element properties - * @returns {boolean} Default implementation always returns true. - */ - protected _shouldRender(_props: object, _changedProps: object, - _prevProps: object): boolean { - return true; - } - - /** - * Override which performs element rendering by calling - * `_render`, `_applyRender`, and finally `_didRender`. - * @param props Current element properties - * @param changedProps Changing element properties - * @param prevProps Previous element properties - */ - _propertiesChanged(props: object, changedProps: object, prevProps: object) { - super._propertiesChanged(props, changedProps, prevProps); - const result = this._render(props); - if (result && this._root !== undefined) { - this._applyRender(result, this._root!); - } - this._didRender(props, changedProps, prevProps); - if (this.__resolveRenderComplete) { - this.__resolveRenderComplete(true); - } - } - - _flushProperties() { - this.__isChanging = true; - this.__isInvalid = false; - super._flushProperties(); - this.__isChanging = false; - } - - /** - * Override which warns when a user attempts to change a property during - * the rendering lifecycle. This is an anti-pattern and should be avoided. - * @param property {string} - * @param value {any} - * @param old {any} - */ - // tslint:disable-next-line no-any - _shouldPropertyChange(property: string, value: any, old: any) { - const change = super._shouldPropertyChange(property, value, old); - if (change && this.__isChanging) { - console.warn( - `Setting properties in response to other properties changing ` + - `considered harmful. Setting '${property}' from ` + - `'${this._getProperty(property)}' to '${value}'.`); - } - return change; + protected update() { + render(this.render(), this.root!, this.localName!); } /** * Implement to describe the DOM which should be rendered in the element. - * Ideally, the implementation is a pure function using only props to describe - * the element template. The implementation must return a `lit-html` - * TemplateResult. By default this template is rendered into the element's - * shadowRoot. This can be customized by implementing `_createRoot`. This - * method must be implemented. + * The implementation must return a `lit-html` TemplateResult. * @param {*} _props Current element properties * @returns {TemplateResult} Must return a lit-html TemplateResult. */ - protected _render(_props: object): TemplateResult { - throw new Error('_render() not implemented'); - } - - /** - * Renders the given lit-html template `result` into the given `node`. - * Implement to customize the way rendering is applied. This is should not - * typically be needed and is provided for advanced use cases. - * @param result {TemplateResult} `lit-html` template result to render - * @param node {Element|DocumentFragment} node into which to render - */ - protected _applyRender(result: TemplateResult, - node: Element|DocumentFragment) { - render(result, node, this.localName!); - } - - /** - * Called after element DOM has been rendered. Implement to - * directly control rendered DOM. Typically this is not needed as `lit-html` - * can be used in the `_render` method to set properties, attributes, and - * event listeners. However, it is sometimes useful for calling methods on - * rendered elements, like calling `focus()` on an element to focus it. - * @param _props Current element properties - * @param _changedProps Changing element properties - * @param _prevProps Previous element properties - */ - protected _didRender(_props: object, _changedProps: object, - _prevProps: object) {} - - /** - * Call to request the element to asynchronously re-render regardless - * of whether or not any property changes are pending. - */ - requestRender() { this._invalidateProperties(); } - - /** - * Override which provides tracking of invalidated state. - */ - _invalidateProperties() { - this.__isInvalid = true; - super._invalidateProperties(); + protected render(): TemplateResult { + throw new Error('render() not implemented'); } - /** - * Returns a promise which resolves after the element next renders. - * The promise resolves to `true` if the element rendered and `false` if the - * element did not render. - * This is useful when users (e.g. tests) need to react to the rendered state - * of the element after a change is made. - * This can also be useful in event handlers if it is desireable to wait - * to send an event until after rendering. If possible implement the - * `_didRender` method to directly respond to rendering within the - * rendering lifecycle. - */ - get renderComplete() { - if (!this.__renderComplete) { - this.__renderComplete = new Promise((resolve) => { - this.__resolveRenderComplete = (value: boolean) => { - this.__resolveRenderComplete = this.__renderComplete = null; - resolve(value); - }; - }); - if (!this.__isInvalid && this.__resolveRenderComplete) { - Promise.resolve().then(() => this.__resolveRenderComplete!(false)); - } - } - return this.__renderComplete; - } -} +} \ No newline at end of file diff --git a/src/test/lit-element_styling_test.ts b/src/test/lit-element_styling_test.ts deleted file mode 100644 index bcc0bb46..00000000 --- a/src/test/lit-element_styling_test.ts +++ /dev/null @@ -1,246 +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 '@webcomponents/shadycss/apply-shim.min.js'; -import { - html, - LitElement, -} from '../lit-element.js'; - -declare global { - interface Window { - ShadyDOM: any; // tslint:disable-line - } -} - -const assert = chai.assert; - -suite('Styling', () => { - let container: HTMLElement; - - setup(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - - teardown(() => { - if (container && container.parentNode) { - container.parentNode.removeChild(container); - } - }); - - test('content shadowRoot is styled', () => { - customElements.define('s-1', class extends LitElement { - _render() { return html` - -
Testing...
`; - } - }); - const el = document.createElement('s-1'); - container.appendChild(el); - const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '2px'); - }); - - test('shared styling rendered into shadowRoot is styled', () => { - const style = html``; - customElements.define('s-2', class extends LitElement { - _render() { return html` - - ${style} -
Testing...
`; - } - }); - const el = document.createElement('s-2'); - container.appendChild(el); - const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '4px'); - }); - - test('custom properties render', () => { - customElements.define('s-3', class extends LitElement { - _render() { return html` - -
Testing...
`; - } - }); - const el = document.createElement('s-3'); - container.appendChild(el); - const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); - }); - - test('custom properties flow to nested elements', () => { - customElements.define('s-4-inner', class extends LitElement { - _render() { return html` - -
Testing...
`; - } - }); - customElements.define('s-4', class extends LitElement { - _render() { return html` - - `; - } - }); - const el = document.createElement('s-4'); - container.appendChild(el); - const div = el.shadowRoot!.querySelector('s-4-inner')!.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); - }); - - test('elements with custom properties can move between elements', (done) => { - customElements.define('s-5-inner', class extends LitElement { - _render() { return html` - -
Testing...
`; - } - }); - customElements.define('s-5', class extends LitElement { - _render() { return html` - - `; - } - }); - customElements.define('s-6', class extends LitElement { - _render() { return html` - `; - } - }); - const el = document.createElement('s-5'); - const el2 = document.createElement('s-6'); - container.appendChild(el); - container.appendChild(el2); - const inner = el.shadowRoot!.querySelector('s-5-inner'); - const div = inner!.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '2px'); - el2!.shadowRoot!.appendChild(inner!); - requestAnimationFrame(() => { - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); - done(); - }); - }); - - test('@apply renders in nested elements', () => { - customElements.define('s-7-inner', class extends LitElement { - _render() { return html` - -
Testing...
`; - } - }); - customElements.define('s-7', class extends LitElement { - _render() { return html` - - `; - } - }); - const el = document.createElement('s-7'); - container.appendChild(el); - const div = el.shadowRoot!.querySelector('s-7-inner')!.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '10px'); - }); - -}); - -suite('ShadyDOM', () => { - - let container: HTMLElement; - - setup(function() { - if (!window.ShadyDOM) { - this.skip(); - } else { - container = document.createElement('div'); - document.body.appendChild(container); - } - }); - - teardown(() => { - if (container && container.parentNode) { - container.parentNode.removeChild(container); - } - }); - - test('properties in styles render with initial value and cannot be changed', async () => { - let border = `6px solid blue`; - customElements.define('shady-1', class extends LitElement { - _render() { return html` - -
Testing...
`; - } - }); - const el = document.createElement('shady-1') as LitElement; - container.appendChild(el); - const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '6px'); - border = `4px solid orange`; - el.requestRender(); - await el.renderComplete; - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '6px'); - }); - -}); diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts deleted file mode 100644 index 0d8db829..00000000 --- a/src/test/lit-element_test.ts +++ /dev/null @@ -1,489 +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 {TemplateResult} from 'lit-html/lit-html.js'; - -import { - classString, - html, - LitElement, - renderAttributes, - styleString, -} from '../lit-element.js'; - -import {stripExpressionDelimeters} from './test-helpers.js'; - -const assert = chai.assert; - -suite('LitElement', () => { - let container: HTMLElement; - - setup(() => { - container = document.createElement('div'); - document.body.appendChild(container); - }); - - teardown(() => { - if (container && container.parentNode) { - container.parentNode.removeChild(container); - } - }); - - test('renders initial content into shadowRoot', () => { - const rendered = `hello world`; - customElements.define('x-1', class extends LitElement { - _render() { return html`${rendered}`; } - }); - const el = document.createElement('x-1'); - container.appendChild(el); - assert.ok(el.shadowRoot); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - rendered); - }); - - test('can set render target to light dom', () => { - const rendered = `hello world`; - customElements.define('x-1a', class extends LitElement { - _render() { return html`${rendered}`; } - - _createRoot() { return this; } - }); - const el = document.createElement('x-1a'); - container.appendChild(el); - assert.notOk(el.shadowRoot); - assert.equal(stripExpressionDelimeters(el.innerHTML), rendered); - }); - - test('renders when created via constructor', () => { - const rendered = `hello world`; - class E extends LitElement { - _render() { return html`${rendered}`; } - } - customElements.define('x-2', E); - const el = new E(); - container.appendChild(el); - assert.ok(el.shadowRoot); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - rendered); - }); - - test('renders changes when properties change', (done) => { - class E extends LitElement { - static get properties() { return {foo : String}; } - - foo = 'one'; - - _render(props: {foo: string}) { return html`${props.foo}`; } - } - customElements.define('x-3', E); - const el = new E(); - container.appendChild(el); - assert.ok(el.shadowRoot); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - 'one'); - el.foo = 'changed'; - requestAnimationFrame(() => { - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - 'changed'); - done(); - }); - }); - - test('renders changes when attributes change', (done) => { - class E extends LitElement { - static get properties() { return {foo : String}; } - - foo = 'one'; - - _render(props: {foo: string}) { return html`${props.foo}`; } - } - customElements.define('x-4', E); - const el = new E(); - container.appendChild(el); - assert.ok(el.shadowRoot); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - 'one'); - el.setAttribute('foo', 'changed'); - requestAnimationFrame(() => { - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - 'changed'); - done(); - }); - }); - - test('_firstRendered call after first render and not subsequent renders', - async () => { - class E extends LitElement { - static get properties() { return {foo : String}; } - - foo = 'one'; - firstRenderedCount = 0; - domAtFirstRendered = ''; - - _firstRendered() { - this.firstRenderedCount++; - this.domAtFirstRendered = - stripExpressionDelimeters(this.shadowRoot!.innerHTML); - } - - _render(props: {foo: string}) { return html`${props.foo}`; } - } - customElements.define('x-5', E); - const el = new E(); - container.appendChild(el); - assert.equal(el.firstRenderedCount, 1); - assert.ok(el.shadowRoot); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - el.domAtFirstRendered); - assert.equal(el.foo, el.domAtFirstRendered); - el.foo = 'two'; - await el.renderComplete; - assert.equal(el.firstRenderedCount, 1); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - el.foo); - assert.notEqual(el.foo, el.domAtFirstRendered); - }); - - test('User defined accessor can trigger rendering', async () => { - class E extends LitElement { - __bar?: number; - - static get properties() { return {foo : Number, bar : Number}; } - - info: string[] = []; - foo = 0; - - get bar() { return this._getProperty('bar'); } - - set bar(value) { - this.__bar = value; - this._setProperty('bar', value); - } - - _render(props: {foo: string, bar: number}) { - this.info.push('render'); - return html`${props.foo}${props.bar}`; - } - } - customElements.define('x-6', E); - const el = new E(); - container.appendChild(el); - el.setAttribute('bar', '20'); - await el.renderComplete; - assert.equal(el.bar, 20); - assert.equal(el.__bar, 20); - assert.equal(stripExpressionDelimeters(el.shadowRoot!.innerHTML), '020'); - }); - - test('render attributes, properties, and event listeners via lit-html', - function() { - class E extends LitElement { - _event?: Event; - - _render() { - const attr = 'attr'; - const prop = 'prop'; - const event = (e: Event) => { this._event = e; }; - return html - `
`; - } - } - customElements.define('x-7', E); - const el = new E(); - container.appendChild(el); - const d = el.shadowRoot!.querySelector('div')! as (HTMLDivElement & - {prop: string}); - assert.equal(d.getAttribute('attr'), 'attr'); - assert.equal(d.prop, 'prop'); - const e = new Event('zug'); - d.dispatchEvent(e); - assert.equal(el._event, e); - }); - - test('renderComplete waits until next rendering', async () => { - class E extends LitElement { - static get properties() { return {foo : Number}; } - - foo = 0; - - _render(props: {foo: string}) { return html`${props.foo}`; } - } - customElements.define('x-8', E); - const el = new E(); - container.appendChild(el); - el.foo++; - await el.renderComplete; - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - '1'); - el.foo++; - await el.renderComplete; - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - '2'); - el.foo++; - await el.renderComplete; - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - '3'); - }); - - test('_shouldRender controls rendering', async () => { - class E extends LitElement { - static get properties() { return {foo : Number}; } - - foo = 0; - renderCount = 0; - allowRender = true; - - _render() { - this.renderCount++; - return html`hi`; - } - - _shouldRender() { return this.allowRender; } - } - customElements.define('x-9', E); - const el = new E(); - container.appendChild(el); - assert.equal(el.renderCount, 1); - el.foo++; - await el.renderComplete; - assert.equal(el.renderCount, 2); - el.allowRender = false; - el.foo++; - await el.renderComplete; - assert.equal(el.renderCount, 2); - el.allowRender = true; - el.foo++; - await el.renderComplete; - assert.equal(el.renderCount, 3); - }); - - test('renderComplete returns true if rendering happened and false otherwise', - async () => { - class E extends LitElement { - - needsRender = true; - - static get properties() { return {foo : Number}; } - - _shouldRender() { return this.needsRender; } - - foo = 0; - - _render(props: {foo: string}) { return html`${props.foo}`; } - } - customElements.define('x-9.1', E); - const el = new E(); - container.appendChild(el); - el.foo++; - let rendered; - rendered = await el.renderComplete; - assert.equal(rendered, true); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - '1'); - el.needsRender = false; - el.foo++; - rendered = await el.renderComplete; - assert.equal(rendered, false); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - '1'); - el.needsRender = true; - el.foo++; - rendered = await el.renderComplete; - assert.equal(rendered, true); - assert.equal( - stripExpressionDelimeters((el.shadowRoot as ShadowRoot).innerHTML), - '3'); - el.requestRender(); - rendered = await el.renderComplete; - assert.equal(rendered, true); - rendered = await el.renderComplete; - assert.equal(rendered, false); - }); - - test( - 'render lifecycle order: _shouldRender, _render, _applyRender, _didRender', - async () => { - class E extends LitElement { - static get properties() { return {foo : Number}; } - - info: Array = []; - - _shouldRender() { - this.info.push('_shouldRender'); - return true; - } - - _render() { - this.info.push('_render'); - return html`hi`; - } - - _applyRender(result: TemplateResult, root: Element|DocumentFragment) { - this.info.push('_applyRender'); - super._applyRender(result, root); - } - - _didRender() { this.info.push('_didRender'); } - } - customElements.define('x-10', E); - const el = new E(); - container.appendChild(el); - await el.renderComplete; - assert.deepEqual( - el.info, - [ '_shouldRender', '_render', '_applyRender', '_didRender' ]); - }); - - test('renderAttributes renders attributes on element', async () => { - class E extends LitElement { - static get properties() { return {foo : Number, bar : Boolean}; } - - foo = 0; - bar = true; - - _render({foo, bar}: {foo: number, bar: boolean}) { - renderAttributes(this, {foo, bar}); - return html`${foo}${bar}`; - } - } - customElements.define('x-11', E); - const el = new E(); - container.appendChild(el); - assert.equal(el.getAttribute('foo'), '0'); - assert.equal(el.getAttribute('bar'), ''); - el.foo = 5; - el.bar = false; - await el.renderComplete; - assert.equal(el.getAttribute('foo'), '5'); - assert.equal(el.hasAttribute('bar'), false); - }); - - test('classString updates classes', async () => { - class E extends LitElement { - static get properties() { - return {foo : Number, bar : Boolean, baz : Boolean}; - } - - foo = 0; - bar = true; - baz = false; - - _render({foo, bar, baz}: {foo: number, bar: boolean, baz: boolean}) { - return html - `
`; - } - } - customElements.define('x-12', E); - const el = new E(); - container.appendChild(el); - const d = el.shadowRoot!.querySelector('div')!; - assert.include(d.className, 'bar'); - el.foo = 1; - el.baz = true; - await el.renderComplete; - assert.include(d.className, 'foo bar zonk'); - el.bar = false; - await el.renderComplete; - assert.include(d.className, 'foo zonk'); - el.foo = 0; - el.baz = false; - await el.renderComplete; - assert.notInclude(d.className, 'foo bar zonk'); - }); - - test('styleString updates style', async () => { - class E extends LitElement { - static get properties() { - return { - marginTop : String, - paddingTop : String, - zug : String - }; - } - - marginTop = ``; - paddingTop = ``; - zug = `0px`; - - _render( - {marginTop, paddingTop, zug}: - {marginTop: string, paddingTop: string, zug: string}) { - return html`
`; - } - } - customElements.define('x-13', E); - const el = new E(); - container.appendChild(el); - const d = el.shadowRoot!.querySelector('div')!; - let computed = getComputedStyle(d); - assert.equal(computed.getPropertyValue('margin-top'), '0px'); - assert.equal(computed.getPropertyValue('height'), '0px'); - el.marginTop = `2px`; - el.paddingTop = `5px`; - await el.renderComplete; - el.offsetWidth; - computed = getComputedStyle(d); - assert.equal(computed.getPropertyValue('margin-top'), '2px'); - assert.equal(computed.getPropertyValue('height'), '0px'); - assert.equal(computed.getPropertyValue('padding-top'), '5px'); - el.marginTop = ``; - el.paddingTop = ``; - el.zug = ``; - await el.renderComplete; - assert.equal(d.style.cssText, ''); - }); - - test('warns when setting properties re-entrantly', async () => { - class E extends LitElement { - _toggle: boolean = false; - - _render() { - this._setProperty('foo', this._toggle ? 'fooToggle' : 'foo'); - return html`hi`; - } - - _didRender() { - this._setProperty('zonk', this._toggle ? 'zonkToggle' : 'zonk'); - } - - } - const calls: IArguments[] = []; - const orig = console.warn; - console.warn = function() { calls.push(arguments); }; - customElements.define('x-14', E); - const el = new E(); - container.appendChild(el); - assert.equal(calls.length, 2); - el._toggle = true; - el.requestRender(); - await el.renderComplete; - assert.equal(calls.length, 4); - console.warn = orig; - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 9da59179..b714db5b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "noFallthroughCasesInSwitch": true, "outDir": "./", // Only necessary because @types/uglify-js can't find types for source-map - "skipLibCheck": true + "skipLibCheck": true, + "experimentalDecorators": true }, "include": [ "custom_typings/**/*.ts", From f8f88620fc385748812f16595c3e97d92590ae9a Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 24 Jul 2018 17:52:40 -0700 Subject: [PATCH 02/65] remove microtask lib in favor of Promise. --- src/lib/microtask.ts | 80 ------------------------------------- src/lib/updating-element.ts | 9 ++--- src/lit-element.ts | 2 +- 3 files changed, 4 insertions(+), 87 deletions(-) delete mode 100644 src/lib/microtask.ts diff --git a/src/lib/microtask.ts b/src/lib/microtask.ts deleted file mode 100644 index d46e6350..00000000 --- a/src/lib/microtask.ts +++ /dev/null @@ -1,80 +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 -*/ - -// Microtask implemented using Mutation Observer -let microtaskCurrHandle = 0; -let microtaskLastHandle = 0; -const microtaskCallbacks: Array = []; -let microtaskNodeContent = 0; -const microtaskNode = document.createTextNode(''); -new MutationObserver(microtaskFlush).observe(microtaskNode, {characterData: true}); - -function microtaskFlush() { - const len = microtaskCallbacks.length; - for (let i = 0; i < len; i++) { - const cb = microtaskCallbacks[i]; - if (cb) { - try { - cb(); - } catch (e) { - setTimeout(() => { throw e; }); - } - } - } - microtaskCallbacks.splice(0, len); - microtaskLastHandle += len; -} - -/** - * Async interface for enqueuing callbacks that run at microtask timing. - * - * Note that microtask timing is achieved via a single `MutationObserver`, - * and thus callbacks enqueued with this API will all run in a single - * batch, and not interleaved with other microtasks such as promises. - * Promises are avoided as an implementation choice for the time being - * due to Safari bugs that cause Promises to lack microtask guarantees. - * - * @namespace - * @summary Async interface for enqueuing callbacks that run at microtask - * timing. - */ -export const microTask = { - - /** - * Enqueues a function called at microtask timing. - * - * @memberof microTask - * @param {!Function=} callback Callback to run - * @return {number} Handle used for canceling task - */ - run(callback: Function) { - microtaskNode.textContent = String(microtaskNodeContent++); - microtaskCallbacks.push(callback); - return microtaskCurrHandle++; - }, - - /** - * Cancels a previously enqueued `microTask` callback. - * - * @memberof microTask - * @param {number} handle Handle returned from `run` of callback to cancel - * @return {void} - */ - cancel(handle: number) { - const idx = handle - microtaskLastHandle; - if (idx >= 0) { - if (!microtaskCallbacks[idx]) { - throw new Error('invalid async handle: ' + handle); - } - microtaskCallbacks[idx] = null; - } - } - -}; diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 4819dcf3..737615ac 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -11,10 +11,6 @@ * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ -import {microTask} from './microtask.js'; - -export {microTask}; - // TODO(sorvell): consider `noAttribute` instead of `attribute` interface PropertyOptions { // true to put property into observedAttributes @@ -346,13 +342,14 @@ export class UpdatingElement extends HTMLElement { * of whether or not any property changes are pending. This method is * automatically called when any registered property changes. */ - protected invalidate() { + protected async invalidate() { if (this._isEnabled) { if (this._isUpdating) { console.warn('Requested an update while updating. This is not supported.'); } else if (!this._isValidating) { this._isValidating = true; - microTask.run(() => this._validate()); + await Promise.resolve(); + this._validate(); } } } diff --git a/src/lit-element.ts b/src/lit-element.ts index 41c66a7e..c876d43a 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -15,7 +15,7 @@ import {render} from 'lit-html/lib/shady-render'; import {TemplateResult} from 'lit-html'; import {UpdatingElement} from './lib/updating-element.js'; -export {microTask, property} from './lib/updating-element.js'; +export {property} from './lib/updating-element.js'; export {html, svg} from 'lit-html/lib/lit-extended'; From df3c39d0042e71cab201a6cdab02a5c3d3c08fe1 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 3 Aug 2018 10:15:31 -0700 Subject: [PATCH 03/65] API changes * Property options have been updated to now include: `attribute`: set to explicitly false to not observe, to a string to customize name, or true or absent to observe lowercased name; `type`: if a function, used to deserialize attribute value, otherwise can be an object with {fromAttribute: deserializing function, toAttribute: serializing function}; `reflect`: if true, property reflects to attribute using type.toAttribute if present; `shouldInvalidate`: function used to determine if a change should trigger invalidation. * setting properties in update now does not trigger invalidation and does not issue a warning. This can and should be done to compute values before updating. * finishUpdate has been added to perform post update tasks like direct dom manipulation. It is an async function which will be awaited before resolving the updateComplete promise. Setting properties in finishUpdate will trigger invalidation and this will finish before the updateComplete promise is resolved. * no longer depends on polymer. --- package.json | 1 - src/lib/render-helpers.ts | 24 +- src/lib/updating-element.ts | 457 +++++++++++++++++++++--------------- src/lit-element.ts | 4 +- tslint.json | 1 - 5 files changed, 268 insertions(+), 219 deletions(-) diff --git a/package.json b/package.json index e96c25c0..7bc02444 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ }, "typings": "lit-element.d.ts", "dependencies": { - "@polymer/polymer": "^3.0.2", "lit-html": "^0.10.2" }, "publishConfig": { diff --git a/src/lib/render-helpers.ts b/src/lib/render-helpers.ts index e89cdafa..708243ba 100644 --- a/src/lib/render-helpers.ts +++ b/src/lib/render-helpers.ts @@ -12,28 +12,6 @@ * http://polymer.github.io/PATENTS.txt */ -import {camelToDashCase} from '@polymer/polymer/lib/utils/case-map.js'; - -/** - * Renders attributes to the given element based on the `attrInfo` object where - * boolean values are added/removed as attributes. - * @param element Element on which to set attributes. - * @param attrInfo Object describing attributes. - */ -export function renderAttributes( - element: HTMLElement, attrInfo: {[name: string]: string|boolean|number}) { - for (const a in attrInfo) { - const v = attrInfo[a] === true ? '' : attrInfo[a]; - if (v || v === '' || v === 0) { - if (element.getAttribute(a) !== v) { - element.setAttribute(a, String(v)); - } - } else if (element.hasAttribute(a)) { - element.removeAttribute(a); - } - } -} - /** * Returns a string of css class names formed by taking the properties * in the `classInfo` object and appending the property name to the string of @@ -64,7 +42,7 @@ export function styleString( for (const name in styleInfo) { const v = styleInfo[name]; if (v || v === 0) { - o.push(`${camelToDashCase(name)}: ${v}`); + o.push(`${name.replace(/([A-Z])/, '-$1').toLowerCase()}: ${v}`); } } return o.join('; '); diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 737615ac..66c73f15 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -11,20 +11,58 @@ * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ -// TODO(sorvell): consider `noAttribute` instead of `attribute` + +/** + * Converts property values to and from attribute values. + */ +type AttributeProcessor = { + /** + * Deserializing function called to convert an attribute value to a property value. + */ + fromAttribute?(value: string): unknown, + /** + * Serializing function called to convert a property value to an attribute value. + */ + toAttribute?(value: unknown): string|null +}; + +/** + * Defines options for a property accessor. + */ interface PropertyOptions { - // true to put property into observedAttributes - attribute?: boolean; - // defaults to lowercase - attributeName?: string; - // custom deserializer - fromAttribute?: Function; - // custom serializer - toAttribute?: Function; - // custom change function - shouldChange?: Function; + /** + * Describes how and if the property should becomes an observedAttribute. + * If explicitly false, the property will not be observed. Otherwise if true + * or absent, the lowercased property name is observed, and if a string + * observe that value. + */ + attribute?: boolean|string; + /** + * Describes how to convert a property value to and from an attribute. If + * the value is a function, it's calle to deserialize an attribute value into + * a property value. If it's an AttributeProcessor, the `fromAttribute` value + * is used to deserialize. If the `reflect` option is also true, then + * `toAttribute` value is used to serialize the property value to an attribute. + */ + type?: AttributeProcessor|Function; + /** + * Describes if setting the property should be reflect to the attribute value. + * If true, then if present the `type.toAttribute` function is used to serialize + * the value; otherwise, the property value is used. + */ + reflect?: boolean; + /** + * Describes if setting a property should trigger invalidation and updating. + * This function takes the new and oldValue and returns true if invalidation + * should occur. If not present, a strict identity check is used. + */ + shouldInvalidate?(value: unknown, oldValue: unknown): boolean; } +/** + * Object that describes accessors to be created on the element prototype. + * An accessor is created for each key with the given options. + */ interface Properties { [key: string]: PropertyOptions; } @@ -57,21 +95,38 @@ function makeProperty(name: string, proto: Object) { }); } +/** + * Creates and sets object used to memoize all class property values. Object + * is chained from superclass. + */ +function ensurePropertyStorage(ctor: typeof UpdatingElement) { + if (!ctor.hasOwnProperty('_classProperties')) { + ctor._classProperties = Object.create(Object.getPrototypeOf(ctor)._classProperties); + } +} + /** * Decorator which creates a property. Optionally a `PropertyOptions` object - * can be supplied with keys: - * * attribute: a boolean that should be true if the property should be an observedAttribute - * * fromAttribute: function used to deserialize an attribute value - * * toAttribute: function used to serialize a property to an attribute value - * * attributeName: string that is a custom attribute name (defaults to lowercased property name) + * can be supplied to describe how the property should be configured. */ export const property = (options?: PropertyOptions) => (proto: Object, name: string) => { const ctor = proto.constructor as typeof UpdatingElement; - ctor._ensurePropertyStorage(); + ensurePropertyStorage(ctor); ctor._classProperties[name] = options || {}; makeProperty(name, proto); }; + +/** + * AttributeProcessor which configures properties which should reflect to and + * from boolean attributes. If the attribute exists, the property becomes true; + * and if the property value is truthy, the attribute is set to an empty string. + */ +export const BooleanAttribute: AttributeProcessor = { + fromAttribute: (value: string) => value !== null, + toAttribute: (value: string) => value ? '' : null +}; + /** * Change function that returns true if `value` is different from `oldValue`. * This method is used as the default for a property's `shouldChange` function. @@ -82,6 +137,13 @@ export function identity(value: any, old: any) { return old !== value && (old === old || value === value); } +enum ValidationState { + Disabled = 0, + Valid, + Invalid, + Updating +} + /** * Base element class which manages element properties and attributes. When * properties change, the `update` method is asynchronously called. This method @@ -89,15 +151,6 @@ export function identity(value: any, old: any) { */ export class UpdatingElement extends HTMLElement { - private _updatePromise: Promise|null = null; - private _isEnabled = false; - private _isValidating = false; - private _hasUpdated = false; - private _isUpdating = false; - private _serializingProperty = false; - private _instanceProperties: AnyObject = {}; - private _updateCompleteResolver: Function|null = null; - /** * Node or ShadowRoot into which element DOM should be renderd. Defaults * to an open shadowRoot. @@ -105,40 +158,29 @@ export class UpdatingElement extends HTMLElement { root?: Element|DocumentFragment; /** - * Object with keys for all properties with their current values. + * Maps attribute names to properties; for example `foobar` attribute + * to `fooBar` property. */ - props: AnyObject = {}; + private static _attributeToPropertyMap: AttributeMap = {}; /** - * Object with keys for any properties that have changed since the last - * update cycle. + * Marks class as having finished creating properties. */ - changedProps: AnyObject = {}; + private static _finalized = true; /** - * Object with keys for all properties with their value from the previous - * update cycle. + * Memoized result of computed observedAttributes. */ - prevProps: AnyObject = {}; - - private static _attributeToPropertyMap: AttributeMap = {}; - private static _finalized = false; private static _observedAttributes: string[]|undefined; - // TODO(sorvell): intended to be private but called by decorator. - static _classProperties: Properties = {}; + // TODO(sorvell): intended to be private but called by decorator. /** - * Implement to return an object with keys that become registered properties, - * changes to which will trigger `update`. A set of options may be supplied - * for a property: - * * attribute: a boolean that should be true if the property should be an observedAttribute - * * fromAttribute: function used to deserialize an attribute value - * * toAttribute: function used to serialize a property to an attribute value - * * attributeName: string that is a custom attribute name (defaults to lowercased property name) + * Memoized list of all class properties, including any superclass properties. */ + static _classProperties: Properties = {}; + static properties: Properties = {}; /** - * Returns a list of attributes corresponding to the registered properties - * that have listed themselves as attributes. + * Returns a list of attributes corresponding to the registered properties. */ static get observedAttributes() { if (!this.hasOwnProperty('_observedAttributes')) { @@ -146,9 +188,8 @@ export class UpdatingElement extends HTMLElement { this._finalize(); this._observedAttributes = []; for (const p in this._classProperties) { - const info = this._classProperties[p]; - if (info.attribute) { - const attr = this.propertyToAttributeName(p); + const attr = this.attributeNameForProperty(p); + if (attr !== undefined) { this._attributeToPropertyMap[attr] = p; this._observedAttributes.push(attr); } @@ -157,78 +198,102 @@ export class UpdatingElement extends HTMLElement { return this._observedAttributes; } - // TODO(sorvell): intended to be private but called by decorator - static _ensurePropertyStorage() { - if (!this.hasOwnProperty('_classProperties')) { - this._classProperties = Object.create(Object.getPrototypeOf(this)._classProperties); - } - } - /** * Creates property accessors for registered properties and ensures * any superclasses are also finalized. */ private static _finalize() { - if (!(this.hasOwnProperty('_finalized') && this._finalized)) { - const superCtor = Object.getPrototypeOf(this); - if (superCtor.prototype instanceof UpdatingElement) { - superCtor._finalize(); - } - this._finalized = true; - const props = this.properties; - for (const p in props) { - makeProperty(p, this.prototype); - } - this._attributeToPropertyMap = Object.create(superCtor._attributeToPropertyMap); - this._ensurePropertyStorage(); - Object.assign(this._classProperties, props); + if (this.hasOwnProperty('_finalized') && this._finalized) { + return; + } + // finalize any superclasses + const superCtor = Object.getPrototypeOf(this); + if (superCtor.prototype instanceof UpdatingElement) { + superCtor._finalize(); + } + this._finalized = true; + // make any properties + const props = this.properties; + for (const p in props) { + makeProperty(p, this.prototype); } + // initialize map populated in observedAttributes + this._attributeToPropertyMap = {}; + // memoize list of all class properties. + ensurePropertyStorage(this); + Object.assign(this._classProperties, props); } /** * Returns the property name for the given attribute `name`. */ - protected static propertyToAttributeName(name: string) { + private static attributeNameForProperty(name: string) { const info = this._classProperties[name]; - return info && info.attributeName || name.toLowerCase(); + const attribute = info && info.attribute; + return attribute === false ? undefined : (typeof attribute === 'string' ? + attribute : name.toLowerCase()); } /** - * Called when a property value is set to determine if the value should change. - * This uses the `shouldChange` property option for the given property `name`. + * Returns true if a property should cause invalidation and update. + * Called when a property value is set and uses the `shouldInvalidate` + * option for the property if present or a strict identity check. */ // tslint:disable-next-line no-any - protected static propertyShouldChange(name: string, value: any, old: any) { + private static propertyShouldInvalidate(name: string, value: any, old: any) { const info = this._classProperties[name]; - const fn = info && info.shouldChange || identity; + const fn = info && info.shouldInvalidate || identity; return fn(value, old); } /** - * Called to deserialize an attribute value to a property value. - * This uses the `fromAttribute` property option for the given property `name`. + * Returns the property value for the given attribute value. + * Called via the `attributeChangedCallback` and uses the property's `type` + * or `type.fromAttribute` property option. */ - // tslint:disable-next-line no-any - protected static propertyValueFromAttribute(name: string, value: string) { + private static propertyValueFromAttribute(name: string, value: string) { const info = this._classProperties[name]; - const fromAttribute = info && info.fromAttribute; + const type = info && info.type; + if (!type) { + return value; + } + const fromAttribute = typeof type === 'function' ? type : type.fromAttribute; return fromAttribute ? fromAttribute(value) : value; } /** - * Called to serialize an property value to an attribute value. If this + * Returns the attribute value for the given property value. If this * returns undefined, the property will *not* be reflected to an attribute. - * This uses the `toAttribute` property option for the given property `name`. + * If this returns null, the attribute will be removed, otherwise the + * attribute will be set to the value. + * This uses the property's `reflect` and `type.toAttribute` property options. */ // tslint:disable-next-line no-any - protected static propertyValueToAttribute(name: string, value: any) { + private static propertyValueToAttribute(name: string, value: any) { const info = this._classProperties[name]; - const toAttribute = info && info.toAttribute; - if (toAttribute !== undefined) { - return toAttribute(value); + if (!info || !info.reflect) { + return; } + const toAttribute = info.type && (info.type as AttributeProcessor).toAttribute || String; + return (typeof toAttribute === 'function') ? toAttribute(value) : null; } + private _validationState: ValidationState = ValidationState.Disabled; + private _serializingInfo: [string, string|null]|null = null; + private _instanceProperties: AnyObject|null = null; + private _validatePromise: any = null; + private _validateResolvers: Function[] = []; + + /** + * Object with keys for all properties with their current values. + */ + private _props: AnyObject = {}; + /** + * Object with keys for any properties that have changed since the last + * update cycle with previous values. + */ + private _changedProps: AnyObject|null = null; + constructor() { super(); this.initialize(); @@ -240,54 +305,43 @@ export class UpdatingElement extends HTMLElement { * registered properties. */ initialize() { - this.createRoot(); - // save instance properties. + this.root = this.createRenderRoot(); + // Apply any properties set on the instance before upgrade time. for (const p in (this.constructor as typeof UpdatingElement)._classProperties) { if (this.hasOwnProperty(p)) { - // tslint:disable-next-line no-any - const me = (this as any); - const value = me[p]; - delete me[p]; - this._instanceProperties[p] = value; + const value = this[p as keyof this]; + delete this[p as keyof this]; + this._instanceProperties = this._instanceProperties || {}; + // NOTE: must capture these into a bag and reset at when validating + // to avoid stomping on a user value set in the constructor. Being + // async doesn't help here since the subclass' constructor value should + // be overwritten. + this._instanceProperties![p] = value; } } + // TODO(sorvell): if we do this here, then no good signal for styleElement. + //this.invalidate(); } /** - * Implement to customize where the element's template is rendered by - * returning an element into which to render. By default this creates - * a shadowRoot for the element. To render into the element's childNodes, - * return `this`. + * Returns the node into which the element should render and by default + * creates and returns an open shadowRoot. Implement to customize where the + * element's DOM is rendered. For example, to render into the element's + * childNodes, return `this`. * @returns {Element|DocumentFragment} Returns a node into which to render. */ - protected createRoot() { - this.root = this.attachShadow({mode : 'open'}); + protected createRenderRoot(): Element|ShadowRoot { + return this.attachShadow({mode : 'open'}); } /** - * Calls `enableUpdate` and uses ShadyCSS to keep element DOM updated. + * Uses ShadyCSS to keep element DOM updated. */ connectedCallback() { - if (window.ShadyCSS && this._hasUpdated) { + if (this._validationState !== ValidationState.Disabled) { window.ShadyCSS.styleElement(this); } - this.enableUpdate(); - } - - /** - * Call to enable element updating. This is called in `connectedCallback` - * but can be called sooner if desired. - */ - enableUpdate() { - if (!this._isEnabled) { - // replay instance properties - for (const p in this._instanceProperties) { - // tslint:disable-next-line no-any - (this as any)[p] = this._instanceProperties[p]; - } - this._isEnabled = true; - this.invalidate(); - } + this.invalidate(); } /** @@ -297,10 +351,11 @@ export class UpdatingElement extends HTMLElement { if (old !== value) { const ctor = (this.constructor as typeof UpdatingElement); const propName = ctor._attributeToPropertyMap[name]; - // TODO(sorvell): currently not used. - if (!this._serializingProperty) { - // tslint:disable-next-line no-any - (this as any)[propName] = ctor.propertyValueFromAttribute(propName, value); + // Use tracking info to avoid deserializing attribute value if it was + // just set from a property setter. + if (!this._serializingInfo || + (this._serializingInfo[0] !== name && this._serializingInfo[1] !== value)) { + this[propName as keyof this] = ctor.propertyValueFromAttribute(propName, value); } } } @@ -310,109 +365,127 @@ export class UpdatingElement extends HTMLElement { * to get property values of registered properties. */ protected getProperty(name: string) { - return this.props[name]; + return this._props[name]; } /** * Sets the property `name` to the given `value`. This method is used * to set property values of registered properties. + * Setting a property value calls `invalidate` which asynchronously updates + * the element. If a property value is set inside the `update` method, + * it does not trigger `invalidate`. */ // tslint:disable-next-line no-any protected setProperty(name: string, value: any) { - const old = this.props[name]; + const old = this._props[name]; const ctor = (this.constructor as typeof UpdatingElement); - if (ctor.propertyShouldChange(name, value, old)) { - this.props[name] = value; - this.changedProps[name] = value; + if (ctor.propertyShouldInvalidate(name, value, old)) { + // track old value when changing. + if (!this._changedProps) { + this._changedProps = {}; + } + if (!(name in this._changedProps)) { + this._changedProps[name] = this._props[name]; + } + this._props[name] = value; const attrValue = ctor.propertyValueToAttribute(name, value); if (attrValue !== undefined) { - // TODO(sorvell): doing this here means we need to allow round tripping - // through property setting to not interfere with the reaction stack. - // However, we could move this to update (as in Polymer) to avoid this - // with a flag, but this is undesirable since we want to keep update - // unimplemented here. - this.setAttribute(name, attrValue); + // track the attr name/value being set to be able to avoid + // reflecting back to the property setter via attributeChangedCallback. + this._serializingInfo = [name, attrValue]; + if (attrValue === null) { + this.removeAttribute(name); + } else { + this.setAttribute(name, attrValue); + } + this._serializingInfo = null; } this.invalidate(); } } /** - * Call to request the element to asynchronously update regardless + * Invalidates the element causing it to asynchronously update regardless * of whether or not any property changes are pending. This method is - * automatically called when any registered property changes. + * automatically called when any registered property changes. Returns a Promise + * that resolves when the element has finished updating. */ - protected async invalidate() { - if (this._isEnabled) { - if (this._isUpdating) { - console.warn('Requested an update while updating. This is not supported.'); - } else if (!this._isValidating) { - this._isValidating = true; - await Promise.resolve(); - this._validate(); + async invalidate() { + // Do not re-queue validation if already invalid (pending) or currently updating. + if (this._validationState === ValidationState.Invalid || + this._validationState === ValidationState.Updating) { + return this._validatePromise; + } + this._validationState = ValidationState.Invalid; + // Make a new validate promise and put the resolver on the stack. + this._validatePromise = new Promise((resolve) => this._validateResolvers.push(resolve)); + // Wait a tick to actually process changes (allows batching). + await 0; + // Mixin instance properties once, if they exist. + if (this._instanceProperties) { + Object.assign(this, this._instanceProperties); + this._instanceProperties = null; + } + // Rip off changedProps. + const changedProps = this._changedProps || {}; + this._changedProps = null; + if (this.shouldUpdate(changedProps)) { + // During update, setting properties does not trigger invalidation. + this._validationState = ValidationState.Updating; + this.update(changedProps); + this._validationState = ValidationState.Valid; + // During finishUpdate, setting properties does trigger invalidation, + // and users may choose to await other state, like children being updated. + await this.finishUpdate(changedProps); + } else { + this._validationState = ValidationState.Valid; + } + // Only resolve the promise if we finish in a valid state (finishUpdate + // did not trigger more work). + if (this._validationState === ValidationState.Valid) { + while (this._validateResolvers.length) { + const resolver = this._validateResolvers.pop(); + if (resolver) { + resolver(); + } } } + return this._validatePromise; } /** - * Validates the element. If `shouldUpdate` returns true then `update` is - * called. When the update is complete `changeProps` and `prevProps` are - * updated and any `updateComplete` promise is resolved. + * Returns a Promise that resolves when the element has finished updating. */ - private _validate() { - this._isValidating = false; - if (!this.shouldUpdate()) { - return; - } - this._isUpdating = true; - this.update(); - this._hasUpdated = true; - this.changedProps = {}; - Object.assign(this.prevProps, this.props); - this._isUpdating = false; - if (this._updateCompleteResolver) { - this._updateCompleteResolver(true); + async updateComplete() { + if (this._changedProps) { + await this.invalidate(); } } /** - * Implement to control if updating should occur when property values - * change or `invalidate` is called. By default, this method always - * returns true, but this can be customized as an optimization to avoid - * rendering work when changes occur which should not be rendered. + * Controls whether or not `update` should be called when the element invalidates. + * By default, this method always returns true, but this can be customized to + * control when to update. + * * @param _changedProperties changed properties with old values */ - protected shouldUpdate(): boolean { + protected shouldUpdate(_changedProperties: AnyObject): boolean { return true; } /** - * Implement to update the element. Typically renders and keeps updated DOM - * in the element's root. + * Updates the element. This method does nothing by default and should be + * implemented to render and keep updated DOM in the element's root. + * * @param _changedProperties changed properties with old values */ - protected update() { - } + protected update(_changedProperties: AnyObject) {} - /** - * Returns a promise which resolves after the element next updates. - * The promise resolves to `true` if the element updated and `false` if the - * element did not. - * This is useful when users (e.g. tests) need to react to the updated state - * of the element after a change is made. - * This can also be useful in event handlers if it is desireable to wait - * to send an event until after updating. + /** + * Finishes updating the element. This method does nothing by default and + * should be implemented to perform post update tasks on element DOM. This + * async function can await other Promises to defer the resolution of the + * `invalidate` and `updateComplete` Proimses. + * * @param _changedProperties changed properties with old values */ - get updateComplete() { - if (!this._updatePromise) { - this._updatePromise = new Promise((resolve) => { - this._updateCompleteResolver = (value: boolean) => { - this._updateCompleteResolver = this._updatePromise = null; - resolve(value); - }; - }); - if (!this._isUpdating) { - Promise.resolve().then(() => this._updateCompleteResolver!(false)); - } - } - return this._updatePromise; - } + protected async finishUpdate(_changedProperties: AnyObject) {} + } \ No newline at end of file diff --git a/src/lit-element.ts b/src/lit-element.ts index c876d43a..a99f0e9c 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -15,7 +15,7 @@ import {render} from 'lit-html/lib/shady-render'; import {TemplateResult} from 'lit-html'; import {UpdatingElement} from './lib/updating-element.js'; -export {property} from './lib/updating-element.js'; +export {property, identity, BooleanAttribute} from './lib/updating-element.js'; export {html, svg} from 'lit-html/lib/lit-extended'; @@ -25,7 +25,7 @@ export class LitElement extends UpdatingElement { * Override which performs element rendering by calling the `render` method. * Override to perform tasks before and/or after updating. */ - protected update() { + protected async update() { render(this.render(), this.root!, this.localName!); } diff --git a/tslint.json b/tslint.json index 974d6b9a..54984fb6 100644 --- a/tslint.json +++ b/tslint.json @@ -6,7 +6,6 @@ true, "spaces" ], - "no-any": true, "prefer-const": true, "no-duplicate-variable": true, "no-eval": true, From dc5d931a22fe0054abeece57cf5cd34158e5ef97 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 3 Aug 2018 17:35:21 -0700 Subject: [PATCH 04/65] Update tests and Readme --- README.md | 114 ++-- demo/lit-element.html | 45 +- src/demo/ts-element.ts | 9 +- src/lib/updating-element.ts | 68 ++- src/lit-element.ts | 4 +- src/test/lit-element_styling_test.ts | 262 +++++++++ src/test/lit-element_test.ts | 794 +++++++++++++++++++++++++++ src/test/render-helpers_test.ts | 122 ++++ src/test/test-helpers.ts | 7 + test/runner.html | 1 + 10 files changed, 1345 insertions(+), 81 deletions(-) create mode 100644 src/test/lit-element_styling_test.ts create mode 100644 src/test/lit-element_test.ts create mode 100644 src/test/render-helpers_test.ts diff --git a/README.md b/README.md index 334dbd9c..52babcbf 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,22 @@ LitElement uses [lit-html](https://github.com/Polymer/lit-html) to render into the element's [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) -and [Polymer's](https://github.com/Polymer/polymer) -[PropertiesMixin](https://github.com/Polymer/polymer/blob/master/lib/mixins/properties-mixin.js) -to help manage element properties and attributes. LitElement reacts to changes in properties +and adds API help manage element properties and attributes. LitElement reacts to changes in properties and renders declaratively using `lit-html`. + * **Setup properties:** LitElement supports a static `properties` getter in which element property + accessors are described. For each property, an object configures settings where the options are: + + * attribute: if false, do not add to observedAttributes, if true or absent, observe the + lowercased property name, if a string observe that value. + * type: if a function, use to deserialize the attribute value to the property value; + also can be an object with `{fromAttribute, toAttribute}` where `fromAttribute` is + the deserialize function and `toAttribute` is a serialize function. + * reflect: if true, on setting the property reflects the property value to an + attribute value using `type.toAttribute` if it exists. + * shouldInvalidate: optional function which should return true if setting the property + should cause the element to invalidate causing it to asynchronously update. + * **React to changes:** LitElement reacts to changes in properties and attributes by asynchronously rendering, ensuring changes are batched. This reduces overhead and maintains consistent state. @@ -70,8 +81,8 @@ and renders declaratively using `lit-html`. 1. Create a class that extends `LitElement`. 1. Implement a static `properties` getter that returns the element's properties (which automatically become observed attributes). - 1. Then implement a `_render(props)` method and use the element's -current properties (props) to return a `lit-html` template result to render + 1. Then implement a `render()` method and use the element's +current properties to return a `lit-html` template result to render into the element. This is the only method that must be implemented by subclasses. ```html @@ -81,11 +92,11 @@ into the element. This is the only method that must be implemented by subclasses class MyElement extends LitElement { - static get properties() { return { mood: String }} + static get properties() { return { mood: {type: String} }} - _render({mood}) { + render() { return html` - Web Components are ${mood}!`; + Web Components are ${this.mood}!`; } } @@ -99,41 +110,66 @@ into the element. This is the only method that must be implemented by subclasses ## API Documentation See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit-element.ts#L90) - for detailed API info, here are some highlights. Note, the leading underscore - is used to indicate that these methods are - [protected](https://en.wikipedia.org/wiki/Class_(computer_programming)#Member_accessibility); - they are not private and can and should be implemented by subclasses. - These methods generally are called as part of the rendering lifecycle and should - not be called in user code unless otherwise indicated. - - * `_createRoot()`: Implement to customize where the + for detailed API info, here are some highlights. + + * `createRenderRoot()` (protected): Implement to customize where the element's template is rendered by returning an element into which to render. By default this creates a shadowRoot for the element. To render into the element's childNodes, return `this`. - * `_firstRendered()`: Called after the element DOM is rendered for the first time. - - * `_shouldRender(props, changedProps, prevProps)`: Implement to control if rendering - should occur when property values change or `invalidate` is called. + * `shouldUpdate(changedProps)` (protected): Implement to control if updating and rendering + should occur when property values change or `invalidate` is called. The `changedProps` + argument is an object with keys for the changed properties pointing to their previous values. By default, this method always returns true, but this can be customized as - an optimization to avoid rendering work when changes occur which should not be rendered. - - * `_render(props)`: Implement to describe the element's DOM using `lit-html`. Ideally, - the `_render` implementation is a pure function using only `props` to describe - the element template. This is the only method that must be implemented by subclasses. - - * `_didRender(props, changedProps, prevProps)`: Called after element DOM has been rendered. - Implement to directly control rendered DOM. Typically this is not needed as `lit-html` - can be used in the `_render` method to set properties, attributes, and - event listeners. However, it is sometimes useful for calling methods on - rendered elements, for example focusing an input: + an optimization to avoid updating work when changes occur which should not be rendered. + + * `update()` (protected): This method calls `render()` and then uses `lit-html` to + render the template DOM. Override to customize how the element renders DOM. Note, + during `update()` setting properties does not trigger `invalidate()`, allowing + property values to be computed and validated. + + * `render()` (protected): Implement to describe the element's DOM using `lit-html`. Ideally, + the `render` implementation is a pure function using only the element's current properties + to describe the element template. This is the only method that must be implemented by subclasses. + Note, since `render()` is called by `update()` setting properties does not trigger + `invalidate()`, allowing property values to be computed and validated. + + * `finishUpdate(changedProps)` (protected): Called after element DOM has been updated and + before the `updateComplete` promise is resolved. The `changedProps` argument is an object + with keys for the changed properties pointing to their previous values. This is an + async function which is *awaited* before resolving the `updateComplete` promise. + Setting properties in `finishUpdate()` does trigger `invalidate()` and blocks + the `updateComplete` promise. Implement to directly control rendered DOM. + Typically this is not needed as `lit-html` can be used in the `render` method + to set properties, attributes, and event listeners. However, it is sometimes useful + for calling methods on rendered elements, for example focusing an input: `this.shadowRoot.querySelector('input').focus()`. - * `renderComplete`: Returns a promise which resolves after the element next renders. + * `updateComplete`: Returns a promise which resolves after the element next renders. - * `_requestRender`: Call to request the element to asynchronously re-render regardless + * `invalidate`: Call to request the element to asynchronously update regardless of whether or not any property changes are pending. +## Update Lifecycle + +* When the element is first connected or a property is set (e.g. `element.foo = 5`) + and the property's `shouldInvalidate(value, oldValue)` returns true. Then + * `invalidate()` tries to update the element after waiting a microtask. Then + * `shouldUpdate(changedProps)` is called and if this returns true which it + does by default: + * `update(changedProps)` is called to update the element. + Note, setting properties inside `update()` will set their values but + will *not* trigger `invalidate()`. This calls + * `render()` which should return a `lit-html` TemplateResult + (e.g. html\`Hello ${world}\`) + * `finishUpdate(changedProps)` is then called to do post update/render tasks. + Note, setting properties here will trigger `invalidate()` and block + the `updateComplete` promise. + * `updateComplete` promise is resolved only if the element is + not in an invalid state. +* Any code awaiting the element's `updateComplete` promise runs and observes + the element in the updated state. + ## Bigger Example ```JavaScript @@ -144,8 +180,8 @@ class MyElement extends LitElement { // Public property API that triggers re-render (synced with attributes) static get properties() { return { - foo: String, - whales: Number + foo: {type: String}, + whales: {type: Number} } } @@ -154,13 +190,13 @@ class MyElement extends LitElement { this.foo = 'foo'; this.addEventListener('click', async (e) => { this.whales++; - await this.renderComplete; + await this.updateComplete; this.dispatchEvent(new CustomEvent('whales', {detail: {whales: this.whales}})) }); } // Render method should return a `TemplateResult` using the provided lit-html `html` tag function - _render({foo, whales}) { + render() { return html` -

Foo: ${foo}

-
whales: ${'🐳'.repeat(whales)}
+

Foo: ${this.foo}

+
whales: ${'🐳'.repeat(this.whales)}
`; } diff --git a/demo/lit-element.html b/demo/lit-element.html index 3e74dcab..98e1a7fc 100644 --- a/demo/lit-element.html +++ b/demo/lit-element.html @@ -16,32 +16,48 @@ Hi
- + diff --git a/src/demo/ts-element.ts b/src/demo/ts-element.ts index 9490baf9..093808c1 100644 --- a/src/demo/ts-element.ts +++ b/src/demo/ts-element.ts @@ -2,17 +2,20 @@ import { LitElement, html, property } from '../lit-element.js'; class TSElement extends LitElement { - @property({attribute: true, fromAttribute: String}) + @property() message = 'Hi'; + @property({attribute: 'more-info', type: (value: string) => `[${value}]`}) + extra = ''; + render() { - const {message} = this; + const {message, extra} = this; return html` TSElement says: ${message} + TSElement says: ${message} ${extra} `; } diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 66c73f15..123526eb 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -29,7 +29,7 @@ type AttributeProcessor = { /** * Defines options for a property accessor. */ -interface PropertyOptions { +export interface PropertyOptions { /** * Describes how and if the property should becomes an observedAttribute. * If explicitly false, the property will not be observed. Otherwise if true @@ -63,7 +63,7 @@ interface PropertyOptions { * Object that describes accessors to be created on the element prototype. * An accessor is created for each key with the given options. */ -interface Properties { +export interface Properties { [key: string]: PropertyOptions; } @@ -279,10 +279,10 @@ export class UpdatingElement extends HTMLElement { } private _validationState: ValidationState = ValidationState.Disabled; - private _serializingInfo: [string, string|null]|null = null; + private _serializingInfo: [string|null, string|null]|null = null; private _instanceProperties: AnyObject|null = null; private _validatePromise: any = null; - private _validateResolvers: Function[] = []; + private _validateResolvers: (() => void)[] = []; /** * Object with keys for all properties with their current values. @@ -319,8 +319,6 @@ export class UpdatingElement extends HTMLElement { this._instanceProperties![p] = value; } } - // TODO(sorvell): if we do this here, then no good signal for styleElement. - //this.invalidate(); } /** @@ -349,14 +347,7 @@ export class UpdatingElement extends HTMLElement { */ attributeChangedCallback(name: string, old: string, value: string) { if (old !== value) { - const ctor = (this.constructor as typeof UpdatingElement); - const propName = ctor._attributeToPropertyMap[name]; - // Use tracking info to avoid deserializing attribute value if it was - // just set from a property setter. - if (!this._serializingInfo || - (this._serializingInfo[0] !== name && this._serializingInfo[1] !== value)) { - this[propName as keyof this] = ctor.propertyValueFromAttribute(propName, value); - } + this._attributeToProperty(name, value); } } @@ -388,19 +379,38 @@ export class UpdatingElement extends HTMLElement { this._changedProps[name] = this._props[name]; } this._props[name] = value; - const attrValue = ctor.propertyValueToAttribute(name, value); - if (attrValue !== undefined) { + this._propertyToAttribute(name, value); + this.invalidate(); + } + } + + private _propertyToAttribute(name: string, value: any) { + const ctor = (this.constructor as typeof UpdatingElement); + const attrValue = ctor.propertyValueToAttribute(name, value); + if (attrValue !== undefined) { + const attr = ctor.attributeNameForProperty(name); + if (attr !== undefined) { // track the attr name/value being set to be able to avoid // reflecting back to the property setter via attributeChangedCallback. - this._serializingInfo = [name, attrValue]; + this._serializingInfo = [attr, attrValue]; if (attrValue === null) { - this.removeAttribute(name); + this.removeAttribute(attr); } else { - this.setAttribute(name, attrValue); + this.setAttribute(attr, attrValue); } this._serializingInfo = null; } - this.invalidate(); + } + } + + private _attributeToProperty(name: string, value: string) { + // Use tracking info to avoid deserializing attribute value if it was + // just set from a property setter. + if (!this._serializingInfo || + (this._serializingInfo[0] !== name && this._serializingInfo[1] !== value)) { + const ctor = (this.constructor as typeof UpdatingElement); + const propName = ctor._attributeToPropertyMap[name]; + this[propName as keyof this] = ctor.propertyValueFromAttribute(propName, value); } } @@ -412,8 +422,7 @@ export class UpdatingElement extends HTMLElement { */ async invalidate() { // Do not re-queue validation if already invalid (pending) or currently updating. - if (this._validationState === ValidationState.Invalid || - this._validationState === ValidationState.Updating) { + if (this._isPendingUpdate()) { return this._validatePromise; } this._validationState = ValidationState.Invalid; @@ -445,21 +454,22 @@ export class UpdatingElement extends HTMLElement { if (this._validationState === ValidationState.Valid) { while (this._validateResolvers.length) { const resolver = this._validateResolvers.pop(); - if (resolver) { - resolver(); - } + resolver!(); } } return this._validatePromise; } + private _isPendingUpdate() { + return this._validationState === ValidationState.Invalid || + this._validationState === ValidationState.Updating; + } + /** * Returns a Promise that resolves when the element has finished updating. */ - async updateComplete() { - if (this._changedProps) { - await this.invalidate(); - } + get updateComplete() { + return this._isPendingUpdate() ? this._validatePromise : Promise.resolve(); } /** diff --git a/src/lit-element.ts b/src/lit-element.ts index a99f0e9c..5e18d434 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -15,7 +15,7 @@ import {render} from 'lit-html/lib/shady-render'; import {TemplateResult} from 'lit-html'; import {UpdatingElement} from './lib/updating-element.js'; -export {property, identity, BooleanAttribute} from './lib/updating-element.js'; +export {property, identity, BooleanAttribute, Properties, PropertyOptions} from './lib/updating-element.js'; export {html, svg} from 'lit-html/lib/lit-extended'; @@ -25,7 +25,7 @@ export class LitElement extends UpdatingElement { * Override which performs element rendering by calling the `render` method. * Override to perform tasks before and/or after updating. */ - protected async update() { + protected update() { render(this.render(), this.root!, this.localName!); } diff --git a/src/test/lit-element_styling_test.ts b/src/test/lit-element_styling_test.ts new file mode 100644 index 00000000..fe1aedb5 --- /dev/null +++ b/src/test/lit-element_styling_test.ts @@ -0,0 +1,262 @@ +/** + * @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 '@webcomponents/shadycss/apply-shim.min.js'; +import { + html, + LitElement, +} from '../lit-element.js'; + +import {generateElementName, nextFrame} from './test-helpers.js'; + +declare global { + interface Window { + ShadyDOM: any; // tslint:disable-line + } +} + +const assert = chai.assert; + +suite('Styling', () => { + let container: HTMLElement; + + setup(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + teardown(() => { + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + }); + + test('content shadowRoot is styled', async () => { + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { return html` + +
Testing...
`; + } + }); + const el = document.createElement(name); + container.appendChild(el); + await (el as LitElement).updateComplete; + const div = el.shadowRoot!.querySelector('div'); + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '2px'); + }); + + test('shared styling rendered into shadowRoot is styled', async () => { + const style = html``; + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { return html` + + ${style} +
Testing...
`; + } + }); + const el = document.createElement(name); + container.appendChild(el); + await (el as LitElement).updateComplete; + const div = el.shadowRoot!.querySelector('div'); + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '4px'); + }); + + test('custom properties render', async () => { + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { return html` + +
Testing...
`; + } + }); + const el = document.createElement(name); + container.appendChild(el); + await (el as LitElement).updateComplete; + const div = el.shadowRoot!.querySelector('div'); + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); + }); + + test('custom properties flow to nested elements', async () => { + customElements.define('x-inner', class extends LitElement { + render() { return html` + +
Testing...
`; + } + }); + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { return html` + + `; + } + }); + const el = document.createElement(name); + container.appendChild(el); + await nextFrame(); + const div = el.shadowRoot!.querySelector('x-inner')!.shadowRoot!.querySelector('div'); + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); + }); + + test('elements with custom properties can move between elements', async () => { + customElements.define('x-inner1', class extends LitElement { + render() { return html` + +
Testing...
`; + } + }); + const name1 = generateElementName(); + customElements.define(name1, class extends LitElement { + render() { return html` + + `; + } + }); + const name2 = generateElementName(); + customElements.define(name2, class extends LitElement { + render() { return html` + `; + } + }); + const el = document.createElement(name1); + const el2 = document.createElement(name2); + container.appendChild(el); + container.appendChild(el2); + let div: Element|null; + await nextFrame(); + const inner = el.shadowRoot!.querySelector('x-inner1'); + div = inner!.shadowRoot!.querySelector('div'); + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '2px'); + el2!.shadowRoot!.appendChild(inner!); + await nextFrame(); + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); + }); + + test('@apply renders in nested elements', async () => { + customElements.define('x-inner2', class extends LitElement { + render() { return html` + +
Testing...
`; + } + }); + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { return html` + + `; + } + }); + const el = document.createElement(name); + container.appendChild(el); + await nextFrame(); + const div = el.shadowRoot!.querySelector('x-inner2')!.shadowRoot!.querySelector('div'); + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '10px'); + }); + +}); + +suite('ShadyDOM', () => { + + let container: HTMLElement; + + setup(function() { + if (!window.ShadyDOM) { + this.skip(); + } else { + container = document.createElement('div'); + document.body.appendChild(container); + } + }); + + teardown(() => { + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + }); + + test('properties in styles render with initial value and cannot be changed', async () => { + let border = `6px solid blue`; + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { return html` + +
Testing...
`; + } + }); + const el = document.createElement(name) as LitElement; + container.appendChild(el); + await el.updateComplete; + const div = el.shadowRoot!.querySelector('div'); + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '6px'); + border = `4px solid orange`; + el.invalidate(); + await el.updateComplete; + assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '6px'); + }); + +}); diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts new file mode 100644 index 00000000..b6472c87 --- /dev/null +++ b/src/test/lit-element_test.ts @@ -0,0 +1,794 @@ +/** + * @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, LitElement, BooleanAttribute} from '../lit-element.js'; + +import {stripExpressionDelimeters, generateElementName} from './test-helpers.js'; + +const assert = chai.assert; + +suite('LitElement', () => { + let container: HTMLElement; + + setup(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + teardown(() => { + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + }); + + test('renders initial content into shadowRoot', async () => { + const rendered = `hello world`; + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { return html`${rendered}`; } + }); + const el = document.createElement(name); + container.appendChild(el); + await new Promise((resolve) => { + setTimeout(() => { + assert.ok(el.shadowRoot); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + rendered); + resolve(); + }); + }); + }); + + test('invalidate waits until update/rendering', async () => { + class E extends LitElement { + updated = 0; + render() { return html`${++this.updated}`; } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.invalidate(); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '1'); + await el.invalidate(); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '2'); + await el.invalidate(); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '3'); + }); + + test('updateComplete waits for invalidate but does not trigger invalidation, async', async () => { + class E extends LitElement { + updated = 0; + render() { return html`${++this.updated}`; } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '1'); + await el.updateComplete; + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '1'); + el.invalidate(); + await el.updateComplete; + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '2'); + await el.updateComplete; + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '2'); + }); + + test('shouldUpdate controls update/rendering', + async () => { + class E extends LitElement { + + needsUpdate = true; + updated = 0; + + shouldUpdate() { return this.needsUpdate; } + + render() { return html`${++this.updated}`; } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '1'); + el.needsUpdate = false; + await el.invalidate(); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '1'); + el.needsUpdate = true; + await el.invalidate(); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '2'); + await el.invalidate(); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + '3'); + }); + + test('can set render target to light dom', async () => { + const rendered = `hello world`; + const name = generateElementName(); + customElements.define(name, class extends LitElement { + render() { return html`${rendered}`; } + + createRenderRoot() { return this; } + }); + const el = document.createElement(name); + container.appendChild(el); + await (el as LitElement).updateComplete; + assert.notOk(el.shadowRoot); + assert.equal(stripExpressionDelimeters(el.innerHTML), rendered); + }); + + test('renders when created via constructor', async () => { + const rendered = `hello world`; + class E extends LitElement { + render() { return html`${rendered}`; } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.ok(el.shadowRoot); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + rendered); + }); + + test('property options', async() => { + + const shouldInvalidate = (value: any, old: any) => old === undefined || value > old; + const fromAttribute = (value: any) => parseInt(value); + const toAttribute = (value: any) => `${value}-attr`; + class E extends LitElement { + static get properties() { + return { + noAttr: {attribute: false}, + atTr: {attribute: true}, + customAttr: {attribute: 'custom', reflect: true}, + shouldInvalidate: {shouldInvalidate}, + fromAttribute: {type: fromAttribute}, + toAttribute: {reflect: true, type: {toAttribute}}, + all: {attribute: 'all-attr', shouldInvalidate, type: {fromAttribute, toAttribute}, reflect: true}, + }; + } + + noAttr = 'noAttr'; + atTr = 'attr'; + customAttr = 'customAttr'; + shouldInvalidate = 10; + fromAttribute = 1; + toAttribute = 1; + all = 10; + + render() { return html``; } + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.noAttr, 'noAttr'); + assert.equal(el.atTr, 'attr'); + assert.equal(el.customAttr, 'customAttr'); + assert.equal(el.shouldInvalidate, 10); + assert.equal(el.fromAttribute, 1); + assert.equal(el.toAttribute, 1); + assert.equal(el.getAttribute('toAttribute'), '1-attr'); + assert.equal(el.all, 10); + assert.equal(el.getAttribute('all-attr'), '10-attr'); + el.setAttribute('noattr', 'noAttr2'); + el.setAttribute('attr', 'attr2'); + el.setAttribute('custom', 'customAttr2'); + el.shouldInvalidate = 5; + el.setAttribute('fromAttribute', '2attrr'); + el.toAttribute = 2; + el.all = 5; + await el.updateComplete; + assert.equal(el.noAttr, 'noAttr'); + assert.equal(el.atTr, 'attr2'); + assert.equal(el.customAttr, 'customAttr2'); + assert.equal(el.shouldInvalidate, 10); + assert.equal(el.fromAttribute, 2); + assert.equal(el.toAttribute, 2); + assert.equal(el.getAttribute('toAttribute'), '2-attr'); + assert.equal(el.all, 10); + el.shouldInvalidate = 15; + el.all = 15; + await el.updateComplete; + assert.equal(el.shouldInvalidate, 15); + assert.equal(el.all, 15); + assert.equal(el.getAttribute('all-attr'), '15-attr'); + el.setAttribute('all-attr', '16-attr'); + await el.updateComplete; + assert.equal(el.getAttribute('all-attr'), '16-attr'); + assert.equal(el.all, 16); + }); + + + test('property options compose when subclassing', async() => { + + const shouldInvalidate = (value: any, old: any) => old === undefined || value > old; + const fromAttribute = (value: any) => parseInt(value); + const toAttribute = (value: any) => `${value}-attr`; + class E extends LitElement { + static get properties() { + return { + noAttr: {attribute: false}, + atTr: {attribute: true}, + customAttr: {}, + shouldInvalidate: {}, + }; + } + + noAttr = 'noAttr'; + atTr = 'attr'; + customAttr = 'customAttr'; + shouldInvalidate = 10; + fromAttribute = 1; + toAttribute = 1; + all = 10; + + render() { return html``; } + + } + customElements.define(generateElementName(), E); + + class F extends E { + static get properties() { + return { + customAttr: {attribute: 'custom', reflect: true}, + shouldInvalidate: {shouldInvalidate}, + fromAttribute: {}, + toAttribute: {}, + }; + } + + noAttr = 'noAttr'; + atTr = 'attr'; + customAttr = 'customAttr'; + shouldInvalidate = 10; + fromAttribute = 1; + toAttribute = 1; + all = 10; + + render() { return html``; } + + } + + class G extends F { + static get properties() { + return { + fromAttribute: {type: fromAttribute}, + toAttribute: {reflect: true, type: {toAttribute}}, + all: {attribute: 'all-attr', shouldInvalidate, type: {fromAttribute, toAttribute}, reflect: true}, + }; + } + + noAttr = 'noAttr'; + atTr = 'attr'; + customAttr = 'customAttr'; + shouldInvalidate = 10; + fromAttribute = 1; + toAttribute = 1; + all = 10; + + render() { return html``; } + + } + + customElements.define(generateElementName(), G); + + const el = new G(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.noAttr, 'noAttr'); + assert.equal(el.atTr, 'attr'); + assert.equal(el.customAttr, 'customAttr'); + assert.equal(el.shouldInvalidate, 10); + assert.equal(el.fromAttribute, 1); + assert.equal(el.toAttribute, 1); + assert.equal(el.getAttribute('toAttribute'), '1-attr'); + assert.equal(el.all, 10); + assert.equal(el.getAttribute('all-attr'), '10-attr'); + el.setAttribute('noattr', 'noAttr2'); + el.setAttribute('attr', 'attr2'); + el.setAttribute('custom', 'customAttr2'); + el.shouldInvalidate = 5; + el.setAttribute('fromAttribute', '2attrr'); + el.toAttribute = 2; + el.all = 5; + await el.updateComplete; + assert.equal(el.noAttr, 'noAttr'); + assert.equal(el.atTr, 'attr2'); + assert.equal(el.customAttr, 'customAttr2'); + assert.equal(el.shouldInvalidate, 10); + assert.equal(el.fromAttribute, 2); + assert.equal(el.toAttribute, 2); + assert.equal(el.getAttribute('toAttribute'), '2-attr'); + assert.equal(el.all, 10); + el.shouldInvalidate = 15; + el.all = 15; + await el.updateComplete; + assert.equal(el.shouldInvalidate, 15); + assert.equal(el.all, 15); + assert.equal(el.getAttribute('all-attr'), '15-attr'); + el.setAttribute('all-attr', '16-attr'); + await el.updateComplete; + assert.equal(el.getAttribute('all-attr'), '16-attr'); + assert.equal(el.all, 16); + + }); + + test('Attributes reflect with type.toAttribute and BooleanAttribute', async () => { + class E extends LitElement { + static get properties() { + return { + foo: {type: Number, reflect: true}, + bar: {type: BooleanAttribute, reflect: true} + }; + } + + foo = 0; + bar = true; + + render() { return html``; } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.getAttribute('foo'), '0'); + assert.equal(el.getAttribute('bar'), ''); + el.foo = 5; + el.bar = false; + await el.updateComplete; + assert.equal(el.getAttribute('foo'), '5'); + assert.equal(el.hasAttribute('bar'), false); + }); + + test('updates/renders when properties change', async () => { + class E extends LitElement { + static get properties() { return { foo: {}}; } + + foo = 'one'; + + render() { return html`${this.foo}`; } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + assert.ok(el.shadowRoot); + await el.updateComplete; + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + 'one'); + el.foo = 'changed'; + await el.updateComplete; + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + 'changed'); + }); + + test('updates/renders when properties and attributes change', async() => { + class E extends LitElement { + static get properties() { + return { + value: {}, + attrValue: {} + }; + } + + value = '1'; + attrValue = 'attr'; + + updatedValue = ''; + updatedAttrValue = ''; + + render() { return html``; } + + update() { + super.update(); + this.updatedValue = this.value; + this.updatedAttrValue = this.attrValue; + } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + assert.ok(el.shadowRoot); + await el.updateComplete; + assert.equal(el.updatedValue, '1'); + assert.equal(el.updatedAttrValue, 'attr'); + el.value = '2'; + await el.updateComplete; + assert.equal(el.updatedValue, '2'); + assert.equal(el.updatedAttrValue, 'attr'); + el.attrValue = 'attr2'; + await el.updateComplete; + assert.equal(el.updatedValue, '2'); + assert.equal(el.updatedAttrValue, 'attr2'); + el.setAttribute('attrValue', 'attr3'); + await el.updateComplete; + assert.equal(el.updatedValue, '2'); + assert.equal(el.updatedAttrValue, 'attr3'); + el.value = '3'; + el.setAttribute('attrValue', 'attr4'); + await el.updateComplete; + assert.equal(el.updatedValue, '3'); + assert.equal(el.updatedAttrValue, 'attr4'); + }); + + test('updates/renders changes when attributes change', async () => { + class E extends LitElement { + static get properties() { + return {foo: {}}; + } + + foo = 'one'; + + render() { return html`${this.foo}`; } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.ok(el.shadowRoot); + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + 'one'); + el.setAttribute('foo', 'changed'); + await el.updateComplete; + assert.equal( + stripExpressionDelimeters(el.shadowRoot!.innerHTML), + 'changed'); + }); + + test('User defined accessor using setProperty/getProperty can trigger update/render', async () => { + class E extends LitElement { + __bar?: number; + + static get properties() { return {foo: {}, bar: {}}; } + + info: string[] = []; + foo = 0; + + get bar() { return this.getProperty('bar'); } + + set bar(value) { + this.__bar = Number(value); + this.setProperty('bar', value); + } + + render() { + this.info.push('render'); + return html`${this.foo}${this.bar}`; + } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + el.setAttribute('bar', '20'); + await el.updateComplete; + assert.equal(el.bar, 20); + assert.equal(el.__bar, 20); + assert.equal(stripExpressionDelimeters(el.shadowRoot!.innerHTML), '020'); + }); + + test('updates/renders attributes, properties, and event listeners via lit-html', + async () => { + class E extends LitElement { + _event?: Event; + + render() { + const attr = 'attr'; + const prop = 'prop'; + const event = (e: Event) => { this._event = e; }; + return html + `
`; + } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + const d = el.shadowRoot!.querySelector('div')! as (HTMLDivElement & + {prop: string}); + assert.equal(d.getAttribute('attr'), 'attr'); + assert.equal(d.prop, 'prop'); + const e = new Event('zug'); + d.dispatchEvent(e); + assert.equal(el._event, e); + }); + + test( + 'render lifecycle order: shouldUpdate, update, render, updateComplete', async () => { + class E extends LitElement { + static get properties() { return { + foo: {type: Number} + }; } + + info: Array = []; + + shouldUpdate() { + this.info.push('shouldUpdate'); + return true; + } + + render() { + this.info.push('render'); + return html`hi`; + } + + update() { + this.info.push('before-update'); + super.update(); + } + + async finishUpdate() { + this.info.push('finishUpdate'); + } + + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + el.info.push('updateComplete'); + assert.deepEqual( + el.info, + [ 'shouldUpdate', 'before-update', 'render', 'finishUpdate', 'updateComplete' ]); + }); + + test('setting properties in update does not trigger invalidation', async () => { + class E extends LitElement { + + static get properties() { + return { + foo: {} + }; + } + promiseFulfilled = false; + foo = 0; + updated = 0; + + update() { + this.updated++; + this.foo++; + super.update(); + } + + render() { + return html`${this.foo}`; + } + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.foo, 1); + assert.equal(el.updated, 1); + assert.equal(el.shadowRoot!.textContent, '1'); + el.foo = 5; + await el.updateComplete; + assert.equal(el.foo, 6); + assert.equal(el.updated, 2); + assert.equal(el.shadowRoot!.textContent, '6'); + }); + + test('setting properties in finishUpdate does trigger invalidation blocks updateComplete', async () => { + class E extends LitElement { + + static get properties() { + return { + foo: {} + }; + } + promiseFulfilled = false; + foo = 0; + updated = 0; + fooMax = 2; + + async finishUpdate() { + this.updated++; + if (this.foo < this.fooMax) { + this.foo++; + } + } + + render() { + return html`${this.foo}`; + } + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.foo, 2); + assert.equal(el.updated, 3); + assert.equal(el.shadowRoot!.textContent, '2'); + el.fooMax = 10; + el.foo = 5; + await el.updateComplete; + assert.equal(el.foo, 10); + assert.equal(el.updated, 9); + assert.equal(el.shadowRoot!.textContent, '10'); + }); + + test('can await promise in finishUpdate', async () => { + class E extends LitElement { + + static get properties() { + return { + foo: {} + }; + } + promiseFulfilled = false; + foo = 0; + + render() { + return html`${this.foo}`; + } + + async finishUpdate() { + await new Promise((resolve) => { + setTimeout(() => { + this.promiseFulfilled = true; + resolve(); + }, 1); + }); + } + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.isTrue(el.promiseFulfilled); + }); + + test('updateComplete resolved after any properties set within finishUpdate', async () => { + class E extends LitElement { + + static get properties() { + return { + foo: {} + }; + } + + foo = 0; + + render() { + return html`${this.foo}`; + } + + async finishUpdate() { + if (this.foo < 10) { + this.foo++; + } + } + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.foo, 10); + assert.equal(el.shadowRoot!.textContent, '10'); + }); + + test('can await sub-element updateComplete in finishUpdate', async () => { + class E extends LitElement { + + static get properties() { + return { + foo: {} + }; + } + promiseFulfilled = false; + foo = 'hi'; + + render() { + return html`${this.foo}`; + } + + async finishUpdate() { + await new Promise((resolve) => { + setTimeout(() => { + this.promiseFulfilled = true; + resolve(); + }, 0); + }); + } + + } + customElements.define('x-1224', E); + + class F extends LitElement { + + inner: E|null = null; + + render() { + return html``; + } + + async finishUpdate() { + this.inner = this.shadowRoot!.querySelector('x-1224'); + this.inner!.foo = 'yo'; + await this.inner!.updateComplete; + } + + } + customElements.define(generateElementName(), F); + const el = new F(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.inner!.shadowRoot!.textContent, 'yo'); + assert.isTrue(el.inner!.promiseFulfilled); + }); + + test('properties set before upgrade are applied', async () => { + const name = generateElementName(); + const el = document.createElement(name); + container.appendChild(el); + (el as any).foo = 'hi'; + (el as any).bar = false; + const objectValue = {}; + (el as any).zug = objectValue; + class E extends LitElement { + static get properties() { + return { + foo: {}, + bar: {}, + zug: {} + }; + } + + foo = ''; + bar = true; + zug = null; + + render() { + return html`test`; + } + } + customElements.define(name, E); + await (el as LitElement).updateComplete; + assert.equal((el as any).foo, 'hi'); + assert.equal((el as any).bar, false); + assert.equal((el as any).zug, objectValue); + }); + +}); diff --git a/src/test/render-helpers_test.ts b/src/test/render-helpers_test.ts new file mode 100644 index 00000000..7cbaaeea --- /dev/null +++ b/src/test/render-helpers_test.ts @@ -0,0 +1,122 @@ +/** + * @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, LitElement} from '../lit-element.js'; +import {classString, styleString} from '../lib/render-helpers.js'; + +import {generateElementName} from './test-helpers.js'; + +const assert = chai.assert; + +suite('Render Helpers', () => { + let container: HTMLElement; + + setup(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + teardown(() => { + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + }); + + test('classString updates classes', async () => { + class E extends LitElement { + static get properties() { + return { + foo: {}, + bar: {}, + baz: {} + }; + } + + foo = 0; + bar = true; + baz = false; + + render() { + const {foo, bar, baz} = this; + return html + `
`; + } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + const d = el.shadowRoot!.querySelector('div')!; + assert.include(d.className, 'bar'); + el.foo = 1; + el.baz = true; + await el.updateComplete; + assert.include(d.className, 'foo bar zonk'); + el.bar = false; + await el.updateComplete; + assert.include(d.className, 'foo zonk'); + el.foo = 0; + el.baz = false; + await el.updateComplete; + assert.notInclude(d.className, 'foo bar zonk'); + }); + + test('styleString updates style', async () => { + class E extends LitElement { + static get properties() { + return { + marginTop: {}, + paddingTop: {}, + zug: {} + }; + } + + marginTop = ``; + paddingTop = ``; + zug = `0px`; + + render() { + const {marginTop, paddingTop, zug} = this; + return html`
`; + } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + const d = el.shadowRoot!.querySelector('div')!; + let computed = getComputedStyle(d); + assert.equal(computed.getPropertyValue('margin-top'), '0px'); + assert.equal(computed.getPropertyValue('height'), '0px'); + el.marginTop = `2px`; + el.paddingTop = `5px`; + await el.updateComplete; + el.offsetWidth; + computed = getComputedStyle(d); + assert.equal(computed.getPropertyValue('margin-top'), '2px'); + assert.equal(computed.getPropertyValue('height'), '0px'); + assert.equal(computed.getPropertyValue('padding-top'), '5px'); + el.marginTop = ``; + el.paddingTop = ``; + el.zug = ``; + await el.updateComplete; + assert.equal(d.style.cssText, ''); + }); + +}); diff --git a/src/test/test-helpers.ts b/src/test/test-helpers.ts index e6e6ca70..64ca038d 100644 --- a/src/test/test-helpers.ts +++ b/src/test/test-helpers.ts @@ -14,3 +14,10 @@ export const stripExpressionDelimeters = (html: string) => html.replace(//g, ''); + +let count = 0; +export const generateElementName = () => `x-${count++}`; + +export const nextFrame = () => new Promise((resolve) => requestAnimationFrame(resolve)); + + diff --git a/test/runner.html b/test/runner.html index cd1f7866..74c4b8fb 100644 --- a/test/runner.html +++ b/test/runner.html @@ -15,5 +15,6 @@ + From 5861e0e4a9535a6e458a21639695c8f332496c59 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 3 Aug 2018 17:44:51 -0700 Subject: [PATCH 05/65] Tweaks based on review. --- src/lib/updating-element.ts | 2 +- src/lit-element.ts | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 123526eb..ce26f539 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -149,7 +149,7 @@ enum ValidationState { * properties change, the `update` method is asynchronously called. This method * should be supplied by subclassers to render updates as desired. */ -export class UpdatingElement extends HTMLElement { +export abstract class UpdatingElement extends HTMLElement { /** * Node or ShadowRoot into which element DOM should be renderd. Defaults diff --git a/src/lit-element.ts b/src/lit-element.ts index 5e18d434..95ec9e28 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -19,24 +19,26 @@ export {property, identity, BooleanAttribute, Properties, PropertyOptions} from export {html, svg} from 'lit-html/lib/lit-extended'; -export class LitElement extends UpdatingElement { +export abstract class LitElement extends UpdatingElement { /** * Override which performs element rendering by calling the `render` method. * Override to perform tasks before and/or after updating. */ protected update() { - render(this.render(), this.root!, this.localName!); + if (this.render) { + render(this.render(), this.root!, this.localName!); + } else { + throw new Error('render() not implemented'); + } } /** - * Implement to describe the DOM which should be rendered in the element. - * The implementation must return a `lit-html` TemplateResult. + Invoked on each update to perform rendering tasks. This method must return a + lit-html TemplateResult. * @param {*} _props Current element properties * @returns {TemplateResult} Must return a lit-html TemplateResult. */ - protected render(): TemplateResult { - throw new Error('render() not implemented'); - } + protected abstract render(): TemplateResult; } \ No newline at end of file From 8f833063046e1fbbb096f32ee0e0a0154cce2e58 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 3 Aug 2018 17:52:11 -0700 Subject: [PATCH 06/65] More review tweaks. --- src/lib/updating-element.ts | 22 +++++++++++----------- src/lit-element.ts | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index ce26f539..92cc7d92 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -79,7 +79,7 @@ interface AnyObject { * Creates a property accessor on the given prototype if one does not exist. * Uses `getProperty` and `setProperty` to manage the property's value. */ -function makeProperty(name: string, proto: Object) { +const makeProperty = (name: string, proto: Object) => { if (proto.hasOwnProperty(name) || (name in proto)) { return; } @@ -99,7 +99,7 @@ function makeProperty(name: string, proto: Object) { * Creates and sets object used to memoize all class property values. Object * is chained from superclass. */ -function ensurePropertyStorage(ctor: typeof UpdatingElement) { +const ensurePropertyStorage = (ctor: typeof UpdatingElement) => { if (!ctor.hasOwnProperty('_classProperties')) { ctor._classProperties = Object.create(Object.getPrototypeOf(ctor)._classProperties); } @@ -132,10 +132,10 @@ export const BooleanAttribute: AttributeProcessor = { * This method is used as the default for a property's `shouldChange` function. */ // tslint:disable-next-line no-any -export function identity(value: any, old: any) { +export const identity = (value: any, old: any) => { // This ensures (old==NaN, value==NaN) always returns false return old !== value && (old === old || value === value); -} +}; enum ValidationState { Disabled = 0, @@ -151,12 +151,6 @@ enum ValidationState { */ export abstract class UpdatingElement extends HTMLElement { - /** - * Node or ShadowRoot into which element DOM should be renderd. Defaults - * to an open shadowRoot. - */ - root?: Element|DocumentFragment; - /** * Maps attribute names to properties; for example `foobar` attribute * to `fooBar` property. @@ -294,6 +288,12 @@ export abstract class UpdatingElement extends HTMLElement { */ private _changedProps: AnyObject|null = null; + /** + * Node or ShadowRoot into which element DOM should be renderd. Defaults + * to an open shadowRoot. + */ + protected renderRoot?: Element|DocumentFragment; + constructor() { super(); this.initialize(); @@ -305,7 +305,7 @@ export abstract class UpdatingElement extends HTMLElement { * registered properties. */ initialize() { - this.root = this.createRenderRoot(); + this.renderRoot = this.createRenderRoot(); // Apply any properties set on the instance before upgrade time. for (const p in (this.constructor as typeof UpdatingElement)._classProperties) { if (this.hasOwnProperty(p)) { diff --git a/src/lit-element.ts b/src/lit-element.ts index 95ec9e28..7906c74a 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -27,7 +27,7 @@ export abstract class LitElement extends UpdatingElement { */ protected update() { if (this.render) { - render(this.render(), this.root!, this.localName!); + render(this.render(), this.renderRoot!, this.localName!); } else { throw new Error('render() not implemented'); } From e18819e95906206e89c0d4227a1e52c047348802 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Fri, 3 Aug 2018 17:56:25 -0700 Subject: [PATCH 07/65] Lint fixes. --- src/lib/updating-element.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 92cc7d92..5c22b6b8 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -93,7 +93,7 @@ const makeProperty = (name: string, proto: Object) => { configurable: true, enumerable: true }); -} +}; /** * Creates and sets object used to memoize all class property values. Object @@ -103,7 +103,7 @@ const ensurePropertyStorage = (ctor: typeof UpdatingElement) => { if (!ctor.hasOwnProperty('_classProperties')) { ctor._classProperties = Object.create(Object.getPrototypeOf(ctor)._classProperties); } -} +}; /** * Decorator which creates a property. Optionally a `PropertyOptions` object From da54543e742b243cf0260ce159197410238d8832 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Sat, 4 Aug 2018 19:25:45 -0700 Subject: [PATCH 08/65] work around custom elements polyfill issue in tests https://github.com/webcomponents/custom-elements/issues/167 --- src/test/lit-element_test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index b6472c87..75ac114b 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -203,14 +203,14 @@ suite('LitElement', () => { assert.equal(el.shouldInvalidate, 10); assert.equal(el.fromAttribute, 1); assert.equal(el.toAttribute, 1); - assert.equal(el.getAttribute('toAttribute'), '1-attr'); + assert.equal(el.getAttribute('toattribute'), '1-attr'); assert.equal(el.all, 10); assert.equal(el.getAttribute('all-attr'), '10-attr'); el.setAttribute('noattr', 'noAttr2'); el.setAttribute('attr', 'attr2'); el.setAttribute('custom', 'customAttr2'); el.shouldInvalidate = 5; - el.setAttribute('fromAttribute', '2attrr'); + el.setAttribute('fromattribute', '2attr'); el.toAttribute = 2; el.all = 5; await el.updateComplete; @@ -220,7 +220,7 @@ suite('LitElement', () => { assert.equal(el.shouldInvalidate, 10); assert.equal(el.fromAttribute, 2); assert.equal(el.toAttribute, 2); - assert.equal(el.getAttribute('toAttribute'), '2-attr'); + assert.equal(el.getAttribute('toattribute'), '2-attr'); assert.equal(el.all, 10); el.shouldInvalidate = 15; el.all = 15; @@ -317,14 +317,14 @@ suite('LitElement', () => { assert.equal(el.shouldInvalidate, 10); assert.equal(el.fromAttribute, 1); assert.equal(el.toAttribute, 1); - assert.equal(el.getAttribute('toAttribute'), '1-attr'); + assert.equal(el.getAttribute('toattribute'), '1-attr'); assert.equal(el.all, 10); assert.equal(el.getAttribute('all-attr'), '10-attr'); el.setAttribute('noattr', 'noAttr2'); el.setAttribute('attr', 'attr2'); el.setAttribute('custom', 'customAttr2'); el.shouldInvalidate = 5; - el.setAttribute('fromAttribute', '2attrr'); + el.setAttribute('fromattribute', '2attr'); el.toAttribute = 2; el.all = 5; await el.updateComplete; @@ -334,7 +334,7 @@ suite('LitElement', () => { assert.equal(el.shouldInvalidate, 10); assert.equal(el.fromAttribute, 2); assert.equal(el.toAttribute, 2); - assert.equal(el.getAttribute('toAttribute'), '2-attr'); + assert.equal(el.getAttribute('toattribute'), '2-attr'); assert.equal(el.all, 10); el.shouldInvalidate = 15; el.all = 15; @@ -437,12 +437,12 @@ suite('LitElement', () => { await el.updateComplete; assert.equal(el.updatedValue, '2'); assert.equal(el.updatedAttrValue, 'attr2'); - el.setAttribute('attrValue', 'attr3'); + el.setAttribute('attrvalue', 'attr3'); await el.updateComplete; assert.equal(el.updatedValue, '2'); assert.equal(el.updatedAttrValue, 'attr3'); el.value = '3'; - el.setAttribute('attrValue', 'attr4'); + el.setAttribute('attrvalue', 'attr4'); await el.updateComplete; assert.equal(el.updatedValue, '3'); assert.equal(el.updatedAttrValue, 'attr4'); From b39b480de6f8752f9cf12f752ef21359b6885d29 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Sat, 4 Aug 2018 19:27:00 -0700 Subject: [PATCH 09/65] Changes based on review * mark update/finishUpdate as abstract * remove stack of promises in `invalidate` --- src/lib/updating-element.ts | 40 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 5c22b6b8..f229b815 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -140,8 +140,7 @@ export const identity = (value: any, old: any) => { enum ValidationState { Disabled = 0, Valid, - Invalid, - Updating + Invalid } /** @@ -276,7 +275,7 @@ export abstract class UpdatingElement extends HTMLElement { private _serializingInfo: [string|null, string|null]|null = null; private _instanceProperties: AnyObject|null = null; private _validatePromise: any = null; - private _validateResolvers: (() => void)[] = []; + private _validateResolver: (() => void)|null = null; /** * Object with keys for all properties with their current values. @@ -426,8 +425,11 @@ export abstract class UpdatingElement extends HTMLElement { return this._validatePromise; } this._validationState = ValidationState.Invalid; - // Make a new validate promise and put the resolver on the stack. - this._validatePromise = new Promise((resolve) => this._validateResolvers.push(resolve)); + // Make a new promise only if the current one is not pending resolution + // (resolver has not been set to null) + if (this._validateResolver === null) { + this._validatePromise = new Promise((resolve) => this._validateResolver = resolve); + } // Wait a tick to actually process changes (allows batching). await 0; // Mixin instance properties once, if they exist. @@ -439,30 +441,30 @@ export abstract class UpdatingElement extends HTMLElement { const changedProps = this._changedProps || {}; this._changedProps = null; if (this.shouldUpdate(changedProps)) { - // During update, setting properties does not trigger invalidation. - this._validationState = ValidationState.Updating; - this.update(changedProps); + // During update (which is abstract), setting properties does not trigger invalidation. + if (this.update !== undefined) { + this.update(changedProps); + } this._validationState = ValidationState.Valid; - // During finishUpdate, setting properties does trigger invalidation, + // During finishUpdate (which is abstract), setting properties does trigger invalidation, // and users may choose to await other state, like children being updated. - await this.finishUpdate(changedProps); + if (this.finishUpdate !== undefined) { + await this.finishUpdate(changedProps); + } } else { this._validationState = ValidationState.Valid; } // Only resolve the promise if we finish in a valid state (finishUpdate // did not trigger more work). - if (this._validationState === ValidationState.Valid) { - while (this._validateResolvers.length) { - const resolver = this._validateResolvers.pop(); - resolver!(); - } + if (this._validationState === ValidationState.Valid && this._validateResolver) { + this._validateResolver(); + this._validateResolver = null; } return this._validatePromise; } private _isPendingUpdate() { - return this._validationState === ValidationState.Invalid || - this._validationState === ValidationState.Updating; + return this._validationState === ValidationState.Invalid; } /** @@ -487,7 +489,7 @@ export abstract class UpdatingElement extends HTMLElement { * implemented to render and keep updated DOM in the element's root. * * @param _changedProperties changed properties with old values */ - protected update(_changedProperties: AnyObject) {} + protected abstract update(_changedProperties: AnyObject): void; /** * Finishes updating the element. This method does nothing by default and @@ -496,6 +498,6 @@ export abstract class UpdatingElement extends HTMLElement { * `invalidate` and `updateComplete` Proimses. * * @param _changedProperties changed properties with old values */ - protected async finishUpdate(_changedProperties: AnyObject) {} + protected abstract finishUpdate(_changedProperties: AnyObject): void; } \ No newline at end of file From 5ef504e494b4ad8d5c5c46694c7c4aa2ce3134d3 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Mon, 6 Aug 2018 18:36:40 -0700 Subject: [PATCH 10/65] Documentation updates and type tweaks based on review. --- README.md | 83 +++++++++++------- demo/lit-element.html | 7 +- package-lock.json | 10 ++- package.json | 2 +- src/lib/render-helpers.ts | 14 ++-- src/lib/updating-element.ts | 157 +++++++++++++++++++---------------- src/lit-element.ts | 4 +- src/test/lit-element_test.ts | 8 +- 8 files changed, 164 insertions(+), 121 deletions(-) diff --git a/README.md b/README.md index 52babcbf..2da7bd52 100644 --- a/README.md +++ b/README.md @@ -13,18 +13,39 @@ element's [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Componen and adds API help manage element properties and attributes. LitElement reacts to changes in properties and renders declaratively using `lit-html`. - * **Setup properties:** LitElement supports a static `properties` getter in which element property - accessors are described. For each property, an object configures settings where the options are: - - * attribute: if false, do not add to observedAttributes, if true or absent, observe the - lowercased property name, if a string observe that value. - * type: if a function, use to deserialize the attribute value to the property value; - also can be an object with `{fromAttribute, toAttribute}` where `fromAttribute` is - the deserialize function and `toAttribute` is a serialize function. - * reflect: if true, on setting the property reflects the property value to an - attribute value using `type.toAttribute` if it exists. - * shouldInvalidate: optional function which should return true if setting the property - should cause the element to invalidate causing it to asynchronously update. + * **Setup properties:** LitElement supports observable properties which may trigger an + update when set. These properties can be written in a few ways: + + * As class fields with the `@property()` [decorator](https://github.com/tc39/proposal-decorators#decorators), + if you're using a compiler that supports them, like TypeScript or Babel. + * With a static `properties` getter. + * By manually writing getters and setters. You can call `setProperty` or `invalidate` + to trigger an update. If you use `setProperty`, you can use `getProperty` in the getter. + + Properties may be given an options argument which is an object that desribes how to + process the property. This may can be either in the `@property({...})` decorator or in the + object returned the `properties` getter, e.g. `static get properties { return { foo: {...} }`. + + Property options include: + + * `attribute`: Describes how and if the property becomes an observedAttribute. + If the value is false, the property is not added to `observedAttributes`. + If true or absent, the lowercased property name is observed (e.g. `fooBar` becomes `fobar`). + If a string, the string value is observed (e.g `attribute: 'foo-bar'`). + * `type`: Describes how to convert the attribute to/from a property. + If this value is a function, it is used to deserialize the attribute value + a the property value. If it's an object, it can have keys for `fromAttribute` and + `toAttribute` where `fromAttribute` is the deserialize function and `toAttribute` + is a serialize function used to convert the property to an attribute. + * `reflect`: Describes if the property should reflect to an attribute. + If true, when the property is set, the attribute value is set using the + attribute name taken from the property's `attribute` and the value + of the property serialized using `type.toAttribute` if it exists. + * `shouldInvalidate`: Describes if setting a property should trigger + invalidation and updating. This function takes the `new` and `oldValue` and + returns true if invalidation should occur. If not present, a strict identity + check is used. This is useful if a property should be considered dirty only + if some condition is met, like if a key of an object value changes. * **React to changes:** LitElement reacts to changes in properties and attributes by asynchronously rendering, ensuring changes are batched. This reduces overhead @@ -79,8 +100,8 @@ and renders declaratively using `lit-html`. ## Minimal Example 1. Create a class that extends `LitElement`. - 1. Implement a static `properties` getter that returns the element's properties - (which automatically become observed attributes). + 1. Use a `@property` decorator to create a property (or implement a static `properties` + getter that returns the element's properties). (which automatically become observed attributes). 1. Then implement a `render()` method and use the element's current properties to return a `lit-html` template result to render into the element. This is the only method that must be implemented by subclasses. @@ -92,7 +113,8 @@ into the element. This is the only method that must be implemented by subclasses class MyElement extends LitElement { - static get properties() { return { mood: {type: String} }} + @property({type: String}) + mood = 'happy'; render() { return html` @@ -121,7 +143,7 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- should occur when property values change or `invalidate` is called. The `changedProps` argument is an object with keys for the changed properties pointing to their previous values. By default, this method always returns true, but this can be customized as - an optimization to avoid updating work when changes occur which should not be rendered. + an optimization to avoid updating work when changes occur, which should not be rendered. * `update()` (protected): This method calls `render()` and then uses `lit-html` to render the template DOM. Override to customize how the element renders DOM. Note, @@ -134,16 +156,16 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- Note, since `render()` is called by `update()` setting properties does not trigger `invalidate()`, allowing property values to be computed and validated. - * `finishUpdate(changedProps)` (protected): Called after element DOM has been updated and - before the `updateComplete` promise is resolved. The `changedProps` argument is an object - with keys for the changed properties pointing to their previous values. This is an - async function which is *awaited* before resolving the `updateComplete` promise. - Setting properties in `finishUpdate()` does trigger `invalidate()` and blocks - the `updateComplete` promise. Implement to directly control rendered DOM. + * `finishUpdate(changedProps): Promise?` (protected): Called after element DOM has been updated and + before the `updateComplete` promise is resolved. Implement to directly control rendered DOM. Typically this is not needed as `lit-html` can be used in the `render` method to set properties, attributes, and event listeners. However, it is sometimes useful for calling methods on rendered elements, for example focusing an input: - `this.shadowRoot.querySelector('input').focus()`. + `this.shadowRoot.querySelector('input').focus()`. The `changedProps` argument is an object + with keys for the changed properties pointing to their previous values. If this function + returns a `Promise`, it will be *awaited* before resolving the `updateComplete` promise. + Setting properties in `finishUpdate()` does trigger `invalidate()` and blocks + the `updateComplete` promise. * `updateComplete`: Returns a promise which resolves after the element next renders. @@ -154,7 +176,8 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- * When the element is first connected or a property is set (e.g. `element.foo = 5`) and the property's `shouldInvalidate(value, oldValue)` returns true. Then - * `invalidate()` tries to update the element after waiting a microtask. Then + * `invalidate()` tries to update the element after waiting a [microtask](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) (at the end + of the event loop, before the next paint). Then * `shouldUpdate(changedProps)` is called and if this returns true which it does by default: * `update(changedProps)` is called to update the element. @@ -178,16 +201,14 @@ import {LitElement, html} from '@polymer/lit-element'; class MyElement extends LitElement { // Public property API that triggers re-render (synced with attributes) - static get properties() { - return { - foo: {type: String}, - whales: {type: Number} - } - } + @property() + foo = 'foo'; + + @property({type: Number}) + wales = 5; constructor() { super(); - this.foo = 'foo'; this.addEventListener('click', async (e) => { this.whales++; await this.updateComplete; diff --git a/demo/lit-element.html b/demo/lit-element.html index 98e1a7fc..a761a7f7 100644 --- a/demo/lit-element.html +++ b/demo/lit-element.html @@ -42,10 +42,12 @@ } } - get zot() { return this.getProperty('zot'); } + // a custom getter/setter can be created to customize property processing + get zot() { return this.getAttribute('zot'); } set zot(value) { - this.setProperty('zot', Number(value)); + this.setAttribute('zot', value); + this.invalidate(); } @@ -54,7 +56,6 @@ this.foo = 'foo'; this.nug = [1, 2, 3]; this.whales = 0; - this.zot = '5'; this.addEventListener('click', async (e) => { this.whales++; await this.updateComplete; diff --git a/package-lock.json b/package-lock.json index 8b6c9632..be10553e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -961,6 +961,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.0.2.tgz", "integrity": "sha512-ow8AAjTe9ps8bantY9IvL0PT+xHf5VN3Cjahfr7gBJAc0lv3jTwGBv7pso65SHyrUJEEHeakhx6iPMl7qY4tfw==", + "dev": true, "requires": { "@webcomponents/shadycss": "^1.2.0" } @@ -1433,7 +1434,8 @@ "@webcomponents/shadycss": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.3.0.tgz", - "integrity": "sha512-xw8WHLmICuB+BzdFLoKnyfL94+RqYMQSpzqmkEIL4B0CZ8vganMbzhYNC1BN/EoLby4TbuS4YVTt8vydwbtGPQ==" + "integrity": "sha512-xw8WHLmICuB+BzdFLoKnyfL94+RqYMQSpzqmkEIL4B0CZ8vganMbzhYNC1BN/EoLby4TbuS4YVTt8vydwbtGPQ==", + "dev": true }, "@webcomponents/webcomponentsjs": { "version": "2.0.2", @@ -8800,9 +8802,9 @@ "dev": true }, "typescript": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", - "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz", + "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==", "dev": true }, "typical": { diff --git a/package.json b/package.json index 7bc02444..eda78c23 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "mocha": "^5.0.5", "tslint": "^5.7.0", "typedoc": "^0.8.0", - "typescript": "^2.7.2", + "typescript": "^3.0.1", "uglify-es": "^3.3.9", "wct-browser-legacy": "^1.0.1", "web-component-tester": "^6.7.1" diff --git a/src/lib/render-helpers.ts b/src/lib/render-helpers.ts index 708243ba..a801b4d9 100644 --- a/src/lib/render-helpers.ts +++ b/src/lib/render-helpers.ts @@ -22,8 +22,8 @@ export function classString( classInfo: {[name: string]: string|boolean|number}) { const o = []; for (const name in classInfo) { - const v = classInfo[name]; - if (v) { + // we explicitly want a loose truthy check here. + if (classInfo[name]) { o.push(name); } } @@ -31,9 +31,11 @@ export function classString( } /** - * Returns a css style string formed by taking the properties in the `styleInfo` - * object and appending the property name (dash-cased) colon the - * property value. Properties are separated by a semi-colon. + * Returns a css style string formed from the `styleInfo` object. Property names + * are automatically converted from *camelCase* to *dash-case*, so that you can use + * unquoted names like `backgroundColor`. The property values are formatted + * as css. For example `{backgroundColor: 'red', borderTop: '5px'}` becomes + * `background-color: red; border-top: 5px;`. * @param styleInfo */ export function styleString( @@ -41,7 +43,7 @@ export function styleString( const o = []; for (const name in styleInfo) { const v = styleInfo[name]; - if (v || v === 0) { + if (v != null) { o.push(`${name.replace(/([A-Z])/, '-$1').toLowerCase()}: ${v}`); } } diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index f229b815..eaec029c 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -15,72 +15,87 @@ /** * Converts property values to and from attribute values. */ -type AttributeProcessor = { +interface AttributeSerializer { + /** * Deserializing function called to convert an attribute value to a property value. */ - fromAttribute?(value: string): unknown, + fromAttribute?(value: string): T; + /** * Serializing function called to convert a property value to an attribute value. */ - toAttribute?(value: unknown): string|null -}; + toAttribute?(value: T): string|null; + +} + +type AttributeType = AttributeSerializer|((value: string) => T); /** * Defines options for a property accessor. */ -export interface PropertyOptions { +export interface PropertyDeclaration { + /** - * Describes how and if the property should becomes an observedAttribute. - * If explicitly false, the property will not be observed. Otherwise if true - * or absent, the lowercased property name is observed, and if a string - * observe that value. + * Describes how and if the property becomes an observedAttribute. + * If the value is false, the property is not added to `observedAttributes`. + * If true or absent, the lowercased property name is observed (e.g. `fooBar` becomes `fobar`). + * If a string, the string value is observed (e.g `attribute: 'foo-bar'`). */ attribute?: boolean|string; + /** - * Describes how to convert a property value to and from an attribute. If - * the value is a function, it's calle to deserialize an attribute value into - * a property value. If it's an AttributeProcessor, the `fromAttribute` value - * is used to deserialize. If the `reflect` option is also true, then - * `toAttribute` value is used to serialize the property value to an attribute. + * Describes how to convert the attribute to/from a property. + * If this value is a function, it is used to deserialize the attribute value + * a the property value. If it's an AttributeSerializer, it can have keys for + * `fromAttribute` and `toAttribute` where `fromAttribute` is the deserialize + * function and `toAttribute` is a serialize function used to convert the property + * to an attribute. */ - type?: AttributeProcessor|Function; + type?: AttributeType; + /** - * Describes if setting the property should be reflect to the attribute value. - * If true, then if present the `type.toAttribute` function is used to serialize - * the value; otherwise, the property value is used. + * Describes if the property should reflect to an attribute. + * If true, when the property is set, the attribute value is set using the + * attribute name taken from the property's `attribute` and the value + * of the property serialized using `type.toAttribute` if it exists. */ reflect?: boolean; + /** * Describes if setting a property should trigger invalidation and updating. * This function takes the new and oldValue and returns true if invalidation - * should occur. If not present, a strict identity check is used. + * should occur. If not present, a strict identity check is used. This is useful + * if a property should be considered dirty only if some condition is met; + * for example, the function could return true only when the key property of + * an object value changed. */ - shouldInvalidate?(value: unknown, oldValue: unknown): boolean; + shouldInvalidate?(value: T, oldValue: T): boolean; + } /** * Object that describes accessors to be created on the element prototype. * An accessor is created for each key with the given options. */ -export interface Properties { - [key: string]: PropertyOptions; +export interface PropertyDeclarations { + [key: string]: PropertyDeclaration; } interface AttributeMap { [key: string]: string; } -interface AnyObject { - [key: string]: {}; +interface PropertyValues { + [key: string]: unknown; } /** * Creates a property accessor on the given prototype if one does not exist. * Uses `getProperty` and `setProperty` to manage the property's value. */ -const makeProperty = (name: string, proto: Object) => { - if (proto.hasOwnProperty(name) || (name in proto)) { +const makeProperty = (name: PropertyKey, proto: Object) => { + if (name in proto) { return; } Object.defineProperty(proto, name, { @@ -106,23 +121,24 @@ const ensurePropertyStorage = (ctor: typeof UpdatingElement) => { }; /** - * Decorator which creates a property. Optionally a `PropertyOptions` object + * Decorator which creates a property. Optionally a `PropertyDeclaration` object * can be supplied to describe how the property should be configured. */ -export const property = (options?: PropertyOptions) => (proto: Object, name: string) => { +export const property = (options: PropertyDeclaration = {}) => (proto: Object, name: string) => { const ctor = proto.constructor as typeof UpdatingElement; ensurePropertyStorage(ctor); - ctor._classProperties[name] = options || {}; + ctor._classProperties[name] = options; makeProperty(name, proto); }; /** - * AttributeProcessor which configures properties which should reflect to and - * from boolean attributes. If the attribute exists, the property becomes true; - * and if the property value is truthy, the attribute is set to an empty string. + * AttributeSerializer which configures properties which should reflect to and + * from boolean attributes. If the attribute exists, the property is set to true. + * If the property value is truthy, the attribute is set to an empty string; + * otherwise, the attribute is removed. */ -export const BooleanAttribute: AttributeProcessor = { +export const BooleanAttribute: AttributeSerializer = { fromAttribute: (value: string) => value !== null, toAttribute: (value: string) => value ? '' : null }; @@ -131,17 +147,15 @@ export const BooleanAttribute: AttributeProcessor = { * Change function that returns true if `value` is different from `oldValue`. * This method is used as the default for a property's `shouldChange` function. */ -// tslint:disable-next-line no-any -export const identity = (value: any, old: any) => { +export const identity = (value: unknown, old: unknown) => { // This ensures (old==NaN, value==NaN) always returns false return old !== value && (old === old || value === value); }; -enum ValidationState { - Disabled = 0, - Valid, - Invalid -} +const disabled = 0; +const valid = 1; +const invalid = 2; +type ValidationState = typeof disabled | typeof valid | typeof invalid; /** * Base element class which manages element properties and attributes. When @@ -155,10 +169,12 @@ export abstract class UpdatingElement extends HTMLElement { * to `fooBar` property. */ private static _attributeToPropertyMap: AttributeMap = {}; + /** * Marks class as having finished creating properties. */ private static _finalized = true; + /** * Memoized result of computed observedAttributes. */ @@ -168,9 +184,9 @@ export abstract class UpdatingElement extends HTMLElement { /** * Memoized list of all class properties, including any superclass properties. */ - static _classProperties: Properties = {}; + static _classProperties: PropertyDeclarations = {}; - static properties: Properties = {}; + static properties: PropertyDeclarations = {}; /** * Returns a list of attributes corresponding to the registered properties. @@ -201,7 +217,7 @@ export abstract class UpdatingElement extends HTMLElement { } // finalize any superclasses const superCtor = Object.getPrototypeOf(this); - if (superCtor.prototype instanceof UpdatingElement) { + if (typeof superCtor._finalize === 'function') { superCtor._finalize(); } this._finalized = true; @@ -267,28 +283,29 @@ export abstract class UpdatingElement extends HTMLElement { if (!info || !info.reflect) { return; } - const toAttribute = info.type && (info.type as AttributeProcessor).toAttribute || String; + const toAttribute = info.type && (info.type as AttributeSerializer).toAttribute || String; return (typeof toAttribute === 'function') ? toAttribute(value) : null; } - private _validationState: ValidationState = ValidationState.Disabled; - private _serializingInfo: [string|null, string|null]|null = null; - private _instanceProperties: AnyObject|null = null; - private _validatePromise: any = null; - private _validateResolver: (() => void)|null = null; + private _validationState: ValidationState = disabled; + private _serializingInfo: [string|null, string|null]|undefined = undefined; + private _instanceProperties: PropertyValues|undefined = undefined; + private _validatePromise: any = undefined; + private _validateResolver: (() => void)|undefined = undefined; /** * Object with keys for all properties with their current values. */ - private _props: AnyObject = {}; + private _props: PropertyValues = {}; + /** * Object with keys for any properties that have changed since the last * update cycle with previous values. */ - private _changedProps: AnyObject|null = null; + private _changedProps?: PropertyValues|undefined; /** - * Node or ShadowRoot into which element DOM should be renderd. Defaults + * Node or ShadowRoot into which element DOM should be rendered. Defaults * to an open shadowRoot. */ protected renderRoot?: Element|DocumentFragment; @@ -335,7 +352,7 @@ export abstract class UpdatingElement extends HTMLElement { * Uses ShadyCSS to keep element DOM updated. */ connectedCallback() { - if (this._validationState !== ValidationState.Disabled) { + if (this._validationState !== disabled) { window.ShadyCSS.styleElement(this); } this.invalidate(); @@ -397,7 +414,7 @@ export abstract class UpdatingElement extends HTMLElement { } else { this.setAttribute(attr, attrValue); } - this._serializingInfo = null; + this._serializingInfo = undefined; } } } @@ -405,7 +422,7 @@ export abstract class UpdatingElement extends HTMLElement { private _attributeToProperty(name: string, value: string) { // Use tracking info to avoid deserializing attribute value if it was // just set from a property setter. - if (!this._serializingInfo || + if (this._serializingInfo === undefined || (this._serializingInfo[0] !== name && this._serializingInfo[1] !== value)) { const ctor = (this.constructor as typeof UpdatingElement); const propName = ctor._attributeToPropertyMap[name]; @@ -424,10 +441,10 @@ export abstract class UpdatingElement extends HTMLElement { if (this._isPendingUpdate()) { return this._validatePromise; } - this._validationState = ValidationState.Invalid; + this._validationState = invalid; // Make a new promise only if the current one is not pending resolution - // (resolver has not been set to null) - if (this._validateResolver === null) { + // (resolver has not been set to undefined) + if (this._validateResolver === undefined) { this._validatePromise = new Promise((resolve) => this._validateResolver = resolve); } // Wait a tick to actually process changes (allows batching). @@ -435,36 +452,36 @@ export abstract class UpdatingElement extends HTMLElement { // Mixin instance properties once, if they exist. if (this._instanceProperties) { Object.assign(this, this._instanceProperties); - this._instanceProperties = null; + this._instanceProperties = undefined; } // Rip off changedProps. const changedProps = this._changedProps || {}; - this._changedProps = null; + this._changedProps = undefined; if (this.shouldUpdate(changedProps)) { // During update (which is abstract), setting properties does not trigger invalidation. - if (this.update !== undefined) { + if (typeof this.update === 'function') { this.update(changedProps); } - this._validationState = ValidationState.Valid; + this._validationState = valid; // During finishUpdate (which is abstract), setting properties does trigger invalidation, // and users may choose to await other state, like children being updated. - if (this.finishUpdate !== undefined) { + if (typeof this.finishUpdate === 'function') { await this.finishUpdate(changedProps); } } else { - this._validationState = ValidationState.Valid; + this._validationState = valid; } // Only resolve the promise if we finish in a valid state (finishUpdate // did not trigger more work). - if (this._validationState === ValidationState.Valid && this._validateResolver) { + if (this._validationState === valid && this._validateResolver !== undefined) { this._validateResolver(); - this._validateResolver = null; + this._validateResolver = undefined; } return this._validatePromise; } private _isPendingUpdate() { - return this._validationState === ValidationState.Invalid; + return this._validationState === invalid; } /** @@ -480,7 +497,7 @@ export abstract class UpdatingElement extends HTMLElement { * control when to update. * * @param _changedProperties changed properties with old values */ - protected shouldUpdate(_changedProperties: AnyObject): boolean { + protected shouldUpdate(_changedProperties: PropertyValues): boolean { return true; } @@ -489,7 +506,7 @@ export abstract class UpdatingElement extends HTMLElement { * implemented to render and keep updated DOM in the element's root. * * @param _changedProperties changed properties with old values */ - protected abstract update(_changedProperties: AnyObject): void; + protected abstract update(_changedProperties: PropertyValues): void; /** * Finishes updating the element. This method does nothing by default and @@ -498,6 +515,6 @@ export abstract class UpdatingElement extends HTMLElement { * `invalidate` and `updateComplete` Proimses. * * @param _changedProperties changed properties with old values */ - protected abstract finishUpdate(_changedProperties: AnyObject): void; + protected finishUpdate?(_changedProperties: PropertyValues): void; } \ No newline at end of file diff --git a/src/lit-element.ts b/src/lit-element.ts index 7906c74a..d2569990 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -15,7 +15,7 @@ import {render} from 'lit-html/lib/shady-render'; import {TemplateResult} from 'lit-html'; import {UpdatingElement} from './lib/updating-element.js'; -export {property, identity, BooleanAttribute, Properties, PropertyOptions} from './lib/updating-element.js'; +export {property, identity, BooleanAttribute, PropertyDeclarations, PropertyDeclaration} from './lib/updating-element.js'; export {html, svg} from 'lit-html/lib/lit-extended'; @@ -26,7 +26,7 @@ export abstract class LitElement extends UpdatingElement { * Override to perform tasks before and/or after updating. */ protected update() { - if (this.render) { + if (typeof this.render === 'function') { render(this.render(), this.renderRoot!, this.localName!); } else { throw new Error('render() not implemented'); diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 75ac114b..0a0f649b 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -12,7 +12,7 @@ * http://polymer.github.io/PATENTS.txt */ -import {html, LitElement, BooleanAttribute} from '../lit-element.js'; +import {html, LitElement, BooleanAttribute, PropertyDeclarations} from '../lit-element.js'; import {stripExpressionDelimeters, generateElementName} from './test-helpers.js'; @@ -241,7 +241,7 @@ suite('LitElement', () => { const fromAttribute = (value: any) => parseInt(value); const toAttribute = (value: any) => `${value}-attr`; class E extends LitElement { - static get properties() { + static get properties(): PropertyDeclarations { return { noAttr: {attribute: false}, atTr: {attribute: true}, @@ -264,7 +264,7 @@ suite('LitElement', () => { customElements.define(generateElementName(), E); class F extends E { - static get properties() { + static get properties(): PropertyDeclarations { return { customAttr: {attribute: 'custom', reflect: true}, shouldInvalidate: {shouldInvalidate}, @@ -286,7 +286,7 @@ suite('LitElement', () => { } class G extends F { - static get properties() { + static get properties(): PropertyDeclarations { return { fromAttribute: {type: fromAttribute}, toAttribute: {reflect: true, type: {toAttribute}}, From 10fdf29ce89713833d31fef4ee3a1e45f7335f9a Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Mon, 6 Aug 2018 19:14:42 -0700 Subject: [PATCH 11/65] Allow symbols as properties Also test subclass/superclass do not improperly interact. --- src/lib/updating-element.ts | 2 + src/test/lit-element_test.ts | 102 +++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index eaec029c..8b6f6d51 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -226,6 +226,8 @@ export abstract class UpdatingElement extends HTMLElement { for (const p in props) { makeProperty(p, this.prototype); } + // support symbols in properties + Object.getOwnPropertySymbols(props).forEach((p) => makeProperty(p, this.prototype)); // initialize map populated in observedAttributes this._attributeToPropertyMap = {}; // memoize list of all class properties. diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 0a0f649b..624ca62e 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -234,6 +234,51 @@ suite('LitElement', () => { assert.equal(el.all, 16); }); + test('properties defined using symbols', async() => { + + const zug = Symbol(); + + class E extends LitElement { + + static get properties() { + return { + foo: {}, + [zug]: {} + }; + } + updated = 0; + foo = 5; + [zug] = 6; + + render() { + return html``; + } + + update() { + this.updated++; + } + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.updated, 1); + assert.equal(el.foo, 5); + assert.equal(el[zug], 6); + el.foo = 55; + await el.updateComplete; + assert.equal(el.updated, 2); + assert.equal(el.foo, 55); + assert.equal(el[zug], 6); + el[zug] = 66; + await el.updateComplete; + assert.equal(el.updated, 3); + assert.equal(el.foo, 55); + assert.equal(el[zug], 66); + + }); + test('property options compose when subclassing', async() => { @@ -349,6 +394,63 @@ suite('LitElement', () => { }); + test('superclass properties not affected by subclass', async() => { + + class E extends LitElement { + static get properties(): PropertyDeclarations { + return { + foo: {attribute: 'zug', reflect: true}, + bar: {reflect: true} + }; + } + + foo = 5; + bar = 'bar'; + + render() { return html``; } + + } + customElements.define(generateElementName(), E); + + class F extends E { + static get properties(): PropertyDeclarations { + return { + foo: {attribute: false}, + nug: {} + }; + } + + foo = 6; + bar = 'subbar'; + nug = 5; + + render() { return html``; } + + } + customElements.define(generateElementName(), F); + + const el = new E(); + const sub = new F(); + container.appendChild(el); + await el.updateComplete; + container.appendChild(sub); + await sub.updateComplete; + + assert.equal(el.foo, 5); + assert.equal(el.getAttribute('zug'), '5'); + assert.isFalse(el.hasAttribute('foo')); + assert.equal(el.bar, 'bar'); + assert.equal(el.getAttribute('bar'), 'bar'); + assert.isUndefined((el as any).nug); + + assert.equal(sub.foo, 6); + assert.isFalse(sub.hasAttribute('zug')); + assert.isFalse(sub.hasAttribute('foo')); + assert.equal(sub.bar, 'subbar'); + assert.equal(sub.getAttribute('bar'), 'subbar'); + assert.equal(sub.nug, 5); + }); + test('Attributes reflect with type.toAttribute and BooleanAttribute', async () => { class E extends LitElement { static get properties() { From f131d96bcb57092c007c1907939d74c1fcc0f706 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 7 Aug 2018 17:34:42 -0700 Subject: [PATCH 12/65] Fix property to attributes serialization Uses `update` to update attributes and simplifies tracking. --- src/lib/updating-element.ts | 39 ++++++++++++++----------- src/lit-element.ts | 6 ++-- src/test/lit-element_test.ts | 56 ++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 8b6f6d51..26e62fed 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -86,7 +86,7 @@ interface AttributeMap { [key: string]: string; } -interface PropertyValues { +export interface PropertyValues { [key: string]: unknown; } @@ -290,10 +290,10 @@ export abstract class UpdatingElement extends HTMLElement { } private _validationState: ValidationState = disabled; - private _serializingInfo: [string|null, string|null]|undefined = undefined; - private _instanceProperties: PropertyValues|undefined = undefined; + private _isReflectingProperty: boolean = false; + private _instanceProperties: PropertyValues|undefined; private _validatePromise: any = undefined; - private _validateResolver: (() => void)|undefined = undefined; + private _validateResolver: (() => void)|undefined; /** * Object with keys for all properties with their current values. @@ -397,7 +397,6 @@ export abstract class UpdatingElement extends HTMLElement { this._changedProps[name] = this._props[name]; } this._props[name] = value; - this._propertyToAttribute(name, value); this.invalidate(); } } @@ -408,15 +407,20 @@ export abstract class UpdatingElement extends HTMLElement { if (attrValue !== undefined) { const attr = ctor.attributeNameForProperty(name); if (attr !== undefined) { - // track the attr name/value being set to be able to avoid - // reflecting back to the property setter via attributeChangedCallback. - this._serializingInfo = [attr, attrValue]; + // Track if the property is being reflected to avoid + // setting the property again via `attributeChangedCallback`. Note: + // 1. this takes advantage of the fact that the callback is synchronous. + // 2. will behave incorrectly if multiple attributes are in the reaction + // stack at time of calling. However, since we process attributes + // in `update` this should not be possible (or an extreme corner case + // that we'd like to discover). + this._isReflectingProperty = true; if (attrValue === null) { this.removeAttribute(attr); } else { this.setAttribute(attr, attrValue); } - this._serializingInfo = undefined; + this._isReflectingProperty = false; } } } @@ -424,8 +428,7 @@ export abstract class UpdatingElement extends HTMLElement { private _attributeToProperty(name: string, value: string) { // Use tracking info to avoid deserializing attribute value if it was // just set from a property setter. - if (this._serializingInfo === undefined || - (this._serializingInfo[0] !== name && this._serializingInfo[1] !== value)) { + if (!this._isReflectingProperty) { const ctor = (this.constructor as typeof UpdatingElement); const propName = ctor._attributeToPropertyMap[name]; this[propName as keyof this] = ctor.propertyValueFromAttribute(propName, value); @@ -461,9 +464,7 @@ export abstract class UpdatingElement extends HTMLElement { this._changedProps = undefined; if (this.shouldUpdate(changedProps)) { // During update (which is abstract), setting properties does not trigger invalidation. - if (typeof this.update === 'function') { - this.update(changedProps); - } + this.update(changedProps); this._validationState = valid; // During finishUpdate (which is abstract), setting properties does trigger invalidation, // and users may choose to await other state, like children being updated. @@ -504,11 +505,15 @@ export abstract class UpdatingElement extends HTMLElement { } /** - * Updates the element. This method does nothing by default and should be - * implemented to render and keep updated DOM in the element's root. + * Updates the element. By default this method reflects property values to attributes. + * It should be implemented to render and keep updated DOM in the element's root. * * @param _changedProperties changed properties with old values */ - protected abstract update(_changedProperties: PropertyValues): void; + protected update(_changedProperties: PropertyValues): void { + for (const name in _changedProperties) { + this._propertyToAttribute(name, (this as any)[name]); + } + } /** * Finishes updating the element. This method does nothing by default and diff --git a/src/lit-element.ts b/src/lit-element.ts index d2569990..c754e141 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -15,7 +15,7 @@ import {render} from 'lit-html/lib/shady-render'; import {TemplateResult} from 'lit-html'; import {UpdatingElement} from './lib/updating-element.js'; -export {property, identity, BooleanAttribute, PropertyDeclarations, PropertyDeclaration} from './lib/updating-element.js'; +export {property, identity, BooleanAttribute, PropertyDeclarations, PropertyDeclaration, PropertyValues} from './lib/updating-element.js'; export {html, svg} from 'lit-html/lib/lit-extended'; @@ -25,7 +25,8 @@ export abstract class LitElement extends UpdatingElement { * Override which performs element rendering by calling the `render` method. * Override to perform tasks before and/or after updating. */ - protected update() { + protected update(_props: PropertyValues) { + super.update(_props); if (typeof this.render === 'function') { render(this.render(), this.renderRoot!, this.localName!); } else { @@ -36,7 +37,6 @@ export abstract class LitElement extends UpdatingElement { /** Invoked on each update to perform rendering tasks. This method must return a lit-html TemplateResult. - * @param {*} _props Current element properties * @returns {TemplateResult} Must return a lit-html TemplateResult. */ protected abstract render(): TemplateResult; diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 624ca62e..68d31081 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -234,6 +234,62 @@ suite('LitElement', () => { assert.equal(el.all, 16); }); + test('attributes deserialize from html', async() => { + + const shouldInvalidate = (value: any, old: any) => old === undefined || value > old; + const fromAttribute = (value: any) => parseInt(value); + const toAttributeOnly = (value: any) => typeof value === 'string' && value.indexOf(`-attr`) > 0 ? value : `${value}-attr`; + const toAttribute = (value: any) => `${value}-attr`; + class E extends LitElement { + static get properties() { + return { + noAttr: {attribute: false}, + atTr: {attribute: true}, + customAttr: {attribute: 'custom', reflect: true}, + shouldInvalidate: {shouldInvalidate}, + fromAttribute: {type: fromAttribute}, + toAttribute: {reflect: true, type: {toAttribute: toAttributeOnly}}, + all: {attribute: 'all-attr', shouldInvalidate, type: {fromAttribute, toAttribute}, reflect: true}, + }; + } + + noAttr = 'noAttr'; + atTr = 'attr'; + customAttr = 'customAttr'; + shouldInvalidate = 10; + fromAttribute = 1; + toAttribute: string|number = 1; + all = 10; + + render() { return html``; } + + } + const name = generateElementName(); + customElements.define(name, E); + container.innerHTML = `<${name} + noattr="1" + attr="2" + custom="3" + shouldInvalidate="5" + fromAttribute="6-attr" + toAttribute="7" + all-attr="11-attr">`; + const el = container.firstChild as E; + await el.updateComplete; + assert.equal(el.noAttr, 'noAttr'); + assert.equal(el.getAttribute('noattr'), '1'); + assert.equal(el.atTr, '2'); + assert.equal(el.customAttr, '3'); + assert.equal(el.getAttribute('custom'), '3'); + assert.equal(el.shouldInvalidate, 10); + assert.equal(el.getAttribute('shouldinvalidate'), '5'); + assert.equal(el.fromAttribute, 6); + assert.equal(el.toAttribute, '7'); + assert.equal(el.getAttribute('toattribute'), '7-attr'); + assert.equal(el.all, 11); + assert.equal(el.getAttribute('all-attr'), '11-attr'); + }); + test('properties defined using symbols', async() => { const zug = Symbol(); From 3b5611cc89b5a77341e33d32a592c2bf58e3d8f4 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 7 Aug 2018 17:59:37 -0700 Subject: [PATCH 13/65] Doc improvements based on review. --- README.md | 29 ++++++++++++++++------------- src/lib/render-helpers.ts | 7 ++++--- src/lib/updating-element.ts | 31 ++++++++++++++++--------------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 2da7bd52..321f8657 100644 --- a/README.md +++ b/README.md @@ -20,29 +20,32 @@ and renders declaratively using `lit-html`. if you're using a compiler that supports them, like TypeScript or Babel. * With a static `properties` getter. * By manually writing getters and setters. You can call `setProperty` or `invalidate` - to trigger an update. If you use `setProperty`, you can use `getProperty` in the getter. + to trigger an update. If you use `setProperty`, you should use `getProperty` in the getter. - Properties may be given an options argument which is an object that desribes how to - process the property. This may can be either in the `@property({...})` decorator or in the - object returned the `properties` getter, e.g. `static get properties { return { foo: {...} }`. + Properties can be given an options argument which is an object that describes how to + process the property. This can be done either in the `@property({...})` decorator or in the + object returned from the `properties` getter, e.g. `static get properties { return { foo: {...} }`. Property options include: - * `attribute`: Describes how and if the property becomes an observedAttribute. + * `attribute`: Describes how and if the property becomes an observed attribute. If the value is false, the property is not added to `observedAttributes`. - If true or absent, the lowercased property name is observed (e.g. `fooBar` becomes `fobar`). + If true or absent, the lowercased property name is observed (e.g. `fooBar` becomes `foobar`). If a string, the string value is observed (e.g `attribute: 'foo-bar'`). - * `type`: Describes how to convert the attribute to/from a property. + * `type`: Describes how to serialize and deserialize the attribute to/from a property. If this value is a function, it is used to deserialize the attribute value a the property value. If it's an object, it can have keys for `fromAttribute` and `toAttribute` where `fromAttribute` is the deserialize function and `toAttribute` - is a serialize function used to convert the property to an attribute. + is a serialize function used to set the property to an attribute. If no `toAttribute` + function is provided and `reflect` is set to true, the property value is set + directly to the attribute. * `reflect`: Describes if the property should reflect to an attribute. - If true, when the property is set, the attribute value is set using the - attribute name taken from the property's `attribute` and the value - of the property serialized using `type.toAttribute` if it exists. + If true, when the property is set, the attribute is set using the + attribute name determined according to the rules for the `attribute` + propety option and the value of the property serialized using the rules from + the `type` property option. * `shouldInvalidate`: Describes if setting a property should trigger - invalidation and updating. This function takes the `new` and `oldValue` and + invalidation and updating. This function takes the `newValue` and `oldValue` and returns true if invalidation should occur. If not present, a strict identity check is used. This is useful if a property should be considered dirty only if some condition is met, like if a key of an object value changes. @@ -205,7 +208,7 @@ class MyElement extends LitElement { foo = 'foo'; @property({type: Number}) - wales = 5; + whales = 5; constructor() { super(); diff --git a/src/lib/render-helpers.ts b/src/lib/render-helpers.ts index a801b4d9..bd055820 100644 --- a/src/lib/render-helpers.ts +++ b/src/lib/render-helpers.ts @@ -13,7 +13,7 @@ */ /** - * Returns a string of css class names formed by taking the properties + * Returns a string of CSS class names formed by taking the properties * in the `classInfo` object and appending the property name to the string of * class names if the property value is truthy. * @param classInfo @@ -22,7 +22,8 @@ export function classString( classInfo: {[name: string]: string|boolean|number}) { const o = []; for (const name in classInfo) { - // we explicitly want a loose truthy check here. + // We explicitly want a loose truthy check here because + // it seems more convenient that '' and 0 are skipped. if (classInfo[name]) { o.push(name); } @@ -31,7 +32,7 @@ export function classString( } /** - * Returns a css style string formed from the `styleInfo` object. Property names + * Returns a CSS style string formed from the `styleInfo` object. Property names * are automatically converted from *camelCase* to *dash-case*, so that you can use * unquoted names like `backgroundColor`. The property values are formatted * as css. For example `{backgroundColor: 'red', borderTop: '5px'}` becomes diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 26e62fed..c2eac8d4 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -37,38 +37,39 @@ type AttributeType = AttributeSerializer|((value: string) => T); export interface PropertyDeclaration { /** - * Describes how and if the property becomes an observedAttribute. + * Describes how and if the property becomes an observed attribute. * If the value is false, the property is not added to `observedAttributes`. - * If true or absent, the lowercased property name is observed (e.g. `fooBar` becomes `fobar`). + * If true or absent, the lowercased property name is observed (e.g. `fooBar` becomes `foobar`). * If a string, the string value is observed (e.g `attribute: 'foo-bar'`). */ attribute?: boolean|string; /** - * Describes how to convert the attribute to/from a property. + * Describes how to serialize and deserialize the attribute to/from a property. * If this value is a function, it is used to deserialize the attribute value - * a the property value. If it's an AttributeSerializer, it can have keys for - * `fromAttribute` and `toAttribute` where `fromAttribute` is the deserialize - * function and `toAttribute` is a serialize function used to convert the property - * to an attribute. + * a the property value. If it's an object, it can have keys for `fromAttribute` and + * `toAttribute` where `fromAttribute` is the deserialize function and `toAttribute` + * is a serialize function used to set the property to an attribute. If no `toAttribute` + * function is provided and `reflect` is set to true, the property value is set + * directly to the attribute. */ type?: AttributeType; /** * Describes if the property should reflect to an attribute. - * If true, when the property is set, the attribute value is set using the - * attribute name taken from the property's `attribute` and the value - * of the property serialized using `type.toAttribute` if it exists. + * If true, when the property is set, the attribute is set using the + * attribute name determined according to the rules for the `attribute` + * propety option and the value of the property serialized using the rules from + * the `type` property option. */ reflect?: boolean; /** * Describes if setting a property should trigger invalidation and updating. - * This function takes the new and oldValue and returns true if invalidation - * should occur. If not present, a strict identity check is used. This is useful - * if a property should be considered dirty only if some condition is met; - * for example, the function could return true only when the key property of - * an object value changed. + * This function takes the `newValue` and `oldValue` and returns true if + * invalidation should occur. If not present, a strict identity check is + * used. This is useful if a property should be considered dirty only + * if some condition is met, like if a key of an object value changes. */ shouldInvalidate?(value: T, oldValue: T): boolean; From 82be06b53ee7d0af555dbee9321d020233702a69 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 8 Aug 2018 08:53:31 -0700 Subject: [PATCH 14/65] Export `PropertyValues` type and fixup usage in tests --- src/lit-element.ts | 2 +- src/test/lit-element_test.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lit-element.ts b/src/lit-element.ts index c754e141..7b74fc8c 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -13,7 +13,7 @@ */ import {render} from 'lit-html/lib/shady-render'; import {TemplateResult} from 'lit-html'; -import {UpdatingElement} from './lib/updating-element.js'; +import {UpdatingElement, PropertyValues} from './lib/updating-element.js'; export {property, identity, BooleanAttribute, PropertyDeclarations, PropertyDeclaration, PropertyValues} from './lib/updating-element.js'; export {html, svg} from 'lit-html/lib/lit-extended'; diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 68d31081..26dfe725 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -12,7 +12,7 @@ * http://polymer.github.io/PATENTS.txt */ -import {html, LitElement, BooleanAttribute, PropertyDeclarations} from '../lit-element.js'; +import {html, LitElement, BooleanAttribute, PropertyDeclarations, PropertyValues} from '../lit-element.js'; import {stripExpressionDelimeters, generateElementName} from './test-helpers.js'; @@ -574,8 +574,8 @@ suite('LitElement', () => { render() { return html``; } - update() { - super.update(); + update(props: PropertyValues) { + super.update(props); this.updatedValue = this.value; this.updatedAttrValue = this.attrValue; } @@ -707,9 +707,9 @@ suite('LitElement', () => { return html`hi`; } - update() { + update(props: PropertyValues) { this.info.push('before-update'); - super.update(); + super.update(props); } async finishUpdate() { @@ -740,10 +740,10 @@ suite('LitElement', () => { foo = 0; updated = 0; - update() { + update(props: PropertyValues) { this.updated++; this.foo++; - super.update(); + super.update(props); } render() { From 3637c1e96a09af83824068b4bcb12792e0fbbbb2 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 8 Aug 2018 11:15:46 -0700 Subject: [PATCH 15/65] Fixes for IE11 --- demo/lit-element.html | 7 ++- src/lib/updating-element.ts | 6 ++- src/test/lit-element_styling_test.ts | 20 ++++---- src/test/lit-element_test.ts | 76 ++++++++++++++-------------- src/test/test-helpers.ts | 4 ++ 5 files changed, 63 insertions(+), 50 deletions(-) diff --git a/demo/lit-element.html b/demo/lit-element.html index a761a7f7..c05e8dfd 100644 --- a/demo/lit-element.html +++ b/demo/lit-element.html @@ -100,7 +100,7 @@

Foo: ${foo}, Bar: ${bar}Foo: ${foo}, Bar: ${bar} { const x = document.querySelector('my-element'); await x.updateComplete; diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index c2eac8d4..d98e4c08 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -227,8 +227,10 @@ export abstract class UpdatingElement extends HTMLElement { for (const p in props) { makeProperty(p, this.prototype); } - // support symbols in properties - Object.getOwnPropertySymbols(props).forEach((p) => makeProperty(p, this.prototype)); + // support symbols in properties (IE11 does not support this) + if (typeof Object.getOwnPropertySymbols === 'function') { + Object.getOwnPropertySymbols(props).forEach((p) => makeProperty(p, this.prototype)); + } // initialize map populated in observedAttributes this._attributeToPropertyMap = {}; // memoize list of all class properties. diff --git a/src/test/lit-element_styling_test.ts b/src/test/lit-element_styling_test.ts index fe1aedb5..31fbb107 100644 --- a/src/test/lit-element_styling_test.ts +++ b/src/test/lit-element_styling_test.ts @@ -18,7 +18,7 @@ import { LitElement, } from '../lit-element.js'; -import {generateElementName, nextFrame} from './test-helpers.js'; +import {generateElementName, nextFrame, getComputedStyleValue} from './test-helpers.js'; declare global { interface Window { @@ -58,7 +58,7 @@ suite('Styling', () => { container.appendChild(el); await (el as LitElement).updateComplete; const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '2px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); }); test('shared styling rendered into shadowRoot is styled', async () => { @@ -83,7 +83,7 @@ suite('Styling', () => { container.appendChild(el); await (el as LitElement).updateComplete; const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '4px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '4px'); }); test('custom properties render', async () => { @@ -105,7 +105,7 @@ suite('Styling', () => { container.appendChild(el); await (el as LitElement).updateComplete; const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '8px'); }); test('custom properties flow to nested elements', async () => { @@ -134,7 +134,7 @@ suite('Styling', () => { container.appendChild(el); await nextFrame(); const div = el.shadowRoot!.querySelector('x-inner')!.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '8px'); }); test('elements with custom properties can move between elements', async () => { @@ -177,10 +177,10 @@ suite('Styling', () => { await nextFrame(); const inner = el.shadowRoot!.querySelector('x-inner1'); div = inner!.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '2px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '2px'); el2!.shadowRoot!.appendChild(inner!); await nextFrame(); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '8px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '8px'); }); test('@apply renders in nested elements', async () => { @@ -211,7 +211,7 @@ suite('Styling', () => { container.appendChild(el); await nextFrame(); const div = el.shadowRoot!.querySelector('x-inner2')!.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '10px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '10px'); }); }); @@ -252,11 +252,11 @@ suite('ShadyDOM', () => { container.appendChild(el); await el.updateComplete; const div = el.shadowRoot!.querySelector('div'); - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '6px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '6px'); border = `4px solid orange`; el.invalidate(); await el.updateComplete; - assert.equal(getComputedStyle(div!).getPropertyValue('border-top-width').trim(), '6px'); + assert.equal(getComputedStyleValue(div!, 'border-top-width').trim(), '6px'); }); }); diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 26dfe725..a4f3cde2 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -290,50 +290,52 @@ suite('LitElement', () => { assert.equal(el.getAttribute('all-attr'), '11-attr'); }); - test('properties defined using symbols', async() => { + if (Object.getOwnPropertySymbols) { + test('properties defined using symbols', async() => { - const zug = Symbol(); + const zug = Symbol(); - class E extends LitElement { + class E extends LitElement { - static get properties() { - return { - foo: {}, - [zug]: {} - }; - } - updated = 0; - foo = 5; - [zug] = 6; + static get properties() { + return { + foo: {}, + [zug]: {} + }; + } + updated = 0; + foo = 5; + [zug] = 6; - render() { - return html``; - } + render() { + return html``; + } - update() { - this.updated++; - } + update() { + this.updated++; + } - } - customElements.define(generateElementName(), E); - const el = new E(); - container.appendChild(el); - await el.updateComplete; - assert.equal(el.updated, 1); - assert.equal(el.foo, 5); - assert.equal(el[zug], 6); - el.foo = 55; - await el.updateComplete; - assert.equal(el.updated, 2); - assert.equal(el.foo, 55); - assert.equal(el[zug], 6); - el[zug] = 66; - await el.updateComplete; - assert.equal(el.updated, 3); - assert.equal(el.foo, 55); - assert.equal(el[zug], 66); + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.updated, 1); + assert.equal(el.foo, 5); + assert.equal(el[zug], 6); + el.foo = 55; + await el.updateComplete; + assert.equal(el.updated, 2); + assert.equal(el.foo, 55); + assert.equal(el[zug], 6); + el[zug] = 66; + await el.updateComplete; + assert.equal(el.updated, 3); + assert.equal(el.foo, 55); + assert.equal(el[zug], 66); - }); + }); + } test('property options compose when subclassing', async() => { diff --git a/src/test/test-helpers.ts b/src/test/test-helpers.ts index 64ca038d..19ad46d0 100644 --- a/src/test/test-helpers.ts +++ b/src/test/test-helpers.ts @@ -20,4 +20,8 @@ export const generateElementName = () => `x-${count++}`; export const nextFrame = () => new Promise((resolve) => requestAnimationFrame(resolve)); +export const getComputedStyleValue = (element: Element, property: string) => + window.ShadyCSS ? window.ShadyCSS.getComputedStyleValue(element, property) : + getComputedStyle(element).getPropertyValue(property); + From bbd3267886ee33b753965dfe226b1bf9721f4b0a Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 8 Aug 2018 13:53:52 -0700 Subject: [PATCH 16/65] Adds `firstUpdated` * Convenience method for performing tasks after the element was first updated. * Also makes presence of ShadyCSS optional. --- package.json | 4 ++-- src/lib/updating-element.ts | 24 ++++++++++++++++----- src/test/lit-element_test.ts | 41 +++++++++++++++++++++++++++++++++--- 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index eda78c23..1de55e14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@polymer/lit-element", - "version": "0.5.2", + "version": "0.6.0-dev.1", "description": "Polymer based lit-html custom element", "license": "BSD-3-Clause", "repository": "Polymer/lit-element", @@ -35,7 +35,7 @@ }, "typings": "lit-element.d.ts", "dependencies": { - "lit-html": "^0.10.2" + "lit-html": "dev" }, "publishConfig": { "access": "public" diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index d98e4c08..caa59399 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -297,6 +297,7 @@ export abstract class UpdatingElement extends HTMLElement { private _instanceProperties: PropertyValues|undefined; private _validatePromise: any = undefined; private _validateResolver: (() => void)|undefined; + private _firstUpdated: boolean = false; /** * Object with keys for all properties with their current values. @@ -357,7 +358,7 @@ export abstract class UpdatingElement extends HTMLElement { * Uses ShadyCSS to keep element DOM updated. */ connectedCallback() { - if (this._validationState !== disabled) { + if (this._validationState !== disabled && typeof window.ShadyCSS !== 'undefined') { window.ShadyCSS.styleElement(this); } this.invalidate(); @@ -474,6 +475,12 @@ export abstract class UpdatingElement extends HTMLElement { if (typeof this.finishUpdate === 'function') { await this.finishUpdate(changedProps); } + if (!this._firstUpdated) { + this._firstUpdated = true; + if (typeof this.firstUpdated === 'function') { + this.firstUpdated(); + } + } } else { this._validationState = valid; } @@ -520,11 +527,18 @@ export abstract class UpdatingElement extends HTMLElement { /** * Finishes updating the element. This method does nothing by default and - * should be implemented to perform post update tasks on element DOM. This - * async function can await other Promises to defer the resolution of the - * `invalidate` and `updateComplete` Proimses. + * can be implemented to perform post update tasks on element DOM. * * @param _changedProperties changed properties with old values + * * @returns {Promise} Optionally can return a promise that blocks + * resolution of the `invalidate` and updateComplete` promise. */ - protected finishUpdate?(_changedProperties: PropertyValues): void; + protected finishUpdate?(_changedProperties: PropertyValues): void|Promise; + /** + * Called with the element is first updated. This method does nothing by + * default and can be implemented to perform post first update tasks on + * element DOM. Any tasks which should synchronous with dynamic updates + * should be implemented in `finishUpdate`. + */ + protected firstUpdated?(): void; } \ No newline at end of file diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index a4f3cde2..73044798 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -691,7 +691,39 @@ suite('LitElement', () => { }); test( - 'render lifecycle order: shouldUpdate, update, render, updateComplete', async () => { + 'firstUpdated called when element first updates', async () => { + class E extends LitElement { + + wasUpdated = 0; + wasFirstUpdated = 0; + + update(_props: PropertyValues) { + this.wasUpdated++; + } + + render() { return html``; } + + firstUpdated() { + this.wasFirstUpdated++; + } + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.wasUpdated, 1); + assert.equal(el.wasFirstUpdated, 1); + await el.invalidate(); + assert.equal(el.wasUpdated, 2); + assert.equal(el.wasFirstUpdated, 1); + await el.invalidate(); + assert.equal(el.wasUpdated, 3); + assert.equal(el.wasFirstUpdated, 1); + }); + + test( + 'render lifecycle order: shouldUpdate, update, render, finishUpdate, firstUpdated, updateComplete', async () => { class E extends LitElement { static get properties() { return { foo: {type: Number} @@ -714,10 +746,13 @@ suite('LitElement', () => { super.update(props); } - async finishUpdate() { + finishUpdate(_changedProps: PropertyValues) { this.info.push('finishUpdate'); } + firstUpdated() { + this.info.push('firistUpdated'); + } } customElements.define(generateElementName(), E); @@ -727,7 +762,7 @@ suite('LitElement', () => { el.info.push('updateComplete'); assert.deepEqual( el.info, - [ 'shouldUpdate', 'before-update', 'render', 'finishUpdate', 'updateComplete' ]); + [ 'shouldUpdate', 'before-update', 'render', 'finishUpdate', 'firistUpdated', 'updateComplete' ]); }); test('setting properties in update does not trigger invalidation', async () => { From 3f0315cd0f710881e24afb2577fd6f7f08dfd51d Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 8 Aug 2018 16:18:51 -0700 Subject: [PATCH 17/65] Doc tweak based on review. --- package.json | 2 +- src/lib/updating-element.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 1de55e14..47260f9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@polymer/lit-element", - "version": "0.6.0-dev.1", + "version": "0.6.0-dev.2", "description": "Polymer based lit-html custom element", "license": "BSD-3-Clause", "repository": "Polymer/lit-element", diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index caa59399..44c97dff 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -67,9 +67,10 @@ export interface PropertyDeclaration { /** * Describes if setting a property should trigger invalidation and updating. * This function takes the `newValue` and `oldValue` and returns true if - * invalidation should occur. If not present, a strict identity check is - * used. This is useful if a property should be considered dirty only - * if some condition is met, like if a key of an object value changes. + * invalidation should occur. If not present, a strict identity check + * (eg. === operator) is used. This is useful if a property should be + * considered dirty only if some condition is met, like if a key of an + * object value changes. */ shouldInvalidate?(value: T, oldValue: T): boolean; From 85f4cf282c5af58d5fb0559397595d3dd58bde45 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Mon, 13 Aug 2018 12:07:48 -0700 Subject: [PATCH 18/65] Use new syntax in lit-html rather than old syntax in lit-extended. --- package-lock.json | 8 ++++---- src/lit-element.ts | 2 +- src/test/lit-element_test.ts | 2 +- src/test/render-helpers_test.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index be10553e..0f72116a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@polymer/lit-element", - "version": "0.5.2", + "version": "0.6.0-dev.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5510,9 +5510,9 @@ } }, "lit-html": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-0.10.2.tgz", - "integrity": "sha512-ue0pIX3Fj5gUsNSozdRQIb1MAgNqFQsHgvHE/FU34xyu9NN/af3EISr7Bb+vP9YeLXIA4vLLOoYp2Z22dVYhww==" + "version": "0.11.0-dev.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-0.11.0-dev.1.tgz", + "integrity": "sha512-bTcdm3hJf0vpRESzgpiypsOrDWxwF0RN+kKdmd8phmjs65AwaD3ha31FHLOsVYT6lgPl/niC5RGnaZIrZT2C9Q==" }, "load-json-file": { "version": "1.1.0", diff --git a/src/lit-element.ts b/src/lit-element.ts index 7b74fc8c..1cd684ca 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -16,7 +16,7 @@ import {TemplateResult} from 'lit-html'; import {UpdatingElement, PropertyValues} from './lib/updating-element.js'; export {property, identity, BooleanAttribute, PropertyDeclarations, PropertyDeclaration, PropertyValues} from './lib/updating-element.js'; -export {html, svg} from 'lit-html/lib/lit-extended'; +export {html, svg} from 'lit-html/lit-html'; export abstract class LitElement extends UpdatingElement { diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 73044798..f11fedbe 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -674,7 +674,7 @@ suite('LitElement', () => { const prop = 'prop'; const event = (e: Event) => { this._event = e; }; return html - `
`; + `
`; } } customElements.define(generateElementName(), E); diff --git a/src/test/render-helpers_test.ts b/src/test/render-helpers_test.ts index 7cbaaeea..07d9f4ca 100644 --- a/src/test/render-helpers_test.ts +++ b/src/test/render-helpers_test.ts @@ -50,7 +50,7 @@ suite('Render Helpers', () => { render() { const {foo, bar, baz} = this; return html - `
`; + `
`; } } customElements.define(generateElementName(), E); @@ -88,7 +88,7 @@ suite('Render Helpers', () => { render() { const {marginTop, paddingTop, zug} = this; - return html`
Date: Mon, 13 Aug 2018 12:29:55 -0700 Subject: [PATCH 19/65] Updates based on review. --- src/lib/updating-element.ts | 67 +++++++++++++++++++----------------- src/test/lit-element_test.ts | 12 +++---- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 44c97dff..9f4fe59e 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -199,7 +199,7 @@ export abstract class UpdatingElement extends HTMLElement { this._finalize(); this._observedAttributes = []; for (const p in this._classProperties) { - const attr = this.attributeNameForProperty(p); + const attr = this._attributeNameForProperty(p); if (attr !== undefined) { this._attributeToPropertyMap[attr] = p; this._observedAttributes.push(attr); @@ -242,9 +242,9 @@ export abstract class UpdatingElement extends HTMLElement { /** * Returns the property name for the given attribute `name`. */ - private static attributeNameForProperty(name: string) { + private static _attributeNameForProperty(name: string) { const info = this._classProperties[name]; - const attribute = info && info.attribute; + const attribute = info !== undefined && info.attribute; return attribute === false ? undefined : (typeof attribute === 'string' ? attribute : name.toLowerCase()); } @@ -254,10 +254,9 @@ export abstract class UpdatingElement extends HTMLElement { * Called when a property value is set and uses the `shouldInvalidate` * option for the property if present or a strict identity check. */ - // tslint:disable-next-line no-any - private static propertyShouldInvalidate(name: string, value: any, old: any) { + private static _propertyShouldInvalidate(name: string, value: unknown, old: unknown) { const info = this._classProperties[name]; - const fn = info && info.shouldInvalidate || identity; + const fn = info !== undefined && info.shouldInvalidate || identity; return fn(value, old); } @@ -266,10 +265,10 @@ export abstract class UpdatingElement extends HTMLElement { * Called via the `attributeChangedCallback` and uses the property's `type` * or `type.fromAttribute` property option. */ - private static propertyValueFromAttribute(name: string, value: string) { + private static _propertyValueFromAttribute(name: string, value: string) { const info = this._classProperties[name]; const type = info && info.type; - if (!type) { + if (type === undefined) { return value; } const fromAttribute = typeof type === 'function' ? type : type.fromAttribute; @@ -283,10 +282,9 @@ export abstract class UpdatingElement extends HTMLElement { * attribute will be set to the value. * This uses the property's `reflect` and `type.toAttribute` property options. */ - // tslint:disable-next-line no-any - private static propertyValueToAttribute(name: string, value: any) { + private static _propertyValueToAttribute(name: string, value: unknown) { const info = this._classProperties[name]; - if (!info || !info.reflect) { + if (info === undefined || info.reflect === undefined) { return; } const toAttribute = info.type && (info.type as AttributeSerializer).toAttribute || String; @@ -296,9 +294,9 @@ export abstract class UpdatingElement extends HTMLElement { private _validationState: ValidationState = disabled; private _isReflectingProperty: boolean = false; private _instanceProperties: PropertyValues|undefined; - private _validatePromise: any = undefined; + private _validatePromise: Promise|undefined; private _validateResolver: (() => void)|undefined; - private _firstUpdated: boolean = false; + private _firstUpdateFinished: boolean = false; /** * Object with keys for all properties with their current values. @@ -327,7 +325,7 @@ export abstract class UpdatingElement extends HTMLElement { * create the element `root` node and captures any pre-set values for * registered properties. */ - initialize() { + protected initialize() { this.renderRoot = this.createRenderRoot(); // Apply any properties set on the instance before upgrade time. for (const p in (this.constructor as typeof UpdatingElement)._classProperties) { @@ -389,11 +387,10 @@ export abstract class UpdatingElement extends HTMLElement { * the element. If a property value is set inside the `update` method, * it does not trigger `invalidate`. */ - // tslint:disable-next-line no-any - protected setProperty(name: string, value: any) { + protected setProperty(name: string, value: unknown) { const old = this._props[name]; const ctor = (this.constructor as typeof UpdatingElement); - if (ctor.propertyShouldInvalidate(name, value, old)) { + if (ctor._propertyShouldInvalidate(name, value, old)) { // track old value when changing. if (!this._changedProps) { this._changedProps = {}; @@ -406,11 +403,11 @@ export abstract class UpdatingElement extends HTMLElement { } } - private _propertyToAttribute(name: string, value: any) { + private _propertyToAttribute(name: string, value: unknown) { const ctor = (this.constructor as typeof UpdatingElement); - const attrValue = ctor.propertyValueToAttribute(name, value); + const attrValue = ctor._propertyValueToAttribute(name, value); if (attrValue !== undefined) { - const attr = ctor.attributeNameForProperty(name); + const attr = ctor._attributeNameForProperty(name); if (attr !== undefined) { // Track if the property is being reflected to avoid // setting the property again via `attributeChangedCallback`. Note: @@ -436,7 +433,7 @@ export abstract class UpdatingElement extends HTMLElement { if (!this._isReflectingProperty) { const ctor = (this.constructor as typeof UpdatingElement); const propName = ctor._attributeToPropertyMap[name]; - this[propName as keyof this] = ctor.propertyValueFromAttribute(propName, value); + this[propName as keyof this] = ctor._propertyValueFromAttribute(propName, value); } } @@ -471,15 +468,21 @@ export abstract class UpdatingElement extends HTMLElement { // During update (which is abstract), setting properties does not trigger invalidation. this.update(changedProps); this._validationState = valid; + if (!this._firstUpdateFinished) { + this._firstUpdateFinished = true; + if (typeof this.finishFirstUpdate === 'function') { + const result = this.finishFirstUpdate(); + if (result != null && typeof (result as PromiseLike).then === 'function') { + await result; + } + } + } // During finishUpdate (which is abstract), setting properties does trigger invalidation, // and users may choose to await other state, like children being updated. if (typeof this.finishUpdate === 'function') { - await this.finishUpdate(changedProps); - } - if (!this._firstUpdated) { - this._firstUpdated = true; - if (typeof this.firstUpdated === 'function') { - this.firstUpdated(); + const result = this.finishUpdate(changedProps); + if (result != null && typeof (result as PromiseLike).then === 'function') { + await result; } } } else { @@ -533,13 +536,15 @@ export abstract class UpdatingElement extends HTMLElement { * * @returns {Promise} Optionally can return a promise that blocks * resolution of the `invalidate` and updateComplete` promise. */ - protected finishUpdate?(_changedProperties: PropertyValues): void|Promise; + protected finishUpdate?(_changedProperties: PropertyValues): void|Promise; /** * Called with the element is first updated. This method does nothing by * default and can be implemented to perform post first update tasks on - * element DOM. Any tasks which should synchronous with dynamic updates - * should be implemented in `finishUpdate`. + * element DOM. Any tasks which depend on dynamic updates should instead + * be implemented in `finishUpdate`. + * * @returns {Promise} Optionally can return a promise that blocks + * resolution of the `invalidate` and updateComplete` promise. */ - protected firstUpdated?(): void; + protected finishFirstUpdate?(): void; } \ No newline at end of file diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index f11fedbe..61dd0013 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -691,7 +691,7 @@ suite('LitElement', () => { }); test( - 'firstUpdated called when element first updates', async () => { + 'finishFirstUpdate called when element first updates', async () => { class E extends LitElement { wasUpdated = 0; @@ -703,7 +703,7 @@ suite('LitElement', () => { render() { return html``; } - firstUpdated() { + finishFirstUpdate() { this.wasFirstUpdated++; } @@ -723,7 +723,7 @@ suite('LitElement', () => { }); test( - 'render lifecycle order: shouldUpdate, update, render, finishUpdate, firstUpdated, updateComplete', async () => { + 'render lifecycle order: shouldUpdate, update, render, finishUpdate, finishFirstUpdate, updateComplete', async () => { class E extends LitElement { static get properties() { return { foo: {type: Number} @@ -750,8 +750,8 @@ suite('LitElement', () => { this.info.push('finishUpdate'); } - firstUpdated() { - this.info.push('firistUpdated'); + finishFirstUpdate() { + this.info.push('finishFirstUpdate'); } } @@ -762,7 +762,7 @@ suite('LitElement', () => { el.info.push('updateComplete'); assert.deepEqual( el.info, - [ 'shouldUpdate', 'before-update', 'render', 'finishUpdate', 'firistUpdated', 'updateComplete' ]); + [ 'shouldUpdate', 'before-update', 'render', 'finishFirstUpdate', 'finishUpdate', 'updateComplete' ]); }); test('setting properties in update does not trigger invalidation', async () => { From e97392e7fc8135c2c4d2d6dc5dcd9b1434754ef4 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Mon, 13 Aug 2018 12:31:45 -0700 Subject: [PATCH 20/65] Import from core. --- src/lit-element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lit-element.ts b/src/lit-element.ts index 1cd684ca..d2495491 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -12,7 +12,7 @@ * http://polymer.github.io/PATENTS.txt */ import {render} from 'lit-html/lib/shady-render'; -import {TemplateResult} from 'lit-html'; +import {TemplateResult} from 'lit-html/core'; import {UpdatingElement, PropertyValues} from './lib/updating-element.js'; export {property, identity, BooleanAttribute, PropertyDeclarations, PropertyDeclaration, PropertyValues} from './lib/updating-element.js'; From 00d9d5cf17ae0cde2333485463ce434834c6d8f7 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Mon, 13 Aug 2018 15:46:13 -0700 Subject: [PATCH 21/65] Updates based on review. --- src/lib/updating-element.ts | 44 +++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 9f4fe59e..95b664d8 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -301,13 +301,13 @@ export abstract class UpdatingElement extends HTMLElement { /** * Object with keys for all properties with their current values. */ - private _props: PropertyValues = {}; + private _propertyValues: PropertyValues = {}; /** * Object with keys for any properties that have changed since the last * update cycle with previous values. */ - private _changedProps?: PropertyValues|undefined; + private _changedProperties?: PropertyValues|undefined; /** * Node or ShadowRoot into which element DOM should be rendered. Defaults @@ -377,7 +377,7 @@ export abstract class UpdatingElement extends HTMLElement { * to get property values of registered properties. */ protected getProperty(name: string) { - return this._props[name]; + return this._propertyValues[name]; } /** @@ -388,17 +388,17 @@ export abstract class UpdatingElement extends HTMLElement { * it does not trigger `invalidate`. */ protected setProperty(name: string, value: unknown) { - const old = this._props[name]; + const old = this._propertyValues[name]; const ctor = (this.constructor as typeof UpdatingElement); if (ctor._propertyShouldInvalidate(name, value, old)) { // track old value when changing. - if (!this._changedProps) { - this._changedProps = {}; + if (!this._changedProperties) { + this._changedProperties = {}; } - if (!(name in this._changedProps)) { - this._changedProps[name] = this._props[name]; + if (!(name in this._changedProperties)) { + this._changedProperties[name] = this._propertyValues[name]; } - this._props[name] = value; + this._propertyValues[name] = value; this.invalidate(); } } @@ -445,7 +445,7 @@ export abstract class UpdatingElement extends HTMLElement { */ async invalidate() { // Do not re-queue validation if already invalid (pending) or currently updating. - if (this._isPendingUpdate()) { + if (this._isPendingUpdate) { return this._validatePromise; } this._validationState = invalid; @@ -462,8 +462,8 @@ export abstract class UpdatingElement extends HTMLElement { this._instanceProperties = undefined; } // Rip off changedProps. - const changedProps = this._changedProps || {}; - this._changedProps = undefined; + const changedProps = this._changedProperties || {}; + this._changedProperties = undefined; if (this.shouldUpdate(changedProps)) { // During update (which is abstract), setting properties does not trigger invalidation. this.update(changedProps); @@ -490,14 +490,14 @@ export abstract class UpdatingElement extends HTMLElement { } // Only resolve the promise if we finish in a valid state (finishUpdate // did not trigger more work). - if (this._validationState === valid && this._validateResolver !== undefined) { - this._validateResolver(); + if (this._validationState === valid) { + this._validateResolver!(); this._validateResolver = undefined; } return this._validatePromise; } - private _isPendingUpdate() { + private get _isPendingUpdate() { return this._validationState === invalid; } @@ -505,7 +505,7 @@ export abstract class UpdatingElement extends HTMLElement { * Returns a Promise that resolves when the element has finished updating. */ get updateComplete() { - return this._isPendingUpdate() ? this._validatePromise : Promise.resolve(); + return this._isPendingUpdate ? this._validatePromise : Promise.resolve(); } /** @@ -521,6 +521,8 @@ export abstract class UpdatingElement extends HTMLElement { /** * Updates the element. By default this method reflects property values to attributes. * It should be implemented to render and keep updated DOM in the element's root. + * Note, within `update()` setting properties does not trigger `invalidate()`, allowing + * property values to be computed and validated before DOM is rendered and updated. * * @param _changedProperties changed properties with old values */ protected update(_changedProperties: PropertyValues): void { @@ -532,8 +534,16 @@ export abstract class UpdatingElement extends HTMLElement { /** * Finishes updating the element. This method does nothing by default and * can be implemented to perform post update tasks on element DOM. + * Note, setting properties in `finishUpdate()` triggers `invalidate()`. + * There are a couple of common cases when it's useful to implement + * `finishUpdate`: + * (1) A property should be updated based on the rendered state of the + * DOM. In this case it's important to avoid creating a loop since setting + * properties triggers invalidate and update. + * (2) The `updateComplete` promise should block on the `updateComplete` promise + * of a rendered `UpdatingElement`. * * @param _changedProperties changed properties with old values - * * @returns {Promise} Optionally can return a promise that blocks + * * @returns {Promise} Optionally, this function can return a promise that blocks * resolution of the `invalidate` and updateComplete` promise. */ protected finishUpdate?(_changedProperties: PropertyValues): void|Promise; From f2f908daa7f4a1fbead32f4e09bf1913e10eb685 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 14 Aug 2018 12:26:00 -0700 Subject: [PATCH 22/65] Ensure properties set in update reflect properly --- package-lock.json | 995 ++++++++++++++++------------------- src/lib/updating-element.ts | 19 +- src/test/lit-element_test.ts | 50 ++ 3 files changed, 517 insertions(+), 547 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f72116a..5851eeec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,117 +5,36 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.53.tgz", - "integrity": "sha1-mA0VYLhjV1v1o3eSUDfgEy71kh4=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-rc.1.tgz", + "integrity": "sha512-qhQo3GqwqMUv03SxxjcEkWtlkEDvFYrBKbJUn4Dtd9amC2cLkJ3me4iYUVSBbVXWbfbVRalEeVBHzX4aQYKnBg==", "dev": true, "requires": { - "@babel/highlight": "7.0.0-beta.53" + "@babel/highlight": "7.0.0-rc.1" } }, "@babel/core": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-beta.53.tgz", - "integrity": "sha1-q2R8+7JyQf0i7DyhNC161Oa1T58=", - "dev": true, - "requires": { - "@babel/code-frame": "7.0.0-beta.53", - "@babel/generator": "7.0.0-beta.53", - "@babel/helpers": "7.0.0-beta.53", - "@babel/parser": "7.0.0-beta.53", - "@babel/template": "7.0.0-beta.53", - "@babel/traverse": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.0-rc.1.tgz", + "integrity": "sha512-CvuSsq+LFs9N4SJG8MnNPI0hnl913HK1OqG3NEfejOKo+JqtVuxpmAFyXIDogX2x668xqFKAW6EQiCIcUHklMg==", + "dev": true, + "requires": { + "@babel/code-frame": "7.0.0-rc.1", + "@babel/generator": "7.0.0-rc.1", + "@babel/helpers": "7.0.0-rc.1", + "@babel/parser": "7.0.0-rc.1", + "@babel/template": "7.0.0-rc.1", + "@babel/traverse": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1", "convert-source-map": "^1.1.0", "debug": "^3.1.0", "json5": "^0.5.0", - "lodash": "^4.17.5", - "micromatch": "^2.3.11", + "lodash": "^4.17.10", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -125,14 +44,14 @@ } }, "@babel/generator": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.53.tgz", - "integrity": "sha1-uMrXLFcr4yNK/94ivm2sxCUOA0s=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-rc.1.tgz", + "integrity": "sha512-Ak4n780/coo+L9GZUS7V/IGJilP11t4UoWl0J9cG3jso4KkDGQcqdx4Y6gJAiXng+sDfvzUmvWfM1hZwH82J0A==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.53", + "@babel/types": "7.0.0-rc.1", "jsesc": "^2.5.1", - "lodash": "^4.17.5", + "lodash": "^4.17.10", "source-map": "^0.5.0", "trim-right": "^1.0.1" }, @@ -146,33 +65,33 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-beta.53.tgz", - "integrity": "sha1-WZYGKDdcvu+WoH7f4co4t1bwGqg=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0-rc.1.tgz", + "integrity": "sha512-GOV2UExs9gAvSrZF4rcgocXXeLJplq2kL2AsCrn6DmGwMUEfo/KB7FhedN3X6cVh0gOqqKkVKXrz3Li1wQ84xQ==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.53" + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-beta.53.tgz", - "integrity": "sha1-RFZwliPX2vqivulPglUD9MDs6Fs=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.0.0-rc.1.tgz", + "integrity": "sha512-O6/szesBinGoExLl01Qg2vb5FaOfifSilgL5GnCZLz5z3Pg9jRolN6rGzQAOa/K9Y01TAmDf1dC06AKQUv3x8g==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53" + "@babel/helper-explode-assignable-expression": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-call-delegate": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-beta.53.tgz", - "integrity": "sha1-ld6Lq9A/nmz08rVkoDhwjBOP/jE=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.0.0-rc.1.tgz", + "integrity": "sha512-3Z+shHGJTQnc61RCFVrQ3OJRmyL8uk4dWCsP8kT7G4inxv/bs6/zLOipK21VMePGpjUA4tnKxJCevMtp9ko4pw==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "7.0.0-beta.53", - "@babel/traverse": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53" + "@babel/helper-hoist-variables": "7.0.0-rc.1", + "@babel/traverse": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-define-map": { @@ -249,75 +168,75 @@ } }, "@babel/helper-explode-assignable-expression": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-beta.53.tgz", - "integrity": "sha1-1bytK2tH9ATAruillk3/2TEkc6g=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.0.0-rc.1.tgz", + "integrity": "sha512-hSa+oxKn9bfbc3Ob1U7QJsO++do2Xe8Ft640alRJpEQ3VWy7tL8ZB+2xqo0pgHKo7rITuSxERz72uZji8dTiWg==", "dev": true, "requires": { - "@babel/traverse": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53" + "@babel/traverse": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-function-name": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.53.tgz", - "integrity": "sha1-USgEro6cvOVDHr6hnkdijC7WU/I=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-rc.1.tgz", + "integrity": "sha512-fDbWxdYYbFNzcI5jn3qsPxHI1UCXwvFk0kGytGce/FEBYEPXBqycKknC8Oqiub8DzGtmTcvnqcm/cl/qxzeuiQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "7.0.0-beta.53", - "@babel/template": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53" + "@babel/helper-get-function-arity": "7.0.0-rc.1", + "@babel/template": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-get-function-arity": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.53.tgz", - "integrity": "sha1-3tiKsp+bHbYch9G7jTijXdp3neY=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-rc.1.tgz", + "integrity": "sha512-5+ydaIRxT42FSDqvoXIDksCGlW1903xC73HQnQCFF1YuV7VcIf+9M4+tRZulLlYlshw7ILA+4SiYsKoDlC0Irg==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.53" + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-hoist-variables": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-beta.53.tgz", - "integrity": "sha1-TCfjuHP6CcWtbpPrQHBMIA+EE3w=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0-rc.1.tgz", + "integrity": "sha512-ttcilOh9SM9eqVlzwz2Lv7B5Dwyaa8TIhi1DDEPnC3CarpNPXFdeCOoxoV5qjHRD1klAT86gczeU4lJnSDKmgA==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.53" + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0-beta.53.tgz", - "integrity": "sha1-D7Dviy07kD0cO/Qm2kp0V14BnOQ=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0-rc.1.tgz", + "integrity": "sha512-o263plHxPo1TxDDUx7gHuQ96Y8QyLs2n4968KZvo2l/9rkwn2L9kcIsRVjlhpPPKTz4tWe/7ZV50zkeDorrK9g==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.53" + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-module-imports": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-beta.53.tgz", - "integrity": "sha1-5zXmqjClBLD52Fw4ptRwqfSqgdk=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0-rc.1.tgz", + "integrity": "sha512-eA8RzanjsZw4X2Cqh3WgVG7zwf1wdSUfXvZOH8Azx1rpwE0hzJ276jDZ3gSOJShsxPVvopHa4h+c2WfEUjW4+Q==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.53", - "lodash": "^4.17.5" + "@babel/types": "7.0.0-rc.1", + "lodash": "^4.17.10" } }, "@babel/helper-module-transforms": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-beta.53.tgz", - "integrity": "sha1-e6IUzcyPhiPy0Xl96v8f80mqzhM=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.0.0-rc.1.tgz", + "integrity": "sha512-nz7FTFXlQ9UYp/dBjad4ZOu3Q4/1n86ysw9z9pjunqeKFNm+JHq7j5BeocFKIQAwul7QbIkSXiYm5EiteCHjiQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "7.0.0-beta.53", - "@babel/helper-simple-access": "7.0.0-beta.53", - "@babel/helper-split-export-declaration": "7.0.0-beta.53", - "@babel/template": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53", - "lodash": "^4.17.5" + "@babel/helper-module-imports": "7.0.0-rc.1", + "@babel/helper-simple-access": "7.0.0-rc.1", + "@babel/helper-split-export-declaration": "7.0.0-rc.1", + "@babel/template": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1", + "lodash": "^4.17.10" } }, "@babel/helper-optimise-call-expression": { @@ -343,31 +262,31 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-beta.53.tgz", - "integrity": "sha1-1kRYY2/8JYtCcUqd2Trrb4uM8+0=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0-rc.1.tgz", + "integrity": "sha512-8ZNzqHXDhT/JjnBvrLKu8AL7NhONVIsnrfyQNm3PJNmufIER5kcIa3OxPMGWgNqox2R8WeQ6YYzYTLNXqq4kgQ==", "dev": true }, "@babel/helper-regex": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0-beta.53.tgz", - "integrity": "sha1-bp0hl7Vid54iVWWUaumoXCFbIl4=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.0.0-rc.1.tgz", + "integrity": "sha512-QXnTXVefioGuXlRMn+MnKKUHwhmdXGKnMvFI1tdHioMnBQEbEHGnmp+aYcddLwJ3KAH/hveaSR95BuWwprW+TA==", "dev": true, "requires": { - "lodash": "^4.17.5" + "lodash": "^4.17.10" } }, "@babel/helper-remap-async-to-generator": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-beta.53.tgz", - "integrity": "sha1-uDSnVy3sF2OJ/6x+djV5WGSQySI=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.0.0-rc.1.tgz", + "integrity": "sha512-skROQSC2fPwmrzAEPT/M7CObnWjJGpdbNLoICZDYHwDiUDe3dk5cQsU9j3tNlBhX14FaC9SjSpCJnSRpXDOWOw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "7.0.0-beta.53", - "@babel/helper-wrap-function": "7.0.0-beta.53", - "@babel/template": "7.0.0-beta.53", - "@babel/traverse": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53" + "@babel/helper-annotate-as-pure": "7.0.0-rc.1", + "@babel/helper-wrap-function": "7.0.0-rc.1", + "@babel/template": "7.0.0-rc.1", + "@babel/traverse": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-replace-supers": { @@ -467,52 +386,52 @@ } }, "@babel/helper-simple-access": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-beta.53.tgz", - "integrity": "sha1-cvbbmr5C+GgfpvAo79WdgVRHUrM=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.0.0-rc.1.tgz", + "integrity": "sha512-mfrHVSG0Dw51ajyL3Ltz+gEYrWAy4+Kl8lb1V/QWR31H7ovha6vNZ4guev/lR4KFu+4hMHogpjh4HB4AShqeMQ==", "dev": true, "requires": { - "@babel/template": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53", - "lodash": "^4.17.5" + "@babel/template": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1", + "lodash": "^4.17.10" } }, "@babel/helper-split-export-declaration": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.53.tgz", - "integrity": "sha1-rvVLix+ZYW6jfJhHhxajeAJjMls=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-rc.1.tgz", + "integrity": "sha512-hz6QmlnaBFYt4ra8DfRLCMgrI7yfwQ13kJtufSO5dVCasxmAng2LeeQiT6H4iN5TpFONcayp5f/2mXqHH/zn/g==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.53" + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-wrap-function": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-beta.53.tgz", - "integrity": "sha1-q/sr+pQBBCurJXwBkPWtbbjfFdU=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.0.0-rc.1.tgz", + "integrity": "sha512-LrqRD4+jEkQGVQsCRi7bPkSmYFAUd3pv9tYAC8nsr9Y0Qfus8oycqxDj60QW4dmigRKBRRbVVLr/0kMI2pk0MA==", "dev": true, "requires": { - "@babel/helper-function-name": "7.0.0-beta.53", - "@babel/template": "7.0.0-beta.53", - "@babel/traverse": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53" + "@babel/helper-function-name": "7.0.0-rc.1", + "@babel/template": "7.0.0-rc.1", + "@babel/traverse": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1" } }, "@babel/helpers": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-beta.53.tgz", - "integrity": "sha1-xDb/uOCTAU2olba7d5f/RuPRzM8=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0-rc.1.tgz", + "integrity": "sha512-4+AkDbZ0Usr7mNH4wGX8fVx4WJzHdrcjRkJy52EIWyBAQEoKqb5HXca1VjejWtnVwaGwW7zk/h6oQ9FQPywQfA==", "dev": true, "requires": { - "@babel/template": "7.0.0-beta.53", - "@babel/traverse": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53" + "@babel/template": "7.0.0-rc.1", + "@babel/traverse": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1" } }, "@babel/highlight": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.53.tgz", - "integrity": "sha1-9OlS2tF4fSBeGI0+OEzc5JyjaPs=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-rc.1.tgz", + "integrity": "sha512-5PgPDV6F5s69XNznTcP0za3qH7qgBkr9DVQTXfZtpF+3iEyuIZB1Mjxu52F5CFxgzQUQJoBYHVxtH4Itdb5MgA==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -521,114 +440,114 @@ } }, "@babel/parser": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.53.tgz", - "integrity": "sha1-H0XrYXv5Rj1IKywE00nZ5O2/SJI=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-rc.1.tgz", + "integrity": "sha512-rC+bIz2eZnJlacERmJO25UAbXVZttcSxh0Px0gRGinOTzug5tL7+L9urfIdSWlv1ZzP03+f2xkOFLOxZqSsVmQ==", "dev": true }, "@babel/plugin-external-helpers": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.0.0-beta.53.tgz", - "integrity": "sha1-WMMIYn4cBXN34ehxpqJ904CNLbc=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-external-helpers/-/plugin-external-helpers-7.0.0-rc.1.tgz", + "integrity": "sha512-g0POypf/vBWEFmNkuwYrWoANrzOL4iSBhFtjSN+0D4BCm4jKtmY6kAOKaqjvWwj5IcqQVQEXqdRUXU0seoBF/g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-beta.53.tgz", - "integrity": "sha1-XFnvZm0Xwn3LVoa3XsMr622MUNY=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.0.0-rc.1.tgz", + "integrity": "sha512-ewJnWv10AFUh+Yi6axMVQKW8L1pZCm86a44m2biYtXNSyt6FyWgdRloBbR7iCviPkeurfTCVdPS61G/t5cXVkQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53", - "@babel/helper-remap-async-to-generator": "7.0.0-beta.53", - "@babel/plugin-syntax-async-generators": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1", + "@babel/helper-remap-async-to-generator": "7.0.0-rc.1", + "@babel/plugin-syntax-async-generators": "7.0.0-rc.1" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-beta.53.tgz", - "integrity": "sha1-5rXwusUBg48W6PPG00sAs+pANdk=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.0.0-rc.1.tgz", + "integrity": "sha512-J9qLEkxuZrYh/mel9RA5wDrMGE7jQMOMa1XPZMysih4C0mveeQUExbAPyrVSrFQo5BXLcLIc6ccM24G9xPCCXA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53", - "@babel/plugin-syntax-object-rest-spread": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1", + "@babel/plugin-syntax-object-rest-spread": "7.0.0-rc.1" } }, "@babel/plugin-syntax-async-generators": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-beta.53.tgz", - "integrity": "sha1-gpvvbxUBeentC7lDM58qMSM6qSE=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.0.0-rc.1.tgz", + "integrity": "sha512-2F5FYc89TCrqE/8+qFlr5jVMTHfkhEOg9JUx+GXI3inW2OfcY+J6bN8EDc8PLz84PHaR8W630YOuh2PveJu3WA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-syntax-dynamic-import": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0-beta.53.tgz", - "integrity": "sha1-s+lA+H7oeq8UORsXHGNHb9Aa8yM=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.0.0-rc.1.tgz", + "integrity": "sha512-9U93f+wnHLOqHYxk1pftQfvWIx4FAKce9C41ZaNPLUffr7+yE+D24rNG0KeG5/ROMbKE3so7d2Qv891ThVZtPw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-syntax-import-meta": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.0.0-beta.53.tgz", - "integrity": "sha1-4ZKYmcLqr1NKbbJFjlOnI0wB0mg=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.0.0-rc.1.tgz", + "integrity": "sha512-pECmr/Eh3GVtzzJYKOOaTcRvNW2+IOD7M/xPONlQ65KgbpMJVygVXS3lMIrdZx2M3buQeTgLGUplq0r28zA0NA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-beta.53.tgz", - "integrity": "sha1-nb12jD8QnwKyT7oXNllp+iXrRYw=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.0.0-rc.1.tgz", + "integrity": "sha512-stOESgG+lc68DSFvXrqoH5dW91ZtedDoR40g9wJ1ruLahCdr9X5hVLv/ddf/g/1zzjevq59A1Q+xdUREhEnrvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-beta.53.tgz", - "integrity": "sha1-p19fqEl6rBcp0DO/QcJQQWudHgQ=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.0.0-rc.1.tgz", + "integrity": "sha512-9JnWkl+iKmjNgMFrLjfGJQm3f66SJxwaYjdsm49Vpvo9x7ADHMGMZYa5Yto9WNQBlIdtf+fhypwBcz6IPxdyvg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-beta.53.tgz", - "integrity": "sha1-REx2HMQhXJeptVb/WMp7p99dQVM=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.0.0-rc.1.tgz", + "integrity": "sha512-8oE9Frx07ILINop9hOejXgcDVhmt4FuB3ZjXnIMcSMkAuiT3xLrxFMDo1Qo0kf5mty2jLlnOO6tbbH0kiIWxWA==", "dev": true, "requires": { - "@babel/helper-module-imports": "7.0.0-beta.53", - "@babel/helper-plugin-utils": "7.0.0-beta.53", - "@babel/helper-remap-async-to-generator": "7.0.0-beta.53" + "@babel/helper-module-imports": "7.0.0-rc.1", + "@babel/helper-plugin-utils": "7.0.0-rc.1", + "@babel/helper-remap-async-to-generator": "7.0.0-rc.1" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-beta.53.tgz", - "integrity": "sha1-CkMiGhsMkM1NCfG0a5Wd0khlf3M=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.0.0-rc.1.tgz", + "integrity": "sha512-dFEgZqmyWXaVYrFU11IgLX8M1+gK7GSU+CVRv42D7P1FFMNndg1u36jXIa7URExEuTeTUykLM/IWgk5pHWxo6A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-beta.53.tgz", - "integrity": "sha1-nv1uUMofo5jcqnEZYh2j8fu4IbY=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.0.0-rc.1.tgz", + "integrity": "sha512-9uGwvSqJcmcKPEkLHA7ffrG0lKXTXprupwGjEKDw27OoRWXHdWUmA4VwpuzMrUsYyV+q+P6mgj6TPzoGJA3fAw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53", - "lodash": "^4.17.5" + "@babel/helper-plugin-utils": "7.0.0-rc.1", + "lodash": "^4.17.10" } }, "@babel/plugin-transform-classes": { @@ -716,238 +635,237 @@ } }, "@babel/plugin-transform-computed-properties": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-beta.53.tgz", - "integrity": "sha1-l0fiYIKulO2lMPmNLCBZ6NLbwAU=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.0.0-rc.1.tgz", + "integrity": "sha512-dfJNqbyF6S8nvFzGc6NthqCqopn1PoY3q2E1KcgrFSgxwYAMOLuhu5eA5iFeXwggp6tIo6OVVXC55/Twsolmow==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-destructuring": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-beta.53.tgz", - "integrity": "sha1-DwrbDhptzTWjZkEBYJ7AYv8SenY=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.0.0-rc.1.tgz", + "integrity": "sha512-YpuGA3cj5+gRD053nWtogo+3wxc10mNAAyf5syXXCVS/cOWpRjc3qPidzHtPodz+v8TgAwwaXwIz/ghLOojRQw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-beta.53.tgz", - "integrity": "sha1-D1WZE6v6GCOcpOCPc+7DbF5XuB8=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.0.0-rc.1.tgz", + "integrity": "sha512-cWyoUi1izJk5JbWFG07GZrZyZgG+DW4axPKI0MA+lSAxjP8VZwFUhJyjT7R4bGN81KTVv1aprKclQnKxN2R0Lw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-beta.53.tgz", - "integrity": "sha1-PiZxeSBMd1GdhBepsZnyUiIejZU=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.0.0-rc.1.tgz", + "integrity": "sha512-5lc0nlX8TPdkHSIX3/3jMtqvvJfzcARcev4qqsaVkXWQ6XNrNnD8ExyTEVgoGhr5Ppz1wA0ymAK8W33uGeKSOg==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "7.0.0-beta.53", - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-builder-binary-assignment-operator-visitor": "7.0.0-rc.1", + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-for-of": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-beta.53.tgz", - "integrity": "sha1-+gZSFeGFacj3TdUktXIeEdzKlzs=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.0.0-rc.1.tgz", + "integrity": "sha512-v09o2ywKHu+b/vkLknjKPV9QXCxuU2cVFxkWhBqcKwl3ERe3clhiab7a/8T9Sc332o4Im6n/LLugKMtpfxqRsQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-function-name": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-beta.53.tgz", - "integrity": "sha1-Kzpbs2TB4cV+zL/iXGv1XygEET4=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.0.0-rc.1.tgz", + "integrity": "sha512-MiUORPQo3kvSCYBn/T6kKIfdDKqFAnEsaiRnTz36Y6M/p6NX7br5MgqPumVNgDboYKQ9kzaFNM8YJvWLcjL6SQ==", "dev": true, "requires": { - "@babel/helper-function-name": "7.0.0-beta.53", - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-function-name": "7.0.0-rc.1", + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-instanceof": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-instanceof/-/plugin-transform-instanceof-7.0.0-beta.53.tgz", - "integrity": "sha1-WC2CtyUYggGtDiIx8fzpTHRaLAY=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-instanceof/-/plugin-transform-instanceof-7.0.0-rc.1.tgz", + "integrity": "sha512-AZc7Ln5Rk3TAPQ3tkuuqL7/p1cUHoVEXBLX19xNXL+pauQ+vllpEcAQdkugkuojZ5KNmBYNRKoGf9oRSxixwDQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-literals": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-beta.53.tgz", - "integrity": "sha1-vsTxROmpbvUSHRQwx+vl/QiGV8k=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.0.0-rc.1.tgz", + "integrity": "sha512-iI468X7shsmB/oIPi8+UfMcOpcQPEsMAz5hDc0H8dKBGUWbPcAlyQpC8CaNDZ7y1/7lK65wtvXs5OGTQd3OsJg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-beta.53.tgz", - "integrity": "sha1-WFTXOeZ5IzqId8C0GCaca+t6Miw=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.0.0-rc.1.tgz", + "integrity": "sha512-xKIF2ZAFOZRgIhEeW6zuyieyqfjft59NaHvb2C7+N9omdFDVkrx5ZeHVLb8y163a3mUb2MqJg1PLfZXdwvz1EA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "7.0.0-beta.53", - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-module-transforms": "7.0.0-rc.1", + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-object-super": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-beta.53.tgz", - "integrity": "sha1-4sTwbts0s9eksnV7oYgp0N8gKcs=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.0.0-rc.1.tgz", + "integrity": "sha512-mwoid0Rx+L55NupRE9xs1JAgFRz0JIYS/JR0aqBlLOQwBY1KrbrAtQfNwHQobwZrP9O24VBRfViMsiYLh/UV4A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53", - "@babel/helper-replace-supers": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1", + "@babel/helper-replace-supers": "7.0.0-rc.1" }, "dependencies": { "@babel/helper-optimise-call-expression": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-beta.53.tgz", - "integrity": "sha1-j8eO9MD2n4uzu980zSMsIBIEFMg=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0-rc.1.tgz", + "integrity": "sha512-XOKPnL/AJz8ZyY553FsMAVt9g/mE1+RQfg5/m3X0K4+RqYviPGZlxwe5mGSd8s2kPSB6D6nZRUfvZFtmFIXEvA==", "dev": true, "requires": { - "@babel/types": "7.0.0-beta.53" + "@babel/types": "7.0.0-rc.1" } }, "@babel/helper-replace-supers": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-beta.53.tgz", - "integrity": "sha1-M5tb3BAilElbGifFWBMjBuG3vKc=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.0.0-rc.1.tgz", + "integrity": "sha512-mcv+NKCazZfdEw7yBe/xROekR3qlFcy18d//mJTKnZb7xx2qFPjZAafkeIlpvzNHwd/WMTHShC4+3WjOL8FD5g==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "7.0.0-beta.53", - "@babel/helper-optimise-call-expression": "7.0.0-beta.53", - "@babel/traverse": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53" + "@babel/helper-member-expression-to-functions": "7.0.0-rc.1", + "@babel/helper-optimise-call-expression": "7.0.0-rc.1", + "@babel/traverse": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1" } } } }, "@babel/plugin-transform-parameters": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-beta.53.tgz", - "integrity": "sha1-7+YM7IzsoNGdXG+hrnm8TjMnnVY=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.0.0-rc.1.tgz", + "integrity": "sha512-PKjm+xf23XvdP0WRj/fIiP3xa5DYOg6qd0150Mpu4JvCIci6vrWvkc+kU9RtwkXLycWRfzdSnnyuSZABxPAP8A==", "dev": true, "requires": { - "@babel/helper-call-delegate": "7.0.0-beta.53", - "@babel/helper-get-function-arity": "7.0.0-beta.53", - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-call-delegate": "7.0.0-rc.1", + "@babel/helper-get-function-arity": "7.0.0-rc.1", + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-regenerator": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-beta.53.tgz", - "integrity": "sha1-T+u/YISvoMHJ7ISX3mjAaV/p2gs=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.0.0-rc.1.tgz", + "integrity": "sha512-a73XZOJGt0Ft8/YbRAUl0Vs1GuPpjB6QVQNYPxWUNXblSiywhkkZxLssHZnao2xTD26kLRfMoXfOtj9FMz5fcw==", "dev": true, "requires": { "regenerator-transform": "^0.13.3" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-beta.53.tgz", - "integrity": "sha1-38SIG2vXZYoAMew7gWPliPCJjUs=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0-rc.1.tgz", + "integrity": "sha512-NkUsTSKL8txvPt9vtdkcbJEyiUtcSOAr6ZnAE+Vg4mB0hYI0sWEJCAzl26KDDFgdVSKJSAaenjX5UR3BAF3KaA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-spread": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-beta.53.tgz", - "integrity": "sha1-g+j2Rsok8cmCKPnxREz2DL1JOLw=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.0.0-rc.1.tgz", + "integrity": "sha512-/3EkUVVi55i/JCbL2CxXTaoCXCopj3qQMTZ0lvgtpepx1yAMpoHYFBNWLIuQmjG7JhDauOwEdBg8TRsneYRmmw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-beta.53.tgz", - "integrity": "sha1-D888mUq92Lq1m6l4L+TZ+KVF1uc=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.0.0-rc.1.tgz", + "integrity": "sha512-sXPFGI3GTtSMxVTDwrRmgwmUcq+l0ovzUZFfAd4YK1zJQ7YQCaCjcmLskuiGM20SoteYserDADg0SrLw+8B8hA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53", - "@babel/helper-regex": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1", + "@babel/helper-regex": "7.0.0-rc.1" } }, "@babel/plugin-transform-template-literals": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-beta.53.tgz", - "integrity": "sha1-+msLQXEA0j4tsUwd9HorGzl48dk=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.0.0-rc.1.tgz", + "integrity": "sha512-xq9eSNA65VXbMmVEjKUXB0czP8y/CRs88S8HcwZbJ7XGo4FARUJV3aGQfIPvGUmbkQegsxZx5rlTPlw3NPl+Aw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "7.0.0-beta.53", - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-annotate-as-pure": "7.0.0-rc.1", + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-beta.53.tgz", - "integrity": "sha1-ZarocamqQPYRSDZlcxIJrr1cKis=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.0.0-rc.1.tgz", + "integrity": "sha512-wUKNscuv3WOOFy3tGOBeayeOLyZjixjOSvb0QNXrCDRuENhfPaFQjZt/T0UDAZN0mXvAQ7Ksx2pOtXBsyIBxUA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53" + "@babel/helper-plugin-utils": "7.0.0-rc.1" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-beta.53.tgz", - "integrity": "sha1-CvdOyAGefVnji+ZNt/YikZQv7SU=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.0.0-rc.1.tgz", + "integrity": "sha512-3yz7ehk0VFLqoKVV1GbTdH2sfMtYznhllkBDtnybveM6MeFA5WYCf6iWf+I/vF/8QIMDd1b4359GGWKCI+KuIQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "7.0.0-beta.53", - "@babel/helper-regex": "7.0.0-beta.53", + "@babel/helper-plugin-utils": "7.0.0-rc.1", + "@babel/helper-regex": "7.0.0-rc.1", "regexpu-core": "^4.1.3" } }, "@babel/template": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.53.tgz", - "integrity": "sha1-MyIpCQDQsYewpxdDgeHzu3EFDS4=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-rc.1.tgz", + "integrity": "sha512-gPLng2iedNlkaGD0UdwaUByQXK8k4bnaoq2RH5JgR2mqHvh2RyjkDdaMbZFlSss1Iu8+PrXwbIRworTl8iRqbA==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.53", - "@babel/parser": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53", - "lodash": "^4.17.5" + "@babel/code-frame": "7.0.0-rc.1", + "@babel/parser": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1", + "lodash": "^4.17.10" } }, "@babel/traverse": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.53.tgz", - "integrity": "sha1-ANMs2NC1j0wB0xFXvmIsZigm00Q=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-rc.1.tgz", + "integrity": "sha512-lNOpJ5xzakg+fCobQQHdeDRYeN54b+bAZpeTYMeeYPAvN+hTldg9/FSNKYEMRs5EWoQ0Yt74gwq98InSORdSDQ==", "dev": true, "requires": { - "@babel/code-frame": "7.0.0-beta.53", - "@babel/generator": "7.0.0-beta.53", - "@babel/helper-function-name": "7.0.0-beta.53", - "@babel/helper-split-export-declaration": "7.0.0-beta.53", - "@babel/parser": "7.0.0-beta.53", - "@babel/types": "7.0.0-beta.53", + "@babel/code-frame": "7.0.0-rc.1", + "@babel/generator": "7.0.0-rc.1", + "@babel/helper-function-name": "7.0.0-rc.1", + "@babel/helper-split-export-declaration": "7.0.0-rc.1", + "@babel/parser": "7.0.0-rc.1", + "@babel/types": "7.0.0-rc.1", "debug": "^3.1.0", "globals": "^11.1.0", - "invariant": "^2.2.0", - "lodash": "^4.17.5" + "lodash": "^4.17.10" } }, "@babel/types": { - "version": "7.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.53.tgz", - "integrity": "sha1-GaRhwNpRVZXftnQLS0Xce7Dms3U=", + "version": "7.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-rc.1.tgz", + "integrity": "sha512-MBwO1JQKin9BwKTGydrYe4VDJbStCUy35IhJzeZt3FByOdx/q3CYaqMRrH70qVD2RA7+Xk8e3RN0mzKZkYBYuQ==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.5", + "lodash": "^4.17.10", "to-fast-properties": "^2.0.0" } }, @@ -958,9 +876,9 @@ "dev": true }, "@polymer/polymer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.0.2.tgz", - "integrity": "sha512-ow8AAjTe9ps8bantY9IvL0PT+xHf5VN3Cjahfr7gBJAc0lv3jTwGBv7pso65SHyrUJEEHeakhx6iPMl7qY4tfw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.0.5.tgz", + "integrity": "sha512-Zbmhtr5vZ3NoHWwFYLKI4ff7yfE6DZopI8vaS7HvmUIuNqsv/EpEDXfNEYjqePQmkMX5LU9OIKV1eX/+9aveow==", "dev": true, "requires": { "@webcomponents/shadycss": "^1.2.0" @@ -973,9 +891,9 @@ "dev": true }, "@polymer/test-fixture": { - "version": "3.0.0-pre.19", - "resolved": "https://registry.npmjs.org/@polymer/test-fixture/-/test-fixture-3.0.0-pre.19.tgz", - "integrity": "sha512-oyltfPEEPF8gzLxSv/CjieO9n0Uani567pcpmAk3IZeOMsFh9OFddZDYCwKx1e9I6lFe9557MaxQHM7EGhLaOQ==", + "version": "3.0.0-pre.21", + "resolved": "https://registry.npmjs.org/@polymer/test-fixture/-/test-fixture-3.0.0-pre.21.tgz", + "integrity": "sha512-IxzUe6YzaORzUksafHAXHprV29YncOJgr0+1zNAifl0/f+cb5iAd4IWUrnsnVFHG5UGTLjvis5RgV6vvIZPDrA==", "dev": true }, "@types/acorn": { @@ -1021,9 +939,9 @@ } }, "@types/bluebird": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.21.tgz", - "integrity": "sha512-6UNEwyw+6SGMC/WMI0ld0PS4st7Qq51qgguFrFizOSpGvZiqe9iswztFSdZvwJBEhLOy2JaxNE6VC7yMAlbfyQ==", + "version": "3.5.23", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.23.tgz", + "integrity": "sha512-xlehmc6RT+wMEhy9ZqeqmozVmuFzTfsaV2NlfFFWhigy7n6sjMbUUB+SZBWK78lZgWHA4DBAdQvQxUvcB8N1tw==", "dev": true }, "@types/body-parser": { @@ -1193,9 +1111,9 @@ } }, "@types/handlebars": { - "version": "4.0.38", - "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.38.tgz", - "integrity": "sha512-oMzU0D7jDp+H2go/i0XqBHfr+HEhYD/e1TvkhHi3yrhQm/7JFR8FJMdvoH76X8G1FBpgc6Pwi+QslCJBeJ1N9g==", + "version": "4.0.39", + "resolved": "https://registry.npmjs.org/@types/handlebars/-/handlebars-4.0.39.tgz", + "integrity": "sha512-vjaS7Q0dVqFp85QhyPSZqDKnTTCemcSHNHFvDdalO1s0Ifz5KuE64jQD5xoUkfdWwF4WpqdJEl7LsWH8rzhKJA==", "dev": true }, "@types/highlight.js": { @@ -1229,9 +1147,9 @@ "optional": true }, "@types/lodash": { - "version": "4.14.109", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.109.tgz", - "integrity": "sha512-hop8SdPUEzbcJm6aTsmuwjIYQo1tqLseKCM+s2bBqTU2gErwI4fE+aqUVOlscPSQbKHKgtMMPoC+h4AIGOJYvw==", + "version": "4.14.116", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz", + "integrity": "sha512-lRnAtKnxMXcYYXqOiotTmJd74uawNWuPnsnPrrO7HiFuE3npE2iQhfABatbYDyxTNqZNuXzcKGhw37R7RjBFLg==", "dev": true }, "@types/marked": { @@ -1253,9 +1171,9 @@ "dev": true }, "@types/mocha": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.4.tgz", - "integrity": "sha512-XMHApnKWI0jvXU5gLcSTsRjJBpSzP0BG+2oGv98JFyS4a5R0tRy0oshHBRndb3BuHb9AwDKaUL8Ja7GfUvsG4g==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz", + "integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==", "dev": true }, "@types/mz": { @@ -1269,9 +1187,9 @@ } }, "@types/node": { - "version": "10.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.3.3.tgz", - "integrity": "sha512-/gwCgiI2e9RzzZTKbl+am3vgNqOt7a9fJ/uxv4SqYKxenoEDNVU3KZEadlpusWhQI0A0dOrZ0T68JYKVjzmgdQ==", + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.7.0.tgz", + "integrity": "sha512-dmYIvoQEZWnyQfgrwPCoxztv/93NYQGEiOoQhuI56rJahv9de6Q2apZl3bufV46YJ0OAXdaktIuw4RIRl4DTeA==", "dev": true }, "@types/opn": { @@ -1361,9 +1279,9 @@ "dev": true }, "@types/uglify-js": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.2.tgz", - "integrity": "sha512-o8hU2+4xsyGC27Vujoklvxl88Ew5zmJuTBYMX1Uro2rYUt4HEFJKL6fuq8aGykvS+ssIsIzerWWP2DRxonownQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.0.3.tgz", + "integrity": "sha512-MAT0BW2ruO0LhQKjvlipLGCF/Yx0y/cj+tT67tK3QIQDrM2+9R78HgJ54VlrE8AbfjYJJBCQCEPM5ZblPVTuww==", "dev": true, "requires": { "source-map": "^0.6.1" @@ -1396,11 +1314,12 @@ } }, "@types/vinyl-fs": { - "version": "2.4.8", - "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-2.4.8.tgz", - "integrity": "sha512-yE2pN9OOrxJVeO7IZLHAHrh5R4Q0osbn5WQRuQU6GdXoK7dNFrMK3K7YhATkzf3z0yQBkol3+gafs7Rp0s7dDg==", + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-2.4.9.tgz", + "integrity": "sha512-Q0EXd6c1fORjiOuK4ZaKdfFcMyFzJlTi56dqktwaWVLIDAzE49wUs3bKnYbZwzyMWoH+NcMWnRuR73S9A0jnRA==", "dev": true, "requires": { + "@types/events": "*", "@types/glob-stream": "*", "@types/node": "*", "@types/vinyl": "*" @@ -1432,15 +1351,15 @@ } }, "@webcomponents/shadycss": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.3.0.tgz", - "integrity": "sha512-xw8WHLmICuB+BzdFLoKnyfL94+RqYMQSpzqmkEIL4B0CZ8vganMbzhYNC1BN/EoLby4TbuS4YVTt8vydwbtGPQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.4.0.tgz", + "integrity": "sha512-/OK+M06S9YbnTXAHm1QtHhONo0msBej/V84x3ZpSV+sJa1jp/FfOv0mW0IWdluU94QlVrPnJxK2t+Czggfivig==", "dev": true }, "@webcomponents/webcomponentsjs": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.0.2.tgz", - "integrity": "sha512-+3515t7jnWsx+mNRECwXbjXBM0DLCNB4uH4DSIbu7akIJn0tO2GZuLvZeBhXbRFQLfohN+2ZTWT44A6EtPhVqA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.0.4.tgz", + "integrity": "sha512-wzPSTmwjAd/0oKW36Yi+cB/BmDrHhcHqGlbqqMjrbPIFkt5Mw7wtvEZQouCrQyBNnRquUhRnSWIBrRijHNBBKg==", "dev": true }, "accepts": { @@ -1738,10 +1657,13 @@ "dev": true }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -1792,9 +1714,9 @@ "dev": true }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, "babel-code-frame": { @@ -2267,8 +2189,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.0.tgz", "integrity": "sha1-o5mS1yNYSBGYK+XikLtqU9hnAPE=", - "dev": true, - "optional": true + "dev": true }, "base64id": { "version": "1.0.0", @@ -2476,6 +2397,16 @@ "https-proxy-agent": "^2.2.1" } }, + "buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.0.tgz", + "integrity": "sha512-nUJyfChH7PMJy75eRDCCKtszSEFokUNXC1hNVSe+o+VdcgvDPLs20k3v8UXI8ruRYAJiYtyRea8mYyqPxoHWDw==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", @@ -2505,9 +2436,9 @@ "dev": true }, "buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, "builtin-modules": { @@ -2679,9 +2610,9 @@ "dev": true }, "ci-info": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", - "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.2.0.tgz", + "integrity": "sha512-U4aoLsSz44FhOyZ2E7bCufaBr2IUzNYujBd+b9vHiFH7SUzIhKcD94PQP5QSFn7ngPof6OF2yPk4/hygqwMJhA==", "dev": true }, "class-utils": { @@ -2758,9 +2689,9 @@ } }, "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, "clone-stats": { @@ -2909,28 +2840,20 @@ "dev": true, "requires": { "mime-db": ">= 1.34.0 < 2" - }, - "dependencies": { - "mime-db": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.34.0.tgz", - "integrity": "sha1-RS0Oz/XDA0am3B5kseruDTcZ/5o=", - "dev": true - } } }, "compression": { - "version": "1.7.2", - "resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz", - "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", + "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", "dev": true, "requires": { - "accepts": "~1.3.4", + "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.13", + "compressible": "~2.0.14", "debug": "2.6.9", "on-headers": "~1.0.1", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "vary": "~1.1.2" }, "dependencies": { @@ -2942,6 +2865,12 @@ "requires": { "ms": "2.0.0" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true } } }, @@ -3068,10 +2997,13 @@ }, "dependencies": { "crc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.5.0.tgz", - "integrity": "sha1-mLi6fUiWZbo5efWbITgTdBAaGWQ=", - "dev": true + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "requires": { + "buffer": "^5.1.0" + } }, "readable-stream": { "version": "2.3.6", @@ -3450,13 +3382,14 @@ } }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "ee-first": { @@ -3580,9 +3513,9 @@ } }, "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esutils": { @@ -3858,9 +3791,9 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, "extend-shallow": { @@ -4106,9 +4039,9 @@ "dev": true }, "follow-redirects": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.1.tgz", - "integrity": "sha512-v9GI1hpaqq1ZZR6pBD1+kI7O24PhDvNGNodjS3MdcEqyrahCp8zbtpv+2B/krUnSmUH80lbAS7MrdeK5IylgKg==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.5.tgz", + "integrity": "sha512-GHjtHDlY/ehslqv0Gr5N0PUJppgg/q0rOBvX0na1s7y1A3LWxPqCYU76s3Z1bM4+UZB4QF0usaXLT5wFpof5PA==", "dev": true, "requires": { "debug": "^3.1.0" @@ -4789,9 +4722,9 @@ } }, "html-minifier": { - "version": "3.5.18", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.18.tgz", - "integrity": "sha512-sczoq/9zeXiKZMj8tsQzHJE7EyjrpMHvblTLuh9o8h5923a6Ts5uQ/3YdY+xIqJYRjzHQPlrHjfjh0BtwPJG0g==", + "version": "3.5.19", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.19.tgz", + "integrity": "sha512-Qr2JC9nsjK8oCrEmuB430ZIA8YWbF3D5LSjywD75FTuXmeqacwHgIM8wp3vHYzzPbklSjp53RdmDuzR4ub2HzA==", "dev": true, "requires": { "camel-case": "3.0.x", @@ -4816,9 +4749,9 @@ "dev": true }, "uglify-js": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.4.tgz", - "integrity": "sha512-RiB1kNcC9RMyqwRrjXC+EjgLoXULoDnCaOnEDzUCHkBN0bHwmtF5rzDMiDWU29gu0kXCRRWwtcTAVFWRECmU2Q==", + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.7.tgz", + "integrity": "sha512-J0M2i1mQA+ze3EdN9SBi751DNdAXmeFLfJrd/MDIkRc3G3Gbb9OPVSx7GIQvVwfWxQARcYV2DTxIkMyDAk3o9Q==", "dev": true, "requires": { "commander": "~2.16.0", @@ -4983,6 +4916,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==", + "dev": true + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -5054,9 +4993,9 @@ } }, "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", "dev": true }, "is-accessor-descriptor": { @@ -5921,18 +5860,18 @@ "dev": true }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "version": "1.35.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz", + "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", "dev": true }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz", + "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.35.0" } }, "minimalistic-assert": { @@ -6529,9 +6468,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-to-regexp": { @@ -6635,9 +6574,9 @@ } }, "polymer-analyzer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/polymer-analyzer/-/polymer-analyzer-3.0.2.tgz", - "integrity": "sha512-a8g+60VagUqmzWttG+/7BBGJiQLdLTIpFzhHp8UVcAitq/Z3ZywWaEGP5C9VEtALRCruVYue+dAsGvxHhNBdJw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/polymer-analyzer/-/polymer-analyzer-3.1.0.tgz", + "integrity": "sha512-TW+SE+dofxSU6+7vJeH//Znjtu1brYE+J4w6M8W+BwoC3FHtBonxp4zCQFf+RnhQGcKIrfHVAxgujzccXer19Q==", "dev": true, "requires": { "@babel/generator": "^7.0.0-beta.42", @@ -6687,9 +6626,9 @@ "dev": true }, "@types/node": { - "version": "9.6.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", - "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", + "version": "9.6.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", + "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", "dev": true }, "chalk": { @@ -6797,9 +6736,9 @@ } }, "@types/node": { - "version": "9.6.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", - "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", + "version": "9.6.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", + "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", "dev": true }, "@types/resolve": { @@ -6862,9 +6801,9 @@ }, "dependencies": { "@types/node": { - "version": "9.6.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", - "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", + "version": "9.6.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", + "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", "dev": true } } @@ -6912,9 +6851,9 @@ }, "dependencies": { "@types/node": { - "version": "9.6.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", - "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", + "version": "9.6.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", + "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", "dev": true }, "debug": { @@ -7012,13 +6951,13 @@ "dev": true }, "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", "dev": true, "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" + "ipaddr.js": "1.8.0" } }, "pseudomap": { @@ -7047,9 +6986,9 @@ "dev": true }, "randomatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", - "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", + "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", "dev": true, "requires": { "is-number": "^4.0.0", @@ -7372,9 +7311,9 @@ "dev": true }, "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { "path-parse": "^1.0.5" @@ -8690,15 +8629,15 @@ "dev": true }, "tslib": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz", - "integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", "dev": true }, "tslint": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz", - "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=", + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", + "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -8712,13 +8651,13 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.12.1" + "tsutils": "^2.27.2" } }, "tsutils": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", - "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -8862,16 +8801,6 @@ "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", "dev": true }, - "underscore.string": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz", - "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=", - "dev": true, - "requires": { - "sprintf-js": "^1.0.3", - "util-deprecate": "^1.0.2" - } - }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -8955,9 +8884,9 @@ } }, "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true }, "unpipe": { @@ -9067,21 +8996,10 @@ } }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "util": { "version": "0.11.0", @@ -9117,9 +9035,9 @@ "dev": true }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -9414,9 +9332,9 @@ }, "dependencies": { "@types/node": { - "version": "9.6.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", - "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", + "version": "9.6.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", + "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", "dev": true, "optional": true } @@ -9439,9 +9357,9 @@ } }, "wd": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/wd/-/wd-1.10.1.tgz", - "integrity": "sha512-5qkDXM8+oRGu0LovGM6iw2Fo6YJfZBJHOGVC0eDi7DK0BVzbXODCUqonHGmOxsBV9BvaSWWQJtnrcjo8Bq6WjQ==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/wd/-/wd-1.10.3.tgz", + "integrity": "sha512-ffqqZDtFFLeg5u/4pw2vYKECW+z+vW6vc+7rcqF15uu1/rmw3BydV84BONNc9DIcQ5Z7gQFS/hAuMvj53eVtSg==", "dev": true, "requires": { "archiver": "2.1.1", @@ -9450,7 +9368,6 @@ "mkdirp": "^0.5.1", "q": "1.4.1", "request": "2.85.0", - "underscore.string": "3.3.4", "vargs": "0.1.0" }, "dependencies": { @@ -9545,9 +9462,9 @@ "dev": true }, "@webcomponents/webcomponentsjs": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-1.2.2.tgz", - "integrity": "sha512-VQlEKZwJFBz4x7VwYdZYeCNYvF39hJHoaGKfcKnv6u01tkXK9c0UCl1Zx4yBrMF+H1+rFvX6PLzDLFgUvZagmQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-1.2.4.tgz", + "integrity": "sha512-JiratNkqWceEsC8Y/IgSR5NvzUFjiUj7K489YU8CP6a9QyKNNFdCZv06tht2uJfAomuXOgXuXktNhD0VtH9v9A==", "dev": true }, "async": { diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 95b664d8..8e97e8cd 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -293,9 +293,9 @@ export abstract class UpdatingElement extends HTMLElement { private _validationState: ValidationState = disabled; private _isReflectingProperty: boolean = false; - private _instanceProperties: PropertyValues|undefined; - private _validatePromise: Promise|undefined; - private _validateResolver: (() => void)|undefined; + private _instanceProperties: PropertyValues|undefined = undefined; + private _validatePromise: Promise|undefined = undefined; + private _validateResolver: (() => void)|undefined = undefined; private _firstUpdateFinished: boolean = false; /** @@ -462,11 +462,11 @@ export abstract class UpdatingElement extends HTMLElement { this._instanceProperties = undefined; } // Rip off changedProps. - const changedProps = this._changedProperties || {}; - this._changedProperties = undefined; + const changedProps = this._changedProperties || (this._changedProperties = {}); if (this.shouldUpdate(changedProps)) { // During update (which is abstract), setting properties does not trigger invalidation. this.update(changedProps); + this._changedProperties = undefined; this._validationState = valid; if (!this._firstUpdateFinished) { this._firstUpdateFinished = true; @@ -486,12 +486,15 @@ export abstract class UpdatingElement extends HTMLElement { } } } else { + this._changedProperties = undefined; this._validationState = valid; } // Only resolve the promise if we finish in a valid state (finishUpdate - // did not trigger more work). - if (this._validationState === valid) { - this._validateResolver!(); + // did not trigger more work). Note, if invalidate is triggered multiple + // times in `finishUpdate`, only the first time will resolve the promise + // by calling `_validateResolver`. This is why we guard for its existence. + if (this._validationState === valid && typeof this._validateResolver === 'function') { + this._validateResolver(); this._validateResolver = undefined; } return this._validatePromise; diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 61dd0013..63f92aa5 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -802,6 +802,56 @@ suite('LitElement', () => { assert.equal(el.shadowRoot!.textContent, '6'); }); + test('setting properties in update reflects to attribute and is included in `changedProps` passed to `finishUpdate`', async () => { + class E extends LitElement { + + static get properties() { + return { + foo: {}, + bar: {}, + zot: {reflect: true} + }; + } + + changedProperties = {}; + + update(changedProperties: PropertyValues) { + (this as any).zot = (this as any).foo + (this as any).bar; + super.update(changedProperties); + } + + finishUpdate(changedProperties: PropertyValues) { + this.changedProperties = changedProperties; + } + + render() { + return html``; + } + + } + customElements.define(generateElementName(), E); + const el = new E() as any; + container.appendChild(el); + await el.updateComplete; + assert.deepEqual(el.changedProperties, {zot: undefined}); + assert.isNaN(el.zot); + assert.equal(el.getAttribute('zot'), 'NaN'); + el.bar = 1; + el.foo = 1; + await el.updateComplete; + assert.equal(el.foo, 1); + assert.equal(el.bar, 1); + assert.equal(el.zot, 2); + assert.deepEqual(el.changedProperties, {foo: undefined, bar: undefined, zot: NaN}); + assert.equal(el.getAttribute('zot'), '2'); + el.bar = 2; + await el.updateComplete; + assert.equal(el.bar, 2); + assert.equal(el.zot, 3); + assert.deepEqual(el.changedProperties, {bar: 1, zot: 2}); + assert.equal(el.getAttribute('zot'), '3'); + }); + test('setting properties in finishUpdate does trigger invalidation blocks updateComplete', async () => { class E extends LitElement { From 8160de88abadef9b32b5079f443121bc0d242fe0 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 14 Aug 2018 15:29:10 -0700 Subject: [PATCH 23/65] Add notes about limited Symbol support and skipped test with linked issue. --- src/lib/updating-element.ts | 6 ++++ src/test/lit-element_test.ts | 57 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 8e97e8cd..fe3d4656 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -229,6 +229,12 @@ export abstract class UpdatingElement extends HTMLElement { makeProperty(p, this.prototype); } // support symbols in properties (IE11 does not support this) + // TODO(sorvell): Currently it's not supported to have property options + // for Symbol properties. To do so would likely require changing + // internal storage to Maps for easier iteration. In particular, `_classProperties` + // and `update(changedProperties)` would both be Maps. + // Need to consider if this is worth doing. See + // https://github.com/Polymer/lit-element/issues/146. if (typeof Object.getOwnPropertySymbols === 'function') { Object.getOwnPropertySymbols(props).forEach((p) => makeProperty(p, this.prototype)); } diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 63f92aa5..ed9c7d0a 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -335,6 +335,63 @@ suite('LitElement', () => { assert.equal(el[zug], 66); }); + + // TODO(sorvell): Skipping since this is not currently supported, see: + // https://github.com/Polymer/lit-element/issues/146 + test.skip('properties as symbols can set property options', async() => { + + const zug = Symbol(); + + class E extends LitElement { + + static get properties() { + return { + foo: {}, + [zug]: {attribute: 'zug', reflect: true, fromAttribute: (value: string) => Number(value) + 100} + }; + } + + constructor() { + super(); + (this as any).updated = 0; + (this as any).foo = 5; + (this as any)[zug] = 6; + } + + render() { + return html``; + } + + update(changedProperties: PropertyValues) { + (this as any).updated++; + super.update(changedProperties); + } + + } + customElements.define(generateElementName(), E); + const el = new E() as any; + container.appendChild(el); + await el.updateComplete; + assert.equal(el.updated, 1); + assert.equal(el.foo, 5); + assert.equal(el[zug], 6); + assert.equal(el.getAttribute('zug'), '6'); + el.foo = 55; + await el.updateComplete; + assert.equal(el.updated, 2); + assert.equal(el.foo, 55); + assert.equal(el[zug], 6); + el[zug] = 66; + await el.updateComplete; + assert.equal(el.updated, 3); + assert.equal(el.foo, 55); + assert.equal(el[zug], 66); + assert.equal(el.getAttribute('zug'), '66'); + el.setAttribute('zug', '10'); + await el.updateComplete; + assert.equal(el[zug], 110); + + }); } From e4c7d9143c4c60dee4fa856fccfc8048d0c539ea Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 14 Aug 2018 15:53:31 -0700 Subject: [PATCH 24/65] Removed getProperty/setProperty This was deemed unnecessary meta-programming. For manual accessor creation, users can manage the value manually and call `invalidate` as an alternative to `set/getProperty`. --- README.md | 5 +- src/lib/updating-element.ts | 97 +++++++++++++++--------------------- src/test/lit-element_test.ts | 12 ++--- 3 files changed, 50 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index 321f8657..1b14ce49 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ and renders declaratively using `lit-html`. * As class fields with the `@property()` [decorator](https://github.com/tc39/proposal-decorators#decorators), if you're using a compiler that supports them, like TypeScript or Babel. * With a static `properties` getter. - * By manually writing getters and setters. You can call `setProperty` or `invalidate` - to trigger an update. If you use `setProperty`, you should use `getProperty` in the getter. + * By manually writing getters and setters. This can be useful if tasks should + be performed when a property is set, for example validation. You can call `invalidate()` + in the setter to trigger an update. Properties can be given an options argument which is an object that describes how to process the property. This can be done either in the `@property({...})` decorator or in the diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index fe3d4656..9a171fd6 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -92,26 +92,6 @@ export interface PropertyValues { [key: string]: unknown; } -/** - * Creates a property accessor on the given prototype if one does not exist. - * Uses `getProperty` and `setProperty` to manage the property's value. - */ -const makeProperty = (name: PropertyKey, proto: Object) => { - if (name in proto) { - return; - } - Object.defineProperty(proto, name, { - get() { - return this.getProperty(name); - }, - set(value) { - this.setProperty(name, value); - }, - configurable: true, - enumerable: true - }); -}; - /** * Creates and sets object used to memoize all class property values. Object * is chained from superclass. @@ -130,7 +110,7 @@ export const property = (options: PropertyDeclaration = {}) => (proto: Object, n const ctor = proto.constructor as typeof UpdatingElement; ensurePropertyStorage(ctor); ctor._classProperties[name] = options; - makeProperty(name, proto); + ctor.createProperty(name); }; @@ -209,6 +189,41 @@ export abstract class UpdatingElement extends HTMLElement { return this._observedAttributes; } + /** + * Creates a property accessor on the element prototype if one does not exist. + * The property setter calls the property's `shouldInvalidate` property option + * or uses a strict identity check to determine if the set should trigger + * invalidation and update. + */ + static createProperty(name: PropertyKey) { + if (name in this.prototype) { + return; + } + const key = typeof name === 'symbol' ? Symbol() : `__${name}`; + Object.defineProperty(this.prototype, name, { + get() { + return this[key]; + }, + set(value) { + const old = this[key]; + const ctor = (this.constructor as typeof UpdatingElement); + if (ctor._propertyShouldInvalidate(name, value, old)) { + // track old value when changing. + if (!this._changedProperties) { + this._changedProperties = {}; + } + if (!(name in this._changedProperties)) { + this._changedProperties[name] = old; + } + this[key] = value; + this.invalidate(); + } + }, + configurable: true, + enumerable: true + }); + } + /** * Creates property accessors for registered properties and ensures * any superclasses are also finalized. @@ -226,7 +241,7 @@ export abstract class UpdatingElement extends HTMLElement { // make any properties const props = this.properties; for (const p in props) { - makeProperty(p, this.prototype); + this.createProperty(p); } // support symbols in properties (IE11 does not support this) // TODO(sorvell): Currently it's not supported to have property options @@ -236,7 +251,7 @@ export abstract class UpdatingElement extends HTMLElement { // Need to consider if this is worth doing. See // https://github.com/Polymer/lit-element/issues/146. if (typeof Object.getOwnPropertySymbols === 'function') { - Object.getOwnPropertySymbols(props).forEach((p) => makeProperty(p, this.prototype)); + Object.getOwnPropertySymbols(props).forEach((p) => this.createProperty(p)); } // initialize map populated in observedAttributes this._attributeToPropertyMap = {}; @@ -260,8 +275,9 @@ export abstract class UpdatingElement extends HTMLElement { * Called when a property value is set and uses the `shouldInvalidate` * option for the property if present or a strict identity check. */ - private static _propertyShouldInvalidate(name: string, value: unknown, old: unknown) { - const info = this._classProperties[name]; + private static _propertyShouldInvalidate(name: PropertyKey, value: unknown, old: unknown) { + // Note, typed using `any` due to TypeScript's lack of support for symbol index keys. + const info = (this._classProperties as any)[name]; const fn = info !== undefined && info.shouldInvalidate || identity; return fn(value, old); } @@ -378,37 +394,6 @@ export abstract class UpdatingElement extends HTMLElement { } } - /** - * Returns the value of the property with `name`. This method is used - * to get property values of registered properties. - */ - protected getProperty(name: string) { - return this._propertyValues[name]; - } - - /** - * Sets the property `name` to the given `value`. This method is used - * to set property values of registered properties. - * Setting a property value calls `invalidate` which asynchronously updates - * the element. If a property value is set inside the `update` method, - * it does not trigger `invalidate`. - */ - protected setProperty(name: string, value: unknown) { - const old = this._propertyValues[name]; - const ctor = (this.constructor as typeof UpdatingElement); - if (ctor._propertyShouldInvalidate(name, value, old)) { - // track old value when changing. - if (!this._changedProperties) { - this._changedProperties = {}; - } - if (!(name in this._changedProperties)) { - this._changedProperties[name] = this._propertyValues[name]; - } - this._propertyValues[name] = value; - this.invalidate(); - } - } - private _propertyToAttribute(name: string, value: unknown) { const ctor = (this.constructor as typeof UpdatingElement); const attrValue = ctor._propertyValueToAttribute(name, value); diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index ed9c7d0a..428a26ee 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -291,7 +291,9 @@ suite('LitElement', () => { }); if (Object.getOwnPropertySymbols) { - test('properties defined using symbols', async() => { + // TODO(sorvell): Skipping symbol tests since support is extremely limited, see: + // https://github.com/Polymer/lit-element/issues/146 + test.skip('properties defined using symbols', async() => { const zug = Symbol(); @@ -336,8 +338,6 @@ suite('LitElement', () => { }); - // TODO(sorvell): Skipping since this is not currently supported, see: - // https://github.com/Polymer/lit-element/issues/146 test.skip('properties as symbols can set property options', async() => { const zug = Symbol(); @@ -690,7 +690,7 @@ suite('LitElement', () => { 'changed'); }); - test('User defined accessor using setProperty/getProperty can trigger update/render', async () => { + test('User defined accessor can trigger update/render', async () => { class E extends LitElement { __bar?: number; @@ -699,11 +699,11 @@ suite('LitElement', () => { info: string[] = []; foo = 0; - get bar() { return this.getProperty('bar'); } + get bar() { return this.__bar; } set bar(value) { this.__bar = Number(value); - this.setProperty('bar', value); + this.invalidate(); } render() { From e6a3c704a76f23470a4214c0db9f914f2298ae52 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 14 Aug 2018 16:23:59 -0700 Subject: [PATCH 25/65] Remove unused `_propertyValues` --- src/lib/updating-element.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 9a171fd6..b574a228 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -320,11 +320,6 @@ export abstract class UpdatingElement extends HTMLElement { private _validateResolver: (() => void)|undefined = undefined; private _firstUpdateFinished: boolean = false; - /** - * Object with keys for all properties with their current values. - */ - private _propertyValues: PropertyValues = {}; - /** * Object with keys for any properties that have changed since the last * update cycle with previous values. From b39b9795ae0cff2b9e40a5794d2501a0248393ab Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 14 Aug 2018 20:18:40 -0700 Subject: [PATCH 26/65] Support properties defined as symbols Fixes #146 Changes the `changedProperties` argument to `shouldUpdate`, `update`, and `finishUpdate` to a Map. --- README.md | 8 ++- src/lib/updating-element.ts | 134 +++++++++++++++++------------------ src/test/lit-element_test.ts | 49 ++++--------- 3 files changed, 88 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 1b14ce49..811e86b9 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- render. By default this creates a shadowRoot for the element. To render into the element's childNodes, return `this`. - * `shouldUpdate(changedProps)` (protected): Implement to control if updating and rendering + * `shouldUpdate(changedProperties)` (protected): Implement to control if updating and rendering should occur when property values change or `invalidate` is called. The `changedProps` argument is an object with keys for the changed properties pointing to their previous values. By default, this method always returns true, but this can be customized as @@ -160,7 +160,7 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- Note, since `render()` is called by `update()` setting properties does not trigger `invalidate()`, allowing property values to be computed and validated. - * `finishUpdate(changedProps): Promise?` (protected): Called after element DOM has been updated and + * `finishUpdate(changedProperties): Promise?` (protected): Called after element DOM has been updated and before the `updateComplete` promise is resolved. Implement to directly control rendered DOM. Typically this is not needed as `lit-html` can be used in the `render` method to set properties, attributes, and event listeners. However, it is sometimes useful @@ -171,6 +171,10 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- Setting properties in `finishUpdate()` does trigger `invalidate()` and blocks the `updateComplete` promise. + * `finishFirstUpdate(changedProperties): Promise?` (protected) Called after element DOM has been + updated the first time. This method can be useful for capturing references to rendered static + nodes that must be directly acted upon, for example in `finishUpdate`. + * `updateComplete`: Returns a promise which resolves after the element next renders. * `invalidate`: Call to request the element to asynchronously update regardless diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index b574a228..7d699e98 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -84,13 +84,11 @@ export interface PropertyDeclarations { [key: string]: PropertyDeclaration; } -interface AttributeMap { - [key: string]: string; -} +type PropertyDeclarationMap = Map; -export interface PropertyValues { - [key: string]: unknown; -} +type AttributeMap = Map; + +export type PropertyValues = Map; /** * Creates and sets object used to memoize all class property values. Object @@ -98,7 +96,7 @@ export interface PropertyValues { */ const ensurePropertyStorage = (ctor: typeof UpdatingElement) => { if (!ctor.hasOwnProperty('_classProperties')) { - ctor._classProperties = Object.create(Object.getPrototypeOf(ctor)._classProperties); + ctor._classProperties = new Map(Object.getPrototypeOf(ctor)._classProperties); } }; @@ -109,7 +107,7 @@ const ensurePropertyStorage = (ctor: typeof UpdatingElement) => { export const property = (options: PropertyDeclaration = {}) => (proto: Object, name: string) => { const ctor = proto.constructor as typeof UpdatingElement; ensurePropertyStorage(ctor); - ctor._classProperties[name] = options; + ctor._classProperties.set(name, options); ctor.createProperty(name); }; @@ -150,7 +148,7 @@ export abstract class UpdatingElement extends HTMLElement { * Maps attribute names to properties; for example `foobar` attribute * to `fooBar` property. */ - private static _attributeToPropertyMap: AttributeMap = {}; + private static _attributeToPropertyMap: AttributeMap = new Map(); /** * Marks class as having finished creating properties. @@ -166,7 +164,7 @@ export abstract class UpdatingElement extends HTMLElement { /** * Memoized list of all class properties, including any superclass properties. */ - static _classProperties: PropertyDeclarations = {}; + static _classProperties: PropertyDeclarationMap = new Map(); static properties: PropertyDeclarations = {}; @@ -178,10 +176,10 @@ export abstract class UpdatingElement extends HTMLElement { // note: piggy backing on this to ensure we're _finalized. this._finalize(); this._observedAttributes = []; - for (const p in this._classProperties) { + for (const p of this._classProperties.keys()) { const attr = this._attributeNameForProperty(p); if (attr !== undefined) { - this._attributeToPropertyMap[attr] = p; + this._attributeToPropertyMap.set(attr, p); this._observedAttributes.push(attr); } } @@ -209,11 +207,8 @@ export abstract class UpdatingElement extends HTMLElement { const ctor = (this.constructor as typeof UpdatingElement); if (ctor._propertyShouldInvalidate(name, value, old)) { // track old value when changing. - if (!this._changedProperties) { - this._changedProperties = {}; - } - if (!(name in this._changedProperties)) { - this._changedProperties[name] = old; + if (!this._changedProperties.has(name)) { + this._changedProperties.set(name, old); } this[key] = value; this.invalidate(); @@ -238,36 +233,29 @@ export abstract class UpdatingElement extends HTMLElement { superCtor._finalize(); } this._finalized = true; + ensurePropertyStorage(this); + // initialize map populated in observedAttributes + this._attributeToPropertyMap = new Map(); // make any properties const props = this.properties; - for (const p in props) { - this.createProperty(p); - } // support symbols in properties (IE11 does not support this) - // TODO(sorvell): Currently it's not supported to have property options - // for Symbol properties. To do so would likely require changing - // internal storage to Maps for easier iteration. In particular, `_classProperties` - // and `update(changedProperties)` would both be Maps. - // Need to consider if this is worth doing. See - // https://github.com/Polymer/lit-element/issues/146. - if (typeof Object.getOwnPropertySymbols === 'function') { - Object.getOwnPropertySymbols(props).forEach((p) => this.createProperty(p)); + const propKeys = [...Object.getOwnPropertyNames(props), + ...(typeof Object.getOwnPropertySymbols === 'function') ? Object.getOwnPropertySymbols(props) : []]; + for (const p of propKeys) { + this.createProperty(p); + // note, use of `any` is due to TypeSript lack of support for symbol in index types + this._classProperties.set(p, (props as any)[p]); } - // initialize map populated in observedAttributes - this._attributeToPropertyMap = {}; - // memoize list of all class properties. - ensurePropertyStorage(this); - Object.assign(this._classProperties, props); } /** * Returns the property name for the given attribute `name`. */ - private static _attributeNameForProperty(name: string) { - const info = this._classProperties[name]; + private static _attributeNameForProperty(name: PropertyKey) { + const info = this._classProperties.get(name); const attribute = info !== undefined && info.attribute; return attribute === false ? undefined : (typeof attribute === 'string' ? - attribute : name.toLowerCase()); + attribute : (typeof name === 'string' ? name.toLowerCase() : undefined)); } /** @@ -276,8 +264,7 @@ export abstract class UpdatingElement extends HTMLElement { * option for the property if present or a strict identity check. */ private static _propertyShouldInvalidate(name: PropertyKey, value: unknown, old: unknown) { - // Note, typed using `any` due to TypeScript's lack of support for symbol index keys. - const info = (this._classProperties as any)[name]; + const info = this._classProperties.get(name); const fn = info !== undefined && info.shouldInvalidate || identity; return fn(value, old); } @@ -287,8 +274,8 @@ export abstract class UpdatingElement extends HTMLElement { * Called via the `attributeChangedCallback` and uses the property's `type` * or `type.fromAttribute` property option. */ - private static _propertyValueFromAttribute(name: string, value: string) { - const info = this._classProperties[name]; + private static _propertyValueFromAttribute(name: PropertyKey, value: string) { + const info = this._classProperties.get(name); const type = info && info.type; if (type === undefined) { return value; @@ -304,8 +291,8 @@ export abstract class UpdatingElement extends HTMLElement { * attribute will be set to the value. * This uses the property's `reflect` and `type.toAttribute` property options. */ - private static _propertyValueToAttribute(name: string, value: unknown) { - const info = this._classProperties[name]; + private static _propertyValueToAttribute(name: PropertyKey, value: unknown) { + const info = this._classProperties.get(name); if (info === undefined || info.reflect === undefined) { return; } @@ -324,7 +311,7 @@ export abstract class UpdatingElement extends HTMLElement { * Object with keys for any properties that have changed since the last * update cycle with previous values. */ - private _changedProperties?: PropertyValues|undefined; + private _changedProperties: PropertyValues = new Map(); /** * Node or ShadowRoot into which element DOM should be rendered. Defaults @@ -338,23 +325,25 @@ export abstract class UpdatingElement extends HTMLElement { } /** - * Performs element initialization. By default this calls `createRoot` to - * create the element `root` node and captures any pre-set values for + * Performs element initialization. By default this calls `createRenderRoot` to + * create the element `renderRoot` node and captures any pre-set values for * registered properties. */ protected initialize() { this.renderRoot = this.createRenderRoot(); // Apply any properties set on the instance before upgrade time. - for (const p in (this.constructor as typeof UpdatingElement)._classProperties) { + for (const p of (this.constructor as typeof UpdatingElement)._classProperties.keys()) { if (this.hasOwnProperty(p)) { const value = this[p as keyof this]; delete this[p as keyof this]; - this._instanceProperties = this._instanceProperties || {}; - // NOTE: must capture these into a bag and reset at when validating + if (!this._instanceProperties) { + this._instanceProperties = new Map(); + } + // NOTE: must capture these into a map and reset at when validating // to avoid stomping on a user value set in the constructor. Being // async doesn't help here since the subclass' constructor value should // be overwritten. - this._instanceProperties![p] = value; + this._instanceProperties.set(p, value); } } } @@ -389,7 +378,7 @@ export abstract class UpdatingElement extends HTMLElement { } } - private _propertyToAttribute(name: string, value: unknown) { + private _propertyToAttribute(name: PropertyKey, value: unknown) { const ctor = (this.constructor as typeof UpdatingElement); const attrValue = ctor._propertyValueToAttribute(name, value); if (attrValue !== undefined) { @@ -418,8 +407,10 @@ export abstract class UpdatingElement extends HTMLElement { // just set from a property setter. if (!this._isReflectingProperty) { const ctor = (this.constructor as typeof UpdatingElement); - const propName = ctor._attributeToPropertyMap[name]; - this[propName as keyof this] = ctor._propertyValueFromAttribute(propName, value); + const propName = ctor._attributeToPropertyMap.get(name); + if (propName !== undefined) { + this[propName as keyof this] = ctor._propertyValueFromAttribute(propName, value); + } } } @@ -444,35 +435,44 @@ export abstract class UpdatingElement extends HTMLElement { await 0; // Mixin instance properties once, if they exist. if (this._instanceProperties) { - Object.assign(this, this._instanceProperties); + for (const [p, v] of this._instanceProperties) { + (this as any)[p] = v; + } this._instanceProperties = undefined; } - // Rip off changedProps. - const changedProps = this._changedProperties || (this._changedProperties = {}); - if (this.shouldUpdate(changedProps)) { - // During update (which is abstract), setting properties does not trigger invalidation. - this.update(changedProps); - this._changedProperties = undefined; + if (this.shouldUpdate(this._changedProperties)) { + // During update, setting properties does not trigger invalidation. + this.update(this._changedProperties); + // copy changedProperties to hand to finishUpdate. + let changedProperties; + const hasFinishUpdate = (typeof this.finishUpdate === 'function'); + // clone changedProperties before resetting only if needed for finishUpdate. + if (hasFinishUpdate) { + changedProperties = new Map(this._changedProperties); + } + this._changedProperties.clear(); this._validationState = valid; if (!this._firstUpdateFinished) { this._firstUpdateFinished = true; if (typeof this.finishFirstUpdate === 'function') { + // During `finishFirstUpdate` (which is optional), setting properties triggers invalidation, + // and users may choose to await other state. const result = this.finishFirstUpdate(); if (result != null && typeof (result as PromiseLike).then === 'function') { await result; } } } - // During finishUpdate (which is abstract), setting properties does trigger invalidation, + // During `finishUpdate` (which is optional), setting properties triggers invalidation, // and users may choose to await other state, like children being updated. - if (typeof this.finishUpdate === 'function') { - const result = this.finishUpdate(changedProps); + if (hasFinishUpdate) { + const result = this.finishUpdate!(changedProperties as PropertyValues); if (result != null && typeof (result as PromiseLike).then === 'function') { await result; } } } else { - this._changedProperties = undefined; + this._changedProperties.clear(); this._validationState = valid; } // Only resolve the promise if we finish in a valid state (finishUpdate @@ -501,7 +501,7 @@ export abstract class UpdatingElement extends HTMLElement { * Controls whether or not `update` should be called when the element invalidates. * By default, this method always returns true, but this can be customized to * control when to update. - * * @param _changedProperties changed properties with old values + * * @param _changedProperties Map of changed properties with old values */ protected shouldUpdate(_changedProperties: PropertyValues): boolean { return true; @@ -512,10 +512,10 @@ export abstract class UpdatingElement extends HTMLElement { * It should be implemented to render and keep updated DOM in the element's root. * Note, within `update()` setting properties does not trigger `invalidate()`, allowing * property values to be computed and validated before DOM is rendered and updated. - * * @param _changedProperties changed properties with old values + * * @param _changedProperties Map of changed properties with old values */ protected update(_changedProperties: PropertyValues): void { - for (const name in _changedProperties) { + for (const name of _changedProperties.keys()) { this._propertyToAttribute(name, (this as any)[name]); } } @@ -531,7 +531,7 @@ export abstract class UpdatingElement extends HTMLElement { * properties triggers invalidate and update. * (2) The `updateComplete` promise should block on the `updateComplete` promise * of a rendered `UpdatingElement`. - * * @param _changedProperties changed properties with old values + * * @param _changedProperties Map of changed properties with old values * * @returns {Promise} Optionally, this function can return a promise that blocks * resolution of the `invalidate` and updateComplete` promise. */ diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 428a26ee..a837d872 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -291,9 +291,7 @@ suite('LitElement', () => { }); if (Object.getOwnPropertySymbols) { - // TODO(sorvell): Skipping symbol tests since support is extremely limited, see: - // https://github.com/Polymer/lit-element/issues/146 - test.skip('properties defined using symbols', async() => { + test('properties defined using symbols', async() => { const zug = Symbol(); @@ -338,7 +336,7 @@ suite('LitElement', () => { }); - test.skip('properties as symbols can set property options', async() => { + test('properties as symbols can set property options', async() => { const zug = Symbol(); @@ -346,51 +344,34 @@ suite('LitElement', () => { static get properties() { return { - foo: {}, - [zug]: {attribute: 'zug', reflect: true, fromAttribute: (value: string) => Number(value) + 100} + [zug]: {attribute: 'zug', reflect: true, type: (value: string) => Number(value) + 100} }; } constructor() { super(); - (this as any).updated = 0; - (this as any).foo = 5; - (this as any)[zug] = 6; + (this as any)[zug] = 5; } render() { return html``; } - update(changedProperties: PropertyValues) { - (this as any).updated++; - super.update(changedProperties); - } - } customElements.define(generateElementName(), E); const el = new E() as any; container.appendChild(el); await el.updateComplete; - assert.equal(el.updated, 1); - assert.equal(el.foo, 5); - assert.equal(el[zug], 6); - assert.equal(el.getAttribute('zug'), '6'); - el.foo = 55; + assert.equal(el[zug], 5); + assert.equal(el.getAttribute('zug'), '5'); + el[zug] = 6; await el.updateComplete; - assert.equal(el.updated, 2); - assert.equal(el.foo, 55); assert.equal(el[zug], 6); - el[zug] = 66; - await el.updateComplete; - assert.equal(el.updated, 3); - assert.equal(el.foo, 55); - assert.equal(el[zug], 66); - assert.equal(el.getAttribute('zug'), '66'); - el.setAttribute('zug', '10'); + assert.equal(el.getAttribute('zug'), '6'); + el.setAttribute('zug', '7'); await el.updateComplete; - assert.equal(el[zug], 110); - + assert.equal(el.getAttribute('zug'), '107'); + assert.equal(el[zug], 107); }); } @@ -870,7 +851,7 @@ suite('LitElement', () => { }; } - changedProperties = {}; + changedProperties: PropertyValues|undefined = undefined; update(changedProperties: PropertyValues) { (this as any).zot = (this as any).foo + (this as any).bar; @@ -890,7 +871,7 @@ suite('LitElement', () => { const el = new E() as any; container.appendChild(el); await el.updateComplete; - assert.deepEqual(el.changedProperties, {zot: undefined}); + assert.deepEqual(el.changedProperties, new Map([['zot', undefined]])); assert.isNaN(el.zot); assert.equal(el.getAttribute('zot'), 'NaN'); el.bar = 1; @@ -899,13 +880,13 @@ suite('LitElement', () => { assert.equal(el.foo, 1); assert.equal(el.bar, 1); assert.equal(el.zot, 2); - assert.deepEqual(el.changedProperties, {foo: undefined, bar: undefined, zot: NaN}); + assert.deepEqual(el.changedProperties, new Map([['foo', undefined], ['bar', undefined], ['zot', NaN]])); assert.equal(el.getAttribute('zot'), '2'); el.bar = 2; await el.updateComplete; assert.equal(el.bar, 2); assert.equal(el.zot, 3); - assert.deepEqual(el.changedProperties, {bar: 1, zot: 2}); + assert.deepEqual(el.changedProperties, new Map([['bar', 1], ['zot', 2]])); assert.equal(el.getAttribute('zot'), '3'); }); From fed3307e445a6981cd3910fa0c6cb411d4c8ea94 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Tue, 14 Aug 2018 20:34:09 -0700 Subject: [PATCH 27/65] Save flags on element by using bitmask Removes `_isReflectingProperty` and `_firstUpdateFinished` flags. --- src/lib/updating-element.ts | 40 ++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 7d699e98..43339493 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -132,10 +132,10 @@ export const identity = (value: unknown, old: unknown) => { return old !== value && (old === old || value === value); }; -const disabled = 0; -const valid = 1; -const invalid = 2; -type ValidationState = typeof disabled | typeof valid | typeof invalid; +const STATE_HAS_UPDATED = 1; +const STATE_IS_VALID = 1 << 2; +const STATE_IS_REFLECTING = 1 << 3; +type ValidationState = typeof STATE_HAS_UPDATED | typeof STATE_IS_VALID | typeof STATE_IS_REFLECTING; /** * Base element class which manages element properties and attributes. When @@ -300,12 +300,10 @@ export abstract class UpdatingElement extends HTMLElement { return (typeof toAttribute === 'function') ? toAttribute(value) : null; } - private _validationState: ValidationState = disabled; - private _isReflectingProperty: boolean = false; + private _validationState: ValidationState = STATE_IS_VALID; private _instanceProperties: PropertyValues|undefined = undefined; private _validatePromise: Promise|undefined = undefined; private _validateResolver: (() => void)|undefined = undefined; - private _firstUpdateFinished: boolean = false; /** * Object with keys for any properties that have changed since the last @@ -363,7 +361,7 @@ export abstract class UpdatingElement extends HTMLElement { * Uses ShadyCSS to keep element DOM updated. */ connectedCallback() { - if (this._validationState !== disabled && typeof window.ShadyCSS !== 'undefined') { + if ((this._validationState & STATE_HAS_UPDATED) && typeof window.ShadyCSS !== 'undefined') { window.ShadyCSS.styleElement(this); } this.invalidate(); @@ -391,13 +389,15 @@ export abstract class UpdatingElement extends HTMLElement { // stack at time of calling. However, since we process attributes // in `update` this should not be possible (or an extreme corner case // that we'd like to discover). - this._isReflectingProperty = true; + // mark state reflecting + this._validationState = this._validationState | STATE_IS_REFLECTING; if (attrValue === null) { this.removeAttribute(attr); } else { this.setAttribute(attr, attrValue); } - this._isReflectingProperty = false; + // mark state not reflecting + this._validationState = this._validationState & ~STATE_IS_REFLECTING; } } } @@ -405,7 +405,7 @@ export abstract class UpdatingElement extends HTMLElement { private _attributeToProperty(name: string, value: string) { // Use tracking info to avoid deserializing attribute value if it was // just set from a property setter. - if (!this._isReflectingProperty) { + if (!(this._validationState & STATE_IS_REFLECTING)) { const ctor = (this.constructor as typeof UpdatingElement); const propName = ctor._attributeToPropertyMap.get(name); if (propName !== undefined) { @@ -425,7 +425,8 @@ export abstract class UpdatingElement extends HTMLElement { if (this._isPendingUpdate) { return this._validatePromise; } - this._validationState = invalid; + // mark state invalid... + this._validationState = this._validationState & ~STATE_IS_VALID; // Make a new promise only if the current one is not pending resolution // (resolver has not been set to undefined) if (this._validateResolver === undefined) { @@ -451,9 +452,11 @@ export abstract class UpdatingElement extends HTMLElement { changedProperties = new Map(this._changedProperties); } this._changedProperties.clear(); - this._validationState = valid; - if (!this._firstUpdateFinished) { - this._firstUpdateFinished = true; + // mark state valid + this._validationState = this._validationState | STATE_IS_VALID; + if (!(this._validationState & STATE_HAS_UPDATED)) { + // mark state has updated + this._validationState = this._validationState | STATE_HAS_UPDATED; if (typeof this.finishFirstUpdate === 'function') { // During `finishFirstUpdate` (which is optional), setting properties triggers invalidation, // and users may choose to await other state. @@ -473,13 +476,14 @@ export abstract class UpdatingElement extends HTMLElement { } } else { this._changedProperties.clear(); - this._validationState = valid; + // mark state valid + this._validationState = this._validationState | STATE_IS_VALID; } // Only resolve the promise if we finish in a valid state (finishUpdate // did not trigger more work). Note, if invalidate is triggered multiple // times in `finishUpdate`, only the first time will resolve the promise // by calling `_validateResolver`. This is why we guard for its existence. - if (this._validationState === valid && typeof this._validateResolver === 'function') { + if ((this._validationState & STATE_IS_VALID) && typeof this._validateResolver === 'function') { this._validateResolver(); this._validateResolver = undefined; } @@ -487,7 +491,7 @@ export abstract class UpdatingElement extends HTMLElement { } private get _isPendingUpdate() { - return this._validationState === invalid; + return !(this._validationState & STATE_IS_VALID); } /** From 5f3cffd87215ca4d9ce6d688bf16fabd727efe78 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 15 Aug 2018 16:28:48 -0700 Subject: [PATCH 28/65] Update package.json --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5851eeec..6a2cf3c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@polymer/lit-element", - "version": "0.6.0-dev.2", + "version": "0.6.0-dev.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5449,9 +5449,9 @@ } }, "lit-html": { - "version": "0.11.0-dev.1", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-0.11.0-dev.1.tgz", - "integrity": "sha512-bTcdm3hJf0vpRESzgpiypsOrDWxwF0RN+kKdmd8phmjs65AwaD3ha31FHLOsVYT6lgPl/niC5RGnaZIrZT2C9Q==" + "version": "0.11.0-dev.2", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-0.11.0-dev.2.tgz", + "integrity": "sha512-TQcVCcTZLvFR5zfLxo+0LcMj4BjQapvo+DWq8ymHoIRuo/MthsyIlwtAkPGbiNsemfSp/7GxhrKcafAj/+q1YA==" }, "load-json-file": { "version": "1.1.0", diff --git a/package.json b/package.json index 47260f9a..22731eb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@polymer/lit-element", - "version": "0.6.0-dev.2", + "version": "0.6.0-dev.3", "description": "Polymer based lit-html custom element", "license": "BSD-3-Clause", "repository": "Polymer/lit-element", From 05ba1e46e95751bc64798d591bab5188c63f7b82 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 09:24:54 -0700 Subject: [PATCH 29/65] Fix IE11 Map support Avoids using Map constructor argument which is not supported on IE11. --- src/lib/updating-element.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 43339493..feabed3c 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -96,7 +96,13 @@ export type PropertyValues = Map; */ const ensurePropertyStorage = (ctor: typeof UpdatingElement) => { if (!ctor.hasOwnProperty('_classProperties')) { - ctor._classProperties = new Map(Object.getPrototypeOf(ctor)._classProperties); + ctor._classProperties = new Map(); + // NOTE: Workaround IE11 not supporting Map constructor argument. + const superProperties = Object.getPrototypeOf(ctor)._classProperties; + if (superProperties !== undefined) { + superProperties.forEach((v: any, k: PropertyKey) => + ctor._classProperties.set(k, v)); + } } }; From 4361daf0deed32e2a036e81955fac7726a1f0f68 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 09:25:32 -0700 Subject: [PATCH 30/65] Update wct package. --- package-lock.json | 178 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 90 insertions(+), 90 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6a2cf3c9..8a8c8b57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1342,12 +1342,12 @@ "optional": true }, "@types/winston": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.3.9.tgz", - "integrity": "sha512-zzruYOEtNgfS3SBjcij1F6HlH6My5n8WrBNhP3fzaRM22ba70QBC2ATs18jGr88Fy43c0z8vFJv5wJankfxv2A==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", "dev": true, "requires": { - "@types/node": "*" + "winston": "*" } }, "@webcomponents/shadycss": { @@ -1702,9 +1702,9 @@ "dev": true }, "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, "aws-sign2": { @@ -2372,9 +2372,9 @@ } }, "browser-capabilities": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/browser-capabilities/-/browser-capabilities-1.1.1.tgz", - "integrity": "sha512-b+zF28HRpaKhdvLGqirkvn8XO+WEpLxAWg+dqa3OAoriVMS2UucVc1xis4Et9vMnQGLSipWks8bDeCeUvuZ0EQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/browser-capabilities/-/browser-capabilities-1.1.2.tgz", + "integrity": "sha512-T9BTu9Lmdrh9XZe0XnUY3jGiBlB0jAkl4M9qvt+1SszqlckgcUTzJuBwD6HNNKjdiDA+18KfiIUJEVxTY2W24g==", "dev": true, "requires": { "@types/ua-parser-js": "^0.7.31", @@ -2610,9 +2610,9 @@ "dev": true }, "ci-info": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.2.0.tgz", - "integrity": "sha512-U4aoLsSz44FhOyZ2E7bCufaBr2IUzNYujBd+b9vHiFH7SUzIhKcD94PQP5QSFn7ngPof6OF2yPk4/hygqwMJhA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.4.0.tgz", + "integrity": "sha512-Oqmw2pVfCl8sCL+1QgMywPfdxPJPkC51y4usw0iiE2S9qnEOAqXy8bwl1CpMpnoU39g4iKJTz6QZj+28FvOnjQ==", "dev": true }, "class-utils": { @@ -2639,18 +2639,18 @@ } }, "clean-css": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", - "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", "dev": true, "requires": { - "source-map": "0.5.x" + "source-map": "~0.6.0" }, "dependencies": { "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } @@ -4039,9 +4039,9 @@ "dev": true }, "follow-redirects": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.5.tgz", - "integrity": "sha512-GHjtHDlY/ehslqv0Gr5N0PUJppgg/q0rOBvX0na1s7y1A3LWxPqCYU76s3Z1bM4+UZB4QF0usaXLT5wFpof5PA==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.6.tgz", + "integrity": "sha512-xay/eYZGgdpb3rpugZj1HunNaPcqc6fud/RW7LNEQntvKzuRO4DDLL+MnJIbTHh6t3Kda3v2RvhY2doxUddnig==", "dev": true, "requires": { "debug": "^3.1.0" @@ -4722,14 +4722,14 @@ } }, "html-minifier": { - "version": "3.5.19", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.19.tgz", - "integrity": "sha512-Qr2JC9nsjK8oCrEmuB430ZIA8YWbF3D5LSjywD75FTuXmeqacwHgIM8wp3vHYzzPbklSjp53RdmDuzR4ub2HzA==", + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz", + "integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==", "dev": true, "requires": { "camel-case": "3.0.x", - "clean-css": "4.1.x", - "commander": "2.16.x", + "clean-css": "4.2.x", + "commander": "2.17.x", "he": "1.1.x", "param-case": "2.1.x", "relateurl": "0.2.x", @@ -4737,26 +4737,10 @@ }, "dependencies": { "commander": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", - "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", "dev": true - }, - "uglify-js": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.7.tgz", - "integrity": "sha512-J0M2i1mQA+ze3EdN9SBi751DNdAXmeFLfJrd/MDIkRc3G3Gbb9OPVSx7GIQvVwfWxQARcYV2DTxIkMyDAk3o9Q==", - "dev": true, - "requires": { - "commander": "~2.16.0", - "source-map": "~0.6.1" - } } } }, @@ -5029,12 +5013,12 @@ } }, "is-ci": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", - "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.0.tgz", + "integrity": "sha512-plgvKjQtalH2P3Gytb7L61Lmz95g2DlpzFiQyRSFew8WoJKxtKRzrZMeyRN2supblm3Psc8OQGy7Xjb6XG11jw==", "dev": true, "requires": { - "ci-info": "^1.0.0" + "ci-info": "^1.3.0" } }, "is-data-descriptor": { @@ -6574,9 +6558,9 @@ } }, "polymer-analyzer": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/polymer-analyzer/-/polymer-analyzer-3.1.0.tgz", - "integrity": "sha512-TW+SE+dofxSU6+7vJeH//Znjtu1brYE+J4w6M8W+BwoC3FHtBonxp4zCQFf+RnhQGcKIrfHVAxgujzccXer19Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/polymer-analyzer/-/polymer-analyzer-3.1.1.tgz", + "integrity": "sha512-h/szQRobLO/fld2BsvTsmCFwtXQRF/tQcwquBuBl7qzgodaHWdvDIOE+VmzmYzM6W9/DW637O7JaeFnbXK/IgA==", "dev": true, "requires": { "@babel/generator": "^7.0.0-beta.42", @@ -6593,7 +6577,6 @@ "@types/doctrine": "^0.0.1", "@types/is-windows": "^0.2.0", "@types/minimatch": "^3.0.1", - "@types/node": "^9.6.4", "@types/parse5": "^2.2.34", "@types/path-is-inside": "^1.0.0", "@types/resolve": "0.0.6", @@ -6625,12 +6608,6 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, - "@types/node": { - "version": "9.6.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", - "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", - "dev": true - }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -6736,9 +6713,9 @@ } }, "@types/node": { - "version": "9.6.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", - "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", + "version": "9.6.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.28.tgz", + "integrity": "sha512-LMSOxMKNJ8tGqUVs8lSIT8RGo1XGWYada/ZU2QZcbcD6AW9futXDE99tfQA0K6DK60GXcwplsGGK5KABRmI5GA==", "dev": true }, "@types/resolve": { @@ -6801,9 +6778,9 @@ }, "dependencies": { "@types/node": { - "version": "9.6.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", - "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", + "version": "9.6.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.28.tgz", + "integrity": "sha512-LMSOxMKNJ8tGqUVs8lSIT8RGo1XGWYada/ZU2QZcbcD6AW9futXDE99tfQA0K6DK60GXcwplsGGK5KABRmI5GA==", "dev": true } } @@ -6851,9 +6828,9 @@ }, "dependencies": { "@types/node": { - "version": "9.6.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", - "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", + "version": "9.6.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.28.tgz", + "integrity": "sha512-LMSOxMKNJ8tGqUVs8lSIT8RGo1XGWYada/ZU2QZcbcD6AW9futXDE99tfQA0K6DK60GXcwplsGGK5KABRmI5GA==", "dev": true }, "debug": { @@ -7243,9 +7220,9 @@ "dev": true }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { @@ -8782,6 +8759,30 @@ } } }, + "uglify-js": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.7.tgz", + "integrity": "sha512-J0M2i1mQA+ze3EdN9SBi751DNdAXmeFLfJrd/MDIkRc3G3Gbb9OPVSx7GIQvVwfWxQARcYV2DTxIkMyDAk3o9Q==", + "dev": true, + "requires": { + "commander": "~2.16.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", @@ -9150,9 +9151,9 @@ "dev": true }, "vscode-uri": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.5.tgz", - "integrity": "sha1-O4majvccN/MFTXm9vdoxx7828g0=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.6.tgz", + "integrity": "sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww==", "dev": true }, "wbuf": { @@ -9332,18 +9333,18 @@ }, "dependencies": { "@types/node": { - "version": "9.6.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.27.tgz", - "integrity": "sha512-fGWGG9Wypv6JZLIrnq9jXFX/FhQzgNccTlojez9hBbQ9UiBdxtc0ONMMe4/vnB2nDgOMDpPR/7HhenUB+Bw5yQ==", + "version": "9.6.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.28.tgz", + "integrity": "sha512-LMSOxMKNJ8tGqUVs8lSIT8RGo1XGWYada/ZU2QZcbcD6AW9futXDE99tfQA0K6DK60GXcwplsGGK5KABRmI5GA==", "dev": true, "optional": true } } }, "wct-sauce": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/wct-sauce/-/wct-sauce-2.0.3.tgz", - "integrity": "sha512-vR+gdd1RJjK6+UaiduNYxxNneIFLAwkpO7FlJR045q0Hguavvax2NvSLw+XibQdE0khQxmjsXSM/rq1bk2tYmg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wct-sauce/-/wct-sauce-2.1.0.tgz", + "integrity": "sha512-c3R4PJcbpS7Gxv2vZ4HDAqpXV6cT9peslAWMU7hHH9PMhKDPbn8RNa6E4DVL0tOmZznB+3cRmtZ6+vJ/aDwu1A==", "dev": true, "optional": true, "requires": { @@ -9419,9 +9420,9 @@ } }, "web-component-tester": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/web-component-tester/-/web-component-tester-6.7.1.tgz", - "integrity": "sha512-i/N0MYLBh9fjzI4pcKvfYiTx4JEr+Zbt2m1/ANovpvT74El55WaiFyiwCdUGhnlX1xy1URGUB2CJgl6gdTBumg==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/web-component-tester/-/web-component-tester-6.8.0.tgz", + "integrity": "sha512-zj1nZ7dq270svfkdPo4Mc4CuCTab/Wp0SMIKb8g6xD0Q76g15ttHyscYAxQkywVuabwgWeTXiotUOiQaiwX8uA==", "dev": true, "requires": { "@polymer/sinonjs": "^1.14.1", @@ -9431,7 +9432,6 @@ "async": "^2.4.1", "body-parser": "^1.17.2", "bower-config": "^1.4.0", - "chai": "^4.0.2", "chalk": "^1.1.3", "cleankill": "^2.0.0", "express": "^4.15.3", @@ -9462,9 +9462,9 @@ "dev": true }, "@webcomponents/webcomponentsjs": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-1.2.4.tgz", - "integrity": "sha512-JiratNkqWceEsC8Y/IgSR5NvzUFjiUj7K489YU8CP6a9QyKNNFdCZv06tht2uJfAomuXOgXuXktNhD0VtH9v9A==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-1.2.5.tgz", + "integrity": "sha512-rhjKZIf9y0kIV3nHyK8qXWN6mactYEzCqZlBjSptkAp9gsYmuUJq4JYwoPl5KHOSk+cTvPXfLDZ0Gru8TDgiVQ==", "dev": true }, "async": { diff --git a/package.json b/package.json index 22731eb0..8146f57d 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "typescript": "^3.0.1", "uglify-es": "^3.3.9", "wct-browser-legacy": "^1.0.1", - "web-component-tester": "^6.7.1" + "web-component-tester": "^6.8.0" }, "typings": "lit-element.d.ts", "dependencies": { From baaf2967d5141f4761ba36689e292e6e5b90ec63 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 10:03:19 -0700 Subject: [PATCH 31/65] Refine properties handling and add optimizations * `createProperty` now takes a PropertyDeclaration and passes this on rather than having to look it up (optimization). Note, this means we now create properties for superClass properties. Also introduces a map used to store reflecting properties (optimization). * correct `shouldInvalidate` property option so that it changes the property value but does not cause invalidation. * renames `identity` to `notEqual` to reflect what it does. --- src/lib/updating-element.ts | 132 +++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index feabed3c..f90c5eaa 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -110,14 +110,12 @@ const ensurePropertyStorage = (ctor: typeof UpdatingElement) => { * Decorator which creates a property. Optionally a `PropertyDeclaration` object * can be supplied to describe how the property should be configured. */ -export const property = (options: PropertyDeclaration = {}) => (proto: Object, name: string) => { +export const property = (options?: PropertyDeclaration) => (proto: Object, name: string) => { const ctor = proto.constructor as typeof UpdatingElement; ensurePropertyStorage(ctor); - ctor._classProperties.set(name, options); - ctor.createProperty(name); + ctor.createProperty(name, options); }; - /** * AttributeSerializer which configures properties which should reflect to and * from boolean attributes. If the attribute exists, the property is set to true. @@ -129,15 +127,28 @@ export const BooleanAttribute: AttributeSerializer = { toAttribute: (value: string) => value ? '' : null }; +export interface ShouldInvalidate { + (value: unknown, old: unknown): boolean; +} + /** * Change function that returns true if `value` is different from `oldValue`. - * This method is used as the default for a property's `shouldChange` function. + * This method is used as the default for a property's `shouldInvalidate` function. */ -export const identity = (value: unknown, old: unknown) => { +export const notEqual: ShouldInvalidate = (value: unknown, old: unknown): boolean => { // This ensures (old==NaN, value==NaN) always returns false return old !== value && (old === old || value === value); }; +const defaultPropertyDeclaration: PropertyDeclaration = { + attribute: true, + type: String, + reflect: false, + shouldInvalidate: notEqual +}; + +const microtaskPromise = Promise.resolve(); + const STATE_HAS_UPDATED = 1; const STATE_IS_VALID = 1 << 2; const STATE_IS_REFLECTING = 1 << 3; @@ -182,8 +193,8 @@ export abstract class UpdatingElement extends HTMLElement { // note: piggy backing on this to ensure we're _finalized. this._finalize(); this._observedAttributes = []; - for (const p of this._classProperties.keys()) { - const attr = this._attributeNameForProperty(p); + for (const [p, v] of this._classProperties) { + const attr = this._attributeNameForProperty(p, v); if (attr !== undefined) { this._attributeToPropertyMap.set(attr, p); this._observedAttributes.push(attr); @@ -199,8 +210,10 @@ export abstract class UpdatingElement extends HTMLElement { * or uses a strict identity check to determine if the set should trigger * invalidation and update. */ - static createProperty(name: PropertyKey) { - if (name in this.prototype) { + static createProperty(name: PropertyKey, options: PropertyDeclaration = defaultPropertyDeclaration) { + this._classProperties.set(name, options); + // Allow user defined accessors by not replacing an existing own-property accessor. + if (this.prototype.hasOwnProperty(name)) { return; } const key = typeof name === 'symbol' ? Symbol() : `__${name}`; @@ -209,14 +222,21 @@ export abstract class UpdatingElement extends HTMLElement { return this[key]; }, set(value) { - const old = this[key]; - const ctor = (this.constructor as typeof UpdatingElement); - if (ctor._propertyShouldInvalidate(name, value, old)) { + const oldValue = this[name]; + this[key] = value; + if ((this.constructor as typeof UpdatingElement)._propertyShouldInvalidate(value, + oldValue, options.shouldInvalidate)) { // track old value when changing. if (!this._changedProperties.has(name)) { - this._changedProperties.set(name, old); + this._changedProperties.set(name, oldValue); + } + // add to reflecting properties set + if (options.reflect === true) { + if (this._reflectingProperties === undefined) { + this._reflectingProperties = new Map(); + } + this._reflectingProperties.set(name, options); } - this[key] = value; this.invalidate(); } }, @@ -248,18 +268,16 @@ export abstract class UpdatingElement extends HTMLElement { const propKeys = [...Object.getOwnPropertyNames(props), ...(typeof Object.getOwnPropertySymbols === 'function') ? Object.getOwnPropertySymbols(props) : []]; for (const p of propKeys) { - this.createProperty(p); // note, use of `any` is due to TypeSript lack of support for symbol in index types - this._classProperties.set(p, (props as any)[p]); + this.createProperty(p, (props as any)[p]); } } /** * Returns the property name for the given attribute `name`. */ - private static _attributeNameForProperty(name: PropertyKey) { - const info = this._classProperties.get(name); - const attribute = info !== undefined && info.attribute; + private static _attributeNameForProperty(name: PropertyKey, options?: PropertyDeclaration) { + const attribute = options !== undefined && options.attribute; return attribute === false ? undefined : (typeof attribute === 'string' ? attribute : (typeof name === 'string' ? name.toLowerCase() : undefined)); } @@ -269,10 +287,8 @@ export abstract class UpdatingElement extends HTMLElement { * Called when a property value is set and uses the `shouldInvalidate` * option for the property if present or a strict identity check. */ - private static _propertyShouldInvalidate(name: PropertyKey, value: unknown, old: unknown) { - const info = this._classProperties.get(name); - const fn = info !== undefined && info.shouldInvalidate || identity; - return fn(value, old); + private static _propertyShouldInvalidate(value: unknown, old: unknown, shouldInvalidate: ShouldInvalidate = notEqual) { + return shouldInvalidate(value, old); } /** @@ -280,9 +296,8 @@ export abstract class UpdatingElement extends HTMLElement { * Called via the `attributeChangedCallback` and uses the property's `type` * or `type.fromAttribute` property option. */ - private static _propertyValueFromAttribute(name: PropertyKey, value: string) { - const info = this._classProperties.get(name); - const type = info && info.type; + private static _propertyValueFromAttribute(value: string, options?: PropertyDeclaration) { + const type = options && options.type; if (type === undefined) { return value; } @@ -297,12 +312,11 @@ export abstract class UpdatingElement extends HTMLElement { * attribute will be set to the value. * This uses the property's `reflect` and `type.toAttribute` property options. */ - private static _propertyValueToAttribute(name: PropertyKey, value: unknown) { - const info = this._classProperties.get(name); - if (info === undefined || info.reflect === undefined) { + private static _propertyValueToAttribute(value: unknown, options?: PropertyDeclaration) { + if (options === undefined || options.reflect === undefined) { return; } - const toAttribute = info.type && (info.type as AttributeSerializer).toAttribute || String; + const toAttribute = options.type && (options.type as AttributeSerializer).toAttribute || String; return (typeof toAttribute === 'function') ? toAttribute(value) : null; } @@ -312,11 +326,16 @@ export abstract class UpdatingElement extends HTMLElement { private _validateResolver: (() => void)|undefined = undefined; /** - * Object with keys for any properties that have changed since the last + * Map with keys for any properties that have changed since the last * update cycle with previous values. */ private _changedProperties: PropertyValues = new Map(); + /** + * Map with keys of properties that should be reflected when updated. + */ + private _reflectingProperties: Map|undefined = undefined; + /** * Node or ShadowRoot into which element DOM should be rendered. Defaults * to an open shadowRoot. @@ -335,7 +354,16 @@ export abstract class UpdatingElement extends HTMLElement { */ protected initialize() { this.renderRoot = this.createRenderRoot(); - // Apply any properties set on the instance before upgrade time. + this._saveInstanceProperties(); + } + + /** + * Fixes any properties set on the instance before upgrade time. + * Otherwise these would shadow the accessor and break these properties. + * The properties are stored in a map which is played back after the constructor + * runs. + */ + private _saveInstanceProperties() { for (const p of (this.constructor as typeof UpdatingElement)._classProperties.keys()) { if (this.hasOwnProperty(p)) { const value = this[p as keyof this]; @@ -343,15 +371,21 @@ export abstract class UpdatingElement extends HTMLElement { if (!this._instanceProperties) { this._instanceProperties = new Map(); } - // NOTE: must capture these into a map and reset at when validating - // to avoid stomping on a user value set in the constructor. Being - // async doesn't help here since the subclass' constructor value should - // be overwritten. this._instanceProperties.set(p, value); } } } + /** + * Applies previously saved instance properties. + */ + private _applyInstanceProperties() { + for (const [p, v] of this._instanceProperties!) { + (this as any)[p] = v; + } + this._instanceProperties = undefined; + } + /** * Returns the node into which the element should render and by default * creates and returns an open shadowRoot. Implement to customize where the @@ -382,11 +416,12 @@ export abstract class UpdatingElement extends HTMLElement { } } - private _propertyToAttribute(name: PropertyKey, value: unknown) { + private _propertyToAttribute(name: PropertyKey, value: unknown, + options: PropertyDeclaration = defaultPropertyDeclaration) { const ctor = (this.constructor as typeof UpdatingElement); - const attrValue = ctor._propertyValueToAttribute(name, value); + const attrValue = ctor._propertyValueToAttribute(value, options); if (attrValue !== undefined) { - const attr = ctor._attributeNameForProperty(name); + const attr = ctor._attributeNameForProperty(name, options); if (attr !== undefined) { // Track if the property is being reflected to avoid // setting the property again via `attributeChangedCallback`. Note: @@ -415,7 +450,8 @@ export abstract class UpdatingElement extends HTMLElement { const ctor = (this.constructor as typeof UpdatingElement); const propName = ctor._attributeToPropertyMap.get(name); if (propName !== undefined) { - this[propName as keyof this] = ctor._propertyValueFromAttribute(propName, value); + const options = ctor._classProperties.get(propName); + this[propName as keyof this] = ctor._propertyValueFromAttribute(value, options); } } } @@ -442,10 +478,7 @@ export abstract class UpdatingElement extends HTMLElement { await 0; // Mixin instance properties once, if they exist. if (this._instanceProperties) { - for (const [p, v] of this._instanceProperties) { - (this as any)[p] = v; - } - this._instanceProperties = undefined; + this._applyInstanceProperties(); } if (this.shouldUpdate(this._changedProperties)) { // During update, setting properties does not trigger invalidation. @@ -504,7 +537,7 @@ export abstract class UpdatingElement extends HTMLElement { * Returns a Promise that resolves when the element has finished updating. */ get updateComplete() { - return this._isPendingUpdate ? this._validatePromise : Promise.resolve(); + return this._isPendingUpdate ? this._validatePromise : microtaskPromise; } /** @@ -525,8 +558,11 @@ export abstract class UpdatingElement extends HTMLElement { * * @param _changedProperties Map of changed properties with old values */ protected update(_changedProperties: PropertyValues): void { - for (const name of _changedProperties.keys()) { - this._propertyToAttribute(name, (this as any)[name]); + if (this._reflectingProperties !== undefined && this._reflectingProperties.size > 0) { + for (const [k, v] of this._reflectingProperties) { + this._propertyToAttribute(k, this[k as keyof this], v); + } + this._reflectingProperties = undefined; } } From f2865de137ebb26745479c899248d5bd2d2d5274 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 10:04:11 -0700 Subject: [PATCH 32/65] Fix exports --- src/lit-element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lit-element.ts b/src/lit-element.ts index d2495491..233138ac 100644 --- a/src/lit-element.ts +++ b/src/lit-element.ts @@ -15,7 +15,7 @@ import {render} from 'lit-html/lib/shady-render'; import {TemplateResult} from 'lit-html/core'; import {UpdatingElement, PropertyValues} from './lib/updating-element.js'; -export {property, identity, BooleanAttribute, PropertyDeclarations, PropertyDeclaration, PropertyValues} from './lib/updating-element.js'; +export * from './lib/updating-element.js'; export {html, svg} from 'lit-html/lit-html'; From d4edcd82496987897c256a0eebce88cc1290b85b Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 10:13:51 -0700 Subject: [PATCH 33/65] Update tests for `shouldInvalidate` fix --- src/test/lit-element_test.ts | 94 ++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index a837d872..030a75c1 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -190,6 +190,13 @@ suite('LitElement', () => { toAttribute = 1; all = 10; + updated = 0; + + update(changed: PropertyValues) { + this.updated++; + super.update(changed); + } + render() { return html``; } } @@ -197,6 +204,7 @@ suite('LitElement', () => { const el = new E(); container.appendChild(el); await el.updateComplete; + assert.equal(el.updated, 1); assert.equal(el.noAttr, 'noAttr'); assert.equal(el.atTr, 'attr'); assert.equal(el.customAttr, 'customAttr'); @@ -209,34 +217,48 @@ suite('LitElement', () => { el.setAttribute('noattr', 'noAttr2'); el.setAttribute('attr', 'attr2'); el.setAttribute('custom', 'customAttr2'); - el.shouldInvalidate = 5; el.setAttribute('fromattribute', '2attr'); el.toAttribute = 2; el.all = 5; await el.updateComplete; + assert.equal(el.updated, 2); assert.equal(el.noAttr, 'noAttr'); assert.equal(el.atTr, 'attr2'); assert.equal(el.customAttr, 'customAttr2'); - assert.equal(el.shouldInvalidate, 10); assert.equal(el.fromAttribute, 2); assert.equal(el.toAttribute, 2); assert.equal(el.getAttribute('toattribute'), '2-attr'); - assert.equal(el.all, 10); - el.shouldInvalidate = 15; + assert.equal(el.all, 5); el.all = 15; await el.updateComplete; - assert.equal(el.shouldInvalidate, 15); + assert.equal(el.updated, 3); assert.equal(el.all, 15); assert.equal(el.getAttribute('all-attr'), '15-attr'); el.setAttribute('all-attr', '16-attr'); await el.updateComplete; + assert.equal(el.updated, 4); assert.equal(el.getAttribute('all-attr'), '16-attr'); assert.equal(el.all, 16); + el.shouldInvalidate = 5; + await el.updateComplete; + assert.equal(el.shouldInvalidate, 5); + assert.equal(el.updated, 4); + el.shouldInvalidate = 15; + await el.updateComplete; + assert.equal(el.shouldInvalidate, 15); + assert.equal(el.updated, 5); + el.setAttribute('all-attr', '5-attr'); + await el.updateComplete; + assert.equal(el.all, 5); + assert.equal(el.updated, 5); + el.all = 15; + await el.updateComplete; + assert.equal(el.all, 15); + assert.equal(el.updated, 6); }); test('attributes deserialize from html', async() => { - const shouldInvalidate = (value: any, old: any) => old === undefined || value > old; const fromAttribute = (value: any) => parseInt(value); const toAttributeOnly = (value: any) => typeof value === 'string' && value.indexOf(`-attr`) > 0 ? value : `${value}-attr`; const toAttribute = (value: any) => `${value}-attr`; @@ -246,17 +268,15 @@ suite('LitElement', () => { noAttr: {attribute: false}, atTr: {attribute: true}, customAttr: {attribute: 'custom', reflect: true}, - shouldInvalidate: {shouldInvalidate}, fromAttribute: {type: fromAttribute}, toAttribute: {reflect: true, type: {toAttribute: toAttributeOnly}}, - all: {attribute: 'all-attr', shouldInvalidate, type: {fromAttribute, toAttribute}, reflect: true}, + all: {attribute: 'all-attr', type: {fromAttribute, toAttribute}, reflect: true}, }; } noAttr = 'noAttr'; atTr = 'attr'; customAttr = 'customAttr'; - shouldInvalidate = 10; fromAttribute = 1; toAttribute: string|number = 1; all = 10; @@ -270,7 +290,6 @@ suite('LitElement', () => { noattr="1" attr="2" custom="3" - shouldInvalidate="5" fromAttribute="6-attr" toAttribute="7" all-attr="11-attr">`; @@ -281,8 +300,6 @@ suite('LitElement', () => { assert.equal(el.atTr, '2'); assert.equal(el.customAttr, '3'); assert.equal(el.getAttribute('custom'), '3'); - assert.equal(el.shouldInvalidate, 10); - assert.equal(el.getAttribute('shouldinvalidate'), '5'); assert.equal(el.fromAttribute, 6); assert.equal(el.toAttribute, '7'); assert.equal(el.getAttribute('toattribute'), '7-attr'); @@ -333,7 +350,6 @@ suite('LitElement', () => { assert.equal(el.updated, 3); assert.equal(el.foo, 55); assert.equal(el[zug], 66); - }); test('properties as symbols can set property options', async() => { @@ -395,9 +411,13 @@ suite('LitElement', () => { atTr = 'attr'; customAttr = 'customAttr'; shouldInvalidate = 10; - fromAttribute = 1; - toAttribute = 1; - all = 10; + + updated = 0; + + update(changed: PropertyValues) { + this.updated++; + super.update(changed); + } render() { return html``; } @@ -414,16 +434,10 @@ suite('LitElement', () => { }; } - noAttr = 'noAttr'; - atTr = 'attr'; - customAttr = 'customAttr'; - shouldInvalidate = 10; fromAttribute = 1; toAttribute = 1; all = 10; - render() { return html``; } - } class G extends F { @@ -435,16 +449,6 @@ suite('LitElement', () => { }; } - noAttr = 'noAttr'; - atTr = 'attr'; - customAttr = 'customAttr'; - shouldInvalidate = 10; - fromAttribute = 1; - toAttribute = 1; - all = 10; - - render() { return html``; } - } customElements.define(generateElementName(), G); @@ -452,6 +456,7 @@ suite('LitElement', () => { const el = new G(); container.appendChild(el); await el.updateComplete; + assert.equal(el.updated, 1); assert.equal(el.noAttr, 'noAttr'); assert.equal(el.atTr, 'attr'); assert.equal(el.customAttr, 'customAttr'); @@ -464,29 +469,44 @@ suite('LitElement', () => { el.setAttribute('noattr', 'noAttr2'); el.setAttribute('attr', 'attr2'); el.setAttribute('custom', 'customAttr2'); - el.shouldInvalidate = 5; el.setAttribute('fromattribute', '2attr'); el.toAttribute = 2; el.all = 5; await el.updateComplete; + assert.equal(el.updated, 2); assert.equal(el.noAttr, 'noAttr'); assert.equal(el.atTr, 'attr2'); assert.equal(el.customAttr, 'customAttr2'); - assert.equal(el.shouldInvalidate, 10); assert.equal(el.fromAttribute, 2); assert.equal(el.toAttribute, 2); assert.equal(el.getAttribute('toattribute'), '2-attr'); - assert.equal(el.all, 10); - el.shouldInvalidate = 15; + assert.equal(el.all, 5); el.all = 15; await el.updateComplete; - assert.equal(el.shouldInvalidate, 15); + assert.equal(el.updated, 3); assert.equal(el.all, 15); assert.equal(el.getAttribute('all-attr'), '15-attr'); el.setAttribute('all-attr', '16-attr'); await el.updateComplete; + assert.equal(el.updated, 4); assert.equal(el.getAttribute('all-attr'), '16-attr'); assert.equal(el.all, 16); + el.shouldInvalidate = 5; + await el.updateComplete; + assert.equal(el.shouldInvalidate, 5); + assert.equal(el.updated, 4); + el.shouldInvalidate = 15; + await el.updateComplete; + assert.equal(el.shouldInvalidate, 15); + assert.equal(el.updated, 5); + el.setAttribute('all-attr', '5-attr'); + await el.updateComplete; + assert.equal(el.all, 5); + assert.equal(el.updated, 5); + el.all = 15; + await el.updateComplete; + assert.equal(el.all, 15); + assert.equal(el.updated, 6); }); From 9f1a709fff23616b4332a28a2e9175fd5a8daa27 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 10:22:06 -0700 Subject: [PATCH 34/65] Remove `BooleanAttribute` and special case `type: Boolean` Change made based on feedback that it seemed weird not to be able to just use `Boolean` as `type` and have the value serialize/deserialize as expected. --- src/lib/updating-element.ts | 21 +++++++++------------ src/test/lit-element_test.ts | 35 ++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index f90c5eaa..5466c02e 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -116,16 +116,9 @@ export const property = (options?: PropertyDeclaration) => (proto: Object, name: ctor.createProperty(name, options); }; -/** - * AttributeSerializer which configures properties which should reflect to and - * from boolean attributes. If the attribute exists, the property is set to true. - * If the property value is truthy, the attribute is set to an empty string; - * otherwise, the attribute is removed. - */ -export const BooleanAttribute: AttributeSerializer = { - fromAttribute: (value: string) => value !== null, - toAttribute: (value: string) => value ? '' : null -}; +// serializer/deserializers for boolean attribute +const fromBooleanAttribute = (value: string) => value !== null; +const toBooleanAttribute = (value: string) => value ? '' : null; export interface ShouldInvalidate { (value: unknown, old: unknown): boolean; @@ -301,7 +294,9 @@ export abstract class UpdatingElement extends HTMLElement { if (type === undefined) { return value; } - const fromAttribute = typeof type === 'function' ? type : type.fromAttribute; + // Note: special case `Boolean` so users can use it as a `type`. + const fromAttribute = type === Boolean ? fromBooleanAttribute : + (typeof type === 'function' ? type : type.fromAttribute); return fromAttribute ? fromAttribute(value) : value; } @@ -316,7 +311,9 @@ export abstract class UpdatingElement extends HTMLElement { if (options === undefined || options.reflect === undefined) { return; } - const toAttribute = options.type && (options.type as AttributeSerializer).toAttribute || String; + // Note: special case `Boolean` so users can use it as a `type`. + const toAttribute = options.type === Boolean ? toBooleanAttribute : + (options.type && (options.type as AttributeSerializer).toAttribute || String); return (typeof toAttribute === 'function') ? toAttribute(value) : null; } diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 030a75c1..eff5b3b0 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -12,7 +12,7 @@ * http://polymer.github.io/PATENTS.txt */ -import {html, LitElement, BooleanAttribute, PropertyDeclarations, PropertyValues} from '../lit-element.js'; +import {html, LitElement, PropertyDeclarations, PropertyValues} from '../lit-element.js'; import {stripExpressionDelimeters, generateElementName} from './test-helpers.js'; @@ -567,16 +567,37 @@ suite('LitElement', () => { assert.equal(sub.nug, 5); }); - test('Attributes reflect with type.toAttribute and BooleanAttribute', async () => { + test('Attributes reflect', async () => { + const suffix = '-reflected'; class E extends LitElement { static get properties() { return { - foo: {type: Number, reflect: true}, - bar: {type: BooleanAttribute, reflect: true} + foo: {reflect: true, type: {toAttribute: (value: any) => `${value}${suffix}`}} }; } foo = 0; + + render() { return html``; } + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.getAttribute('foo'), `0${suffix}`); + el.foo = 5; + await el.updateComplete; + assert.equal(el.getAttribute('foo'), `5${suffix}`); + }); + + test('Attributes reflect with type: Boolean', async () => { + class E extends LitElement { + static get properties() { + return { + bar: {type: Boolean, reflect: true} + }; + } + bar = true; render() { return html``; } @@ -585,13 +606,13 @@ suite('LitElement', () => { const el = new E(); container.appendChild(el); await el.updateComplete; - assert.equal(el.getAttribute('foo'), '0'); assert.equal(el.getAttribute('bar'), ''); - el.foo = 5; el.bar = false; await el.updateComplete; - assert.equal(el.getAttribute('foo'), '5'); assert.equal(el.hasAttribute('bar'), false); + el.bar = true; + await el.updateComplete; + assert.equal(el.getAttribute('bar'), ''); }); test('updates/renders when properties change', async () => { From 25f4ca613aa14b6db9734a9e2dba6621537af503 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 10:56:32 -0700 Subject: [PATCH 35/65] Adds `invalidateProperty(name, oldValue)` This method should be called in manually created property setters to trigger an invalidation and honor any configured property options for the given property. --- README.md | 34 +++++++++++--------- src/lib/updating-element.ts | 49 ++++++++++++++++++++--------- src/test/lit-element_test.ts | 60 ++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 811e86b9..2c5707f9 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ and renders declaratively using `lit-html`. if you're using a compiler that supports them, like TypeScript or Babel. * With a static `properties` getter. * By manually writing getters and setters. This can be useful if tasks should - be performed when a property is set, for example validation. You can call `invalidate()` - in the setter to trigger an update. + be performed when a property is set, for example validation. Call `invalidateProperty(name, oldValue)` + in the setter to trigger an update and use any configured property options. Properties can be given an options argument which is an object that describes how to process the property. This can be done either in the `@property({...})` decorator or in the @@ -138,10 +138,11 @@ into the element. This is the only method that must be implemented by subclasses See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit-element.ts#L90) for detailed API info, here are some highlights. - * `createRenderRoot()` (protected): Implement to customize where the - element's template is rendered by returning an element into which to - render. By default this creates a shadowRoot for the element. - To render into the element's childNodes, return `this`. + * `render()` (protected): Implement to describe the element's DOM using `lit-html`. Ideally, + the `render` implementation is a pure function using only the element's current properties + to describe the element template. This is the only method that must be implemented by subclasses. + Note, since `render()` is called by `update()` setting properties does not trigger + `invalidate()`, allowing property values to be computed and validated. * `shouldUpdate(changedProperties)` (protected): Implement to control if updating and rendering should occur when property values change or `invalidate` is called. The `changedProps` @@ -154,12 +155,6 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- during `update()` setting properties does not trigger `invalidate()`, allowing property values to be computed and validated. - * `render()` (protected): Implement to describe the element's DOM using `lit-html`. Ideally, - the `render` implementation is a pure function using only the element's current properties - to describe the element template. This is the only method that must be implemented by subclasses. - Note, since `render()` is called by `update()` setting properties does not trigger - `invalidate()`, allowing property values to be computed and validated. - * `finishUpdate(changedProperties): Promise?` (protected): Called after element DOM has been updated and before the `updateComplete` promise is resolved. Implement to directly control rendered DOM. Typically this is not needed as `lit-html` can be used in the `render` method @@ -171,16 +166,25 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- Setting properties in `finishUpdate()` does trigger `invalidate()` and blocks the `updateComplete` promise. - * `finishFirstUpdate(changedProperties): Promise?` (protected) Called after element DOM has been + * `finishFirstUpdate(): Promise?` (protected) Called after element DOM has been updated the first time. This method can be useful for capturing references to rendered static nodes that must be directly acted upon, for example in `finishUpdate`. - * `updateComplete`: Returns a promise which resolves after the element next renders. + * `updateComplete`: Returns a promise which resolves after the element next updates and renders. * `invalidate`: Call to request the element to asynchronously update regardless of whether or not any property changes are pending. -## Update Lifecycle + * `invalidateProperty(name, oldValue)`: Triggers an invalidation for a specific property. + This is useful when manually implementing a propert setter. Call `invalidateProperty` + instead of `invalidate` to ensure that any configured property options are honored. + + * `createRenderRoot()` (protected): Implement to customize where the + element's template is rendered by returning an element into which to + render. By default this creates a shadowRoot for the element. + To render into the element's childNodes, return `this`. + +## Advanced: Update Lifecycle * When the element is first connected or a property is set (e.g. `element.foo = 5`) and the property's `shouldInvalidate(value, oldValue)` returns true. Then diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 5466c02e..97804900 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -217,21 +217,7 @@ export abstract class UpdatingElement extends HTMLElement { set(value) { const oldValue = this[name]; this[key] = value; - if ((this.constructor as typeof UpdatingElement)._propertyShouldInvalidate(value, - oldValue, options.shouldInvalidate)) { - // track old value when changing. - if (!this._changedProperties.has(name)) { - this._changedProperties.set(name, oldValue); - } - // add to reflecting properties set - if (options.reflect === true) { - if (this._reflectingProperties === undefined) { - this._reflectingProperties = new Map(); - } - this._reflectingProperties.set(name, options); - } - this.invalidate(); - } + this.invalidateProperty(name, oldValue, options); }, configurable: true, enumerable: true @@ -453,6 +439,39 @@ export abstract class UpdatingElement extends HTMLElement { } } + /** + * Triggers an invalidation and records an old value for the specified + * property to be presented in the `changedProperties` argument to `update` + * and `finishUpdate`. When manually creating a property setter, this + * method should be called to trigger an invalidation that honors any of the + * property options specified for the given property. + * + * @param name {PropertyKey} + * @param oldValue {any} + */ + protected invalidateProperty(name: PropertyKey, oldValue: any, options?: PropertyDeclaration) { + // if not passed in, take options from class properties. + if (options === undefined) { + options = (this.constructor as typeof UpdatingElement)._classProperties.get(name) || + defaultPropertyDeclaration; + } + if ((this.constructor as typeof UpdatingElement)._propertyShouldInvalidate(this[name as keyof this], + oldValue, options.shouldInvalidate)) { + // track old value when changing. + if (!this._changedProperties.has(name)) { + this._changedProperties.set(name, oldValue); + } + // add to reflecting properties set + if (options.reflect === true) { + if (this._reflectingProperties === undefined) { + this._reflectingProperties = new Map(); + } + this._reflectingProperties.set(name, options); + } + this.invalidate(); + } + } + /** * Invalidates the element causing it to asynchronously update regardless * of whether or not any property changes are pending. This method is diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index eff5b3b0..9434d701 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -743,6 +743,66 @@ suite('LitElement', () => { assert.equal(stripExpressionDelimeters(el.shadowRoot!.innerHTML), '020'); }); + test('User defined accessor can use property options via `invalidateProperty`', async () => { + const fromAttribute = (value: any) => parseInt(value); + const toAttribute = (value: any) => `${value}-attr`; + const shouldInvalidate = (value: any, old: any) => isNaN(old) || value > old; + class E extends LitElement { + + updated = 0; + __bar: any; + + static get properties() { + return { + bar: {attribute: 'attr-bar', reflect: true, type: {fromAttribute, toAttribute}, shouldInvalidate} + }; + } + + constructor() { + super(); + this.bar = 5; + } + + update(changed: PropertyValues) { + super.update(changed); + this.updated++; + } + + get bar() { return this.__bar; } + + set bar(value) { + const old = this.bar; + this.__bar = Number(value); + this.invalidateProperty('bar', old); + } + + render() { return html``; } + + } + customElements.define(generateElementName(), E); + const el = new E(); + container.appendChild(el); + await el.updateComplete; + assert.equal(el.updated, 1); + assert.equal(el.bar, 5); + assert.equal(el.getAttribute('attr-bar'), `5-attr`); + el.setAttribute('attr-bar', '7'); + await el.updateComplete; + assert.equal(el.updated, 2); + assert.equal(el.bar, 7); + assert.equal(el.getAttribute('attr-bar'), `7-attr`); + el.bar = 4; + await el.updateComplete; + assert.equal(el.updated, 2); + assert.equal(el.bar, 4); + assert.equal(el.getAttribute('attr-bar'), `7-attr`); + el.setAttribute('attr-bar', '3'); + await el.updateComplete; + assert.equal(el.updated, 2); + assert.equal(el.bar, 3); + assert.equal(el.getAttribute('attr-bar'), `3`); + }); + test('updates/renders attributes, properties, and event listeners via lit-html', async () => { class E extends LitElement { From 9f8e145b4516d94ebc19ff05c583bcc475e5d26c Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 15:29:24 -0700 Subject: [PATCH 36/65] Simplify update cycle based on feedback * invalidate always completes in a microtask and should not be pushed out. * properties set in `finishUpdate`are set after the next `updateComplete` resolves * adds tests for customizing the timing of `updateComplete` --- README.md | 34 +++++--- demo/lit-element.html | 25 ++++-- src/lib/updating-element.ts | 164 ++++++++++++++++------------------- src/test/lit-element_test.ts | 109 ++++++++++++++--------- 4 files changed, 177 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index 2c5707f9..cc6d6599 100644 --- a/README.md +++ b/README.md @@ -155,29 +155,36 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- during `update()` setting properties does not trigger `invalidate()`, allowing property values to be computed and validated. - * `finishUpdate(changedProperties): Promise?` (protected): Called after element DOM has been updated and + * `finishUpdate(changedProperties)`: (protected): Called after element DOM has been updated and before the `updateComplete` promise is resolved. Implement to directly control rendered DOM. Typically this is not needed as `lit-html` can be used in the `render` method to set properties, attributes, and event listeners. However, it is sometimes useful for calling methods on rendered elements, for example focusing an input: `this.shadowRoot.querySelector('input').focus()`. The `changedProps` argument is an object - with keys for the changed properties pointing to their previous values. If this function - returns a `Promise`, it will be *awaited* before resolving the `updateComplete` promise. - Setting properties in `finishUpdate()` does trigger `invalidate()` and blocks - the `updateComplete` promise. + with keys for the changed properties pointing to their previous values. - * `finishFirstUpdate(): Promise?` (protected) Called after element DOM has been + * `finishFirstUpdate()`: (protected) Called after element DOM has been updated the first time. This method can be useful for capturing references to rendered static nodes that must be directly acted upon, for example in `finishUpdate`. - * `updateComplete`: Returns a promise which resolves after the element next updates and renders. + * `updateComplete`: Returns a Promise that resolves when the element has finished updating + to a boolean value that is true if the element finished the update + without triggering another update. This can happen if a property + is set in `finishUpdate` for example. + This getter can be implemented to await additional state. For example, it + is sometimes useful to await a rendered element before fulfilling this + promise. To do this, first await `super.updateComplete` then any subsequent + state. * `invalidate`: Call to request the element to asynchronously update regardless - of whether or not any property changes are pending. + of whether or not any property changes are pending. This should only be called + when an element should update based on some state not stored in properties, + since setting properties automically calls `invalidate`. - * `invalidateProperty(name, oldValue)`: Triggers an invalidation for a specific property. - This is useful when manually implementing a propert setter. Call `invalidateProperty` - instead of `invalidate` to ensure that any configured property options are honored. + * `invalidateProperty(name, oldValue)` (protected): Triggers an invalidation for + a specific property. This is useful when manually implementing a propert setter. + Call `invalidateProperty` instead of `invalidate` to ensure that any configured + property options are honored. * `createRenderRoot()` (protected): Implement to customize where the element's template is rendered by returning an element into which to @@ -197,9 +204,10 @@ See the [source](https://github.com/PolymerLabs/lit-element/blob/master/src/lit- will *not* trigger `invalidate()`. This calls * `render()` which should return a `lit-html` TemplateResult (e.g. html\`Hello ${world}\`) + * `finishFirstUpdate()` is then called to do post *first* update/render tasks. + Note, setting properties here will trigger `invalidate()`. * `finishUpdate(changedProps)` is then called to do post update/render tasks. - Note, setting properties here will trigger `invalidate()` and block - the `updateComplete` promise. + Note, setting properties here will trigger `invalidate()`. * `updateComplete` promise is resolved only if the element is not in an invalid state. * Any code awaiting the element's `updateComplete` promise runs and observes diff --git a/demo/lit-element.html b/demo/lit-element.html index c05e8dfd..fe913acd 100644 --- a/demo/lit-element.html +++ b/demo/lit-element.html @@ -104,24 +104,31 @@

Foo: ${foo}, Bar: ${bar} { + await super.updateComplete; + await this._inner.updateComplete; + while (!await super.updateComplete) {}; + })(); + }; + } - console.log('defined class'); customElements.define('my-element', MyElement); - console.log('get', customElements.get('my-element')); (async () => { const x = document.querySelector('my-element'); await x.updateComplete; - console.log(x.shadowRoot.querySelector('x-inner').shadowRoot.textContent); + console.log('updateComplete!', x.shadowRoot.querySelector('x-inner').shadowRoot.textContent, x.whales); })(); diff --git a/src/lib/updating-element.ts b/src/lib/updating-element.ts index 97804900..603496a5 100644 --- a/src/lib/updating-element.ts +++ b/src/lib/updating-element.ts @@ -90,30 +90,12 @@ type AttributeMap = Map; export type PropertyValues = Map; -/** - * Creates and sets object used to memoize all class property values. Object - * is chained from superclass. - */ -const ensurePropertyStorage = (ctor: typeof UpdatingElement) => { - if (!ctor.hasOwnProperty('_classProperties')) { - ctor._classProperties = new Map(); - // NOTE: Workaround IE11 not supporting Map constructor argument. - const superProperties = Object.getPrototypeOf(ctor)._classProperties; - if (superProperties !== undefined) { - superProperties.forEach((v: any, k: PropertyKey) => - ctor._classProperties.set(k, v)); - } - } -}; - /** * Decorator which creates a property. Optionally a `PropertyDeclaration` object * can be supplied to describe how the property should be configured. */ export const property = (options?: PropertyDeclaration) => (proto: Object, name: string) => { - const ctor = proto.constructor as typeof UpdatingElement; - ensurePropertyStorage(ctor); - ctor.createProperty(name, options); + (proto.constructor as typeof UpdatingElement).createProperty(name, options); }; // serializer/deserializers for boolean attribute @@ -140,12 +122,12 @@ const defaultPropertyDeclaration: PropertyDeclaration = { shouldInvalidate: notEqual }; -const microtaskPromise = Promise.resolve(); +const microtaskPromise = new Promise((resolve) => resolve(true)); const STATE_HAS_UPDATED = 1; -const STATE_IS_VALID = 1 << 2; +const STATE_IS_UPDATING = 1 << 2; const STATE_IS_REFLECTING = 1 << 3; -type ValidationState = typeof STATE_HAS_UPDATED | typeof STATE_IS_VALID | typeof STATE_IS_REFLECTING; +type ValidationState = typeof STATE_HAS_UPDATED | typeof STATE_IS_UPDATING | typeof STATE_IS_REFLECTING; /** * Base element class which manages element properties and attributes. When @@ -170,11 +152,10 @@ export abstract class UpdatingElement extends HTMLElement { */ private static _observedAttributes: string[]|undefined; - // TODO(sorvell): intended to be private but called by decorator. /** * Memoized list of all class properties, including any superclass properties. */ - static _classProperties: PropertyDeclarationMap = new Map(); + private static _classProperties: PropertyDeclarationMap = new Map(); static properties: PropertyDeclarations = {}; @@ -204,6 +185,16 @@ export abstract class UpdatingElement extends HTMLElement { * invalidation and update. */ static createProperty(name: PropertyKey, options: PropertyDeclaration = defaultPropertyDeclaration) { + // ensure private storage for property declarations. + if (!this.hasOwnProperty('_classProperties')) { + this._classProperties = new Map(); + // NOTE: Workaround IE11 not supporting Map constructor argument. + const superProperties = Object.getPrototypeOf(this)._classProperties; + if (superProperties !== undefined) { + superProperties.forEach((v: any, k: PropertyKey) => + this._classProperties.set(k, v)); + } + } this._classProperties.set(name, options); // Allow user defined accessors by not replacing an existing own-property accessor. if (this.prototype.hasOwnProperty(name)) { @@ -238,7 +229,6 @@ export abstract class UpdatingElement extends HTMLElement { superCtor._finalize(); } this._finalized = true; - ensurePropertyStorage(this); // initialize map populated in observedAttributes this._attributeToPropertyMap = new Map(); // make any properties @@ -266,7 +256,8 @@ export abstract class UpdatingElement extends HTMLElement { * Called when a property value is set and uses the `shouldInvalidate` * option for the property if present or a strict identity check. */ - private static _propertyShouldInvalidate(value: unknown, old: unknown, shouldInvalidate: ShouldInvalidate = notEqual) { + private static _propertyShouldInvalidate(value: unknown, old: unknown, + shouldInvalidate: ShouldInvalidate = notEqual) { return shouldInvalidate(value, old); } @@ -303,10 +294,9 @@ export abstract class UpdatingElement extends HTMLElement { return (typeof toAttribute === 'function') ? toAttribute(value) : null; } - private _validationState: ValidationState = STATE_IS_VALID; + private _validationState: ValidationState = 0; private _instanceProperties: PropertyValues|undefined = undefined; private _validatePromise: Promise|undefined = undefined; - private _validateResolver: (() => void)|undefined = undefined; /** * Map with keys for any properties that have changed since the last @@ -475,85 +465,79 @@ export abstract class UpdatingElement extends HTMLElement { /** * Invalidates the element causing it to asynchronously update regardless * of whether or not any property changes are pending. This method is - * automatically called when any registered property changes. Returns a Promise - * that resolves when the element has finished updating. + * automatically called when any registered property changes. */ async invalidate() { - // Do not re-queue validation if already invalid (pending) or currently updating. - if (this._isPendingUpdate) { - return this._validatePromise; - } - // mark state invalid... - this._validationState = this._validationState & ~STATE_IS_VALID; - // Make a new promise only if the current one is not pending resolution - // (resolver has not been set to undefined) - if (this._validateResolver === undefined) { - this._validatePromise = new Promise((resolve) => this._validateResolver = resolve); + if (!this._isUpdating) { + // mark state invalid... + this._validationState = this._validationState | STATE_IS_UPDATING; + let resolver: any; + this._validatePromise = new Promise((r) => { + this._validatePromise = undefined; + resolver = r; + }); + await microtaskPromise; + this._validate(); + resolver!(!this._isUpdating); } - // Wait a tick to actually process changes (allows batching). - await 0; + return this._validatePromise; + } + + private get _isUpdating() { + return (this._validationState & STATE_IS_UPDATING); + } + + /** + * Validates the element by updating it via `update`, `finishUpdate`, + * and `finishFirstUpdate`. + */ + private _validate() { // Mixin instance properties once, if they exist. if (this._instanceProperties) { this._applyInstanceProperties(); } - if (this.shouldUpdate(this._changedProperties)) { - // During update, setting properties does not trigger invalidation. - this.update(this._changedProperties); - // copy changedProperties to hand to finishUpdate. - let changedProperties; - const hasFinishUpdate = (typeof this.finishUpdate === 'function'); - // clone changedProperties before resetting only if needed for finishUpdate. - if (hasFinishUpdate) { - changedProperties = new Map(this._changedProperties); - } - this._changedProperties.clear(); - // mark state valid - this._validationState = this._validationState | STATE_IS_VALID; - if (!(this._validationState & STATE_HAS_UPDATED)) { - // mark state has updated - this._validationState = this._validationState | STATE_HAS_UPDATED; - if (typeof this.finishFirstUpdate === 'function') { - // During `finishFirstUpdate` (which is optional), setting properties triggers invalidation, - // and users may choose to await other state. - const result = this.finishFirstUpdate(); - if (result != null && typeof (result as PromiseLike).then === 'function') { - await result; - } - } - } - // During `finishUpdate` (which is optional), setting properties triggers invalidation, - // and users may choose to await other state, like children being updated. - if (hasFinishUpdate) { - const result = this.finishUpdate!(changedProperties as PropertyValues); - if (result != null && typeof (result as PromiseLike).then === 'function') { - await result; - } + if (!this.shouldUpdate(this._changedProperties)) { + this._markUpdated(); + return; + } + // During update, setting properties does not trigger invalidation. + this.update(this._changedProperties); + // copy changedProperties to hand to finishUpdate. + const changedProperties = this._changedProperties; + this._markUpdated(); + // After update (finishFirstUpdate, finishUpdate), properties *do* trigger invalidation. + if (!(this._validationState & STATE_HAS_UPDATED)) { + // mark state has updated + this._validationState = this._validationState | STATE_HAS_UPDATED; + if (typeof this.finishFirstUpdate === 'function') { + this.finishFirstUpdate(); } - } else { - this._changedProperties.clear(); - // mark state valid - this._validationState = this._validationState | STATE_IS_VALID; } - // Only resolve the promise if we finish in a valid state (finishUpdate - // did not trigger more work). Note, if invalidate is triggered multiple - // times in `finishUpdate`, only the first time will resolve the promise - // by calling `_validateResolver`. This is why we guard for its existence. - if ((this._validationState & STATE_IS_VALID) && typeof this._validateResolver === 'function') { - this._validateResolver(); - this._validateResolver = undefined; + if (typeof this.finishUpdate === 'function') { + this.finishUpdate(changedProperties); } - return this._validatePromise; } - private get _isPendingUpdate() { - return !(this._validationState & STATE_IS_VALID); + private _markUpdated() { + this._changedProperties = new Map(); + this._validationState = this._validationState & ~STATE_IS_UPDATING; } /** - * Returns a Promise that resolves when the element has finished updating. + * Returns a Promise that resolves when the element has finished updating + * to a boolean value that is true if the element finished the update + * without triggering another update. This can happen if a property + * is set in `finishUpdate` for example. + * This getter can be implemented to await additional state. For example, it + * is sometimes useful to await a rendered element before fulfilling this + * promise. To do this, first await `super.updateComplete` then any subsequent + * state. + * + * @returns {Promise} The promise returns a boolean that indicates if the + * update resolved without triggering another update. */ get updateComplete() { - return this._isPendingUpdate ? this._validatePromise : microtaskPromise; + return this._validatePromise || microtaskPromise; } /** diff --git a/src/test/lit-element_test.ts b/src/test/lit-element_test.ts index 9434d701..35f65757 100644 --- a/src/test/lit-element_test.ts +++ b/src/test/lit-element_test.ts @@ -991,7 +991,7 @@ suite('LitElement', () => { assert.equal(el.getAttribute('zot'), '3'); }); - test('setting properties in finishUpdate does trigger invalidation blocks updateComplete', async () => { + test('setting properties in finishUpdate does trigger invalidation and does not block updateComplete', async () => { class E extends LitElement { static get properties() { @@ -999,20 +999,23 @@ suite('LitElement', () => { foo: {} }; } - promiseFulfilled = false; foo = 0; updated = 0; fooMax = 2; - async finishUpdate() { + update(changed: PropertyValues) { this.updated++; + super.update(changed); + } + + finishUpdate() { if (this.foo < this.fooMax) { this.foo++; } } render() { - return html`${this.foo}`; + return html``; } } @@ -1020,18 +1023,14 @@ suite('LitElement', () => { const el = new E(); container.appendChild(el); await el.updateComplete; - assert.equal(el.foo, 2); - assert.equal(el.updated, 3); - assert.equal(el.shadowRoot!.textContent, '2'); - el.fooMax = 10; - el.foo = 5; + assert.equal(el.foo, 1); + assert.equal(el.updated, 1); await el.updateComplete; - assert.equal(el.foo, 10); - assert.equal(el.updated, 9); - assert.equal(el.shadowRoot!.textContent, '10'); + assert.equal(el.foo, 2); + assert.equal(el.updated, 2); }); - test('can await promise in finishUpdate', async () => { + test('updateComplete can block properties set in finishUpdate', async () => { class E extends LitElement { static get properties() { @@ -1039,20 +1038,29 @@ suite('LitElement', () => { foo: {} }; } - promiseFulfilled = false; - foo = 0; + foo = 1; + updated = 0; + fooMax = 10; + + update(changed: PropertyValues) { + this.updated++; + super.update(changed); + } + + finishUpdate() { + if (this.foo < this.fooMax) { + this.foo++; + } + } render() { - return html`${this.foo}`; + return html``; } - async finishUpdate() { - await new Promise((resolve) => { - setTimeout(() => { - this.promiseFulfilled = true; - resolve(); - }, 1); - }); + get updateComplete() { + return (async () => { + while (!await super.updateComplete) {} + })(); } } @@ -1060,10 +1068,11 @@ suite('LitElement', () => { const el = new E(); container.appendChild(el); await el.updateComplete; - assert.isTrue(el.promiseFulfilled); + assert.equal(el.foo, 10); + assert.equal(el.updated, 10); }); - test('updateComplete resolved after any properties set within finishUpdate', async () => { + test('can await promise in updateComplete', async () => { class E extends LitElement { static get properties() { @@ -1071,17 +1080,23 @@ suite('LitElement', () => { foo: {} }; } - + promiseFulfilled = false; foo = 0; render() { return html`${this.foo}`; } - async finishUpdate() { - if (this.foo < 10) { - this.foo++; - } + get updateComplete() { + return (async () => { + await super.updateComplete; + await new Promise((resolve) => { + setTimeout(() => { + this.promiseFulfilled = true; + resolve(); + }, 1); + }); + })(); } } @@ -1089,11 +1104,10 @@ suite('LitElement', () => { const el = new E(); container.appendChild(el); await el.updateComplete; - assert.equal(el.foo, 10); - assert.equal(el.shadowRoot!.textContent, '10'); + assert.isTrue(el.promiseFulfilled); }); - test('can await sub-element updateComplete in finishUpdate', async () => { + test('can await sub-element updateComplete', async () => { class E extends LitElement { static get properties() { @@ -1108,13 +1122,16 @@ suite('LitElement', () => { return html`${this.foo}`; } - async finishUpdate() { - await new Promise((resolve) => { - setTimeout(() => { - this.promiseFulfilled = true; - resolve(); - }, 0); - }); + get updateComplete() { + return (async () => { + await super.updateComplete; + await new Promise((resolve) => { + setTimeout(() => { + this.promiseFulfilled = true; + resolve(); + }, 1); + }); + })(); } } @@ -1128,10 +1145,16 @@ suite('LitElement', () => { return html``; } - async finishUpdate() { + finishFirstUpdate() { this.inner = this.shadowRoot!.querySelector('x-1224'); - this.inner!.foo = 'yo'; - await this.inner!.updateComplete; + } + + get updateComplete() { + return (async () => { + await super.updateComplete; + this.inner!.foo = 'yo'; + await this.inner!.updateComplete; + })(); } } From 59cd15e86e0684bb657f1ad340a0494da5209069 Mon Sep 17 00:00:00 2001 From: Steven Orvell Date: Wed, 22 Aug 2018 15:58:15 -0700 Subject: [PATCH 37/65] Documentation fixes based on feedback. --- README.md | 51 +++++++++++++++++-------------------- src/lib/updating-element.ts | 42 +++++++++++++----------------- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index cc6d6599..0e65fb07 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,11 @@ LitElement uses [lit-html](https://github.com/Polymer/lit-html) to render into the element's [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) -and adds API help manage element properties and attributes. LitElement reacts to changes in properties +and adds API to help manage element properties and attributes. LitElement reacts to changes in properties and renders declaratively using `lit-html`. - * **Setup properties:** LitElement supports observable properties which may trigger an - update when set. These properties can be written in a few ways: + * **Setup properties:** LitElement supports observable properties that cause the element to update. + These properties can be declared in a few ways: * As class fields with the `@property()` [decorator](https://github.com/tc39/proposal-decorators#decorators), if you're using a compiler that supports them, like TypeScript or Babel. @@ -29,8 +29,8 @@ and renders declaratively using `lit-html`. Property options include: - * `attribute`: Describes how and if the property becomes an observed attribute. - If the value is false, the property is not added to `observedAttributes`. + * `attribute`: Describes how and whether the property becomes an observed attribute. + If the value is `false`, the property is not added to `observedAttributes`. If true or absent, the lowercased property name is observed (e.g. `fooBar` becomes `foobar`). If a string, the string value is observed (e.g `attribute: 'foo-bar'`). * `type`: Describes how to serialize and deserialize the attribute to/from a property. @@ -38,16 +38,16 @@ and renders declaratively using `lit-html`. a the property value. If it's an object, it can have keys for `fromAttribute` and `toAttribute` where `fromAttribute` is the deserialize function and `toAttribute` is a serialize function used to set the property to an attribute. If no `toAttribute` - function is provided and `reflect` is set to true, the property value is set + function is provided and `reflect` is set to `true`, the property value is set directly to the attribute. * `reflect`: Describes if the property should reflect to an attribute. - If true, when the property is set, the attribute is set using the + If `true`, when the property is set, the attribute is set using the attribute name determined according to the rules for the `attribute` propety option and the value of the property serialized using the rules from the `type` property option. * `shouldInvalidate`: Describes if setting a property should trigger invalidation and updating. This function takes the `newValue` and `oldValue` and - returns true if invalidation should occur. If not present, a strict identity + returns `true` if invalidation should occur. If not present, a strict identity check is used. This is useful if a property should be considered dirty only if some condition is met, like if a key of an object value changes. @@ -63,8 +63,9 @@ and renders declaratively using `lit-html`. * static elements: ``` html`
Hi
` ``` * expression: ``` html`
${disabled ? 'Off' : 'On'}
` ``` - * attribute: ``` html`
` ``` - * event handler: ``` html`` ``` + * property: ``` html`` ``` + * attribute: ``` html`
` ``` + * event handler: ``` html`` ``` ## Getting started @@ -113,7 +114,7 @@ into the element. This is the only method that must be implemented by subclasses ```html