From 5efd243958dceff76527d55b8d08d32869f7b8cc Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Mon, 13 Jul 2020 11:28:55 +0200 Subject: [PATCH 1/5] chore: add types setup --- .circleci/config.yml | 1 + .gitignore | 4 +++ package.json | 8 +++++- tsconfig.build.types.json | 8 ++++++ tsconfig.json | 24 ++++++++++++++++++ yarn.lock | 51 ++++++++++++++++++++++++++++++++++----- 6 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 tsconfig.build.types.json create mode 100644 tsconfig.json diff --git a/.circleci/config.yml b/.circleci/config.yml index e140409c4..1e22885ff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,6 +57,7 @@ jobs: command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - run: git config --global user.email circleci@circleci - run: git config --global user.name CircleCI + - run: npm run build:types - run: name: Publish package command: "./node_modules/.bin/lerna publish --message 'chore: release new versions' --exact --yes" diff --git a/.gitignore b/.gitignore index 1df5c5078..c0ec93709 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,10 @@ yarn-error.log /build/ /bundlesize/dist/ +## types +*.d.ts +!packages/*/types/* + ## temp folders /.tmp/ /coverage/ diff --git a/package.json b/package.json index 4f19dcd20..3ee31dc1a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "packages/*" ], "scripts": { + "build:docs": "wca analyze \"packages/tabs/**/*.js\"", + "build:types": "tsc -p tsconfig.build.types.json", "bundlesize": "rollup -c bundlesize/rollup.config.js && bundlesize", "dev-server": "es-dev-server", "format": "npm run format:eslint && npm run format:prettier", @@ -16,6 +18,7 @@ "lint:eslint": "eslint --ext .js,.html .", "lint:markdownlint": "git ls-files '*.md' | xargs markdownlint --ignore '**/CHANGELOG.md'", "lint:prettier": "prettier \"**/*.js\" --list-different || (echo '↑↑ these files are not prettier formatted ↑↑' && exit 1)", + "lint:types": "tsc", "lint:versions": "node ./scripts/lint-versions.js", "start": "npm run storybook", "storybook": "start-storybook -p 9001", @@ -33,10 +36,11 @@ "@open-wc/building-rollup": "^1.2.1", "@open-wc/demoing-storybook": "^2.0.2", "@open-wc/eslint-config": "^1.0.0", - "@open-wc/testing": "^2.5.0", + "@open-wc/testing": "^2.5.18", "@open-wc/testing-helpers": "^1.0.0", "@storybook/addon-a11y": "~5.0.0", "@web/test-runner": "^0.6.18", + "@types/chai-dom": "^0.0.8", "@webcomponents/webcomponentsjs": "^2.2.5", "babel-eslint": "^8.2.6", "babel-polyfill": "^6.26.0", @@ -56,6 +60,8 @@ "rimraf": "^2.6.3", "rollup": "^2.0.0", "sinon": "^7.2.2", + "typescript": "^3.8.3", + "web-component-analyzer": "^1.0.3", "webpack-merge": "^4.1.5", "whatwg-fetch": "^3.0.0" }, diff --git a/tsconfig.build.types.json b/tsconfig.build.types.json new file mode 100644 index 000000000..eb0daa6a4 --- /dev/null +++ b/tsconfig.build.types.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "noEmit": false + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..02514af21 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "lib": ["es2017", "dom"], + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true, + "noImplicitThis": true, + "alwaysStrict": true, + "types": ["node", "mocha"], + "esModuleInterop": true + }, + "include": ["packages/core/**/*.js", "packages/tabs/**/*.js"], + "exclude": [ + "node_modules", + "**/node_modules/*", + "**/coverage/*", + "**/dist/**/*", + "packages/**/test-helpers" + ] +} diff --git a/yarn.lock b/yarn.lock index 496fcb7a9..869c7b1d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2413,11 +2413,16 @@ "@open-wc/semantic-dom-diff" "^0.13.16" "@types/chai" "^4.1.7" -"@open-wc/dedupe-mixin@^1.2.1", "@open-wc/dedupe-mixin@^1.2.17": +"@open-wc/dedupe-mixin@^1.2.17": version "1.2.17" resolved "https://registry.yarnpkg.com/@open-wc/dedupe-mixin/-/dedupe-mixin-1.2.17.tgz#50fb903fc8785639487d7987caae45d7bba08ec7" integrity sha512-9A3WohqNxEloJa4y1DuBL5zH12cNRNW1vsrkiaLMnOGuQdhibs2XY1oliudsKpvIeNjDXRVRPUdIIzn65BypCw== +"@open-wc/dedupe-mixin@^1.2.18": + version "1.2.18" + resolved "https://registry.yarnpkg.com/@open-wc/dedupe-mixin/-/dedupe-mixin-1.2.18.tgz#2a86672fb3558fe2a2e1c5587dbaa0b485567ef1" + integrity sha512-1HpblP5edeENi0SKms7B+PKYdxHMBIQpaf0nAgTVsZeYgM9OJ3r9nrK/0MOUBZODAOZ1quvO3wlpuljq2hZPWA== + "@open-wc/demoing-storybook@^2.0.2": version "2.3.11" resolved "https://registry.yarnpkg.com/@open-wc/demoing-storybook/-/demoing-storybook-2.3.11.tgz#4c4887760591c6e58fb25852c92f0357c3445246" @@ -2516,7 +2521,7 @@ lit-element "^2.2.1" lit-html "^1.0.0" -"@open-wc/testing@^2.5.0": +"@open-wc/testing@^2.5.18": version "2.5.18" resolved "https://registry.yarnpkg.com/@open-wc/testing/-/testing-2.5.18.tgz#b05215535a22515fbd98183eface1c33fd804a5d" integrity sha512-poFIaGEsHseNEbAE/pGjzZGbSksxqsL4CRr+MSPUEotzhbVa3BzA3JzPHfn3FD1zVGlBcNEU0kFa0jj/Goc52w== @@ -2807,6 +2812,13 @@ resolved "https://registry.yarnpkg.com/@types/caniuse-api/-/caniuse-api-3.0.0.tgz#af31cc52062be0ab24583be072fd49b634dcc2fe" integrity sha512-wT1VfnScjAftZsvLYaefu/UuwYJdYBwD2JDL2OQd01plGmuAoir5V6HnVHgrfh7zEwcasoiyO2wQ+W58sNh2sw== +"@types/chai-dom@^0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/chai-dom/-/chai-dom-0.0.8.tgz#832b68d78390c80030e64d8fd4990e514484e91d" + integrity sha512-F5vAuz2hp7VY+0UmkFLgTwyeHDddIw1c/JbAQZp2lgpZOd3ujlHFijWO4sHdo7sun9HrzwnSJlZfINdKrUwI+w== + dependencies: + "@types/chai" "*" + "@types/chai-dom@^0.0.9": version "0.0.9" resolved "https://registry.yarnpkg.com/@types/chai-dom/-/chai-dom-0.0.9.tgz#77379354efec2568284ca355fff6a4f85f5a66f4" @@ -6438,6 +6450,18 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.1.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-glob@^3.1.1: version "3.2.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.2.tgz#ade1a9d91148965d4bf7c51f72e1ca662d32e63d" @@ -13026,6 +13050,11 @@ try-to-catch@^1.0.2: resolved "https://registry.yarnpkg.com/try-to-catch/-/try-to-catch-1.1.1.tgz#770162dd13b9a0e55da04db5b7f888956072038a" integrity sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA== +ts-simple-type@~0.3.6: + version "0.3.7" + resolved "https://registry.yarnpkg.com/ts-simple-type/-/ts-simple-type-0.3.7.tgz#1e77222c3d90d7093f80a954e74c725fd99c911c" + integrity sha512-bDXWURwpDpe1mA5E9eldmI0Mpt9zGprhtN/ZTLOJjsAMyeMy1UT7WvGRQghYewIYBYxDZurChhe4DrsPbcCVrA== + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" @@ -13127,10 +13156,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.6.4: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== +typescript@^3.5.3, typescript@^3.8.3: + version "3.9.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.6.tgz#8f3e0198a34c3ae17091b35571d3afd31999365a" + integrity sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw== typical@^4.0.0: version "4.0.0" @@ -13629,6 +13658,16 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-component-analyzer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/web-component-analyzer/-/web-component-analyzer-1.0.3.tgz#da73dff15d6a8f4864311664476f0f436274e97d" + integrity sha512-QA6GVVJrKRPHLVqPv4evY0H+du1yY+E1q8c82bdY5e10+pWsRfeYA+Hsh2r8yl1EGQVC55SeV3tGvJ6+CxaH/Q== + dependencies: + fast-glob "^3.1.0" + ts-simple-type "~0.3.6" + typescript "^3.5.3" + yargs "^15.0.2" + web-namespaces@^1.0.0, web-namespaces@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" From ec65da5da6ae9096c546fc46583ad3a99458d8e6 Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Mon, 13 Jul 2020 11:30:21 +0200 Subject: [PATCH 2/5] feat(core): fix types and export type definition files for core --- packages/core/index.js | 5 - packages/core/package.json | 7 +- packages/core/src/DelegateMixin.js | 326 +++++++-------- packages/core/src/DisabledMixin.js | 105 ++--- .../core/src/DisabledWithTabIndexMixin.js | 140 +++---- packages/core/src/LionSingleton.js | 5 +- packages/core/src/SlotMixin.js | 117 +++--- packages/core/src/UpdateStylesMixin.js | 125 +++--- .../core/src/differentKeyEventNamesShimIE.js | 10 +- packages/core/test/DelegateMixin.test.js | 370 ++++++++++-------- packages/core/test/DisabledMixin.test.js | 31 +- .../test/DisabledWithTabIndexMixin.test.js | 30 +- packages/core/test/LionSingleton.test.js | 14 + packages/core/test/SlotMixin.test.js | 58 +-- packages/core/test/UpdateStylesMixin.test.js | 107 +++-- packages/core/types/DelegateMixinTypes.d.ts | 51 +++ packages/core/types/DisabledMixinTypes.d.ts | 29 ++ .../types/DisabledWithTabIndexMixinTypes.d.ts | 29 ++ packages/core/types/SlotMixinTypes.d.ts | 53 +++ .../core/types/UpdateStylesMixinTypes.d.ts | 34 ++ 20 files changed, 943 insertions(+), 703 deletions(-) create mode 100644 packages/core/types/DelegateMixinTypes.d.ts create mode 100644 packages/core/types/DisabledMixinTypes.d.ts create mode 100644 packages/core/types/DisabledWithTabIndexMixinTypes.d.ts create mode 100644 packages/core/types/SlotMixinTypes.d.ts create mode 100644 packages/core/types/UpdateStylesMixinTypes.d.ts diff --git a/packages/core/index.js b/packages/core/index.js index 0985137e7..5b06d917f 100644 --- a/packages/core/index.js +++ b/packages/core/index.js @@ -1,8 +1,3 @@ -/** - * Info for TypeScript users: - * For now please import types from lit-element and lit-html directly. - */ - // lit-element export { css, diff --git a/packages/core/package.json b/packages/core/package.json index 8f19358eb..c0a367df0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", + "test", "test-helpers", - "translations" + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", @@ -26,7 +29,7 @@ }, "sideEffects": false, "dependencies": { - "@open-wc/dedupe-mixin": "^1.2.1", + "@open-wc/dedupe-mixin": "^1.2.18", "@open-wc/scoped-elements": "^1.0.3", "lit-element": "^2.2.1", "lit-html": "^1.0.0" diff --git a/packages/core/src/DelegateMixin.js b/packages/core/src/DelegateMixin.js index 04f5ee9a1..3c4dba16c 100644 --- a/packages/core/src/DelegateMixin.js +++ b/packages/core/src/DelegateMixin.js @@ -1,203 +1,177 @@ /* eslint-disable class-methods-use-this */ - import { dedupeMixin } from '@open-wc/dedupe-mixin'; /** - * # DelegateMixin - * Forwards defined events, methods, properties and attributes to the defined target. - * - * @example - * get delegations() { - * return { - * ...super.delegations, - * target: () => this.shadowRoot.getElementById('button1'), - * events: ['click'], - * methods: ['click'], - * properties: ['disabled'], - * attributes: ['disabled'], - * }; - * } - * - * render() { - * return html` - * - * `; - * } - * - * @type {function()} - * @polymerMixin - * @mixinFunction + * @typedef {import('../types/DelegateMixinTypes').DelegateMixin} DelegateMixin */ -export const DelegateMixin = dedupeMixin( - superclass => - // eslint-disable-next-line - class DelegateMixin extends superclass { - constructor() { - super(); - this.__eventsQueue = []; - this.__propertiesQueue = {}; - this.__setupPropertyDelegation(); - } - /** - * @returns {{target: null, events: Array, methods: Array, properties: Array, attributes: Array}} - */ - get delegations() { - return { - target: null, - events: [], - methods: [], - properties: [], - attributes: [], - }; - } +/** + * @typedef DelegateEvent + * @property {string} type - Type of event + * @property {Array} args - Event arguments + */ - connectedCallback() { - if (super.connectedCallback) { - super.connectedCallback(); - } - this._connectDelegateMixin(); - } +/** @type {DelegateMixin} */ +const DelegateMixinImplementation = superclass => + // eslint-disable-next-line + class DelegateMixin extends superclass { + constructor() { + super(); - updated(...args) { - super.updated(...args); - this._connectDelegateMixin(); - } + /** @type {DelegateEvent[]} */ + this.__eventsQueue = []; - /** - * @param {string} type - * @param {Object} args - */ - addEventListener(type, ...args) { - const delegatedEvents = this.delegations.events; - if (delegatedEvents.indexOf(type) > -1) { - if (this.delegationTarget) { - this.delegationTarget.addEventListener(type, ...args); - } else { - this.__eventsQueue.push({ type, args }); - } + /** @type {Object.} */ + this.__propertiesQueue = {}; + this.__setupPropertyDelegation(); + } + + /** + * @returns {{target: Function, events: string[], methods: string[], properties: string[], attributes: string[]}} + */ + get delegations() { + return { + target: () => {}, + events: [], + methods: [], + properties: [], + attributes: [], + }; + } + + connectedCallback() { + if (super.connectedCallback) { + super.connectedCallback(); + } + this._connectDelegateMixin(); + } + + /** @param {import('lit-element').PropertyValues } changedProperties */ + updated(changedProperties) { + super.updated(changedProperties); + this._connectDelegateMixin(); + } + + /** + * @param {string} type + * @param {...Object} args + */ + addEventListener(type, ...args) { + const delegatedEvents = this.delegations.events; + if (delegatedEvents.indexOf(type) > -1) { + if (this.delegationTarget) { + this.delegationTarget.addEventListener(type, ...args); } else { - super.addEventListener(type, ...args); + this.__eventsQueue.push({ type, args }); } + } else { + super.addEventListener(type, ...args); } + } - /** - * @param {string} name - * @param {string} value - */ - setAttribute(name, value) { - const attributeNames = this.delegations.attributes; - if (attributeNames.indexOf(name) > -1) { - if (this.delegationTarget) { - this.delegationTarget.setAttribute(name, value); - } - super.removeAttribute(name); - } else { - super.setAttribute(name, value); - } - } - - /** - * @param {string} name - */ - removeAttribute(name) { - const attributeNames = this.delegations.attributes; - if (attributeNames.indexOf(name) > -1) { - if (this.delegationTarget) { - this.delegationTarget.removeAttribute(name); - } + /** + * @param {string} name + * @param {string} value + */ + setAttribute(name, value) { + const attributeNames = this.delegations.attributes; + if (attributeNames.indexOf(name) > -1) { + if (this.delegationTarget) { + this.delegationTarget.setAttribute(name, value); } super.removeAttribute(name); + } else { + super.setAttribute(name, value); } + } - /** - * @protected - */ - _connectDelegateMixin() { - if (this.__connectedDelegateMixin) return; - - if (!this.delegationTarget) { - this.delegationTarget = this.delegations.target(); - } - + /** + * @param {string} name + */ + removeAttribute(name) { + const attributeNames = this.delegations.attributes; + if (attributeNames.indexOf(name) > -1) { if (this.delegationTarget) { - this.__emptyEventListenerQueue(); - this.__emptyPropertiesQueue(); - this.__initialAttributeDelegation(); - - this.__connectedDelegateMixin = true; + this.delegationTarget.removeAttribute(name); } } + super.removeAttribute(name); + } - /** - * @private - */ - __setupPropertyDelegation() { - const propertyNames = this.delegations.properties.concat(this.delegations.methods); - propertyNames.forEach(propertyName => { - Object.defineProperty(this, propertyName, { - get() { - const target = this.delegationTarget; - if (target) { - if (typeof target[propertyName] === 'function') { - return target[propertyName].bind(target); - } - return target[propertyName]; + _connectDelegateMixin() { + if (this.__connectedDelegateMixin) return; + + if (!this.delegationTarget) { + this.delegationTarget = this.delegations.target(); + } + + if (this.delegationTarget) { + this.__emptyEventListenerQueue(); + this.__emptyPropertiesQueue(); + this.__initialAttributeDelegation(); + + this.__connectedDelegateMixin = true; + } + } + + __setupPropertyDelegation() { + const propertyNames = this.delegations.properties.concat(this.delegations.methods); + propertyNames.forEach(propertyName => { + Object.defineProperty(this, propertyName, { + get() { + const target = this.delegationTarget; + if (target) { + if (typeof target[propertyName] === 'function') { + return target[propertyName].bind(target); } - if (this.__propertiesQueue[propertyName]) { - return this.__propertiesQueue[propertyName]; + return target[propertyName]; + } + if (this.__propertiesQueue[propertyName]) { + return this.__propertiesQueue[propertyName]; + } + // This is the moment the attribute is not delegated (and thus removed) yet. + // and the property is not set, but the attribute is (it serves as a fallback for + // __propertiesQueue). + return this.getAttribute(propertyName); + }, + set(newValue) { + if (this.delegationTarget) { + const oldValue = this.delegationTarget[propertyName]; + this.delegationTarget[propertyName] = newValue; + // connect with observer system if available + if (typeof this.triggerObserversFor === 'function') { + this.triggerObserversFor(propertyName, newValue, oldValue); } - // This is the moment the attribute is not delegated (and thus removed) yet. - // and the property is not set, but the attribute is (it serves as a fallback for - // __propertiesQueue). - return this.getAttribute(propertyName); - }, - set(newValue) { - if (this.delegationTarget) { - const oldValue = this.delegationTarget[propertyName]; - this.delegationTarget[propertyName] = newValue; - // connect with observer system if available - if (typeof this.triggerObserversFor === 'function') { - this.triggerObserversFor(propertyName, newValue, oldValue); - } - } else { - this.__propertiesQueue[propertyName] = newValue; - } - }, - }); + } else { + this.__propertiesQueue[propertyName] = newValue; + } + }, }); - } + }); + } - /** - * @private - */ - __initialAttributeDelegation() { - const attributeNames = this.delegations.attributes; - attributeNames.forEach(attributeName => { - const attributeValue = this.getAttribute(attributeName); - if (typeof attributeValue === 'string') { - this.delegationTarget.setAttribute(attributeName, attributeValue); - super.removeAttribute(attributeName); - } - }); - } + __initialAttributeDelegation() { + const attributeNames = this.delegations.attributes; + attributeNames.forEach(attributeName => { + const attributeValue = this.getAttribute(attributeName); + if (typeof attributeValue === 'string') { + this.delegationTarget.setAttribute(attributeName, attributeValue); + super.removeAttribute(attributeName); + } + }); + } - /** - * @private - */ - __emptyEventListenerQueue() { - this.__eventsQueue.forEach(ev => { - this.delegationTarget.addEventListener(ev.type, ...ev.args); - }); - } + __emptyEventListenerQueue() { + this.__eventsQueue.forEach(ev => { + this.delegationTarget.addEventListener(ev.type, ...ev.args); + }); + } - /** - * @private - */ - __emptyPropertiesQueue() { - Object.keys(this.__propertiesQueue).forEach(propName => { - this.delegationTarget[propName] = this.__propertiesQueue[propName]; - }); - } - }, -); + __emptyPropertiesQueue() { + Object.keys(this.__propertiesQueue).forEach(propName => { + this.delegationTarget[propName] = this.__propertiesQueue[propName]; + }); + } + }; + +export const DelegateMixin = dedupeMixin(DelegateMixinImplementation); diff --git a/packages/core/src/DisabledMixin.js b/packages/core/src/DisabledMixin.js index 3119670f6..1dea190e8 100644 --- a/packages/core/src/DisabledMixin.js +++ b/packages/core/src/DisabledMixin.js @@ -1,64 +1,67 @@ import { dedupeMixin } from '@open-wc/dedupe-mixin'; /** - * #DisabledMixin - * - * @polymerMixin - * @mixinFunction + * @typedef {import('../types/DisabledMixinTypes').DisabledMixin} DisabledMixin */ -export const DisabledMixin = dedupeMixin( - superclass => - // eslint-disable-next-line no-shadow - class DisabledMixin extends superclass { - static get properties() { - return { - disabled: { - type: Boolean, - reflect: true, - }, - }; - } - constructor() { - super(); +/** @type {DisabledMixin} */ +const DisabledMixinImplementation = superclass => + // eslint-disable-next-line no-shadow + class DisabledMixinHost extends superclass { + static get properties() { + return { + disabled: { + type: Boolean, + reflect: true, + }, + }; + } + + constructor() { + super(); + this.__requestedToBeDisabled = false; + this.__isUserSettingDisabled = true; + this.__restoreDisabledTo = false; + this.disabled = false; + } + + makeRequestToBeDisabled() { + if (this.__requestedToBeDisabled === false) { + this.__requestedToBeDisabled = true; + this.__restoreDisabledTo = this.disabled; + this.__internalSetDisabled(true); + } + } + + retractRequestToBeDisabled() { + if (this.__requestedToBeDisabled === true) { this.__requestedToBeDisabled = false; - this.__isUserSettingDisabled = true; - - this.__restoreDisabledTo = false; - this.disabled = false; + this.__internalSetDisabled(this.__restoreDisabledTo); } + } - makeRequestToBeDisabled() { - if (this.__requestedToBeDisabled === false) { - this.__requestedToBeDisabled = true; + /** @param {boolean} value */ + __internalSetDisabled(value) { + this.__isUserSettingDisabled = false; + this.disabled = value; + this.__isUserSettingDisabled = true; + } + + /** + * @param {PropertyKey} name + * @param {?} oldValue + */ + _requestUpdate(name, oldValue) { + super._requestUpdate(name, oldValue); + if (name === 'disabled') { + if (this.__isUserSettingDisabled) { this.__restoreDisabledTo = this.disabled; + } + if (this.disabled === false && this.__requestedToBeDisabled === true) { this.__internalSetDisabled(true); } } + } + }; - retractRequestToBeDisabled() { - if (this.__requestedToBeDisabled === true) { - this.__requestedToBeDisabled = false; - this.__internalSetDisabled(this.__restoreDisabledTo); - } - } - - __internalSetDisabled(value) { - this.__isUserSettingDisabled = false; - this.disabled = value; - this.__isUserSettingDisabled = true; - } - - _requestUpdate(name, oldValue) { - super._requestUpdate(name, oldValue); - if (name === 'disabled') { - if (this.__isUserSettingDisabled) { - this.__restoreDisabledTo = this.disabled; - } - if (this.disabled === false && this.__requestedToBeDisabled === true) { - this.__internalSetDisabled(true); - } - } - } - }, -); +export const DisabledMixin = dedupeMixin(DisabledMixinImplementation); diff --git a/packages/core/src/DisabledWithTabIndexMixin.js b/packages/core/src/DisabledWithTabIndexMixin.js index e0570bacf..0ecea056e 100644 --- a/packages/core/src/DisabledWithTabIndexMixin.js +++ b/packages/core/src/DisabledWithTabIndexMixin.js @@ -2,85 +2,91 @@ import { dedupeMixin } from '@open-wc/dedupe-mixin'; import { DisabledMixin } from './DisabledMixin.js'; /** - * #DisabledWithTabIndexMixin - * - * @polymerMixin - * @mixinFunction + * @typedef {import('../types/DisabledWithTabIndexMixinTypes').DisabledWithTabIndexMixin} DisabledWithTabIndexMixin */ -export const DisabledWithTabIndexMixin = dedupeMixin( - superclass => - // eslint-disable-next-line no-shadow - class DisabledWithTabIndexMixin extends DisabledMixin(superclass) { - static get properties() { - return { - // we use a property here as if we use the native tabIndex we can not set a default value - // in the constructor as it synchronously sets the attribute which is not allowed in the - // constructor phase - tabIndex: { - type: Number, - reflect: true, - attribute: 'tabindex', - }, - }; + +/** @type {DisabledWithTabIndexMixin} */ +const DisabledWithTabIndexMixinImplementation = superclass => + // eslint-disable-next-line no-shadow + class DisabledWithTabIndexMixinHost extends DisabledMixin(superclass) { + static get properties() { + return { + // we use a property here as if we use the native tabIndex we can not set a default value + // in the constructor as it synchronously sets the attribute which is not allowed in the + // constructor phase + tabIndex: { + type: Number, + reflect: true, + attribute: 'tabindex', + }, + }; + } + + constructor() { + super(); + this.__isUserSettingTabIndex = true; + this.__restoreTabIndexTo = 0; + this.__internalSetTabIndex(0); + } + + makeRequestToBeDisabled() { + super.makeRequestToBeDisabled(); + if (this.__requestedToBeDisabled === false && this.tabIndex != null) { + this.__restoreTabIndexTo = this.tabIndex; } + } - constructor() { - super(); - this.__isUserSettingTabIndex = true; - - this.__restoreTabIndexTo = 0; - this.__internalSetTabIndex(0); + retractRequestToBeDisabled() { + super.retractRequestToBeDisabled(); + if (this.__requestedToBeDisabled === true) { + this.__internalSetTabIndex(this.__restoreTabIndexTo); } + } - makeRequestToBeDisabled() { - super.makeRequestToBeDisabled(); - if (this.__requestedToBeDisabled === false) { - this.__restoreTabIndexTo = this.tabIndex; - } - } + /** + * @param {number} value + */ + __internalSetTabIndex(value) { + this.__isUserSettingTabIndex = false; + this.tabIndex = value; + this.__isUserSettingTabIndex = true; + } - retractRequestToBeDisabled() { - super.retractRequestToBeDisabled(); - if (this.__requestedToBeDisabled === true) { + /** + * @param {PropertyKey} name + * @param {?} oldValue + */ + _requestUpdate(name, oldValue) { + super._requestUpdate(name, oldValue); + + if (name === 'disabled') { + if (this.disabled) { + this.__internalSetTabIndex(-1); + } else { this.__internalSetTabIndex(this.__restoreTabIndexTo); } } - __internalSetTabIndex(value) { - this.__isUserSettingTabIndex = false; - this.tabIndex = value; - this.__isUserSettingTabIndex = true; - } - - _requestUpdate(name, oldValue) { - super._requestUpdate(name, oldValue); - - if (name === 'disabled') { - if (this.disabled) { - this.__internalSetTabIndex(-1); - } else { - this.__internalSetTabIndex(this.__restoreTabIndexTo); - } + if (name === 'tabIndex') { + if (this.__isUserSettingTabIndex && this.tabIndex != null) { + this.__restoreTabIndexTo = this.tabIndex; } - if (name === 'tabIndex') { - if (this.__isUserSettingTabIndex) { - this.__restoreTabIndexTo = this.tabIndex; - } - - if (this.tabIndex !== -1 && this.__requestedToBeDisabled === true) { - this.__internalSetTabIndex(-1); - } - } - } - - firstUpdated(changedProperties) { - super.firstUpdated(changedProperties); - // for ShadyDom the timing is a little different and we need to make sure - // the tabindex gets correctly updated here - if (this.disabled) { + if (this.tabIndex !== -1 && this.__requestedToBeDisabled === true) { this.__internalSetTabIndex(-1); } } - }, -); + } + + /** @param {import('lit-element').PropertyValues } changedProperties */ + firstUpdated(changedProperties) { + super.firstUpdated(changedProperties); + // for ShadyDom the timing is a little different and we need to make sure + // the tabindex gets correctly updated here + if (this.disabled) { + this.__internalSetTabIndex(-1); + } + } + }; + +export const DisabledWithTabIndexMixin = dedupeMixin(DisabledWithTabIndexMixinImplementation); diff --git a/packages/core/src/LionSingleton.js b/packages/core/src/LionSingleton.js index 5353ff8c5..de864ab3f 100644 --- a/packages/core/src/LionSingleton.js +++ b/packages/core/src/LionSingleton.js @@ -6,10 +6,11 @@ */ export class LionSingleton { /** - * @param {function()} mixin + * @param {function} mixin */ static addInstanceMixin(mixin) { if (!this.__instanceMixins) { + /** @type {function[]} */ this.__instanceMixins = []; } this.__instanceMixins.push(mixin); @@ -26,6 +27,8 @@ export class LionSingleton { Klass = mixin(Klass); }); } + // Ignoring, because it's up to the extension layer to accept arguments in its constructor + // @ts-ignore-next-line return new Klass(...args); } diff --git a/packages/core/src/SlotMixin.js b/packages/core/src/SlotMixin.js index 5d1e81f76..7ccc31b34 100644 --- a/packages/core/src/SlotMixin.js +++ b/packages/core/src/SlotMixin.js @@ -1,78 +1,59 @@ /* eslint-disable class-methods-use-this */ - import { dedupeMixin } from '@open-wc/dedupe-mixin'; + /** - * # SlotMixin - * `SlotMixin`, when attached to the DOM it creates content for defined slots in the Light DOM. - * The content element is created using a factory function and is assigned a slot name from the key. - * Existing slot content is not overridden. - * - * The purpose is to have the default content in the Light DOM rather than hidden in Shadow DOM - * like default slot content works natively. - * - * @example - * get slots() { - * return { - * ...super.slots, - * // appends
to the Light DOM of this element - * foo: () => document.createElement('div'), - * }; - * } - * - * @type {function()} - * @polymerMixin - * @mixinFunction + * @typedef {import('../types/SlotMixinTypes').SlotMixin} SlotMixin + * @typedef {import('../types/SlotMixinTypes').SlotsMap} SlotsMap */ -export const SlotMixin = dedupeMixin( - superclass => - // eslint-disable-next-line no-unused-vars, no-shadow - class SlotMixin extends superclass { - /** - * @returns {{}} - */ - get slots() { - return {}; - } - constructor() { - super(); - this.__privateSlots = new Set(null); - } +/** @type {SlotMixin} */ +const SlotMixinImplementation = superclass => + // eslint-disable-next-line no-unused-vars, no-shadow + class SlotMixinHost extends superclass { + /** + * @return {SlotsMap} + */ + get slots() { + return {}; + } - connectedCallback() { - if (super.connectedCallback) { - super.connectedCallback(); - } - this._connectSlotMixin(); - } + constructor() { + super(); + this.__privateSlots = new Set(null); + } - /** - * @protected - */ - _connectSlotMixin() { - if (!this.__isConnectedSlotMixin) { - Object.keys(this.slots).forEach(slotName => { - if (!this.querySelector(`[slot=${slotName}]`)) { - const slotFactory = this.slots[slotName]; - const slotContent = slotFactory(); - if (slotContent instanceof Element) { - slotContent.setAttribute('slot', slotName); - this.appendChild(slotContent); - this.__privateSlots.add(slotName); - } // ignore non-elements to enable conditional slots + connectedCallback() { + if (super.connectedCallback) { + super.connectedCallback(); + } + this._connectSlotMixin(); + } + + _connectSlotMixin() { + if (!this.__isConnectedSlotMixin) { + Object.keys(this.slots).forEach(slotName => { + if (!this.querySelector(`[slot=${slotName}]`)) { + const slotFactory = this.slots[slotName]; + const slotContent = slotFactory(); + // ignore non-elements to enable conditional slots + if (slotContent instanceof Element) { + slotContent.setAttribute('slot', slotName); + this.appendChild(slotContent); + this.__privateSlots.add(slotName); } - }); - this.__isConnectedSlotMixin = true; - } + } + }); + this.__isConnectedSlotMixin = true; } + } - /** - * @protected - * @param {string} slotName Name of the slot - * @return {boolean} true if given slot name been created by SlotMixin - */ - _isPrivateSlot(slotName) { - return this.__privateSlots.has(slotName); - } - }, -); + /** + * @param {string} slotName Name of the slot + * @return {boolean} true if given slot name been created by SlotMixin + */ + _isPrivateSlot(slotName) { + return this.__privateSlots.has(slotName); + } + }; + +export const SlotMixin = dedupeMixin(SlotMixinImplementation); diff --git a/packages/core/src/UpdateStylesMixin.js b/packages/core/src/UpdateStylesMixin.js index 3ad4f37e3..753760dd6 100644 --- a/packages/core/src/UpdateStylesMixin.js +++ b/packages/core/src/UpdateStylesMixin.js @@ -1,59 +1,78 @@ /* global ShadyCSS */ import { dedupeMixin } from '@open-wc/dedupe-mixin'; -export const UpdateStylesMixin = dedupeMixin( - superclass => - // eslint-disable-next-line no-shadow - class UpdateStylesMixin extends superclass { - /** - * @example - * - * - * - * - * $0.updateStyles({'background': 'orange', '--foo': '#fff'}) - * Chrome, Firefox: - * IE: - * => to head: - * - * @param {Object} updateStyles - */ - updateStyles(updateStyles) { - const styleString = this.getAttribute('style') || this.getAttribute('data-style') || ''; - const currentStyles = styleString.split(';').reduce((acc, stylePair) => { - const parts = stylePair.split(':'); - if (parts.length === 2) { - /* eslint-disable-next-line prefer-destructuring */ - acc[parts[0]] = parts[1]; - } - return acc; - }, {}); +/** + * @typedef {import('../types/UpdateStylesMixinTypes').UpdateStylesMixin} UpdateStylesMixin + * @typedef {import('../types/UpdateStylesMixinTypes').StylesMap} StylesMap + */ - const newStyles = { ...currentStyles, ...updateStyles }; - let newStylesString = ''; - if (typeof ShadyCSS === 'object' && !ShadyCSS.nativeShadow) { - // No ShadowDOM => IE, Edge - const newCssVariablesObj = {}; - Object.keys(newStyles).forEach(key => { - if (key.indexOf('--') === -1) { - newStylesString += `${key}:${newStyles[key]};`; - } else { - newCssVariablesObj[key] = newStyles[key]; - } - }); - this.setAttribute('style', newStylesString); - ShadyCSS.styleSubtree(this, newCssVariablesObj); - } else { - // has shadowdom => Chrome, Firefox, Safari - Object.keys(newStyles).forEach(key => { - newStylesString += `${key}: ${newStyles[key]};`; - }); - this.setAttribute('style', newStylesString); +/** @type {UpdateStylesMixin} */ +const UpdateStylesMixinImplementation = superclass => + // eslint-disable-next-line no-shadow + class UpdateStylesMixinHost extends superclass { + /** + * @example + * + * + * + * + * $0.updateStyles({'background': 'orange', '--foo': '#fff'}) + * Chrome, Firefox: + * IE: + * => to head: + * + * @param {StylesMap} updateStyles + */ + updateStyles(updateStyles) { + const styleString = this.getAttribute('style') || this.getAttribute('data-style') || ''; + + /** + * reducer function + * @param {Object.} acc + * @param {string} stylePair + */ + const reducer = (acc, stylePair) => { + /** @type {Array.} */ + const parts = stylePair.split(':'); + if (parts.length === 2) { + // eslint-disable-next-line prefer-destructuring + acc[parts[0]] = parts[1]; } + return acc; + }; + const currentStyles = styleString.split(';').reduce(reducer, {}); + + const newStyles = { ...currentStyles, ...updateStyles }; + let newStylesString = ''; + // @ts-ignore not sure how to type ShadyCSS.. + if (typeof ShadyCSS === 'object' && !ShadyCSS.nativeShadow) { + // No ShadowDOM => IE, Edge + + /** @type {Object.} */ + const newCssVariablesObj = {}; + + Object.keys(newStyles).forEach(key => { + if (key.indexOf('--') === -1) { + newStylesString += `${key}:${newStyles[key]};`; + } else { + newCssVariablesObj[key] = newStyles[key]; + } + }); + this.setAttribute('style', newStylesString); + // @ts-ignore not sure how to type ShadyCSS.. + ShadyCSS.styleSubtree(this, newCssVariablesObj); + } else { + // has shadowdom => Chrome, Firefox, Safari + Object.keys(newStyles).forEach(key => { + newStylesString += `${key}: ${newStyles[key]};`; + }); + this.setAttribute('style', newStylesString); } - }, -); + } + }; + +export const UpdateStylesMixin = dedupeMixin(UpdateStylesMixinImplementation); diff --git a/packages/core/src/differentKeyEventNamesShimIE.js b/packages/core/src/differentKeyEventNamesShimIE.js index f67a28ae9..c62b5e8c5 100644 --- a/packages/core/src/differentKeyEventNamesShimIE.js +++ b/packages/core/src/differentKeyEventNamesShimIE.js @@ -3,6 +3,7 @@ if (typeof window.KeyboardEvent !== 'function') { const event = KeyboardEvent.prototype; const descriptor = Object.getOwnPropertyDescriptor(event, 'key'); if (descriptor) { + /** @type {Object.} */ const keys = { Win: 'Meta', Scroll: 'ScrollLock', @@ -26,10 +27,13 @@ if (typeof window.KeyboardEvent !== 'function') { Object.defineProperty(event, 'key', { // eslint-disable-next-line object-shorthand, func-names get: function () { - const key = descriptor.get.call(this); + if (descriptor.get) { + const key = descriptor.get.call(this); - // eslint-disable-next-line no-prototype-builtins - return keys.hasOwnProperty(key) ? keys[key] : key; + // eslint-disable-next-line no-prototype-builtins + return keys.hasOwnProperty(key) ? keys[key] : key; + } + return undefined; }, }); } diff --git a/packages/core/test/DelegateMixin.test.js b/packages/core/test/DelegateMixin.test.js index 09191b15e..a9265bf7e 100644 --- a/packages/core/test/DelegateMixin.test.js +++ b/packages/core/test/DelegateMixin.test.js @@ -14,7 +14,7 @@ describe('DelegateMixin', () => { get delegations() { return { ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), + target: () => this.shadowRoot?.getElementById('button1'), events: ['click'], }; } @@ -27,7 +27,7 @@ describe('DelegateMixin', () => { const element = await fixture(`<${tag}>`); const cb = sinon.spy(); element.addEventListener('click', cb); - element.shadowRoot.getElementById('button1').click(); + element.shadowRoot?.getElementById('button1')?.click(); expect(cb.callCount).to.equal(1); }); @@ -37,7 +37,7 @@ describe('DelegateMixin', () => { get delegations() { return { ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), + target: () => this.shadowRoot?.getElementById('button1'), events: ['click'], }; } @@ -47,12 +47,12 @@ describe('DelegateMixin', () => { } }, ); - const element = document.createElement(tag); + const element = /** @type {LitElement} */ (document.createElement(tag)); const cb = sinon.spy(); element.addEventListener('click', cb); document.body.appendChild(element); await element.updateComplete; - element.shadowRoot.getElementById('button1').click(); + element.shadowRoot?.getElementById('button1')?.click(); expect(cb.callCount).to.equal(1); // cleanup @@ -80,37 +80,37 @@ describe('DelegateMixin', () => { }, ); - const element = await fixture(` - <${tag}>`); + const element = await fixture(`<${tag}>`); const cb = sinon.spy(); element.addEventListener('click', cb); - Array.from(element.children) - .find(child => child.slot === 'button') - .click(); + const childEl = /** @type {HTMLElement} */ (Array.from(element.children)?.find( + child => child.slot === 'button', + )); + childEl?.click(); expect(cb.callCount).to.equal(1); }); it('will still support other events', async () => { - const tag = defineCE( - class extends DelegateMixin(LitElement) { - get delegations() { - return { - ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), - events: ['click'], - }; - } + class FooDelegate extends DelegateMixin(LitElement) { + get delegations() { + return { + ...super.delegations, + target: () => this.shadowRoot?.getElementById('button1'), + events: ['click'], + }; + } - render() { - return html``; - } + render() { + return html``; + } - foo() { - this.dispatchEvent(new CustomEvent('foo-event', { bubbles: true, composed: true })); - } - }, - ); - const element = await fixture(`<${tag}>`); + foo() { + this.dispatchEvent(new CustomEvent('foo-event', { bubbles: true, composed: true })); + } + } + + const tag = defineCE(FooDelegate); + const element = /** @type {FooDelegate} */ (await fixture(`<${tag}>`)); const cb = sinon.spy(); element.addEventListener('foo-event', cb); element.foo(); @@ -123,7 +123,7 @@ describe('DelegateMixin', () => { get delegations() { return { ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), + target: () => this.shadowRoot?.getElementById('button1'), methods: ['click'], }; } @@ -133,9 +133,9 @@ describe('DelegateMixin', () => { } }, ); - const element = await fixture(`<${tag}>`); + const element = /** @type {HTMLElement} */ (await fixture(`<${tag}>`)); const cb = sinon.spy(); - element.shadowRoot.getElementById('button1').addEventListener('click', cb); + element.shadowRoot?.getElementById('button1')?.addEventListener('click', cb); element.click(); expect(cb.callCount).to.equal(1); }); @@ -147,56 +147,69 @@ describe('DelegateMixin', () => { this.foo = { a: 'a', b: 'b' }; } + /** + * @param {?} a + * @param {?} b + */ setFooAandB(a, b) { this.foo.a = a; this.foo.b = b; } } customElements.define('delegate-argument-sub', DelegateArgumentSub); - const tag = defineCE( - class extends DelegateMixin(LitElement) { - get delegations() { - return { - ...super.delegations, - target: () => this.shadowRoot.getElementById('sub'), - methods: ['setFooAandB'], - }; - } - render() { - return html``; - } - }, - ); + class DelegateArgumentParent extends DelegateMixin(LitElement) { + get delegations() { + return { + ...super.delegations, + target: () => this.shadowRoot?.getElementById('sub'), + methods: ['setFooAandB'], + }; + } - const element = await fixture(`<${tag}>`); - element.disabled = true; + render() { + return html``; + } + } + const tag = defineCE(DelegateArgumentParent); + + const element = /** @type {DelegateArgumentParent} */ (await fixture(`<${tag}>`)); + + // @ts-ignore because this method, even though it doesn't exist on the parent, gets delegated through delegations to the child, where it does exist! element.setFooAandB('newA', 'newB'); - expect(element.shadowRoot.getElementById('sub').foo.a).to.equal('newA'); - expect(element.shadowRoot.getElementById('sub').foo.b).to.equal('newB'); + + const sub = /** @type {DelegateArgumentSub} */ (element.shadowRoot?.getElementById('sub')); + expect(sub.foo.a).to.equal('newA'); + expect(sub.foo.b).to.equal('newB'); }); it('will set delegated properties', async () => { - const tag = defineCE( - class extends DelegateMixin(LitElement) { - get delegations() { - return { - ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), - properties: ['disabled'], - }; - } + class PropDelegate extends DelegateMixin(LitElement) { + get delegations() { + return { + ...super.delegations, + target: () => this.shadowRoot?.getElementById('button1'), + properties: ['disabled'], + }; + } - render() { - return html``; - } - }, - ); - const element = await fixture(`<${tag}>`); + render() { + return html``; + } + } + const tag = defineCE(PropDelegate); + const element = /** @type {PropDelegate} */ (await fixture(`<${tag}>`)); + + // @ts-ignore ignoring this one, because disabled is delegated through target so it indeed does not inherently exist on the div element element.disabled = true; + await element.updateComplete; - expect(element.shadowRoot.getElementById('button1').disabled).to.equal(true); - expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true); + + /** @typedef {Object.} Btn */ + /** @typedef {Btn & HTMLElement} DelegatedBtn */ + const btn = /** @type {DelegatedBtn} */ (element.shadowRoot?.getElementById('button1')); + expect(btn?.disabled).to.equal(true); + expect(btn?.hasAttribute('disabled')).to.equal(true); }); it('delegates properties before delegation target is attached to DOM', async () => { @@ -205,7 +218,7 @@ describe('DelegateMixin', () => { get delegations() { return { ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), + target: () => this.shadowRoot?.getElementById('button1'), properties: ['disabled'], }; } @@ -215,12 +228,18 @@ describe('DelegateMixin', () => { } }, ); - const element = document.createElement(tag); + /** @typedef {Object.} Btn */ + /** @typedef {Btn & LitElement} DelegatedEl */ + const element = /** @type {DelegatedEl} */ (document.createElement(tag)); + element.disabled = true; document.body.appendChild(element); await element.updateComplete; - expect(element.shadowRoot.getElementById('button1').disabled).to.equal(true); + /** @typedef {Btn & HTMLElement} DelegatedBtn */ + const btn = /** @type {DelegatedBtn} */ (element.shadowRoot?.getElementById('button1')); + + expect(btn?.disabled).to.equal(true); // cleanup document.body.removeChild(element); }); @@ -231,7 +250,7 @@ describe('DelegateMixin', () => { get delegations() { return { ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), + target: () => this.shadowRoot?.getElementById('button1'), attributes: ['disabled'], }; } @@ -241,11 +260,11 @@ describe('DelegateMixin', () => { } }, ); - const element = await fixture(`<${tag}>`); + const element = /** @type {LitElement} */ (await fixture(`<${tag}>`)); element.setAttribute('disabled', ''); await element.updateComplete; expect(element.hasAttribute('disabled')).to.equal(false); - expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true); + expect(element.shadowRoot?.getElementById('button1')?.hasAttribute('disabled')).to.equal(true); }); it('will read inital attributes', async () => { @@ -254,7 +273,7 @@ describe('DelegateMixin', () => { get delegations() { return { ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), + target: () => this.shadowRoot?.getElementById('button1'), attributes: ['disabled'], }; } @@ -266,7 +285,7 @@ describe('DelegateMixin', () => { ); const element = await fixture(`<${tag} disabled>`); expect(element.hasAttribute('disabled')).to.equal(false); - expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true); + expect(element.shadowRoot?.getElementById('button1')?.hasAttribute('disabled')).to.equal(true); }); it('will delegate removeAttribute', async () => { @@ -275,7 +294,7 @@ describe('DelegateMixin', () => { get delegations() { return { ...super.delegations, - target: () => this.shadowRoot.getElementById('button1'), + target: () => this.shadowRoot?.getElementById('button1'), attributes: ['disabled'], }; } @@ -285,135 +304,146 @@ describe('DelegateMixin', () => { } }, ); - const element = await fixture(`<${tag} disabled>`); - element.removeAttribute('disabled', ''); + const element = /** @type {LitElement} */ (await fixture(`<${tag} disabled>`)); + element.removeAttribute('disabled'); await element.updateComplete; expect(element.hasAttribute('disabled')).to.equal(false); - expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(false); + expect(element.shadowRoot?.getElementById('button1')?.hasAttribute('disabled')).to.equal(false); }); it('respects user defined values for delegated attributes and properties', async () => { - const tag = defineCE( - class extends DelegateMixin(LitElement) { - get delegations() { - return { - ...super.delegations, - // this just means itś config is set to the queue when called before connectedCallback - target: () => this.scheduledElement, - attributes: ['type'], - properties: ['type'], - }; - } + class ScheduledElement extends DelegateMixin(LitElement) { + get delegations() { + return { + ...super.delegations, + // this just means itś config is set to the queue when called before connectedCallback + target: () => this.scheduledElement, + attributes: ['type'], + properties: ['type'], + }; + } - get scheduledElement() { - return this.querySelector('input'); - } + get scheduledElement() { + return this.querySelector('input'); + } - constructor() { - super(); - this.type = 'email'; // 1. here we set the delegated prop and it should be scheduled - } + constructor() { + super(); + this.type = 'email'; // 1. here we set the delegated prop and it should be scheduled + } - connectedCallback() { - // 2. this is where we add teh delegation target (so after 1) - this.appendChild(document.createElement('input')); - super.connectedCallback(); // let the DelegateMixin do its work - } - }, - ); + connectedCallback() { + // 2. this is where we add teh delegation target (so after 1) + this.appendChild(document.createElement('input')); + super.connectedCallback(); // let the DelegateMixin do its work + } + } + + const tag = defineCE(ScheduledElement); const tagName = unsafeStatic(tag); + // Here, the Application Developerd tries to set the type via attribute - const elementAttr = await fixture(`<${tag} type="radio">`); - expect(elementAttr.scheduledElement.type).to.equal('radio'); + const elementAttr = /** @type {ScheduledElement} */ (await fixture( + `<${tag} type="radio">`, + )); + expect(elementAttr.scheduledElement?.type).to.equal('radio'); // Here, the Application Developer tries to set the type via property - const elementProp = await fixture(html`<${tagName} .type=${'radio'}>`); - expect(elementProp.scheduledElement.type).to.equal('radio'); + const elementProp = /** @type {ScheduledElement} */ (await fixture( + html`<${tagName} .type=${'radio'}>`, + )); + expect(elementProp.scheduledElement?.type).to.equal('radio'); }); it(`uses attribute value as a fallback for delegated property getter when property not set by user and delegationTarget not ready`, async () => { - const tag = defineCE( - class extends DelegateMixin(LitElement) { - get delegations() { - return { - ...super.delegations, - target: () => this.delegatedEl, - properties: ['type'], - attributes: ['type'], - }; - } + class FallbackEl extends DelegateMixin(LitElement) { + get delegations() { + return { + ...super.delegations, + target: () => this.delegatedEl, + properties: ['type'], + attributes: ['type'], + }; + } - get delegatedEl() { - // returns null, so we can test that "cached" attr is used as fallback - return null; - } - }, - ); - const element = await fixture(`<${tag} type="radio">`); + get delegatedEl() { + // returns null, so we can test that "cached" attr is used as fallback + return null; + } + } + const tag = defineCE(FallbackEl); + const element = /** @type {FallbackEl} */ (await fixture(`<${tag} type="radio">`)); expect(element.delegatedEl).to.equal(null); + // @ts-ignore ignoring this one, because type is delegated through target so it indeed does not inherently exist on the div element expect(element.type).to.equal('radio'); // value retrieved from host instead of delegatedTarget }); it('works with connectedCallback', async () => { - const tag = await defineCE( - class extends DelegateMixin(HTMLElement) { - get delegations() { - return { - ...super.delegations, - target: () => this.querySelector('div'), - properties: ['foo'], - }; - } - }, - ); - const element = await fixture(`<${tag}>
`); + class ConnectedElement extends DelegateMixin(HTMLElement) { + get delegations() { + return { + ...super.delegations, + target: () => this.querySelector('div'), + properties: ['foo'], + }; + } + } + const tag = await defineCE(ConnectedElement); + const element = /** @type {ConnectedElement} */ (await fixture(`<${tag}>
`)); + + // @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element element.foo = 'new'; - expect(element.querySelector('div').foo).to.equal('new'); + // @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element + expect(element.querySelector('div')?.foo).to.equal('new'); }); it('works with shadow dom', async () => { - const tag = await defineCE( - class extends DelegateMixin(LitElement) { - get delegations() { - return { - ...super.delegations, - target: () => this.shadowRoot.querySelector('div'), - properties: ['foo'], - }; - } + class A extends DelegateMixin(LitElement) { + get delegations() { + return { + ...super.delegations, + target: () => this.shadowRoot?.querySelector('div'), + properties: ['foo'], + }; + } - render() { - return html`
`; - } - }, - ); + render() { + return html`
`; + } + } + const tag = await defineCE(A); const element = await fixture(`<${tag}>`); + + // @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element element.foo = 'new'; - expect(element.shadowRoot.querySelector('div').foo).to.equal('new'); + // @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element + expect(element.shadowRoot?.querySelector('div')?.foo).to.equal('new'); }); it('works with light dom', async () => { - const tag = await defineCE( - class extends DelegateMixin(LitElement) { - get delegations() { - return { - ...super.delegations, - target: () => this.querySelector('div'), - properties: ['foo'], - }; - } + class A extends DelegateMixin(LitElement) { + get delegations() { + return { + ...super.delegations, + target: () => this.querySelector('div'), + properties: ['foo'], + }; + } - createRenderRoot() { - return this; - } + createRenderRoot() { + return this; + } - render() { - return html`
`; - } - }, - ); + render() { + return html`
`; + } + } + + const tag = await defineCE(A); const element = await fixture(`<${tag}>`); + // @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element element.foo = 'new'; - expect(element.querySelector('div').foo).to.equal('new'); + // @ts-ignore ignoring this one, because foo is delegated through target so it indeed does not inherently exist on the div element + expect(element.querySelector('div')?.foo).to.equal('new'); }); }); diff --git a/packages/core/test/DisabledMixin.test.js b/packages/core/test/DisabledMixin.test.js index c9bcf11a7..52f1bc9cc 100644 --- a/packages/core/test/DisabledMixin.test.js +++ b/packages/core/test/DisabledMixin.test.js @@ -3,21 +3,26 @@ import { LitElement } from '../index.js'; import { DisabledMixin } from '../src/DisabledMixin.js'; describe('DisabledMixin', () => { + class CanBeDisabled extends DisabledMixin(LitElement) {} before(() => { - class CanBeDisabled extends DisabledMixin(LitElement) {} customElements.define('can-be-disabled', CanBeDisabled); }); it('reflects disabled to attribute', async () => { - const el = await fixture(html``); + const el = /** @type {CanBeDisabled} */ (await fixture( + html``, + )); expect(el.hasAttribute('disabled')).to.be.false; + el.makeRequestToBeDisabled(); el.disabled = true; await el.updateComplete; expect(el.hasAttribute('disabled')).to.be.true; }); it('can be requested to be disabled', async () => { - const el = await fixture(html``); + const el = /** @type {CanBeDisabled} */ (await fixture( + html``, + )); el.makeRequestToBeDisabled(); expect(el.disabled).to.be.true; await el.updateComplete; @@ -25,7 +30,9 @@ describe('DisabledMixin', () => { }); it('will not allow to become enabled after makeRequestToBeDisabled()', async () => { - const el = await fixture(html``); + const el = /** @type {CanBeDisabled} */ (await fixture( + html``, + )); el.makeRequestToBeDisabled(); expect(el.disabled).to.be.true; @@ -34,14 +41,18 @@ describe('DisabledMixin', () => { }); it('will stay disabled after retractRequestToBeDisabled() if it was disabled before', async () => { - const el = await fixture(html``); + const el = /** @type {CanBeDisabled} */ (await fixture( + html``, + )); el.makeRequestToBeDisabled(); el.retractRequestToBeDisabled(); expect(el.disabled).to.be.true; }); it('will become enabled after retractRequestToBeDisabled() if it was enabled before', async () => { - const el = await fixture(html``); + const el = /** @type {CanBeDisabled} */ (await fixture( + html``, + )); el.makeRequestToBeDisabled(); expect(el.disabled).to.be.true; el.retractRequestToBeDisabled(); @@ -49,7 +60,9 @@ describe('DisabledMixin', () => { }); it('may allow multiple calls to makeRequestToBeDisabled()', async () => { - const el = await fixture(html``); + const el = /** @type {CanBeDisabled} */ (await fixture( + html``, + )); el.makeRequestToBeDisabled(); el.makeRequestToBeDisabled(); el.retractRequestToBeDisabled(); @@ -57,7 +70,9 @@ describe('DisabledMixin', () => { }); it('will restore last state after retractRequestToBeDisabled()', async () => { - const el = await fixture(html``); + const el = /** @type {CanBeDisabled} */ (await fixture( + html``, + )); el.makeRequestToBeDisabled(); el.disabled = true; el.retractRequestToBeDisabled(); diff --git a/packages/core/test/DisabledWithTabIndexMixin.test.js b/packages/core/test/DisabledWithTabIndexMixin.test.js index 3a037f8bc..4afd740da 100644 --- a/packages/core/test/DisabledWithTabIndexMixin.test.js +++ b/packages/core/test/DisabledWithTabIndexMixin.test.js @@ -4,23 +4,23 @@ import { LitElement } from '../index.js'; import { DisabledWithTabIndexMixin } from '../src/DisabledWithTabIndexMixin.js'; describe('DisabledWithTabIndexMixin', () => { + class WithTabIndex extends DisabledWithTabIndexMixin(LitElement) {} before(() => { - class WithTabIndex extends DisabledWithTabIndexMixin(LitElement) {} customElements.define('can-be-disabled-with-tab-index', WithTabIndex); }); it('has an initial tabIndex of 0', async () => { - const el = await fixture(html` + const el = /** @type {WithTabIndex} */ (await fixture(html` - `); + `)); expect(el.tabIndex).to.equal(0); expect(el.getAttribute('tabindex')).to.equal('0'); }); it('sets tabIndex to -1 if disabled', async () => { - const el = await fixture(html` + const el = /** @type {WithTabIndex} */ (await fixture(html` - `); + `)); el.disabled = true; expect(el.tabIndex).to.equal(-1); await el.updateComplete; @@ -28,9 +28,9 @@ describe('DisabledWithTabIndexMixin', () => { }); it('disabled does not override user provided tabindex', async () => { - const el = await fixture(html` + const el = /** @type {WithTabIndex} */ (await fixture(html` - `); + `)); expect(el.getAttribute('tabindex')).to.equal('-1'); el.disabled = false; await el.updateComplete; @@ -38,9 +38,9 @@ describe('DisabledWithTabIndexMixin', () => { }); it('can be disabled imperatively', async () => { - const el = await fixture(html` + const el = /** @type {WithTabIndex} */ (await fixture(html` - `); + `)); expect(el.getAttribute('tabindex')).to.equal('-1'); el.disabled = false; @@ -55,9 +55,9 @@ describe('DisabledWithTabIndexMixin', () => { }); it('will not allow to change tabIndex after makeRequestToBeDisabled()', async () => { - const el = await fixture(html` + const el = /** @type {WithTabIndex} */ (await fixture(html` - `); + `)); el.makeRequestToBeDisabled(); el.tabIndex = 5; @@ -67,9 +67,9 @@ describe('DisabledWithTabIndexMixin', () => { }); it('will restore last tabIndex after retractRequestToBeDisabled()', async () => { - const el = await fixture(html` + const el = /** @type {WithTabIndex} */ (await fixture(html` - `); + `)); el.makeRequestToBeDisabled(); expect(el.tabIndex).to.equal(-1); await el.updateComplete; @@ -96,9 +96,9 @@ describe('DisabledWithTabIndexMixin', () => { }); it('may allow multiple calls to retractRequestToBeDisabled', async () => { - const el = await fixture(html` + const el = /** @type {WithTabIndex} */ (await fixture(html` - `); + `)); el.retractRequestToBeDisabled(); el.retractRequestToBeDisabled(); expect(el.disabled).to.be.true; diff --git a/packages/core/test/LionSingleton.test.js b/packages/core/test/LionSingleton.test.js index b6208fb3b..27d808294 100644 --- a/packages/core/test/LionSingleton.test.js +++ b/packages/core/test/LionSingleton.test.js @@ -11,6 +11,11 @@ describe('LionSingleton', () => { it('supports parameters for .getInstance(foo, bar)', async () => { class MySingleton extends LionSingleton { + /** + * + * @param {string} foo + * @param {string} bar + */ constructor(foo, bar) { super(); this.foo = foo; @@ -59,6 +64,7 @@ describe('LionSingleton', () => { }); it('can at any time add mixins via .addInstanceMixin()', () => { + // @ts-ignore because we're getting rid of LionSingleton altogether const MyMixin = superclass => class extends superclass { constructor() { @@ -72,6 +78,7 @@ describe('LionSingleton', () => { const mySingleton = MySingleton.getInstance(); expect(mySingleton.myMixin).to.be.true; + // @ts-ignore because we're getting rid of LionSingleton altogether const OtherMixin = superclass => class extends superclass { constructor() { @@ -89,6 +96,7 @@ describe('LionSingleton', () => { }); it('can provide new instances (with applied Mixins) via .getNewInstance()', async () => { + // @ts-ignore because we're getting rid of LionSingleton altogether const MyMixin = superclass => class extends superclass { constructor() { @@ -100,13 +108,19 @@ describe('LionSingleton', () => { MySingleton.addInstanceMixin(MyMixin); const singletonOne = MySingleton.getNewInstance(); + // @ts-ignore because we're getting rid of LionSingleton altogether singletonOne.one = true; + // @ts-ignore because we're getting rid of LionSingleton altogether expect(singletonOne.myMixin).to.be.true; + // @ts-ignore because we're getting rid of LionSingleton altogether expect(singletonOne.one).to.be.true; const singletonTwo = MySingleton.getNewInstance(); + // @ts-ignore because we're getting rid of LionSingleton altogether expect(singletonTwo.myMixin).to.be.true; + // @ts-ignore because we're getting rid of LionSingleton altogether expect(singletonTwo.one).to.be.undefined; + // @ts-ignore because we're getting rid of LionSingleton altogether expect(singletonOne.one).to.be.true; // to be sure }); }); diff --git a/packages/core/test/SlotMixin.test.js b/packages/core/test/SlotMixin.test.js index 0ff42feba..675c875ca 100644 --- a/packages/core/test/SlotMixin.test.js +++ b/packages/core/test/SlotMixin.test.js @@ -14,8 +14,8 @@ describe('SlotMixin', () => { } }, ); - const element = await fixture(`<${tag}>`); - expect(element.children[0].slot).to.equal('feedback'); + const el = await fixture(`<${tag}>`); + expect(el.children[0].slot).to.equal('feedback'); }); it('does not override user provided slots', async () => { @@ -31,7 +31,7 @@ describe('SlotMixin', () => { ); const el = await fixture(`<${tag}>

user-content

`); expect(el.children[0].tagName).to.equal('P'); - expect(el.children[0].innerText).to.equal('user-content'); + expect(/** @type HTMLParagraphElement */ (el.children[0]).innerText).to.equal('user-content'); }); it('supports complex dom trees as element', async () => { @@ -57,10 +57,12 @@ describe('SlotMixin', () => { } }, ); - const element = await fixture(`<${tag}>`); - expect(element.children[0].slot).to.equal('feedback'); - expect(element.children[0].getAttribute('foo')).to.equal('bar'); - expect(element.children[0].children[0].innerText).to.equal('cat'); + const el = await fixture(`<${tag}>`); + expect(el.children[0].slot).to.equal('feedback'); + expect(el.children[0].getAttribute('foo')).to.equal('bar'); + expect(/** @type HTMLParagraphElement */ (el.children[0].children[0]).innerText).to.equal( + 'cat', + ); }); it('supports conditional slots', async () => { @@ -82,35 +84,37 @@ describe('SlotMixin', () => { } }, ); - const elementSlot = await fixture(`<${tag}><${tag}>`); - expect(elementSlot.querySelector('#someSlot')).to.exist; + const elSlot = await fixture(`<${tag}><${tag}>`); + expect(elSlot.querySelector('#someSlot')).to.exist; renderSlot = false; - const elementNoSlot = await fixture(`<${tag}><${tag}>`); - expect(elementNoSlot.querySelector('#someSlot')).to.not.exist; + const elNoSlot = await fixture(`<${tag}><${tag}>`); + expect(elNoSlot.querySelector('#someSlot')).to.not.exist; }); it("allows to check which slots have been created via this._isPrivateSlot('slotname')", async () => { let renderSlot = true; - const tag = defineCE( - class extends SlotMixin(HTMLElement) { - get slots() { - return { - ...super.slots, - conditional: () => (renderSlot ? document.createElement('div') : undefined), - }; - } + class SlotPrivateText extends SlotMixin(HTMLElement) { + get slots() { + return { + ...super.slots, + conditional: () => (renderSlot ? document.createElement('div') : undefined), + }; + } - didCreateConditionalSlot() { - return this._isPrivateSlot('conditional'); - } - }, - ); - const el = await fixture(`<${tag}><${tag}>`); + didCreateConditionalSlot() { + return this._isPrivateSlot('conditional'); + } + } + + const tag = defineCE(SlotPrivateText); + const el = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`)); expect(el.didCreateConditionalSlot()).to.be.true; - const elUserSlot = await fixture(`<${tag}>

foo

<${tag}>`); + const elUserSlot = /** @type {SlotPrivateText} */ (await fixture( + `<${tag}>

foo

<${tag}>`, + )); expect(elUserSlot.didCreateConditionalSlot()).to.be.false; renderSlot = false; - const elNoSlot = await fixture(`<${tag}><${tag}>`); + const elNoSlot = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`)); expect(elNoSlot.didCreateConditionalSlot()).to.be.false; }); }); diff --git a/packages/core/test/UpdateStylesMixin.test.js b/packages/core/test/UpdateStylesMixin.test.js index c8c99f307..8c2dc3843 100644 --- a/packages/core/test/UpdateStylesMixin.test.js +++ b/packages/core/test/UpdateStylesMixin.test.js @@ -4,82 +4,75 @@ import { UpdateStylesMixin } from '../src/UpdateStylesMixin.js'; describe('UpdateStylesMixin', () => { it('handles css variables && direct e.g. host css properties correctly', async () => { - const tag = defineCE( - class extends UpdateStylesMixin(LitElement) { - static get styles() { - return [ - css` - :host { - text-align: right; + class UpdateStylesElement extends UpdateStylesMixin(LitElement) { + static get styles() { + return [ + css` + :host { + text-align: right; - --color: rgb(128, 128, 128); - } + --color: rgb(128, 128, 128); + } - h1 { - color: var(--color); - } - `, - ]; - } + h1 { + color: var(--color); + } + `, + ]; + } - render() { - return html`

hey

`; - } - }, - ); - const el = await fixture(`<${tag}>`); - expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal( - 'rgb(128, 128, 128)', - ); + render() { + return html`

hey

`; + } + } + + const tag = defineCE(UpdateStylesElement); + const el = /** @type {UpdateStylesElement} */ (await fixture(`<${tag}>`)); + const header = /** @type {Element} */ (el.shadowRoot?.getElementById('header')); + + expect(window.getComputedStyle(header).color).to.equal('rgb(128, 128, 128)'); expect(window.getComputedStyle(el).textAlign).to.equal('right'); el.updateStyles({ '--color': 'rgb(255, 0, 0)', 'text-align': 'center', }); - await tag.updateComplete; - expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal( - 'rgb(255, 0, 0)', - ); + await el.updateComplete; + expect(window.getComputedStyle(header).color).to.equal('rgb(255, 0, 0)'); expect(window.getComputedStyle(el).textAlign).to.equal('center'); }); it('preserves existing styles', async () => { - const tag = defineCE( - class extends UpdateStylesMixin(LitElement) { - static get styles() { - return [ - css` - :host { - --color: rgb(128, 128, 128); - } + class UpdateStylesElement extends UpdateStylesMixin(LitElement) { + static get styles() { + return [ + css` + :host { + --color: rgb(128, 128, 128); + } - h1 { - color: var(--color); - } - `, - ]; - } + h1 { + color: var(--color); + } + `, + ]; + } - render() { - return html`

hey

`; - } - }, - ); - const el = await fixture(`<${tag}>`); - expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal( - 'rgb(128, 128, 128)', - ); + render() { + return html`

hey

`; + } + } + const tag = defineCE(UpdateStylesElement); + const el = /** @type {UpdateStylesElement} */ (await fixture(`<${tag}>`)); + const header = /** @type {Element} */ (el.shadowRoot?.getElementById('header')); + + expect(window.getComputedStyle(header).color).to.equal('rgb(128, 128, 128)'); el.updateStyles({ '--color': 'rgb(255, 0, 0)' }); - await tag.updateComplete; - expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal( - 'rgb(255, 0, 0)', - ); + expect(window.getComputedStyle(header).color).to.equal('rgb(255, 0, 0)'); el.updateStyles({ 'text-align': 'left' }); - await tag.updateComplete; - const styles = window.getComputedStyle(el.shadowRoot.getElementById('header')); + const styles = window.getComputedStyle(header); expect(styles.color).to.equal('rgb(255, 0, 0)'); expect(styles.textAlign).to.equal('left'); }); diff --git a/packages/core/types/DelegateMixinTypes.d.ts b/packages/core/types/DelegateMixinTypes.d.ts new file mode 100644 index 000000000..0840c3a64 --- /dev/null +++ b/packages/core/types/DelegateMixinTypes.d.ts @@ -0,0 +1,51 @@ +import { Constructor } from '@open-wc/dedupe-mixin'; + +export type Delegations = { + target: Function; + events: string[]; + methods: string[]; + properties: string[]; + attributes: string[]; +}; + +export declare class DelegateMixinHost { + delegations: Delegations; + + protected _connectDelegateMixin(): void; + + private __setupPropertyDelegation(): void; + + private __initialAttributeDelegation(): void; + + private __emptyEventListenerQueue(): void; + + private __emptyPropertiesQueue(): void; +} + +/** + * # DelegateMixin + * Forwards defined events, methods, properties and attributes to the defined target. + * + * @example + * get delegations() { + * return { + * ...super.delegations, + * target: () => this.shadowRoot.getElementById('button1'), + * events: ['click'], + * methods: ['click'], + * properties: ['disabled'], + * attributes: ['disabled'], + * }; + * } + * + * render() { + * return html` + * + * `; + * } + */ +declare function DelegateMixinImplementation>( + superclass: T, +): T & Constructor; + +export type DelegateMixin = typeof DelegateMixinImplementation; diff --git a/packages/core/types/DisabledMixinTypes.d.ts b/packages/core/types/DisabledMixinTypes.d.ts new file mode 100644 index 000000000..ade1a1178 --- /dev/null +++ b/packages/core/types/DisabledMixinTypes.d.ts @@ -0,0 +1,29 @@ +import { Constructor } from '@open-wc/dedupe-mixin'; + +export declare class DisabledMixinHost { + static get properties(): { + disabled: { + type: BooleanConstructor; + reflect: boolean; + }; + }; + disabled: boolean; + + /** + * Makes request to make the element disabled + */ + public makeRequestToBeDisabled(): void; + + /** + * Retract request to make the element disabled and restore disabled to previous + */ + public retractRequestToBeDisabled(): void; + + private __internalSetDisabled(value: boolean): void; +} + +export declare function DisabledMixinImplementation>( + superclass: T, +): T & Constructor; + +export type DisabledMixin = typeof DisabledMixinImplementation; diff --git a/packages/core/types/DisabledWithTabIndexMixinTypes.d.ts b/packages/core/types/DisabledWithTabIndexMixinTypes.d.ts new file mode 100644 index 000000000..358005a3b --- /dev/null +++ b/packages/core/types/DisabledWithTabIndexMixinTypes.d.ts @@ -0,0 +1,29 @@ +import { Constructor } from '@open-wc/dedupe-mixin'; +import { DisabledMixinHost } from './DisabledMixinTypes'; +export declare class DisabledWithTabIndexMixinHost { + static get properties(): { + tabIndex: { + type: NumberConstructor; + reflect: boolean; + attribute: string; + }; + }; + tabIndex: number; + /** + * Makes request to make the element disabled and set the tabindex + */ + public makeRequestToBeDisabled(): void; + + /** + * Retract request to make the element disabled and restore disabled and tabindex to previous + */ + public retractRequestToBeDisabled(): void; + + private __internalSetTabIndex(value: boolean): void; +} + +export declare function DisabledWithTabIndexMixinImplementation>( + superclass: T, +): T & Constructor & Constructor; + +export type DisabledWithTabIndexMixin = typeof DisabledWithTabIndexMixinImplementation; diff --git a/packages/core/types/SlotMixinTypes.d.ts b/packages/core/types/SlotMixinTypes.d.ts new file mode 100644 index 000000000..d911b267f --- /dev/null +++ b/packages/core/types/SlotMixinTypes.d.ts @@ -0,0 +1,53 @@ +import { Constructor } from '@open-wc/dedupe-mixin'; + +declare function slotFunction(): HTMLElement | undefined; + +export type SlotsMap = { + [key: string]: typeof slotFunction; +}; + +export declare class SlotMixinHost { + /** + * Obtains all the slots to create + */ + slots: SlotsMap; + + /** + * Starts the creation of slots + */ + protected _connectSlotMixin(): void; + + /** + * Useful to decide if a given slot should be manipulated depending on if it was auto generated + * or not. + * + * @param {string} slotName Name of the slot + * @return {boolean} true if given slot name been created by SlotMixin + */ + protected _isPrivateSlot(slotName: string): boolean; +} + +/** + * # SlotMixin + * + * `SlotMixin`, when attached to the DOM it creates content for defined slots in the Light DOM. + * The content element is created using a factory function and is assigned a slot name from the key. + * Existing slot content is not overridden. + * + * The purpose is to have the default content in the Light DOM rather than hidden in Shadow DOM + * like default slot content works natively. + * + * @example + * get slots() { + * return { + * ...super.slots, + * // appends
to the Light DOM of this element + * foo: () => document.createElement('div'), + * }; + * } + */ +export declare function SlotMixinImplementation>( + superclass: T, +): T & Constructor; + +export type SlotMixin = typeof SlotMixinImplementation; diff --git a/packages/core/types/UpdateStylesMixinTypes.d.ts b/packages/core/types/UpdateStylesMixinTypes.d.ts new file mode 100644 index 000000000..a0d931c3c --- /dev/null +++ b/packages/core/types/UpdateStylesMixinTypes.d.ts @@ -0,0 +1,34 @@ +import { Constructor } from '@open-wc/dedupe-mixin'; + +export type StylesMap = { + [key: string]: string; +}; +export declare class UpdateStylesMixinHost { + /** + * @example + * + * + * + * + * $0.updateStyles({'background': 'orange', '--foo': '#fff'}) + * Chrome, Firefox: + * IE: + * => to head: + * + * @param {StylesMap} updateStyles + */ + public updateStyles(updateStyles: StylesMap): void; +} + +/** + * # UpdateStylesMixin + */ +declare function UpdateStylesMixinImplementation>( + superclass: T, +): T & Constructor; + +export type UpdateStylesMixin = typeof UpdateStylesMixinImplementation; From 0cf8a0c9212faae42b95a84c5a49b3e94035bcef Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Mon, 13 Jul 2020 11:30:55 +0200 Subject: [PATCH 3/5] feat(tabs): fix types and export type definition files for tabs --- packages/tabs/package.json | 5 +- packages/tabs/src/LionTabs.js | 264 +++++++++++++++++---------- packages/tabs/test/lion-tabs.test.js | 126 ++++++------- 3 files changed, 238 insertions(+), 157 deletions(-) diff --git a/packages/tabs/package.json b/packages/tabs/package.json index c90c8ad55..0d892b12f 100644 --- a/packages/tabs/package.json +++ b/packages/tabs/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/tabs/src/LionTabs.js b/packages/tabs/src/LionTabs.js index d1f742e78..a55fc1c69 100644 --- a/packages/tabs/src/LionTabs.js +++ b/packages/tabs/src/LionTabs.js @@ -1,77 +1,114 @@ import { css, html, LitElement } from '@lion/core'; -const uuid = () => Math.random().toString(36).substr(2, 10); +/** + * @typedef {Object} StoreEntry + * @property {HTMLElement} el Dom Element + * @property {string} uid Unique ID for the entry + * @property {HTMLElement} button Button HTMLElement for the entry + * @property {HTMLElement} panel Panel HTMLElement for the entry + * @property {EventHandlerNonNull} clickHandler executed on click event + * @property {EventHandlerNonNull} keydownHandler executed on keydown event + * @property {EventHandlerNonNull} keyupHandler executed on keyup event + */ -const setupPanel = ({ element, uid }) => { - element.setAttribute('id', `panel-${uid}`); - element.setAttribute('role', 'tabpanel'); - element.setAttribute('aria-labelledby', `button-${uid}`); -}; +function uuid() { + return Math.random().toString(36).substr(2, 10); +} -const selectPanel = element => { - element.setAttribute('selected', true); -}; +/** + * @param {StoreEntry} options + */ +function setupPanel({ el, uid }) { + el.setAttribute('id', `panel-${uid}`); + el.setAttribute('role', 'tabpanel'); + el.setAttribute('aria-labelledby', `button-${uid}`); +} -const deselectPanel = element => { - element.removeAttribute('selected'); -}; +/** + * @param {HTMLElement} el + */ +function selectPanel(el) { + el.setAttribute('selected', 'true'); +} -const setupButton = ({ element, uid, clickHandler, keydownHandler, keyupHandler }) => { - element.setAttribute('id', `button-${uid}`); - element.setAttribute('role', 'tab'); - element.setAttribute('aria-controls', `panel-${uid}`); - element.addEventListener('click', clickHandler); - element.addEventListener('keyup', keyupHandler); - element.addEventListener('keydown', keydownHandler); -}; +/** + * @param {HTMLElement} el + */ +function deselectPanel(el) { + el.removeAttribute('selected'); +} -const cleanButton = ({ element, clickHandler, keydownHandler, keyupHandler }) => { - element.removeAttribute('id'); - element.removeAttribute('role'); - element.removeAttribute('aria-controls'); - element.removeEventListener('click', clickHandler); - element.removeEventListener('keyup', keyupHandler); - element.removeEventListener('keydown', keydownHandler); -}; +/** + * @param {StoreEntry} options + */ +function setupButton({ el, uid, clickHandler, keydownHandler, keyupHandler }) { + el.setAttribute('id', `button-${uid}`); + el.setAttribute('role', 'tab'); + el.setAttribute('aria-controls', `panel-${uid}`); + el.addEventListener('click', clickHandler); + el.addEventListener('keyup', keyupHandler); + el.addEventListener('keydown', keydownHandler); +} -const selectButton = (element, withFocus = false) => { +/** + * @param {StoreEntry} options + */ +function cleanButton({ el, clickHandler, keydownHandler, keyupHandler }) { + el.removeAttribute('id'); + el.removeAttribute('role'); + el.removeAttribute('aria-controls'); + el.removeEventListener('click', clickHandler); + el.removeEventListener('keyup', keyupHandler); + el.removeEventListener('keydown', keydownHandler); +} + +/** + * @param {HTMLElement} el + * @param {boolean} withFocus + */ +function selectButton(el, withFocus = false) { if (withFocus) { - element.focus(); + el.focus(); } - element.setAttribute('selected', true); - element.setAttribute('aria-selected', true); - element.setAttribute('tabindex', 0); -}; + el.setAttribute('selected', 'true'); + el.setAttribute('aria-selected', 'true'); + el.setAttribute('tabindex', '0'); +} -const deselectButton = element => { - element.removeAttribute('selected'); - element.setAttribute('aria-selected', false); - element.setAttribute('tabindex', -1); -}; +/** + * @param {HTMLElement} el + */ +function deselectButton(el) { + el.removeAttribute('selected'); + el.setAttribute('aria-selected', 'false'); + el.setAttribute('tabindex', '-1'); +} -const handleButtonKeydown = e => { - switch (e.key) { +/** + * @param {Event} ev + */ +function handleButtonKeydown(ev) { + const _ev = /** @type {KeyboardEvent} */ (ev); + switch (_ev.key) { case 'ArrowDown': case 'ArrowRight': case 'ArrowUp': case 'ArrowLeft': case 'Home': case 'End': - e.preventDefault(); + _ev.preventDefault(); /* no default */ } -}; +} export class LionTabs extends LitElement { static get properties() { return { - /** - * index number of the selected tab. - */ selectedIndex: { type: Number, - value: 0, + attribute: 'selected-index', + reflect: true, }, }; } @@ -117,28 +154,42 @@ export class LionTabs extends LitElement { constructor() { super(); + /** + * An index number of the selected tab + */ this.selectedIndex = 0; } - firstUpdated() { - super.firstUpdated(); + /** @param {import('lit-element').PropertyValues } changedProperties */ + firstUpdated(changedProperties) { + super.firstUpdated(changedProperties); this.__setupSlots(); } __setupSlots() { - const tabSlot = this.shadowRoot.querySelector('slot[name=tab]'); - const handleSlotChange = () => { - this.__cleanStore(); - this.__setupStore(); - this.__updateSelected(false); - }; - tabSlot.addEventListener('slotchange', handleSlotChange); + if (this.shadowRoot) { + const tabSlot = this.shadowRoot.querySelector('slot[name=tab]'); + const handleSlotChange = () => { + this.__cleanStore(); + this.__setupStore(); + this.__updateSelected(false); + }; + + if (tabSlot) { + tabSlot.addEventListener('slotchange', handleSlotChange); + } + } } __setupStore() { + /** @type {StoreEntry[]} */ this.__store = []; - const buttons = this.querySelectorAll('[slot="tab"]'); - const panels = this.querySelectorAll('[slot="panel"]'); + const buttons = /** @type {HTMLElement[]} */ (Array.from( + this.querySelectorAll('[slot="tab"]'), + )); + const panels = /** @type {HTMLElement[]} */ (Array.from( + this.querySelectorAll('[slot="panel"]'), + )); if (buttons.length !== panels.length) { // eslint-disable-next-line no-console console.warn( @@ -149,19 +200,25 @@ export class LionTabs extends LitElement { buttons.forEach((button, index) => { const uid = uuid(); const panel = panels[index]; + + /** @type {StoreEntry} */ const entry = { uid, + el: button, button, panel, clickHandler: this.__createButtonClickHandler(index), - keydownHandler: handleButtonKeydown, + keydownHandler: handleButtonKeydown.bind(this), keyupHandler: this.__handleButtonKeyup.bind(this), }; - setupPanel({ element: entry.panel, ...entry }); - setupButton({ element: entry.button, ...entry }); + setupPanel({ ...entry, el: entry.panel }); + setupButton(entry); deselectPanel(entry.panel); deselectButton(entry.button); - this.__store.push(entry); + + if (this.__store) { + this.__store.push(entry); + } }); } @@ -170,44 +227,57 @@ export class LionTabs extends LitElement { return; } this.__store.forEach(entry => { - cleanButton({ element: entry.button, ...entry }); + cleanButton(entry); }); } + /** + * @param {number} index + * @returns {EventHandlerNonNull} + */ __createButtonClickHandler(index) { return () => { this._setSelectedIndexWithFocus(index); }; } - __handleButtonKeyup(e) { - switch (e.key) { - case 'ArrowDown': - case 'ArrowRight': - if (this.selectedIndex + 1 >= this._pairCount) { + /** + * @param {Event} ev + */ + __handleButtonKeyup(ev) { + const _ev = /** @type {KeyboardEvent} */ (ev); + if (typeof this.selectedIndex === 'number') { + switch (_ev.key) { + case 'ArrowDown': + case 'ArrowRight': + if (this.selectedIndex + 1 >= this._pairCount) { + this._setSelectedIndexWithFocus(0); + } else { + this._setSelectedIndexWithFocus(this.selectedIndex + 1); + } + break; + case 'ArrowUp': + case 'ArrowLeft': + if (this.selectedIndex <= 0) { + this._setSelectedIndexWithFocus(this._pairCount - 1); + } else { + this._setSelectedIndexWithFocus(this.selectedIndex - 1); + } + break; + case 'Home': this._setSelectedIndexWithFocus(0); - } else { - this._setSelectedIndexWithFocus(this.selectedIndex + 1); - } - break; - case 'ArrowUp': - case 'ArrowLeft': - if (this.selectedIndex <= 0) { + break; + case 'End': this._setSelectedIndexWithFocus(this._pairCount - 1); - } else { - this._setSelectedIndexWithFocus(this.selectedIndex - 1); - } - break; - case 'Home': - this._setSelectedIndexWithFocus(0); - break; - case 'End': - this._setSelectedIndexWithFocus(this._pairCount - 1); - break; - /* no default */ + break; + /* no default */ + } } } + /** + * @param {number} value The new index + */ set selectedIndex(value) { const stale = this.__selectedIndex; this.__selectedIndex = value; @@ -216,6 +286,9 @@ export class LionTabs extends LitElement { this.requestUpdate('selectedIndex', stale); } + /** + * @param {number} value The new index for focus + */ _setSelectedIndexWithFocus(value) { const stale = this.__selectedIndex; this.__selectedIndex = value; @@ -224,24 +297,29 @@ export class LionTabs extends LitElement { this.requestUpdate('selectedIndex', stale); } + /** + * @return {number} + */ get selectedIndex() { - return this.__selectedIndex; + return this.__selectedIndex || 0; } get _pairCount() { - return this.__store.length; + return (this.__store && this.__store.length) || 0; } __updateSelected(withFocus = false) { - if (!(this.__store && this.__store[this.selectedIndex])) { + if ( + !(this.__store && typeof this.selectedIndex === 'number' && this.__store[this.selectedIndex]) + ) { return; } - const previousButton = Array.from(this.children).find( + const previousButton = /** @type {HTMLElement} */ (Array.from(this.children).find( child => child.slot === 'tab' && child.hasAttribute('selected'), - ); - const previousPanel = Array.from(this.children).find( + )); + const previousPanel = /** @type {HTMLElement} */ (Array.from(this.children).find( child => child.slot === 'panel' && child.hasAttribute('selected'), - ); + )); if (previousButton) { deselectButton(previousButton); } diff --git a/packages/tabs/test/lion-tabs.test.js b/packages/tabs/test/lion-tabs.test.js index c58765612..5ed322f8e 100644 --- a/packages/tabs/test/lion-tabs.test.js +++ b/packages/tabs/test/lion-tabs.test.js @@ -1,6 +1,9 @@ import { expect, fixture, html } from '@open-wc/testing'; import sinon from 'sinon'; +/** + * @typedef {import('../src/LionTabs.js').LionTabs} LionTabs + */ import '../lion-tabs.js'; const basicTabs = html` @@ -17,36 +20,34 @@ const basicTabs = html` describe('', () => { describe('Tabs', () => { it('sets selectedIndex to 0 by default', async () => { - const el = await fixture(basicTabs); + const el = /** @type {LionTabs} */ (await fixture(basicTabs)); expect(el.selectedIndex).to.equal(0); }); it('can programmatically set selectedIndex', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
tab 1
panel 1
tab 2
panel 2
- `); + `)); expect(el.selectedIndex).to.equal(1); - expect( - Array.from(el.children).find( - child => child.slot === 'tab' && child.hasAttribute('selected'), - ).textContent, - ).to.equal('tab 2'); + let selectedTab = /** @type {Element} */ (Array.from(el.children).find( + child => child.slot === 'tab' && child.hasAttribute('selected'), + )); + expect(selectedTab.textContent).to.equal('tab 2'); el.selectedIndex = 0; - expect( - Array.from(el.children).find( - child => child.slot === 'tab' && child.hasAttribute('selected'), - ).textContent, - ).to.equal('tab 1'); + selectedTab = /** @type {Element} */ (Array.from(el.children).find( + child => child.slot === 'tab' && child.hasAttribute('selected'), + )); + expect(selectedTab.textContent).to.equal('tab 1'); }); it('has [selected] on current selected tab which serves as styling hook', async () => { - const el = await fixture(basicTabs); + const el = /** @type {LionTabs} */ (await fixture(basicTabs)); const tabs = el.querySelectorAll('[slot=tab]'); el.selectedIndex = 0; expect(tabs[0]).to.have.attribute('selected'); @@ -58,7 +59,7 @@ describe('', () => { }); it('sends event "selected-changed" for every selected state change', async () => { - const el = await fixture(basicTabs); + const el = /** @type {LionTabs} */ (await fixture(basicTabs)); const spy = sinon.spy(); el.addEventListener('selected-changed', spy); el.selectedIndex = 1; @@ -75,18 +76,18 @@ describe('', () => { `); expect(spy.callCount).to.equal(1); - console.warn.restore(); + spy.restore(); }); }); describe('Tabs ([slot=tab])', () => { it('adds role=tab', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel
- `); + `)); expect(Array.from(el.children).find(child => child.slot === 'tab')).to.have.attribute( 'role', 'tab', @@ -101,7 +102,7 @@ describe('', () => { describe('Tab Panels (slot=panel)', () => { it('are visible when corresponding tab is selected ', async () => { - const el = await fixture(basicTabs); + const el = /** @type {LionTabs} */ (await fixture(basicTabs)); const panels = el.querySelectorAll('[slot=panel]'); el.selectedIndex = 0; expect(panels[0]).to.be.visible; @@ -125,14 +126,14 @@ describe('', () => { */ describe('User interaction', () => { it('selects a tab on click', async () => { - const el = await fixture(basicTabs); + const el = /** @type {LionTabs} */ (await fixture(basicTabs)); const tabs = el.querySelectorAll('[slot=tab]'); tabs[1].dispatchEvent(new Event('click')); expect(el.selectedIndex).to.equal(1); }); it('selects next tab on [arrow-right] and [arrow-down]', async () => { - const el = await fixture(basicTabs); + const el = /** @type {LionTabs} */ (await fixture(basicTabs)); const tabs = el.querySelectorAll('[slot=tab]'); tabs[0].dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowRight' })); expect(el.selectedIndex).to.equal(1); @@ -141,7 +142,7 @@ describe('', () => { }); it('selects previous tab on [arrow-left] and [arrow-up]', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
@@ -150,7 +151,7 @@ describe('', () => {
panel 3
- `); + `)); const tabs = el.querySelectorAll('[slot=tab]'); el.selectedIndex = 2; tabs[2].dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowLeft' })); @@ -160,29 +161,29 @@ describe('', () => { }); it('selects first tab on [home]', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); + `)); const tabs = el.querySelectorAll('[slot=tab]'); tabs[1].dispatchEvent(new KeyboardEvent('keyup', { key: 'Home' })); expect(el.selectedIndex).to.equal(0); }); it('selects last tab on [end]', async () => { - const el = await fixture(basicTabs); + const el = /** @type {LionTabs} */ (await fixture(basicTabs)); const tabs = el.querySelectorAll('[slot=tab]'); tabs[0].dispatchEvent(new KeyboardEvent('keyup', { key: 'End' })); expect(el.selectedIndex).to.equal(2); }); it('selects first tab on [arrow-right] if on last tab', async () => { - const el = await fixture(html` - + const el = /** @type {LionTabs} */ (await fixture(html` +
panel 1
@@ -190,14 +191,14 @@ describe('', () => {
panel 3
- `); + `)); const tabs = el.querySelectorAll('[slot=tab]'); tabs[2].dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowRight' })); expect(el.selectedIndex).to.equal(0); }); it('selects last tab on [arrow-left] if on first tab', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
@@ -206,7 +207,7 @@ describe('', () => {
panel 3
- `); + `)); const tabs = el.querySelectorAll('[slot=tab]'); tabs[0].dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowLeft' })); expect(el.selectedIndex).to.equal(2); @@ -215,7 +216,7 @@ describe('', () => { describe('Content distribution', () => { it('should work with append children', async () => { - const el = await fixture(basicTabs); + const el = /** @type {LionTabs} */ (await fixture(basicTabs)); const c = 2; const n = el.children.length / 2; for (let i = n + 1; i < n + c + 1; i += 1) { @@ -230,29 +231,28 @@ describe('', () => { } el.selectedIndex = el.children.length / 2 - 1; await el.updateComplete; - expect( - Array.from(el.children).find( - child => child.slot === 'tab' && child.hasAttribute('selected'), - ).textContent, - ).to.equal('tab 5'); - expect( - Array.from(el.children).find( - child => child.slot === 'panel' && child.hasAttribute('selected'), - ).textContent, - ).to.equal('panel 5'); + const selectedTab = Array.from(el.children).find( + child => child.slot === 'tab' && child.hasAttribute('selected'), + ); + expect(selectedTab && selectedTab.textContent).to.equal('tab 5'); + + const selectedPanel = Array.from(el.children).find( + child => child.slot === 'panel' && child.hasAttribute('selected'), + ); + expect(selectedPanel && selectedPanel.textContent).to.equal('panel 5'); }); }); describe('Initializing without Focus', () => { it('does not focus a tab when setting selectedIndex property', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); + `)); el.selectedIndex = 1; expect(el.querySelector('[slot="tab"]:nth-of-type(2)') === document.activeElement).to.be @@ -260,56 +260,56 @@ describe('', () => { }); it('does not focus a tab on firstUpdate', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); + `)); const tabs = Array.from(el.children).filter(child => child.slot === 'tab'); expect(tabs.some(tab => tab === document.activeElement)).to.be.false; }); it('focuses on a tab when setting with _setSelectedIndexWithFocus method', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); + `)); el._setSelectedIndexWithFocus(1); expect(el.querySelector('[slot="tab"]:nth-of-type(2)') === document.activeElement).to.be.true; }); }); it('focuses on a tab when the selected tab is changed by user interaction', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); - const secondTab = el.querySelector('[slot="tab"]:nth-of-type(2)'); + `)); + const secondTab = /** @type {Element} */ (el.querySelector('[slot="tab"]:nth-of-type(2)')); secondTab.dispatchEvent(new MouseEvent('click')); expect(secondTab === document.activeElement).to.be.true; }); describe('Accessibility', () => { it('does not make panels focusable', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); + `)); expect(Array.from(el.children).find(child => child.slot === 'panel')).to.not.have.attribute( 'tabindex', ); @@ -319,7 +319,7 @@ describe('', () => { }); it('makes selected tab focusable (other tabs are unfocusable)', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
@@ -328,7 +328,7 @@ describe('', () => {
panel 3
- `); + `)); const tabs = el.querySelectorAll('[slot=tab]'); expect(tabs[0]).to.have.attribute('tabindex', '0'); expect(tabs[1]).to.have.attribute('tabindex', '-1'); @@ -337,14 +337,14 @@ describe('', () => { describe('Tabs', () => { it('links ids of content items to tab via [aria-controls]', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); + `)); const tabs = el.querySelectorAll('[slot=tab]'); const panels = el.querySelectorAll('[slot=panel]'); expect(tabs[0].getAttribute('aria-controls')).to.equal(panels[0].id); @@ -352,7 +352,7 @@ describe('', () => { }); it('adds aria-selected=“true” to selected tab', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
@@ -361,7 +361,7 @@ describe('', () => {
panel 3
- `); + `)); const tabs = el.querySelectorAll('[slot=tab]'); expect(tabs[0].getAttribute('aria-selected')).to.equal('true'); @@ -372,28 +372,28 @@ describe('', () => { describe('panels', () => { it('adds role="tabpanel" to panels', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); + `)); const panels = el.querySelectorAll('[slot=panel]'); expect(panels[0]).to.have.attribute('role', 'tabpanel'); expect(panels[1]).to.have.attribute('role', 'tabpanel'); }); it('adds aria-labelledby referring to tab ids', async () => { - const el = await fixture(html` + const el = /** @type {LionTabs} */ (await fixture(html`
panel 1
panel 2
- `); + `)); const panels = el.querySelectorAll('[slot=panel]'); const tabs = el.querySelectorAll('[slot=tab]'); expect(panels[0]).to.have.attribute('aria-labelledby', tabs[0].id); From de4dbca4ba52ad09276b2c8db1adf027e433a4ad Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Mon, 13 Jul 2020 11:31:53 +0200 Subject: [PATCH 4/5] chore: limit concurrency to throttle RAM usage circleci --- web-test-runner.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web-test-runner.config.js b/web-test-runner.config.js index f31e99520..8e89074df 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -1,6 +1,7 @@ module.exports = { nodeResolve: true, sessionStartTimeout: 30000, + concurrency: 5, coverageConfig: { threshold: { statements: 80, From 263607fd63de816343c8ec7c04aa56b0c942fac4 Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Mon, 13 Jul 2020 11:32:19 +0200 Subject: [PATCH 5/5] chore: align package jsons files property everywhere --- packages/accordion/package.json | 5 ++++- packages/ajax/package.json | 5 ++++- packages/button/package.json | 5 ++++- packages/calendar/package.json | 4 +++- packages/checkbox-group/package.json | 5 ++++- packages/dialog/package.json | 6 +++++- packages/fieldset/package.json | 5 ++++- packages/form-core/package.json | 5 +++-- packages/form-integrations/package.json | 6 ++++-- packages/form/package.json | 5 ++++- packages/icon/package.json | 5 ++++- packages/input-amount/package.json | 5 ++++- packages/input-date/package.json | 5 ++++- packages/input-datepicker/package.json | 4 +++- packages/input-email/package.json | 5 ++++- packages/input-iban/package.json | 5 ++++- packages/input-range/package.json | 7 ++++++- packages/input/package.json | 5 ++++- packages/localize/package.json | 4 +++- packages/overlays/package.json | 5 +++-- packages/providence-analytics/package.json | 2 +- packages/radio-group/package.json | 5 ++++- packages/select-rich/package.json | 5 ++++- packages/select/package.json | 5 ++++- packages/singleton-manager/package.json | 5 ++++- packages/steps/package.json | 5 ++++- packages/switch/package.json | 5 ++++- packages/textarea/package.json | 5 ++++- packages/tooltip/package.json | 5 ++++- packages/validate-messages/package.json | 5 ++++- 30 files changed, 115 insertions(+), 33 deletions(-) diff --git a/packages/accordion/package.json b/packages/accordion/package.json index 2fa567917..2cc9ca1b2 100644 --- a/packages/accordion/package.json +++ b/packages/accordion/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/ajax/package.json b/packages/ajax/package.json index edef4c223..1c998c981 100644 --- a/packages/ajax/package.json +++ b/packages/ajax/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/button/package.json b/packages/button/package.json index 4b95097f5..25afc4c13 100644 --- a/packages/button/package.json +++ b/packages/button/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/calendar/package.json b/packages/calendar/package.json index e0f28dec4..d1b2ae038 100644 --- a/packages/calendar/package.json +++ b/packages/calendar/package.json @@ -13,12 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", "test-helpers", - "translations" + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/checkbox-group/package.json b/packages/checkbox-group/package.json index 3dfb6baf0..a338c99e2 100644 --- a/packages/checkbox-group/package.json +++ b/packages/checkbox-group/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/dialog/package.json b/packages/dialog/package.json index afad2c209..6474bceee 100644 --- a/packages/dialog/package.json +++ b/packages/dialog/package.json @@ -13,10 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", - "test" + "test", + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/fieldset/package.json b/packages/fieldset/package.json index 56bbd4482..70283cc9e 100644 --- a/packages/fieldset/package.json +++ b/packages/fieldset/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/form-core/package.json b/packages/form-core/package.json index 801aa430e..2fcc70928 100644 --- a/packages/form-core/package.json +++ b/packages/form-core/package.json @@ -13,13 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", "test-helpers", - "test-suites", - "translations" + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/form-integrations/package.json b/packages/form-integrations/package.json index 7e1ea4da1..796eae0b6 100644 --- a/packages/form-integrations/package.json +++ b/packages/form-integrations/package.json @@ -13,12 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", - "dev-assets", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js" diff --git a/packages/form/package.json b/packages/form/package.json index abcf0112d..a6876f6e5 100644 --- a/packages/form/package.json +++ b/packages/form/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/icon/package.json b/packages/icon/package.json index d1abcd8e6..3ebf276b4 100644 --- a/packages/icon/package.json +++ b/packages/icon/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/input-amount/package.json b/packages/input-amount/package.json index 25a098866..1954b5039 100644 --- a/packages/input-amount/package.json +++ b/packages/input-amount/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/input-date/package.json b/packages/input-date/package.json index fb0d8246c..cf0c1c8eb 100644 --- a/packages/input-date/package.json +++ b/packages/input-date/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/input-datepicker/package.json b/packages/input-datepicker/package.json index e018f6904..86c820738 100644 --- a/packages/input-datepicker/package.json +++ b/packages/input-datepicker/package.json @@ -13,12 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", "test-helpers", - "translations" + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/input-email/package.json b/packages/input-email/package.json index 63489e474..fb2b5f716 100644 --- a/packages/input-email/package.json +++ b/packages/input-email/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/input-iban/package.json b/packages/input-iban/package.json index d2631de57..73cee7044 100644 --- a/packages/input-iban/package.json +++ b/packages/input-iban/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/input-range/package.json b/packages/input-range/package.json index 360c5e03e..f9deb8faa 100644 --- a/packages/input-range/package.json +++ b/packages/input-range/package.json @@ -13,9 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", + "docs", "src", - "test" + "test", + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/input/package.json b/packages/input/package.json index 364a071a6..422ab07d3 100644 --- a/packages/input/package.json +++ b/packages/input/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/localize/package.json b/packages/localize/package.json index 3a169c91e..045a8c143 100644 --- a/packages/localize/package.json +++ b/packages/localize/package.json @@ -13,12 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", "test-helpers", - "translations" + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/overlays/package.json b/packages/overlays/package.json index e4887d4c2..762861c94 100644 --- a/packages/overlays/package.json +++ b/packages/overlays/package.json @@ -13,13 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", "test-helpers", - "test-suites", - "translations" + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/providence-analytics/package.json b/packages/providence-analytics/package.json index 1a9d4a440..cc95d6f92 100644 --- a/packages/providence-analytics/package.json +++ b/packages/providence-analytics/package.json @@ -49,7 +49,7 @@ "parse5": "^5.1.1", "read-package-tree": "5.3.1", "semver": "^7.1.3", - "typescript": "^3.6.4" + "typescript": "^3.8.3" }, "devDependencies": { "mermaid": "^8.2.6", diff --git a/packages/radio-group/package.json b/packages/radio-group/package.json index 8cd568e34..fd3d4972f 100644 --- a/packages/radio-group/package.json +++ b/packages/radio-group/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/select-rich/package.json b/packages/select-rich/package.json index 6f71cc176..9911631c9 100644 --- a/packages/select-rich/package.json +++ b/packages/select-rich/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/select/package.json b/packages/select/package.json index 6c6401e34..63e6f6c30 100644 --- a/packages/select/package.json +++ b/packages/select/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/singleton-manager/package.json b/packages/singleton-manager/package.json index 1a6eecb1b..c109c683a 100644 --- a/packages/singleton-manager/package.json +++ b/packages/singleton-manager/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "test-helpers" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/steps/package.json b/packages/steps/package.json index 59b3cf536..171352851 100644 --- a/packages/steps/package.json +++ b/packages/steps/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/switch/package.json b/packages/switch/package.json index 532c54fb5..594eeee4d 100644 --- a/packages/switch/package.json +++ b/packages/switch/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/textarea/package.json b/packages/textarea/package.json index c49767679..5b7f95091 100644 --- a/packages/textarea/package.json +++ b/packages/textarea/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/tooltip/package.json b/packages/tooltip/package.json index e71de956d..8e58c026b 100644 --- a/packages/tooltip/package.json +++ b/packages/tooltip/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js", diff --git a/packages/validate-messages/package.json b/packages/validate-messages/package.json index 7cff66dc1..df34a89ed 100644 --- a/packages/validate-messages/package.json +++ b/packages/validate-messages/package.json @@ -13,11 +13,14 @@ "main": "index.js", "module": "index.js", "files": [ + "*.d.ts", "*.js", "docs", "src", "test", - "translations" + "test-helpers", + "translations", + "types" ], "scripts": { "prepublishOnly": "../../scripts/npm-prepublish.js",