From 9112d243dba86d385acabcf2dfff1d3776f01b73 Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Thu, 14 Jan 2021 13:08:23 +0100 Subject: [PATCH] fix: upgrade to latest scoped elements, add missing types --- .changeset/lemon-countries-clap.md | 23 +++ .../docs/google-combobox/google-combobox.js | 2 +- packages/combobox/src/LionCombobox.js | 4 +- packages/combobox/test/lion-combobox.test.js | 16 +- packages/core/package.json | 2 +- .../test/form-integrations.test.js | 8 +- .../form-integrations/test/form-reset.test.js | 7 +- .../test/helpers/umbrella-form.js | 10 +- .../test/model-value-consistency.test.js | 95 +++++++-- .../test/model-value-event.test.js | 40 ++-- packages/overlays/src/utils/deep-contains.js | 2 +- .../overlays/src/utils/is-equal-config.js | 4 +- .../test/utils-tests/contain-focus.test.js | 9 +- .../test/utils-tests/deep-contains.test.js | 32 +-- .../test/utils-tests/is-equal-config.test.js | 31 ++- .../select-rich/test/lion-select-rich.test.js | 194 +++++++++--------- tsconfig.json | 4 - yarn.lock | 8 +- 18 files changed, 295 insertions(+), 196 deletions(-) create mode 100644 .changeset/lemon-countries-clap.md diff --git a/.changeset/lemon-countries-clap.md b/.changeset/lemon-countries-clap.md new file mode 100644 index 000000000..66a5dd482 --- /dev/null +++ b/.changeset/lemon-countries-clap.md @@ -0,0 +1,23 @@ +--- +'@lion/calendar': patch +'@lion/checkbox-group': patch +'@lion/combobox': patch +'@lion/core': patch +'@lion/fieldset': patch +'@lion/form-core': patch +'@lion/form-integrations': patch +'@lion/input-date': patch +'@lion/input-datepicker': patch +'@lion/input-email': patch +'@lion/input-iban': patch +'@lion/listbox': patch +'@lion/localize': patch +'@lion/overlays': patch +'@lion/pagination': patch +'@lion/progress-indicator': patch +'@lion/radio-group': patch +'@lion/select-rich': patch +'@lion/switch': patch +--- + +Fix missing types and update to latest scoped elements to fix constructor type. diff --git a/packages/combobox/docs/google-combobox/google-combobox.js b/packages/combobox/docs/google-combobox/google-combobox.js index f4aa653a7..c318e676b 100644 --- a/packages/combobox/docs/google-combobox/google-combobox.js +++ b/packages/combobox/docs/google-combobox/google-combobox.js @@ -445,7 +445,7 @@ export class GoogleCombobox extends LionCombobox { } _showOverlayCondition(options) { - return this.focused || super.showOverlayCondition(options); + return this.focused || super._showOverlayCondition(options); } __resetFocus() { diff --git a/packages/combobox/src/LionCombobox.js b/packages/combobox/src/LionCombobox.js index 7d66ec08b..730f04e70 100644 --- a/packages/combobox/src/LionCombobox.js +++ b/packages/combobox/src/LionCombobox.js @@ -372,7 +372,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * * @example * _showOverlayCondition(options) { - * return this.focused || super.showOverlayCondition(options); + * return this.focused || super._showOverlayCondition(options); * } * * @example @@ -382,7 +382,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) { * * @example * _showOverlayCondition(options) { - * return options.currentValue.length > 4 && super.showOverlayCondition(options); + * return options.currentValue.length > 4 && super._showOverlayCondition(options); * } * * @param {{ currentValue: string, lastKey:string }} options diff --git a/packages/combobox/test/lion-combobox.test.js b/packages/combobox/test/lion-combobox.test.js index a28951bf3..ffcc06161 100644 --- a/packages/combobox/test/lion-combobox.test.js +++ b/packages/combobox/test/lion-combobox.test.js @@ -8,7 +8,6 @@ import { Required } from '@lion/form-core'; import { LionCombobox } from '../src/LionCombobox.js'; /** - * @typedef {import('../src/LionCombobox.js').LionCombobox} LionCombobox * @typedef {import('../types/SelectionDisplay').SelectionDisplay} SelectionDisplay */ @@ -384,8 +383,9 @@ describe('lion-combobox', () => { describe('Subclassers', () => { it('allows to control overlay visibility via "_showOverlayCondition"', async () => { class ShowOverlayConditionCombobox extends LionCombobox { + /** @param {{ currentValue: string, lastKey:string }} options */ _showOverlayCondition(options) { - return this.focused || super.showOverlayCondition(options); + return this.focused || super._showOverlayCondition(options); } } const tagName = defineCE(ShowOverlayConditionCombobox); @@ -575,6 +575,7 @@ describe('lion-combobox', () => { Item 1 `)); + // @ts-ignore sinon type error const spy = sinon.spy(el._selectionDisplayNode, 'onComboboxElementUpdated'); el.requestUpdate('modelValue'); await el.updateComplete; @@ -823,6 +824,11 @@ describe('lion-combobox', () => { `)); expect(el._inputNode.value).to.equal(''); + /** + * @param {'none' | 'list' | 'inline' | 'both'} autocomplete + * @param {number|number[]} index + * @param {string} valueOnClose + */ async function performChecks(autocomplete, index, valueOnClose) { await el.updateComplete; el.opened = true; @@ -945,7 +951,7 @@ describe('lion-combobox', () => { `)); // This ensures autocomplete would be off originally el.autocomplete = 'list'; - await mimicUserTypingAdvanced(el, 'vi'); // so we have options ['Victoria Plum'] + await mimicUserTypingAdvanced(el, ['v', 'i']); // so we have options ['Victoria Plum'] await el.updateComplete; expect(el.checkedIndex).to.equal(3); }); @@ -1057,6 +1063,10 @@ describe('lion-combobox', () => { return true; } + /** + * @param {?} modelValue + * @param {?} oldModelValue + */ // eslint-disable-next-line no-unused-vars _syncToTextboxMultiple(modelValue, oldModelValue) { // In a real scenario (depending on how selection display works), diff --git a/packages/core/package.json b/packages/core/package.json index 6f2e58232..cdb6d2769 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,7 +32,7 @@ "sideEffects": false, "dependencies": { "@open-wc/dedupe-mixin": "^1.2.18", - "@open-wc/scoped-elements": "^1.3.2", + "@open-wc/scoped-elements": "^1.3.3", "lit-element": "~2.4.0", "lit-html": "^1.3.0" }, diff --git a/packages/form-integrations/test/form-integrations.test.js b/packages/form-integrations/test/form-integrations.test.js index ae14f72f4..6d28f3bd6 100644 --- a/packages/form-integrations/test/form-integrations.test.js +++ b/packages/form-integrations/test/form-integrations.test.js @@ -1,10 +1,14 @@ import { expect, fixture, html } from '@open-wc/testing'; import './helpers/umbrella-form.js'; +/** + * @typedef {import('./helpers/umbrella-form.js').UmbrellaForm} UmbrellaForm + */ + // Test umbrella form. describe('Form Integrations', () => { it('".serializedValue" returns all non disabled fields based on form structure', async () => { - const el = await fixture(html``); + const el = /** @type {UmbrellaForm} */ (await fixture(html``)); await el.updateComplete; const formEl = el._lionFormNode; expect(formEl.serializedValue).to.eql({ @@ -29,7 +33,7 @@ describe('Form Integrations', () => { }); it('".formattedValue" returns all non disabled fields based on form structure', async () => { - const el = await fixture(html``); + const el = /** @type {UmbrellaForm} */ (await fixture(html``)); await el.updateComplete; const formEl = el._lionFormNode; expect(formEl.formattedValue).to.eql({ diff --git a/packages/form-integrations/test/form-reset.test.js b/packages/form-integrations/test/form-reset.test.js index 2462c5bdd..d2a0c54a0 100644 --- a/packages/form-integrations/test/form-reset.test.js +++ b/packages/form-integrations/test/form-reset.test.js @@ -18,6 +18,7 @@ import '@lion/textarea/lion-textarea'; import { elementUpdated, expect, fixture, html } from '@open-wc/testing'; describe(`Submitting/Resetting Form`, async () => { + /** @type {import('@lion/form').LionForm} */ let el; beforeEach(async () => { el = await fixture(html` @@ -121,7 +122,7 @@ describe(`Submitting/Resetting Form`, async () => { }); it('Submitting a form should make submitted true for all fields', async () => { - el.querySelector('#submit_button').click(); + /** @type {import('@lion/button').LionButton} */ (el.querySelector('#submit_button')).click(); await elementUpdated(el); await el.updateComplete; el.formElements.forEach(field => { @@ -130,8 +131,8 @@ describe(`Submitting/Resetting Form`, async () => { }); it('Resetting a form should reset metadata of all fields', async () => { - el.querySelector('#submit_button').click(); - el.querySelector('#reset_button').click(); + /** @type {import('@lion/button').LionButton} */ (el.querySelector('#submit_button')).click(); + /** @type {import('@lion/button').LionButton} */ (el.querySelector('#reset_button')).click(); await elementUpdated(el); await el.updateComplete; expect(el.submitted).to.be.false; diff --git a/packages/form-integrations/test/helpers/umbrella-form.js b/packages/form-integrations/test/helpers/umbrella-form.js index da9e44136..363c90447 100644 --- a/packages/form-integrations/test/helpers/umbrella-form.js +++ b/packages/form-integrations/test/helpers/umbrella-form.js @@ -22,7 +22,9 @@ import '@lion/button/lion-button.js'; export class UmbrellaForm extends LitElement { get _lionFormNode() { - return this.shadowRoot.querySelector('lion-form'); + return /** @type {import('@lion/form').LionForm} */ (this.shadowRoot?.querySelector( + 'lion-form', + )); } render() { @@ -113,8 +115,10 @@ export class UmbrellaForm extends LitElement { - ev.currentTarget.parentElement.parentElement.parentElement.resetGroup()} + @click=${() => { + const lionForm = this._lionFormNode; + lionForm.resetGroup(); + }} >Reset diff --git a/packages/form-integrations/test/model-value-consistency.test.js b/packages/form-integrations/test/model-value-consistency.test.js index f2eace889..7679ae1ed 100644 --- a/packages/form-integrations/test/model-value-consistency.test.js +++ b/packages/form-integrations/test/model-value-consistency.test.js @@ -29,14 +29,29 @@ import '@lion/fieldset/lion-fieldset.js'; import '@lion/form/lion-form.js'; import '@lion/form-core/lion-field.js'; +/** + * @typedef {import('@lion/core').LitElement} LitElement + * @typedef {import('@lion/form-core').LionField} LionField + * @typedef {import('@lion/form-core/types/FormControlMixinTypes').FormControlHost & HTMLElement & {__parentFormGroup?: HTMLElement, checked?: boolean, disabled: boolean, hasFeedbackFor: string[], makeRequestToBeDisabled: Function }} FormControl + * @typedef {import('@lion/input').LionInput} LionInput + * @typedef {import('@lion/select').LionSelect} LionSelect + * @typedef {import('@lion/listbox').LionOption} LionOption + */ + const featureName = 'model value'; -const getFirstPaintTitle = count => `should dispatch ${count} time(s) on first paint`; -const getInteractionTitle = count => `should dispatch ${count} time(s) on interaction`; +const getFirstPaintTitle = /** @param {number} count */ count => + `should dispatch ${count} time(s) on first paint`; +const getInteractionTitle = /** @param {number} count */ count => + `should dispatch ${count} time(s) on interaction`; const firstStampCount = 1; const interactionCount = 1; +/** + * @param {string} tagname + * @param {number} count + */ const fieldDispatchesCountOnFirstPaint = (tagname, count) => { const tag = unsafeStatic(tagname); const spy = sinon.spy(); @@ -46,11 +61,15 @@ const fieldDispatchesCountOnFirstPaint = (tagname, count) => { }); }; +/** + * @param {string} tagname + * @param {number} count + */ const fieldDispatchesCountOnInteraction = (tagname, count) => { const tag = unsafeStatic(tagname); const spy = sinon.spy(); it(getInteractionTitle(count), async () => { - const el = await fixture(html`<${tag}>`); + const el = /** @type {LionField} */ (await fixture(html`<${tag}>`)); el.addEventListener('model-value-changed', spy); // TODO: discuss if this is the "correct" way to interact with component el.modelValue = 'foo'; @@ -59,6 +78,10 @@ const fieldDispatchesCountOnInteraction = (tagname, count) => { }); }; +/** + * @param {string} tagname + * @param {number} count + */ const choiceDispatchesCountOnFirstPaint = (tagname, count) => { const tag = unsafeStatic(tagname); const spy = sinon.spy(); @@ -68,17 +91,28 @@ const choiceDispatchesCountOnFirstPaint = (tagname, count) => { }); }; +/** + * @param {string} tagname + * @param {number} count + */ const choiceDispatchesCountOnInteraction = (tagname, count) => { const tag = unsafeStatic(tagname); const spy = sinon.spy(); it(getInteractionTitle(count), async () => { - const el = await fixture(html`<${tag} .choiceValue="${'option'}">`); + const el = /** @type {HTMLElement & {checked: boolean}} */ (await fixture( + html`<${tag} .choiceValue="${'option'}">`, + )); el.addEventListener('model-value-changed', spy); el.checked = true; expect(spy.callCount).to.equal(count); }); }; +/** + * @param {string} groupTagname + * @param {string} itemTagname + * @param {number} count + */ const choiceGroupDispatchesCountOnFirstPaint = (groupTagname, itemTagname, count) => { const groupTag = unsafeStatic(groupTagname); const itemTag = unsafeStatic(itemTagname); @@ -96,6 +130,11 @@ const choiceGroupDispatchesCountOnFirstPaint = (groupTagname, itemTagname, count }); }; +/** + * @param {string} groupTagname + * @param {string} itemTagname + * @param {number} count + */ const choiceGroupDispatchesCountOnInteraction = (groupTagname, itemTagname, count) => { const groupTag = unsafeStatic(groupTagname); const itemTag = unsafeStatic(itemTagname); @@ -110,13 +149,17 @@ const choiceGroupDispatchesCountOnInteraction = (groupTagname, itemTagname, coun `); el.addEventListener('model-value-changed', spy); - const option2 = el.querySelector(`${itemTagname}:nth-child(2)`); + const option2 = /** @type {HTMLElement & {checked: boolean}} */ (el.querySelector( + `${itemTagname}:nth-child(2)`, + )); option2.checked = true; expect(spy.callCount).to.equal(count); spy.resetHistory(); - const option3 = el.querySelector(`${itemTagname}:nth-child(3)`); + const option3 = /** @type {HTMLElement & {checked: boolean}} */ (el.querySelector( + `${itemTagname}:nth-child(3)`, + )); option3.checked = true; expect(spy.callCount).to.equal(count); }); @@ -178,7 +221,7 @@ describe('lion-select', () => { it(getInteractionTitle(interactionCount), async () => { const spy = sinon.spy(); - const el = await fixture(html` + const el = /** @type {LionSelect} */ (await fixture(html` - `); + `)); el.addEventListener('model-value-changed', spy); - const option2 = el.querySelector('option:nth-child(2)'); + const option2 = /** @type {HTMLOptionElement} */ (el.querySelector('option:nth-child(2)')); // mimic user input option2.selected = true; @@ -198,7 +241,7 @@ describe('lion-select', () => { spy.resetHistory(); - const option3 = el.querySelector('option:nth-child(3)'); + const option3 = /** @type {HTMLOptionElement} */ (el.querySelector('option:nth-child(3)')); // mimic user input option3.selected = true; @@ -238,13 +281,13 @@ describe('lion-select', () => { `); el.addEventListener('model-value-changed', spy); - const option2 = el.querySelector('lion-option:nth-child(2)'); + const option2 = /** @type {LionOption} */ (el.querySelector('lion-option:nth-child(2)')); option2.checked = true; expect(spy.callCount).to.equal(interactionCount); spy.resetHistory(); - const option3 = el.querySelector('lion-option:nth-child(3)'); + const option3 = /** @type {LionOption} */ (el.querySelector('lion-option:nth-child(3)')); option3.checked = true; expect(spy.callCount).to.equal(interactionCount); }); @@ -274,7 +317,7 @@ describe('lion-fieldset', () => { `); el.addEventListener('model-value-changed', spy); - const input = el.querySelector('lion-input'); + const input = /** @type {LionInput} */ (el.querySelector('lion-input')); input.modelValue = 'foo'; expect(spy.callCount).to.equal(interactionCount); }); @@ -337,8 +380,9 @@ describe('detail.isTriggeredByUser', () => { * - false: when child formElement condition for RegularField is not met */ - const featureDetectChoiceField = el => 'checked' in el && 'choiceValue' in el; - const featureDetectOptionChoiceField = el => 'active' in el; + const featureDetectChoiceField = /** @param {HTMLElement} el */ el => + 'checked' in el && 'choiceValue' in el; + const featureDetectOptionChoiceField = /** @param {HTMLElement} el */ el => 'active' in el; /** * @param {FormControl} el @@ -355,9 +399,8 @@ describe('detail.isTriggeredByUser', () => { } /** - * @param {FormControl} el + * @param {FormControl & {value: string}} el * @param {string} newViewValue - * @returns {'RegularField'|'ChoiceField'|'OptionChoiceField'|'ChoiceGroupField'|'FormOrFieldset'} */ function mimicUserInput(el, newViewValue) { const type = detectType(el); @@ -391,27 +434,39 @@ describe('detail.isTriggeredByUser', () => { } else if (controlName === 'field') { childrenEl = await fixture(html``); } - const el = await fixture(html`<${tag}>${childrenEl}`); + + const el = /** @type {LitElement & FormControl & {value: string} & {registrationComplete: Promise} & {formElements: Array.}} */ (await fixture( + html`<${tag}>${childrenEl}`, + )); await el.registrationComplete; el.addEventListener('model-value-changed', spy); + /** + * @param {FormControl & {value: string}} formControl + */ function expectCorrectEventMetaRegularField(formControl) { - mimicUserInput(formControl, 'userValue', 'RegularField'); + mimicUserInput(formControl, 'userValue'); expect(spy.firstCall.args[0].detail.isTriggeredByUser).to.be.true; // eslint-disable-next-line no-param-reassign formControl.modelValue = 'programmaticValue'; expect(spy.secondCall.args[0].detail.isTriggeredByUser).to.be.false; } + /** + * @param {FormControl & {value: string}} formControl + */ function resetChoiceFieldToForceRepropagation(formControl) { // eslint-disable-next-line no-param-reassign formControl.checked = false; spy.resetHistory(); } + /** + * @param {FormControl & {value: string}} formControl + */ function expectCorrectEventMetaChoiceField(formControl) { resetChoiceFieldToForceRepropagation(formControl); - mimicUserInput(formControl, 'userValue', 'ChoiceField'); + mimicUserInput(formControl, 'userValue'); expect(spy.firstCall.args[0].detail.isTriggeredByUser).to.be.true; resetChoiceFieldToForceRepropagation(formControl); diff --git a/packages/form-integrations/test/model-value-event.test.js b/packages/form-integrations/test/model-value-event.test.js index 0004423f2..f579c9677 100644 --- a/packages/form-integrations/test/model-value-event.test.js +++ b/packages/form-integrations/test/model-value-event.test.js @@ -4,20 +4,28 @@ import { expect, html, fixture } from '@open-wc/testing'; // eslint-disable-next-line import/no-extraneous-dependencies import sinon from 'sinon'; +/** + * @typedef {import('@lion/input').LionInput} LionInput + * @typedef {import('@lion/fieldset').LionFieldset} LionFieldset + * @typedef {import('@lion/core').TemplateResult} TemplateResult + */ +const inputFixture = /** @type {(arg: TemplateResult) => Promise} */ (fixture); +const fieldsetFixture = /** @type {(arg: TemplateResult) => Promise} */ (fixture); + describe('model value event', () => { describe('form path', () => { it('should be property', async () => { const spy = sinon.spy(); - const input = await fixture(html``); + const input = await inputFixture(html``); input.addEventListener('model-value-changed', spy); input.modelValue = 'woof'; const e = spy.firstCall.args[0]; - expect(e.detail).to.have.a.property('formPath'); + expect(e.detail).to.have.property('formPath'); }); it('should contain dispatching field', async () => { const spy = sinon.spy(); - const input = await fixture(html``); + const input = await inputFixture(html``); input.addEventListener('model-value-changed', spy); input.modelValue = 'foo'; const e = spy.firstCall.args[0]; @@ -26,14 +34,14 @@ describe('model value event', () => { it('should contain field and group', async () => { const spy = sinon.spy(); - const fieldset = await fixture(html` + const fieldset = await fieldsetFixture(html` `); await fieldset.registrationComplete; fieldset.addEventListener('model-value-changed', spy); - const input = fieldset.querySelector('lion-input'); + const input = /** @type {LionInput} */ (fieldset.querySelector('lion-input')); input.modelValue = 'foo'; const e = spy.firstCall.args[0]; expect(e.detail.formPath).to.eql([input, fieldset]); @@ -41,15 +49,15 @@ describe('model value event', () => { it('should contain deep elements', async () => { const spy = sinon.spy(); - const grandparent = await fixture(html` + const grandparent = await fieldsetFixture(html` `); - const parent = grandparent.querySelector('[name=parent]'); - const input = grandparent.querySelector('[name=input]'); + const parent = /** @type {LionFieldset} */ (grandparent.querySelector('[name=parent]')); + const input = /** @type {LionInput} */ (grandparent.querySelector('[name=input]')); await grandparent.registrationComplete; await parent.registrationComplete; @@ -61,7 +69,7 @@ describe('model value event', () => { it('should ignore elements that are not fields or fieldsets', async () => { const spy = sinon.spy(); - const grandparent = await fixture(html` + const grandparent = await fieldsetFixture(html`
@@ -74,8 +82,8 @@ describe('model value event', () => {
`); - const parent = grandparent.querySelector('[name=parent]'); - const input = grandparent.querySelector('[name=input]'); + const parent = /** @type {LionFieldset} */ (grandparent.querySelector('[name=parent]')); + const input = /** @type {LionInput} */ (grandparent.querySelector('[name=input]')); await grandparent.registrationComplete; await parent.registrationComplete; @@ -87,10 +95,11 @@ describe('model value event', () => { }); describe('signature', () => { + /** @type {?} */ let e; beforeEach(async () => { const spy = sinon.spy(); - const el = await fixture(html``); + const el = await inputFixture(html``); el.addEventListener('model-value-changed', spy); el.modelValue = 'foo'; // eslint-disable-next-line prefer-destructuring @@ -112,18 +121,19 @@ describe('model value event', () => { describe('propagation', () => { it('should dispatch different event at each level', async () => { - const grandparent = await fixture(html` + const grandparent = await fieldsetFixture(html` `); - const parent = grandparent.querySelector('[name="parent"]'); - const input = grandparent.querySelector('[name="input"]'); + const parent = /** @type {LionFieldset} */ (grandparent.querySelector('[name="parent"]')); + const input = /** @type {LionInput} */ (grandparent.querySelector('[name="input"]')); await grandparent.registrationComplete; await parent.registrationComplete; + /** @type {sinon.SinonSpy[]} */ const spies = []; [grandparent, parent, input].forEach(element => { const spy = sinon.spy(); diff --git a/packages/overlays/src/utils/deep-contains.js b/packages/overlays/src/utils/deep-contains.js index 0df64dc14..b87e5a35e 100644 --- a/packages/overlays/src/utils/deep-contains.js +++ b/packages/overlays/src/utils/deep-contains.js @@ -1,7 +1,7 @@ /** * Whether first element contains the second element, also goes through shadow roots * @param {HTMLElement|ShadowRoot} el - * @param {HTMLElement} targetEl + * @param {HTMLElement|ShadowRoot} targetEl * @returns {boolean} */ export function deepContains(el, targetEl) { diff --git a/packages/overlays/src/utils/is-equal-config.js b/packages/overlays/src/utils/is-equal-config.js index 7e0051ecf..dba2eeede 100644 --- a/packages/overlays/src/utils/is-equal-config.js +++ b/packages/overlays/src/utils/is-equal-config.js @@ -6,8 +6,8 @@ * Compares two OverlayConfigs to equivalence. Intended to prevent unnecessary resets. * Note that it doesn't cover as many use cases as common implementations, such as Lodash isEqual. * - * @param {OverlayConfig} a - * @param {OverlayConfig} b + * @param {Partial} a + * @param {Partial} b * @returns {boolean} Whether the configs are equivalent */ export function isEqualConfig(a, b) { diff --git a/packages/overlays/test/utils-tests/contain-focus.test.js b/packages/overlays/test/utils-tests/contain-focus.test.js index a45843acd..b7fcb3552 100644 --- a/packages/overlays/test/utils-tests/contain-focus.test.js +++ b/packages/overlays/test/utils-tests/contain-focus.test.js @@ -1,5 +1,4 @@ import { expect, fixture, html } from '@open-wc/testing'; -// @ts-expect-error import { renderLitAsNode } from '@lion/helpers'; import { getDeepActiveElement } from '../../src/utils/get-deep-active-element.js'; import { getFocusableElements } from '../../src/utils/get-focusable-elements.js'; @@ -72,9 +71,11 @@ function createShadowDomNode() { `); - const rootElementShadow = shadowDomNode.querySelector('#rootElementShadow'); - rootElementShadow.attachShadow({ mode: 'open' }); - rootElementShadow.shadowRoot.appendChild(interactionElementsNode); + const rootElementShadow = shadowDomNode?.querySelector('#rootElementShadow'); + rootElementShadow?.attachShadow({ mode: 'open' }); + if (interactionElementsNode) { + rootElementShadow?.shadowRoot?.appendChild(interactionElementsNode); + } return shadowDomNode; } diff --git a/packages/overlays/test/utils-tests/deep-contains.test.js b/packages/overlays/test/utils-tests/deep-contains.test.js index fa743a885..e5cc9425e 100644 --- a/packages/overlays/test/utils-tests/deep-contains.test.js +++ b/packages/overlays/test/utils-tests/deep-contains.test.js @@ -30,7 +30,12 @@ describe('deepContains()', () => { expect(deepContains(shadowRoot, shadowElementChild)).to.be.true; // Siblings - expect(deepContains(element.firstElementChild, element.lastElementChild)).to.be.false; + expect( + deepContains( + /** @type {HTMLElement} */ (element.firstElementChild), + /** @type {HTMLElement} */ (element.lastElementChild), + ), + ).to.be.false; // Unrelated expect(deepContains(lightChildEl, shadowElementChild)).to.be.false; }); @@ -95,16 +100,19 @@ describe('deepContains()', () => { `)); - expect(deepContains(element, element.firstElementChild)).to.be.true; - expect(deepContains(element, element.firstElementChild.shadowRoot)).to.be.true; - expect(deepContains(element, element.firstElementChild.shadowRoot.children[1])).to.be.true; - expect(deepContains(element, element.firstElementChild.shadowRoot.children[1].shadowRoot)).to.be - .true; - expect( - deepContains( - element, - element.firstElementChild.shadowRoot.children[1].shadowRoot.lastElementChild, - ), - ).to.be.true; + const elementFirstChild = /** @type {HTMLElement} */ (element.firstElementChild); + const elementFirstChildShadow = /** @type {ShadowRoot} */ (elementFirstChild.shadowRoot); + const elementFirstChildShadowChildren = /** @type {HTMLElement[]} */ (Array.from( + elementFirstChildShadow.children, + )); + const elementFirstChildShadowChildShadow = /** @type {ShadowRoot} */ (elementFirstChildShadowChildren[1] + .shadowRoot); + const elementFirstChildShadowChildShadowLastChild = /** @type {HTMLElement} */ (elementFirstChildShadowChildShadow.lastElementChild); + + expect(deepContains(element, elementFirstChild)).to.be.true; + expect(deepContains(element, elementFirstChildShadow)).to.be.true; + expect(deepContains(element, elementFirstChildShadowChildren[1])).to.be.true; + expect(deepContains(element, elementFirstChildShadowChildShadow)).to.be.true; + expect(deepContains(element, elementFirstChildShadowChildShadowLastChild)).to.be.true; }); }); diff --git a/packages/overlays/test/utils-tests/is-equal-config.test.js b/packages/overlays/test/utils-tests/is-equal-config.test.js index d6e1b78f1..d13a77431 100644 --- a/packages/overlays/test/utils-tests/is-equal-config.test.js +++ b/packages/overlays/test/utils-tests/is-equal-config.test.js @@ -3,14 +3,15 @@ import { isEqualConfig } from '../../src/utils/is-equal-config.js'; function TestConfig() { return { - placementMode: 'local', + placementMode: /** @type {'local'|'global'} */ ('local'), hidesOnOutsideClick: true, popperConfig: { - modifiers: { - offset: { + modifiers: [ + { + name: 'offset', enabled: false, }, - }, + ], }, }; } @@ -31,13 +32,13 @@ describe('isEqualConfig()', () => { it('compares prop count', () => { const config = TestConfig(); - expect(isEqualConfig(config, { ...config, extra: 'value' })).eql(false); - expect(isEqualConfig({ ...config, extra: 'value' }, config)).eql(false); + expect(isEqualConfig(config, { ...config, isBlocking: true })).eql(false); + expect(isEqualConfig({ ...config, isBlocking: true }, config)).eql(false); }); it('regards missing props different from ones with undefined value', () => { const config = TestConfig(); - expect(isEqualConfig(config, { ...config, extra: undefined })).eql(false); + expect(isEqualConfig(config, { ...config, referenceNode: undefined })).eql(false); }); it('compares nested props', () => { @@ -46,12 +47,7 @@ describe('isEqualConfig()', () => { ...config, popperConfig: { ...config.popperConfig, - modifiers: { - ...config.popperConfig.modifiers, - offset: { - ...config.popperConfig.modifiers.offset, - }, - }, + modifiers: [...config.popperConfig.modifiers], }, }; expect(isEqualConfig(config, sameConfig)).eql(true); @@ -59,12 +55,13 @@ describe('isEqualConfig()', () => { ...config, popperConfig: { ...config.popperConfig, - modifiers: { + modifiers: [ ...config.popperConfig.modifiers, - offset: { - enabled: !config.popperConfig.modifiers.offset.enabled, + { + name: 'offset', + enabled: !config.popperConfig.modifiers.find(mod => mod.name === 'offset')?.enabled, }, - }, + ], }, }; expect(isEqualConfig(config, differentConfig)).eql(false); diff --git a/packages/select-rich/test/lion-select-rich.test.js b/packages/select-rich/test/lion-select-rich.test.js index fdd76fc4f..35f58cb67 100644 --- a/packages/select-rich/test/lion-select-rich.test.js +++ b/packages/select-rich/test/lion-select-rich.test.js @@ -1,5 +1,4 @@ import { LitElement } from '@lion/core'; -// @ts-expect-error import { renderLitAsNode } from '@lion/helpers'; import { OverlayController } from '@lion/overlays'; import { LionOption } from '@lion/listbox'; @@ -10,7 +9,7 @@ import { html, nextFrame, unsafeStatic, - fixture, + fixture as _fixture, } from '@open-wc/testing'; import { LionSelectInvoker, LionSelectRich } from '../index.js'; @@ -19,6 +18,12 @@ import '@lion/listbox/lion-option.js'; import '@lion/listbox/lion-options.js'; import '../lion-select-rich.js'; +/** + * @typedef {import('@lion/core').TemplateResult} TemplateResult + */ + +const fixture = /** @type {(arg: TemplateResult) => Promise} */ (_fixture); + /** * @param {LionSelectRich} lionSelectEl */ @@ -26,10 +31,16 @@ function getProtectedMembers(lionSelectEl) { // @ts-ignore protected members allowed in test const { _invokerNode: invoker, + // @ts-ignore _feedbackNode: feedback, + // @ts-ignore _labelNode: label, + // @ts-ignore _helpTextNode: helpText, + // @ts-ignore _listboxNode: listbox, + // @ts-ignore + _overlayCtrl: overlay, } = lionSelectEl; return { invoker, @@ -37,53 +48,53 @@ function getProtectedMembers(lionSelectEl) { label, helpText, listbox, + overlay, }; } describe('lion-select-rich', () => { it('clicking the label should focus the invoker', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); expect(document.activeElement === document.body).to.be.true; - el._labelNode.click(); + const { label } = getProtectedMembers(el); + label.click(); // @ts-ignore allow protected access in tests expect(document.activeElement === el._invokerNode).to.be.true; }); it('checks the first enabled option', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` - `)); + `); expect(el.activeIndex).to.equal(0); expect(el.checkedIndex).to.equal(0); }); it('still has a checked value while disabled', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Red Hotpink Blue - `)); + `); expect(el.modelValue).to.equal('Red'); }); it('supports having no default selection initially', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Red Hotpink Teal - `)); + `); const { invoker } = getProtectedMembers(el); expect(invoker.selectedElement).to.be.undefined; expect(el.modelValue).to.equal(''); @@ -91,9 +102,7 @@ describe('lion-select-rich', () => { describe('Invoker', () => { it('generates an lion-select-invoker if no invoker is provided', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); // @ts-ignore allow protected access in tests expect(el._invokerNode).to.exist; @@ -102,24 +111,24 @@ describe('lion-select-rich', () => { }); it('sets the first option as the selectedElement if no option is checked', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 Item 2 - `)); + `); const options = el.formElements; // @ts-ignore allow protected access in tests expect(el._invokerNode.selectedElement).dom.to.equal(options[0]); }); it('syncs the selected element to the invoker', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 Item 2 - `)); + `); const options = el.querySelectorAll('lion-option'); // @ts-ignore allow protected access in tests expect(el._invokerNode.selectedElement).dom.to.equal(options[1]); @@ -131,12 +140,12 @@ describe('lion-select-rich', () => { }); it('delegates readonly to the invoker', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 Item 2 - `)); + `); expect(el.hasAttribute('readonly')).to.be.true; // @ts-ignore allow protected access in tests @@ -144,11 +153,11 @@ describe('lion-select-rich', () => { }); it('delegates singleOption to the invoker', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 - `)); + `); expect(el.singleOption).to.be.true; // @ts-ignore allow protected access in tests @@ -169,16 +178,16 @@ describe('lion-select-rich', () => { ); const tagString = unsafeStatic(tag); - const firstOption = renderLitAsNode( + const firstOption = /** @type {LionOption} */ (renderLitAsNode( html`<${tagString} checked .choiceValue=${10}>`, - ); + )); - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` ${firstOption} <${tagString} .choiceValue=${20}> - `)); + `); // @ts-ignore allow protected access in tests expect(el._invokerNode.shadowRoot.firstElementChild.textContent).to.equal('10'); @@ -191,16 +200,17 @@ describe('lion-select-rich', () => { }); it('inherits the content width including arrow width', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 Item 2 with long label - `)); + `); el.opened = true; const options = el.formElements; await el.updateComplete; - expect(el._invokerNode.clientWidth).to.equal(options[1].clientWidth); + const { invoker } = getProtectedMembers(el); + expect(invoker.clientWidth).to.equal(options[1].clientWidth); const newOption = /** @type {LionOption} */ (document.createElement('lion-option')); newOption.choiceValue = 30; @@ -208,26 +218,22 @@ describe('lion-select-rich', () => { el._inputNode.appendChild(newOption); await el.updateComplete; - expect(el._invokerNode.clientWidth).to.equal(options[2].clientWidth); + expect(invoker.clientWidth).to.equal(options[2].clientWidth); el._inputNode.removeChild(newOption); await el.updateComplete; - expect(el._invokerNode.clientWidth).to.equal(options[1].clientWidth); + expect(invoker.clientWidth).to.equal(options[1].clientWidth); }); }); describe('overlay', () => { it('should be closed by default', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); expect(el.opened).to.be.false; }); it('shows/hides the listbox via opened attribute', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); el.opened = true; await el.updateComplete; // @ts-ignore allow protected access in tests @@ -241,10 +247,8 @@ describe('lion-select-rich', () => { }); it('syncs opened state with overlay shown', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); - const outerEl = /** @type {HTMLButtonElement} */ (await fixture( + const el = await fixture(html` `); + const outerEl = /** @type {HTMLButtonElement} */ (await _fixture( '', )); @@ -257,9 +261,7 @@ describe('lion-select-rich', () => { }); it('will focus the listbox on open and invoker on close', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); // @ts-ignore allow protected access in tests await el._overlayCtrl.show(); await el.updateComplete; @@ -278,12 +280,12 @@ describe('lion-select-rich', () => { }); it('opens the listbox with checked option as active', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 Item 2 - `)); + `); // @ts-ignore allow protected access in tests await el._overlayCtrl.show(); await el.updateComplete; @@ -294,25 +296,25 @@ describe('lion-select-rich', () => { }); it('stays closed on click if it is disabled or readonly or has a single option', async () => { - const elReadOnly = /** @type {LionSelectRich} */ (await fixture(html` + const elReadOnly = await fixture(html` Item 1 Item 2 - `)); + `); - const elDisabled = /** @type {LionSelectRich} */ (await fixture(html` + const elDisabled = await fixture(html` Item 1 Item 2 - `)); + `); - const elSingleoption = /** @type {LionSelectRich} */ (await fixture(html` + const elSingleoption = await fixture(html` Item 1 - `)); + `); // @ts-ignore allow protected access in tests elReadOnly._invokerNode.click(); @@ -331,13 +333,13 @@ describe('lion-select-rich', () => { }); it('sets inheritsReferenceWidth to min by default', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Red Hotpink Teal - `)); + `); // @ts-ignore allow protected access in tests expect(el._overlayCtrl.inheritsReferenceWidth).to.equal('min'); @@ -349,26 +351,28 @@ describe('lion-select-rich', () => { }); it('should override the inheritsWidth prop when no default selected feature is used', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Red Hotpink Teal - `)); + `); + + const { overlay } = getProtectedMembers(el); // The default is min, so we override that behavior here // @ts-ignore allow protected access in tests - el._overlayCtrl.updateConfig({ inheritsReferenceWidth: 'full' }); + overlay.updateConfig({ inheritsReferenceWidth: 'full' }); el._initialInheritsReferenceWidth = 'full'; // @ts-ignore allow protected access in tests - expect(el._overlayCtrl.inheritsReferenceWidth).to.equal('full'); + expect(overlay.inheritsReferenceWidth).to.equal('full'); el.opened = true; await el.updateComplete; // Opens while hasNoDefaultSelected = true, so we expect an override // @ts-ignore allow protected access in tests - expect(el._overlayCtrl.inheritsReferenceWidth).to.equal('min'); + expect(overlay.inheritsReferenceWidth).to.equal('min'); // Emulate selecting hotpink, it closing, and opening it again el.modelValue = 'hotpink'; @@ -378,7 +382,7 @@ describe('lion-select-rich', () => { el.opened = true; await el.updateComplete; await el.updateComplete; // safari takes a little longer - await el._overlayCtrl._showComplete; + await overlay._showComplete; // noDefaultSelected will now flip the override back to what was the initial reference width // @ts-ignore allow protected access in tests @@ -386,12 +390,12 @@ describe('lion-select-rich', () => { }); it('should have singleOption only if there is exactly one option', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 Item 2 - `)); + `); expect(el.singleOption).to.be.false; // @ts-ignore allow protected access in tests expect(el._invokerNode.singleOption).to.be.false; @@ -418,18 +422,16 @@ describe('lion-select-rich', () => { describe('interaction-mode', () => { it('allows to specify an interaction-mode which determines other behaviors', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` - `)); + `); expect(el.interactionMode).to.equal('mac'); }); }); describe('Keyboard navigation', () => { it('opens the listbox with [Enter] key via click handler', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); // @ts-ignore allow protected access in tests el._invokerNode.click(); await aTimeout(0); @@ -437,9 +439,7 @@ describe('lion-select-rich', () => { }); it('opens the listbox with [ ](Space) key via click handler', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); // @ts-ignore allow protected access in tests el._invokerNode.click(); await aTimeout(0); @@ -447,18 +447,14 @@ describe('lion-select-rich', () => { }); it('closes the listbox with [Escape] key once opened', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); // @ts-ignore allow protected access in tests el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(el.opened).to.be.false; }); it('closes the listbox with [Tab] key once opened', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); // tab can only be caught via keydown // @ts-ignore allow protected access in tests el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' })); @@ -468,9 +464,7 @@ describe('lion-select-rich', () => { describe('Mouse navigation', () => { it('opens the listbox via click on invoker', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); expect(el.opened).to.be.false; // @ts-ignore allow protected access in tests el._invokerNode.click(); @@ -479,11 +473,11 @@ describe('lion-select-rich', () => { }); it('closes the listbox when an option gets clicked', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 - `)); + `); expect(el.opened).to.be.true; el.formElements[0].click(); expect(el.opened).to.be.false; @@ -492,9 +486,7 @@ describe('lion-select-rich', () => { describe('Keyboard navigation Windows', () => { it('closes the listbox with [Enter] key once opened', async () => { - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); // @ts-ignore allow protected access in tests el._listboxNode.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); expect(el.opened).to.be.false; @@ -503,12 +495,12 @@ describe('lion-select-rich', () => { describe('Keyboard navigation Mac', () => { it('checks active item and closes the listbox with [Enter] key via click handler once opened', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 Item 2 - `)); + `); // changes active but not checked el.activeIndex = 1; @@ -520,18 +512,18 @@ describe('lion-select-rich', () => { }); it('opens the listbox with [ArrowUp] key', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` - `)); + `); el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowUp' })); await el.updateComplete; expect(el.opened).to.be.true; }); it('opens the listbox with [ArrowDown] key', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` - `)); + `); el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown' })); await el.updateComplete; expect(el.opened).to.be.true; @@ -540,12 +532,12 @@ describe('lion-select-rich', () => { describe('Accessibility', () => { it('has the right references to its inner elements', async () => { - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` Item 1 Item 2 - `)); + `); const { invoker, feedback, label, helpText } = getProtectedMembers(el); expect(invoker.getAttribute('aria-labelledby')).to.contain(label.id); @@ -557,9 +549,7 @@ describe('lion-select-rich', () => { it('notifies when the listbox is expanded or not', async () => { // smoke test for overlay functionality - const el = /** @type {LionSelectRich} */ (await fixture( - html` `, - )); + const el = await fixture(html` `); const { invoker } = getProtectedMembers(el); expect(invoker.getAttribute('aria-expanded')).to.equal('false'); @@ -619,9 +609,9 @@ describe('lion-select-rich', () => { }, ); const mySelectContainerTag = unsafeStatic(mySelectContainerTagString); - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` <${mySelectContainerTag}> - `)); + `); const selectRich = /** @type {LionSelectRich} */ ( /** @type {ShadowRoot} */ (el.shadowRoot).querySelector('lion-select-rich') @@ -667,7 +657,7 @@ describe('lion-select-rich', () => { const mySelectTag = unsafeStatic(mySelectTagString); - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` <${mySelectTag} label="Favorite color" name="color"> ${Array(2).map( @@ -677,7 +667,7 @@ describe('lion-select-rich', () => { )} - `)); + `); await el.updateComplete; // @ts-ignore allow protected member access in tests expect(el._overlayCtrl.placementMode).to.equal('global'); @@ -708,7 +698,7 @@ describe('lion-select-rich', () => { ); const selectTag = unsafeStatic(selectTagName); - const el = /** @type {LionSelectRich} */ (await fixture(html` + const el = await fixture(html` <${selectTag} id="color" name="color" label="Favorite color" has-no-default-selected> Red @@ -716,7 +706,7 @@ describe('lion-select-rich', () => { Teal - `)); + `); const { invoker } = getProtectedMembers(el); expect( diff --git a/tsconfig.json b/tsconfig.json index 4819d0f7a..e97b85874 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,10 +22,6 @@ "**/dist/**/*", "packages/**/test-helpers", "packages/**/docs/**/*", - "packages/combobox/test/**/*.js", // TODO: Needs to get typed! - "packages/form-integrations/test/**/*.js", // TODO: Needs to get typed! - "packages/overlays/test/utils-tests/**/*.js", // TODO: Needs to get typed! - "packages/select-rich/test/**/*.js", // TODO: Needs to get typed! "packages/singleton-manager/demo/", "packages/singleton-manager/test/", // ignore test/demos for singleton manager until overlays are typed as it's used in there diff --git a/yarn.lock b/yarn.lock index d10a1578a..d2c11a724 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1834,10 +1834,10 @@ "@open-wc/rollup-plugin-html" "^1.2.5" polyfills-loader "^1.7.5" -"@open-wc/scoped-elements@^1.2.4", "@open-wc/scoped-elements@^1.3.2": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@open-wc/scoped-elements/-/scoped-elements-1.3.2.tgz#6ae54c49731bbe8c3e0b5383c989f983dcdfacf5" - integrity sha512-DoP3XA8r03tGx+IrlJwP/voLuDFkyS56kvwhmXIhpESo7M5jMt5e0zScNrawj7EMe4b5gDaJjorx2Jza8FLaLw== +"@open-wc/scoped-elements@^1.2.4", "@open-wc/scoped-elements@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@open-wc/scoped-elements/-/scoped-elements-1.3.3.tgz#fe008aef4d74fb00c553c900602960638fc1c7b0" + integrity sha512-vFIQVYYjFw67odUE4JzZOpctnF7S/2DX+S+clrL3bQPql7HvEnV0wMFwOWUavQTuCJi0rfU8GTcNMiUybio+Yg== dependencies: "@open-wc/dedupe-mixin" "^1.3.0" lit-html "^1.0.0"