diff --git a/packages/scoped-elements/src/ScopedElementsMixin.js b/packages/scoped-elements/src/ScopedElementsMixin.js index 55d5cd22..91588914 100644 --- a/packages/scoped-elements/src/ScopedElementsMixin.js +++ b/packages/scoped-elements/src/ScopedElementsMixin.js @@ -6,7 +6,9 @@ import { defineScopedElement, registerElement } from './registerElement.js'; import { shadyTemplateFactory } from './shadyTemplateFactory.js'; /** + * @typedef {import('./types').ScopedElementsMixin} ScopedElementsMixin * @typedef {import('./types').ScopedElementsMap} ScopedElementsMap + * @typedef {import("lit-element").LitElement} LitElement * @typedef {import('lit-html/lib/shady-render').ShadyRenderOptions} ShadyRenderOptions * @typedef {function(TemplateResult, Element|DocumentFragment|ShadowRoot, ShadyRenderOptions): void} RenderFunction */ @@ -111,64 +113,63 @@ const scopedElementsTemplateFactory = ( return shadyTemplateFactory(scopeName)(newTemplate); }; -export const ScopedElementsMixin = dedupeMixin( - superclass => - // eslint-disable-next-line no-shadow - class ScopedElementsMixin extends superclass { - /** - * Obtains the scoped elements definitions map - * - * @returns {ScopedElementsMap} - */ - static get scopedElements() { - return {}; +/** @type {ScopedElementsMixin} */ +const ScopedElementsMixinImplementation = superclass => + class ScopedElementsHost extends superclass { + /** + * Obtains the scoped elements definitions map + * + * @returns {ScopedElementsMap} + */ + static get scopedElements() { + return {}; + } + + /** @override */ + static render(template, container, options) { + if (!options || typeof options !== 'object' || !options.scopeName) { + throw new Error('The `scopeName` option is required.'); } + const { scopeName } = options; - /** @override */ - static render(template, container, options) { - if (!options || typeof options !== 'object' || !options.scopeName) { - throw new Error('The `scopeName` option is required.'); - } - const { scopeName } = options; + const templateCache = getTemplateCache(this); + const tagsCache = getTagsCache(this); + const { scopedElements } = this; - const templateCache = getTemplateCache(this); - const tagsCache = getTagsCache(this); - const { scopedElements } = this; + return super.render(template, container, { + ...options, + templateFactory: scopedElementsTemplateFactory( + scopeName, + scopedElements, + templateCache, + tagsCache, + ), + }); + } - // @ts-ignore - return super.render(template, container, { - ...options, - templateFactory: scopedElementsTemplateFactory( - scopeName, - scopedElements, - templateCache, - tagsCache, - ), - }); - } + /** + * Defines a scoped element + * + * @param {string} tagName + * @param {typeof HTMLElement} klass + */ + defineScopedElement(tagName, klass) { + return defineScopedElement(tagName, klass, getTagsCache(this.constructor)); + } - /** - * Defines a scoped element - * - * @param {string} tagName - * @param {typeof HTMLElement} klass - */ - defineScopedElement(tagName, klass) { - return defineScopedElement(tagName, klass, getTagsCache(this.constructor)); - } + /** + * Returns a scoped tag name + * + * @param {string} tagName + * @returns {string|undefined} + */ + static getScopedTagName(tagName) { + const klass = this.scopedElements[tagName]; - /** - * Returns a scoped tag name - * - * @param {string} tagName - * @returns {string|undefined} - */ - static getScopedTagName(tagName) { - const klass = this.scopedElements[tagName]; + return klass + ? registerElement(tagName, klass, getTagsCache(this)) + : getTagsCache(this).get(tagName); + } + }; - return klass - ? registerElement(tagName, klass, getTagsCache(this)) - : getTagsCache(this).get(tagName); - } - }, -); +export const ScopedElementsMixin = dedupeMixin(ScopedElementsMixinImplementation); diff --git a/packages/scoped-elements/src/types.d.ts b/packages/scoped-elements/src/types.d.ts index 36967ba9..df457255 100644 --- a/packages/scoped-elements/src/types.d.ts +++ b/packages/scoped-elements/src/types.d.ts @@ -1,3 +1,27 @@ +import { Constructor } from "@open-wc/dedupe-mixin"; +import { LitElement } from "lit-element"; + export type ScopedElementsMap = { [key: string]: typeof HTMLElement; } + +export declare class ScopedElementsHost { + /** + * Obtains the scoped elements definitions map + */ + static scopedElements: ScopedElementsMap; + + /** + * Returns a scoped tag name + */ + static getScopedTagName(tagName: string): string; + + /** + * Defines a scoped element + */ + defineScopedElement(tagName: string, klass: Constructor): void +} + +declare function ScopedElementsMixinImplementation>(superclass: T): T & Constructor + +export type ScopedElementsMixin = typeof ScopedElementsMixinImplementation; diff --git a/packages/scoped-elements/test/ScopedElementsMixin.test.js b/packages/scoped-elements/test/ScopedElementsMixin.test.js index 360141de..5f16d755 100644 --- a/packages/scoped-elements/test/ScopedElementsMixin.test.js +++ b/packages/scoped-elements/test/ScopedElementsMixin.test.js @@ -22,7 +22,6 @@ describe('ScopedElementsMixin', () => { it('has a default value for "static get scopedElements()" of {}', async () => { const tag = defineCE(class extends ScopedElementsMixin(LitElement) {}); const el = await fixture(`<${tag}>`); - // @ts-ignore expect(el.constructor.scopedElements).to.deep.equal({}); }); @@ -257,7 +256,6 @@ describe('ScopedElementsMixin', () => { expect(el.shadowRoot.children[1]).to.not.be.an.instanceOf(FeatureB); expect(el.shadowRoot.children[2]).to.not.undefined; - // @ts-ignore el.defineScopedElement('feature-b', FeatureB); expect(el.shadowRoot.children[1]).to.be.an.instanceOf(FeatureB); @@ -359,9 +357,7 @@ describe('ScopedElementsMixin', () => { const el = await fixture(`<${tag}>`); - // @ts-ignore expect(el.constructor.getScopedTagName('feature-a')).to.match(tagRegExp); - // @ts-ignore expect(el.constructor.getScopedTagName('feature-b')).to.match(tagRegExp); }); @@ -387,7 +383,6 @@ describe('ScopedElementsMixin', () => { const el = await fixture(`<${tag}>`); - // @ts-ignore expect(el.constructor.getScopedTagName('unregistered-feature')).to.match(tagRegExp); }); }); diff --git a/packages/testing-helpers/src/litFixture.js b/packages/testing-helpers/src/litFixture.js index c47ccc08..451e794b 100644 --- a/packages/testing-helpers/src/litFixture.js +++ b/packages/testing-helpers/src/litFixture.js @@ -7,7 +7,8 @@ import { getScopedElementsTemplate } from './scopedElementsWrapper.js'; /** * @typedef { - import('lit-html').TemplateResult | import('lit-html').TemplateResult[] + import('lit-html').TemplateResult + | import('lit-html').TemplateResult[] | Node | Node[] | string | string[] | number | number[] @@ -59,16 +60,20 @@ export function litFixtureSync(template, options = {}) { * @returns {Promise} */ export async function litFixture(template, options = {}) { + /** @type {T} */ + // NB: in the case of scopedElements, this is ScopedElementsTestWrapper, not T, + // but that's only a small lie const el = litFixtureSync(template, options); await elementUpdated(el); if (options.scopedElements) { - const [node] = Array.from(el.shadowRoot.childNodes).filter(isUsefulNode); - await elementUpdated(/** @type {T} */ (node)); + const [node] = + /** @type {T[]} */ + (Array.from(el.shadowRoot.childNodes).filter(isUsefulNode)); + await elementUpdated(node.firstElementChild); - return /** @type {T} */ (node); + return node; } - // @ts-ignore return el; } diff --git a/packages/testing-helpers/src/scopedElementsWrapper.js b/packages/testing-helpers/src/scopedElementsWrapper.js index 12b7a007..d70efb61 100644 --- a/packages/testing-helpers/src/scopedElementsWrapper.js +++ b/packages/testing-helpers/src/scopedElementsWrapper.js @@ -1,6 +1,5 @@ import { ScopedElementsMixin } from '@open-wc/scoped-elements'; import { html, LitElement, TemplateResult } from 'lit-element'; -import { nothing } from 'lit-html'; import { isIterable } from './lib.js'; /** @typedef {import('@open-wc/scoped-elements').ScopedElementsMap} ScopedElementsMap */ @@ -31,13 +30,13 @@ class ScopedElementsTestWrapper extends ScopedElementsMixin(LitElement) { /** @type {ScopedElementsMap} */ this.scopedElements = {}; - /** @type {TemplateResult|{}} */ - this.template = nothing; + /** @type {import('./litFixture').LitHTMLRenderable} */ + // eslint-disable-next-line babel/no-unused-expressions + this.template; } - async firstUpdated() { - // @ts-ignore - await super.firstUpdated(); + firstUpdated(_changed) { + super.firstUpdated(_changed); Object.keys(this.scopedElements).forEach(key => this.defineScopedElement(key, this.scopedElements[key]), @@ -49,7 +48,6 @@ class ScopedElementsTestWrapper extends ScopedElementsMixin(LitElement) { } } -// @ts-ignore customElements.define('scoped-elements-test-wrapper', ScopedElementsTestWrapper); /**