diff --git a/packages/calendar/src/LionCalendar.js b/packages/calendar/src/LionCalendar.js index b04270260..56294c666 100644 --- a/packages/calendar/src/LionCalendar.js +++ b/packages/calendar/src/LionCalendar.js @@ -17,7 +17,7 @@ import { calendarStyle } from './calendarStyle.js'; import { createDay } from './utils/createDay.js'; /** - * @customElement + * @customElement lion-calendar */ export class LionCalendar extends LocalizeMixin(LitElement) { static get localizeNamespaces() { @@ -26,55 +26,40 @@ export class LionCalendar extends LocalizeMixin(LitElement) { 'lion-calendar': locale => { switch (locale) { case 'bg-BG': - case 'bg': return import('../translations/bg.js'); case 'cs-CZ': - case 'cs': return import('../translations/cs.js'); case 'de-AT': case 'de-DE': - case 'de': return import('../translations/de.js'); case 'en-AU': case 'en-GB': case 'en-PH': case 'en-US': - case 'en': return import('../translations/en.js'); case 'es-ES': - case 'es': return import('../translations/es.js'); case 'fr-FR': case 'fr-BE': - case 'fr': return import('../translations/fr.js'); case 'hu-HU': - case 'hu': return import('../translations/hu.js'); case 'it-IT': - case 'it': return import('../translations/it.js'); case 'nl-BE': case 'nl-NL': - case 'nl': return import('../translations/nl.js'); case 'pl-PL': - case 'pl': return import('../translations/pl.js'); case 'ro-RO': - case 'ro': return import('../translations/ro.js'); case 'ru-RU': - case 'ru': return import('../translations/ru.js'); case 'sk-SK': - case 'sk': return import('../translations/sk.js'); case 'uk-UA': - case 'uk': return import('../translations/uk.js'); case 'zh-CN': - case 'zh': return import('../translations/zh.js'); default: return import(`../translations/${locale}.js`); diff --git a/packages/checkbox-group/stories/index.stories.js b/packages/checkbox-group/stories/index.stories.js index 46fe26558..d6e031fec 100644 --- a/packages/checkbox-group/stories/index.stories.js +++ b/packages/checkbox-group/stories/index.stories.js @@ -47,7 +47,7 @@ storiesOf('Forms|Checkbox Group', module) name="scientists[]" label="Francis Bacon" .choiceValue=${'Francis Bacon'} - .choiceChecked=${true} + checked > ', () => { await nextFrame(); expect(el.error.required).to.be.true; - el.formElements['sports[]'][0].choiceChecked = true; + el.formElements['sports[]'][0].checked = true; expect(el.error.required).to.be.undefined; }); }); diff --git a/packages/checkbox/README.md b/packages/checkbox/README.md index afce4ff6e..7269f4dcb 100644 --- a/packages/checkbox/README.md +++ b/packages/checkbox/README.md @@ -6,9 +6,9 @@ ## Features -- Get or set the checked state (boolean) - `choiceChecked()` -- Get or set the value of the choice - `choiceValue()` +- Get the checked state (boolean) - `checked` boolean attribute - Pre-select an option by setting the `checked` boolean attribute +- Get or set the value of the choice - `choiceValue()` ## How to use diff --git a/packages/choice-input/src/ChoiceInputMixin.js b/packages/choice-input/src/ChoiceInputMixin.js index 01927ee58..a9c10ef66 100644 --- a/packages/choice-input/src/ChoiceInputMixin.js +++ b/packages/choice-input/src/ChoiceInputMixin.js @@ -74,7 +74,6 @@ export const ChoiceInputMixin = superclass => updated(c) { super.updated(c); if (c.has('modelValue')) { - this._reflectCheckedToCssClass({ modelValue: this.modelValue }); this.__syncCheckedToInputElement(); } } @@ -125,7 +124,6 @@ export const ChoiceInputMixin = superclass => connectedCallback() { super.connectedCallback(); this.addEventListener('user-input-changed', this.__toggleChecked); - this._reflectCheckedToCssClass(); } disconnectedCallback() { @@ -146,10 +144,10 @@ export const ChoiceInputMixin = superclass => } __syncCheckedToInputElement() { - // .inputElement might not be available yet(slot content) + // ._inputNode might not be available yet(slot content) // or at all (no reliance on platform construct, in case of [role=option]) - if (this.inputElement) { - this.inputElement.checked = this.checked; + if (this._inputNode) { + this._inputNode.checked = this.checked; } } @@ -217,23 +215,4 @@ export const ChoiceInputMixin = superclass => * Synchronization from user input is already arranged in this Mixin. */ _syncValueUpwards() {} - - /** - * @deprecated use .checked - */ - get choiceChecked() { - return this.checked; - } - - /** - * @deprecated use .checked - */ - set choiceChecked(c) { - this.checked = c; - } - - /** @deprecated for styling purposes, use [checked] attribute */ - _reflectCheckedToCssClass() { - this.classList[this.checked ? 'add' : 'remove']('state-checked'); - } }; diff --git a/packages/choice-input/test/ChoiceInputMixin.test.js b/packages/choice-input/test/ChoiceInputMixin.test.js index 9c53fc66e..c02720f19 100644 --- a/packages/choice-input/test/ChoiceInputMixin.test.js +++ b/packages/choice-input/test/ChoiceInputMixin.test.js @@ -39,7 +39,7 @@ describe('ChoiceInputMixin', () => { expect(el.modelValue.value).to.equal(date); }); - it('fires one "model-value-changed" event if choiceValue or choiceChecked or modelValue changed', async () => { + it('fires one "model-value-changed" event if choiceValue or checked state or modelValue changed', async () => { let counter = 0; const el = await fixture(html` { `); expect(counter).to.equal(0); // Here we try to mimic user interaction by firing browser events - const nativeInput = el.inputElement; + const nativeInput = el._inputNode; nativeInput.dispatchEvent(new CustomEvent('input', { bubbles: true })); // fired by (at least) Chrome expect(counter).to.equal(0); nativeInput.dispatchEvent(new CustomEvent('change', { bubbles: true })); @@ -112,14 +112,14 @@ describe('ChoiceInputMixin', () => { expect(el.checked).to.be.true; await el.updateComplete; - expect(el.inputElement.checked).to.be.true; + expect(el._inputNode.checked).to.be.true; }); it('can be checked and unchecked via user interaction', async () => { const el = await fixture(``); - el.inputElement.click(); + el._inputNode.click(); expect(el.checked).to.be.true; - el.inputElement.click(); + el._inputNode.click(); expect(el.checked).to.be.false; }); @@ -193,8 +193,8 @@ describe('ChoiceInputMixin', () => { elChecked.checked = true; // Via user interaction - el.inputElement.click(); - elChecked.inputElement.click(); + el._inputNode.click(); + elChecked._inputNode.click(); await el.updateComplete; expect(hasAttr(el)).to.equal(true, 'user click checked'); expect(hasAttr(elChecked)).to.equal(false, 'user click unchecked'); @@ -210,65 +210,6 @@ describe('ChoiceInputMixin', () => { expect(hasAttr(el)).to.equal(true, 'modelValue checked'); expect(hasAttr(elChecked)).to.equal(false, 'modelValue unchecked'); }); - - it('[deprecated] synchronizes checked state to class "state-checked" for styling purposes', async () => { - const hasClass = el => [].slice.call(el.classList).indexOf('state-checked') > -1; - const el = await fixture(``); - const elChecked = await fixture(html` - - - - `); - - // Initial values - expect(hasClass(el)).to.equal(false, 'inital unchecked element'); - expect(hasClass(elChecked)).to.equal(true, 'inital checked element'); - - // Programmatically via checked - el.checked = true; - elChecked.checked = false; - - await el.updateComplete; - expect(hasClass(el)).to.equal(true, 'programmatically checked'); - expect(hasClass(elChecked)).to.equal(false, 'programmatically unchecked'); - - // reset - el.checked = false; - elChecked.checked = true; - - // Via user interaction - el.inputElement.click(); - elChecked.inputElement.click(); - await el.updateComplete; - expect(hasClass(el)).to.equal(true, 'user click checked'); - expect(hasClass(elChecked)).to.equal(false, 'user click unchecked'); - - // reset - el.checked = false; - elChecked.checked = true; - - // Programmatically via modelValue - el.modelValue = { value: '', checked: true }; - elChecked.modelValue = { value: '', checked: false }; - await el.updateComplete; - expect(hasClass(el)).to.equal(true, 'modelValue checked'); - expect(hasClass(elChecked)).to.equal(false, 'modelValue unchecked'); - }); - - it('[deprecated] uses choiceChecked to set checked state', async () => { - const el = await fixture(html` - - `); - expect(el.choiceChecked).to.be.false; - el.choiceChecked = true; - expect(el.checked).to.be.true; - expect(el.modelValue).to.deep.equal({ - checked: true, - value: 'foo', - }); - await el.updateComplete; - expect(el.inputElement.checked).to.be.true; - }); }); describe('Format/parse/serialize loop', () => { diff --git a/packages/core/README.md b/packages/core/README.md index d5b606fd7..2f7fce4ee 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -4,14 +4,7 @@ ## Deprecations -The following files/features are deprecated - -- CssClassMixin -- DomHelpersMixin (only $$id, $$slot is deprecated) -- ElementMixin -- EventMixin -- ObserverMixin -- lit-html.js +Currently all deprecations are removed due to alpha state. ## Deduping of mixins diff --git a/packages/core/index.js b/packages/core/index.js index f4e80eeaf..632387c09 100644 --- a/packages/core/index.js +++ b/packages/core/index.js @@ -54,8 +54,8 @@ export { // ours export { dedupeMixin } from './src/dedupeMixin.js'; export { DelegateMixin } from './src/DelegateMixin.js'; -export { DomHelpersMixin } from './src/DomHelpersMixin.js'; export { LionSingleton } from './src/LionSingleton.js'; export { SlotMixin } from './src/SlotMixin.js'; export { DisabledMixin } from './src/DisabledMixin.js'; export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js'; +export { UpdateStylesMixin } from './src/UpdateStylesMixin.js'; diff --git a/packages/core/src/CssClassMixin.js b/packages/core/src/CssClassMixin.js deleted file mode 100644 index c6d966434..000000000 --- a/packages/core/src/CssClassMixin.js +++ /dev/null @@ -1,58 +0,0 @@ -import { dedupeMixin } from './dedupeMixin.js'; - -/** - * # CssClassMixin - * `CssClassMixin` is a base mixin for the use of css in lion-components. - * - * **Deprecated**: A custom element should not modify it's own classes - * - * @deprecated - * @type {function()} - * @polymerMixin - * @mixinFunction - */ -export const CssClassMixin = dedupeMixin( - superclass => - // eslint-disable-next-line - class CssClassMixin extends superclass { - update(changedProps) { - super.update(changedProps); - this._updateCssClasses(changedProps); - } - - /** - * This function will check for 'empty': it returns true when an array or object has - * no keys or when a value is falsy. - - * - * @param {Object} value - * @returns {boolean} - * @private - */ - static _isEmpty(value) { - if (typeof value === 'object') { - return Object.keys(value).length === 0; - } - return !value; - } - - /** - * This function updates css classes - * - * @param {Object} newValues - * @private - */ - _updateCssClasses(changedProps) { - Array.from(changedProps.keys()).forEach(property => { - const klass = this.constructor.properties[property].nonEmptyToClass; - if (klass) { - if (this.constructor._isEmpty(this[property])) { - this.classList.remove(klass); - } else { - this.classList.add(klass); - } - } - }); - } - }, -); diff --git a/packages/core/src/DelegateMixin.js b/packages/core/src/DelegateMixin.js index 0856d23cd..a4193916f 100644 --- a/packages/core/src/DelegateMixin.js +++ b/packages/core/src/DelegateMixin.js @@ -10,7 +10,7 @@ import { dedupeMixin } from './dedupeMixin.js'; * get delegations() { * return { * ...super.delegations, - * target: () => this.$id('button1'), + * target: () => this.shadowRoot.getElementById('button1'), * events: ['click'], * methods: ['click'], * properties: ['disabled'], diff --git a/packages/core/src/DomHelpersMixin.js b/packages/core/src/DomHelpersMixin.js deleted file mode 100644 index ebe999c79..000000000 --- a/packages/core/src/DomHelpersMixin.js +++ /dev/null @@ -1,125 +0,0 @@ -import { dedupeMixin } from './dedupeMixin.js'; - -/** - * - * @returns {{$id: {}, $name: {}, $$id: {}, $$slot: {}}} - */ -function generateEmptyCache() { - return { - $id: {}, - $name: {}, - $$id: {}, - $$slot: {}, - }; -} - -/** - * # DomHelpersMixin - * `DomHelpersMixin` provides access to element in shadow and light DOM with "id" attribute, - * it provides access to element in shadow DOM with "name" attribute and - * provides access to element in Light DOM with "slot" attribute. - * It memorizes element reference in cache and can be removed from cache - * (individually or completely) via _clearDomCache(). - * - * @example - * this.$id('foo') to access the element with the id 'foo' in shadow DOM - * this.$name('foo') to access the element with name 'foo' in shadow DOM - * this.$$id('foo') to access the element with the id 'foo' when not in shadow DOM - * this.$$slot('foo') to access the element with the slot 'foo' when in light DOM - * - * @type {function()} - * @polymerMixin - * @mixinFunction - */ -export const DomHelpersMixin = dedupeMixin( - superclass => - // eslint-disable-next-line - class DomHelpersMixin extends superclass { - constructor() { - super(); - this.__domHelpersCache = generateEmptyCache(); - } - - /** - * To access an element with the id 'foo' in shadow DOM - * - * @param {number} id - * @returns {*|undefined} - */ - $id(id) { - let element = this.__domHelpersCache.$id[id]; - if (!element) { - element = this.shadowRoot.getElementById(id); - this.__domHelpersCache.$id[id] = element; - } - - return element || undefined; - } - - /** - * Provides access to the named slot node in shadow DOM for this name - * - * @param {string} name - * @returns {*|undefined} - */ - $name(name) { - let element = this.__domHelpersCache.$name[name]; - if (!element) { - element = this.shadowRoot.querySelector(`[name="${name}"]`); - this.__domHelpersCache.$name[name] = element; - } - return element || undefined; - } - - /** - * To access an element with the id 'foo' in light DOM - * - * **Deprecated**: LightDom may change underneath you - you should not cache it - * - * @deprecated - * @param {number} id - * @returns {*|undefined} - */ - $$id(id) { - let element = this.__domHelpersCache.$$id[id]; - if (!element) { - element = this.querySelector(`#${id}`); - this.__domHelpersCache.$$id[id] = element; - } - return element || undefined; - } - - /** - * To access the element with the slot 'foo' when in light DOM - * - * **Deprecated**: LightDom may change underneath you - you should not cache it - * - * @deprecated - * @param {string} slot - * @returns {*|undefined} - */ - $$slot(slot) { - let element = this.__domHelpersCache.$$slot[slot]; - if (!element) { - element = Array.from(this.children).find(child => child.slot === slot); - this.__domHelpersCache.$$slot[slot] = element; - } - return element || undefined; - } - - /** - * Remove from cache (individually or completely) via _clearDomCache() - * - * @param {string} type - * @param {number} id - * @private - */ - _clearDomCache(type, id) { - if (type) { - this.__domHelpersCache[type][id] = undefined; - } else { - this.__domHelpersCache = generateEmptyCache(); - } - } - }, -); diff --git a/packages/core/src/EventMixin.js b/packages/core/src/EventMixin.js deleted file mode 100644 index d5b93772f..000000000 --- a/packages/core/src/EventMixin.js +++ /dev/null @@ -1,129 +0,0 @@ -/* eslint-disable class-methods-use-this */ - -import { dedupeMixin } from './dedupeMixin.js'; - -/** - * # EventMixin - * `EventMixin` provides a declarative way for registering event handlers, - * keeping performance and ease of use in mind - * - * **Deprecated**: Please use add/removeEventListener in connected/disconnectedCallback - * - * @deprecated - * @example - * get events() { - * return { - * ...super.events, - * '_onButton1Click': [() => this.$id('button1'), 'click'], - * '_onButton2Focus': [() => this.$id('button2'), 'focus'], - * '_onButton2Blur': [() => this.$id('button2'), 'blur'], - * }; - * } - * render() { - * return html` - * - * - * `; - * } - * - * @polymerMixin - * @mixinFunction - */ -export const EventMixin = dedupeMixin( - superclass => - // eslint-disable-next-line - class EventMixin extends superclass { - /** - * @returns {{}} - */ - get events() { - return {}; - } - - constructor() { - super(); - this.__eventsCache = []; - this.__boundEvents = {}; - - Object.keys(this.events).forEach(eventFunctionName => { - this.__boundEvents[eventFunctionName] = this[eventFunctionName].bind(this); - }); - } - - updated() { - if (super.updated) super.updated(); - this.__registerEvents(); - } - - connectedCallback() { - if (super.connectedCallback) super.connectedCallback(); - this.__registerEvents(); - } - - disconnectedCallback() { - if (super.disconnectedCallback) super.disconnectedCallback(); - this.__unregisterEvents(); - } - - /** - * @private - */ - __registerEvents() { - Object.keys(this.events).forEach(eventFunctionName => { - const [targetFunction, eventNames] = this.events[eventFunctionName]; - const target = targetFunction(); - if (target) { - const eventFunction = this.__boundEvents[eventFunctionName]; - const eventNamesToProcess = typeof eventNames === 'string' ? [eventNames] : eventNames; - eventNamesToProcess.forEach(eventName => { - if (!this.constructor._isProcessed(target, eventName, eventFunction)) { - target.addEventListener(eventName, eventFunction); - this.__eventsCache.push([target, eventName, eventFunctionName]); - this.constructor._markProcessed(target, eventName, eventFunction); - } - }); - } - }); - } - - /** - * @param {Object} target - * @param {string} eventName - * @param {function()} eventFunction - * @returns {*|Boolean|boolean} - * @private - */ - static _isProcessed(target, eventName, eventFunction) { - const mixinData = target.__eventMixinProcessed; - return mixinData && mixinData[eventName] && mixinData[eventName].has(eventFunction); - } - - /** - * @param {Object} target - * @param {string} eventName - * @param {function()} eventFunction - * @private - */ - static _markProcessed(target, eventName, eventFunction) { - let mixinData = target.__eventMixinProcessed; - mixinData = mixinData || {}; - mixinData[eventName] = mixinData[eventName] || new Set(); - mixinData[eventName].add(eventFunction); - target.__eventMixinProcessed = mixinData; // eslint-disable-line no-param-reassign - } - - /** - * @private - */ - __unregisterEvents() { - let data = this.__eventsCache.pop(); - while (data) { - const [target, eventName, eventFunctionName] = data; - const eventFunction = this.__boundEvents[eventFunctionName]; - target.removeEventListener(eventName, eventFunction); - delete target.__eventMixinProcessed; - data = this.__eventsCache.pop(); - } - } - }, -); diff --git a/packages/core/src/LionLitElement.js b/packages/core/src/LionLitElement.js deleted file mode 100644 index 3a6514ca1..000000000 --- a/packages/core/src/LionLitElement.js +++ /dev/null @@ -1,10 +0,0 @@ -import { LitElement } from 'lit-element'; -import { ElementMixin } from './ElementMixin.js'; - -export { css } from 'lit-element'; -export { html } from './lit-html.js'; - -/** - * @deprecated use LitElement instead. - */ -export class LionLitElement extends ElementMixin(LitElement) {} diff --git a/packages/core/src/ObserverMixin.js b/packages/core/src/ObserverMixin.js deleted file mode 100644 index 9168e4fb1..000000000 --- a/packages/core/src/ObserverMixin.js +++ /dev/null @@ -1,198 +0,0 @@ -import { dedupeMixin } from './dedupeMixin.js'; - -/** - * - * @type {Symbol} - */ -const undefinedSymbol = Symbol('this value should actually be undefined when passing on'); - -/** - * # ObserverMixin - * `ObserverMixin` warns the developer if something unexpected happens and provides - * triggerObserversFor() which can be used within a setter to hook into the observer system. - * It has syncObservers, which call observers immediately when the observed property - * is changed (newValue !== oldValue) and asyncObservers, which makes only one call - * to observer even if multiple observed attributes changed. - * - * **Deprecated**: Please use LitElement update/updated instead. - * - * @deprecated - * @type {function()} - * @polymerMixin - * @mixinFunction - */ -export const ObserverMixin = dedupeMixin( - superclass => - // eslint-disable-next-line - class ObserverMixin extends superclass { - /** - * @returns {{}} - */ - static get syncObservers() { - return {}; - } - - /** - * @returns {{}} - */ - static get asyncObservers() { - return {}; - } - - constructor() { - super(); - this.__initializeObservers('sync'); - this.__initializeObservers('async'); - this.__asyncObserversQueue = {}; - this.__asyncObserversNewValues = {}; - this.__asyncObserversOldValues = {}; - } - - /** - * @param {string} property - * @param {*} newValue - * @param {*} oldValue - */ - triggerObserversFor(property, newValue, oldValue) { - this.__executeSyncObserversFor(property, newValue, oldValue); - this.__addToAsyncObserversQueue(property, newValue, oldValue); - this.updateComplete.then(() => { - this.__emptyAsyncObserversQueue(); - }); - } - - /** - * Sync hooks into UpdatingElement mixin - * - * @param {string} property - * @param {any} oldValue - * @private - */ - _requestUpdate(property, oldValue) { - super._requestUpdate(property, oldValue); - this.__executeSyncObserversFor(property, this[property], oldValue); - } - - /** - * Async hook into Updating Element - * - * @param {Map} changedProperties - */ - update(changedProperties) { - super.update(changedProperties); - this.__addMultipleToAsyncObserversQueue(changedProperties); - this.__emptyAsyncObserversQueue(); - } - - /** - * @param {string} type - * @private - */ - __initializeObservers(type) { - this[`__${type}ObserversForProperty`] = {}; - Object.keys(this.constructor[`${type}Observers`]).forEach(observerFunctionName => { - const propertiesToObserve = this.constructor[`${type}Observers`][observerFunctionName]; - if (typeof this[observerFunctionName] === 'function') { - propertiesToObserve.forEach(property => { - if (!this[`__${type}ObserversForProperty`][property]) { - this[`__${type}ObserversForProperty`][property] = []; - } - this[`__${type}ObserversForProperty`][property].push(observerFunctionName); - }); - } else { - throw new Error( - `${this.localName} does not have a function called ${observerFunctionName}`, - ); - } - }); - } - - /** - * @param {string} observedProperty - * @param {*} newValue - * @param {*} oldValue - * @private - */ - __executeSyncObserversFor(observedProperty, newValue, oldValue) { - if (newValue === oldValue) return; - const functionsToCall = {}; - if (this.__syncObserversForProperty[observedProperty]) { - this.__syncObserversForProperty[observedProperty].forEach(observerFunctionName => { - functionsToCall[observerFunctionName] = true; - }); - } - - Object.keys(functionsToCall).forEach(functionName => { - const newValues = {}; - const oldValues = {}; - this.constructor.syncObservers[functionName].forEach(property => { - newValues[property] = observedProperty === property ? newValue : this[property]; - oldValues[property] = observedProperty === property ? oldValue : this[property]; - }); - this[functionName](newValues, oldValues); - }); - } - - /** - * @param {string} property - * @param {*} newValue - * @param {*} oldValue - * @private - */ - __addToAsyncObserversQueue(property, newValue, oldValue) { - this.__asyncObserversNewValues[property] = newValue; - if (this.__asyncObserversOldValues[property] === undefined) { - // only get old value once - this.__asyncObserversOldValues[property] = oldValue; - } - if (oldValue === undefined) { - // special case for undefined - this.__asyncObserversOldValues[property] = undefinedSymbol; - } - if (this.__asyncObserversForProperty[property]) { - this.__asyncObserversForProperty[property].forEach(observerFunctionName => { - this.__asyncObserversQueue[observerFunctionName] = true; - }); - } - } - - /** - * @param {Map} oldValues - * @private - */ - __addMultipleToAsyncObserversQueue(oldValues) { - if (!oldValues) return; - oldValues.forEach((oldValue, property) => { - this.__addToAsyncObserversQueue(property, this[property], oldValue); - }); - } - - /** - * @private - */ - __emptyAsyncObserversQueue() { - Object.keys(this.__asyncObserversQueue).forEach(functionName => { - this[functionName]( - this.__asyncObserversNewValues, - this.__getOldValuesWithRealUndefined(), - ); - }); - this.__asyncObserversNewValues = {}; - this.__asyncObserversOldValues = {}; - this.__asyncObserversQueue = {}; - } - - /** - * @returns {{}} - * @private - */ - __getOldValuesWithRealUndefined() { - const result = {}; - Object.keys(this.__asyncObserversOldValues).forEach(key => { - const value = this.__asyncObserversOldValues[key]; - result[key] = value === undefinedSymbol ? undefined : value; - }); - return result; - } - }, -); diff --git a/packages/core/src/SlotMixin.js b/packages/core/src/SlotMixin.js index 1caf0ba67..85275d9db 100644 --- a/packages/core/src/SlotMixin.js +++ b/packages/core/src/SlotMixin.js @@ -1,7 +1,6 @@ /* eslint-disable class-methods-use-this */ import { dedupeMixin } from './dedupeMixin.js'; -import { DomHelpersMixin } from './DomHelpersMixin.js'; /** * # SlotMixin @@ -28,7 +27,7 @@ import { DomHelpersMixin } from './DomHelpersMixin.js'; export const SlotMixin = dedupeMixin( superclass => // eslint-disable-next-line no-unused-vars, no-shadow - class SlotMixin extends DomHelpersMixin(superclass) { + class SlotMixin extends superclass { /** * @returns {{}} */ @@ -54,7 +53,7 @@ export const SlotMixin = dedupeMixin( _connectSlotMixin() { if (!this.__isConnectedSlotMixin) { Object.keys(this.slots).forEach(slotName => { - if (!this.$$slot(slotName)) { + if (!this.querySelector(`[slot=${slotName}]`)) { const slotFactory = this.slots[slotName]; const slotContent = slotFactory(); if (slotContent instanceof Element) { diff --git a/packages/core/src/ElementMixin.js b/packages/core/src/UpdateStylesMixin.js similarity index 88% rename from packages/core/src/ElementMixin.js rename to packages/core/src/UpdateStylesMixin.js index e6b7f3ca1..0f5b2d9fe 100644 --- a/packages/core/src/ElementMixin.js +++ b/packages/core/src/UpdateStylesMixin.js @@ -1,14 +1,10 @@ /* global ShadyCSS */ import { dedupeMixin } from './dedupeMixin.js'; -import { DomHelpersMixin } from './DomHelpersMixin.js'; -/** - * @deprecated please apply DomHelpersMixin and UpdateStylesMixin if needed yourself - */ -export const ElementMixin = dedupeMixin( +export const UpdateStylesMixin = dedupeMixin( superclass => // eslint-disable-next-line no-shadow - class ElementMixin extends DomHelpersMixin(superclass) { + class UpdateStylesMixin extends superclass { /** * @example * diff --git a/packages/core/src/lit-html.js b/packages/core/src/lit-html.js deleted file mode 100644 index 1c8b7412e..000000000 --- a/packages/core/src/lit-html.js +++ /dev/null @@ -1,3 +0,0 @@ -// deprecated! -export { html, render } from 'lit-html'; -export { render as renderShady } from 'lit-html/lib/shady-render.js'; diff --git a/packages/core/test/CssClassMixin.test.js b/packages/core/test/CssClassMixin.test.js deleted file mode 100644 index 76ea361b6..000000000 --- a/packages/core/test/CssClassMixin.test.js +++ /dev/null @@ -1,48 +0,0 @@ -import { expect, fixture } from '@open-wc/testing'; - -import { LionLitElement } from '../src/LionLitElement.js'; - -import { CssClassMixin } from '../src/CssClassMixin.js'; - -describe('CssClassMixin', () => { - it('reflects non empty values to given class name', async () => { - let toClassInstance; - - async function checkProp(newValue, bool) { - toClassInstance.foo = newValue; - await toClassInstance.updateComplete; - expect(toClassInstance.classList.contains('state-foo')).to.equal(bool); - } - - class ToClassElement extends CssClassMixin(LionLitElement) { - static get properties() { - return { - foo: { - nonEmptyToClass: 'state-foo', - }, - }; - } - } - customElements.define('to-class-element', ToClassElement); - toClassInstance = await fixture(``); - - // Init - expect(toClassInstance.classList.contains('state-foo')).to.equal(false); - - // Boolean - await checkProp(true, true); - await checkProp(false, false); - - // Falsy - await checkProp('foo', true); - await checkProp('', false); - - // Non empty object - await checkProp({ foo: 'bar' }, true); - await checkProp({}, false); - - // Non empty array - await checkProp(['foo'], true); - await checkProp([], false); - }); -}); diff --git a/packages/core/test/DelegateMixin.test.js b/packages/core/test/DelegateMixin.test.js index f35580203..6bf9e901b 100644 --- a/packages/core/test/DelegateMixin.test.js +++ b/packages/core/test/DelegateMixin.test.js @@ -1,7 +1,6 @@ import { expect, fixture, defineCE, unsafeStatic, html } from '@open-wc/testing'; import sinon from 'sinon'; -import { LionLitElement } from '../src/LionLitElement.js'; -import { ObserverMixin } from '../src/ObserverMixin.js'; +import { LitElement } from '../index.js'; import { DelegateMixin } from '../src/DelegateMixin.js'; @@ -12,11 +11,11 @@ describe('DelegateMixin', () => { it('delegates events', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), events: ['click'], }; } @@ -31,17 +30,17 @@ describe('DelegateMixin', () => { const element = await fixture(`<${tag}>`); const cb = sinon.spy(); element.addEventListener('click', cb); - element.$id('button1').click(); + element.shadowRoot.getElementById('button1').click(); expect(cb.callCount).to.equal(1); }); it('delegates events before delegation target is attached to DOM', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), events: ['click'], }; } @@ -58,7 +57,7 @@ describe('DelegateMixin', () => { element.addEventListener('click', cb); document.body.appendChild(element); await element.updateComplete; - element.$id('button1').click(); + element.shadowRoot.getElementById('button1').click(); expect(cb.callCount).to.equal(1); // cleanup @@ -67,11 +66,11 @@ describe('DelegateMixin', () => { it('delegates if light and shadow dom is used at the same time', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$$slot('button'), + target: () => this.querySelector('[slot=button]'), events: ['click'], methods: ['click'], }; @@ -90,17 +89,17 @@ describe('DelegateMixin', () => { <${tag}>`); const cb = sinon.spy(); element.addEventListener('click', cb); - element.$$slot('button').click(); + element.querySelector('[slot=button]').click(); expect(cb.callCount).to.equal(1); }); it('will still support other events', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), events: ['click'], }; } @@ -125,11 +124,11 @@ describe('DelegateMixin', () => { it('will call delegated methods', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), methods: ['click'], }; } @@ -143,13 +142,13 @@ describe('DelegateMixin', () => { ); const element = await fixture(`<${tag}>`); const cb = sinon.spy(); - element.$id('button1').addEventListener('click', cb); + element.shadowRoot.getElementById('button1').addEventListener('click', cb); element.click(); expect(cb.callCount).to.equal(1); }); it('supports arguments for delegated methods', async () => { - class DelegateArgumentSub extends LionLitElement { + class DelegateArgumentSub extends LitElement { constructor() { super(); this.foo = { a: 'a', b: 'b' }; @@ -162,11 +161,11 @@ describe('DelegateMixin', () => { } customElements.define('delegate-argument-sub', DelegateArgumentSub); const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('sub'), + target: () => this.shadowRoot.getElementById('sub'), methods: ['setFooAandB'], }; } @@ -182,17 +181,17 @@ describe('DelegateMixin', () => { const element = await fixture(`<${tag}>`); element.disabled = true; element.setFooAandB('newA', 'newB'); - expect(element.$id('sub').foo.a).to.equal('newA'); - expect(element.$id('sub').foo.b).to.equal('newB'); + expect(element.shadowRoot.getElementById('sub').foo.a).to.equal('newA'); + expect(element.shadowRoot.getElementById('sub').foo.b).to.equal('newB'); }); it('will set delegated properties', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), properties: ['disabled'], }; } @@ -207,17 +206,17 @@ describe('DelegateMixin', () => { const element = await fixture(`<${tag}>`); element.disabled = true; await element.updateComplete; - expect(element.$id('button1').disabled).to.equal(true); - expect(element.$id('button1').hasAttribute('disabled')).to.equal(true); + expect(element.shadowRoot.getElementById('button1').disabled).to.equal(true); + expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true); }); it('delegates properties before delegation target is attached to DOM', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), properties: ['disabled'], }; } @@ -233,7 +232,7 @@ describe('DelegateMixin', () => { element.disabled = true; document.body.appendChild(element); await element.updateComplete; - expect(element.$id('button1').disabled).to.equal(true); + expect(element.shadowRoot.getElementById('button1').disabled).to.equal(true); // cleanup document.body.removeChild(element); @@ -241,11 +240,11 @@ describe('DelegateMixin', () => { it('will delegate setAttribute', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), attributes: ['disabled'], }; } @@ -261,16 +260,16 @@ describe('DelegateMixin', () => { element.setAttribute('disabled', ''); await element.updateComplete; expect(element.hasAttribute('disabled')).to.equal(false); - expect(element.$id('button1').hasAttribute('disabled')).to.equal(true); + expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true); }); it('will read inital attributes', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), attributes: ['disabled'], }; } @@ -284,16 +283,16 @@ describe('DelegateMixin', () => { ); const element = await fixture(`<${tag} disabled>`); expect(element.hasAttribute('disabled')).to.equal(false); - expect(element.$id('button1').hasAttribute('disabled')).to.equal(true); + expect(element.shadowRoot.getElementById('button1').hasAttribute('disabled')).to.equal(true); }); it('will delegate removeAttribute', async () => { const tag = defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, - target: () => this.$id('button1'), + target: () => this.shadowRoot.getElementById('button1'), attributes: ['disabled'], }; } @@ -309,12 +308,12 @@ describe('DelegateMixin', () => { element.removeAttribute('disabled', ''); await element.updateComplete; expect(element.hasAttribute('disabled')).to.equal(false); - expect(element.$id('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(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, @@ -353,7 +352,7 @@ describe('DelegateMixin', () => { 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(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, @@ -393,7 +392,7 @@ describe('DelegateMixin', () => { it('works with shadow dom', async () => { const tag = await defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, @@ -416,7 +415,7 @@ describe('DelegateMixin', () => { it('works with light dom', async () => { const tag = await defineCE( - class extends DelegateMixin(LionLitElement) { + class extends DelegateMixin(LitElement) { get delegations() { return { ...super.delegations, @@ -440,67 +439,4 @@ describe('DelegateMixin', () => { element.foo = 'new'; expect(element.querySelector('div').foo).to.equal('new'); }); - - it('integrates with the Observer System', async () => { - const tag = await defineCE( - class extends DelegateMixin(ObserverMixin(LionLitElement)) { - get delegations() { - return { - ...super.delegations, - target: () => this.querySelector('div'), - properties: ['size'], - }; - } - - static get syncObservers() { - return { _onSyncSizeChanged: ['size'] }; - } - - static get asyncObservers() { - return { _onAsyncSizeChanged: ['size'] }; - } - - render() { - return html` -
- `; - } - - createRenderRoot() { - return this; - } - - _onSyncSizeChanged() {} - - _onAsyncSizeChanged() {} - }, - ); - const el = await fixture(`<${tag}>
`); - const asyncSpy = sinon.spy(el, '_onAsyncSizeChanged'); - const syncSpy = sinon.spy(el, '_onSyncSizeChanged'); - - el.size = 'tiny'; - expect(syncSpy.callCount).to.equal(1); - expect(syncSpy.calledWith({ size: 'tiny' }, { size: undefined })).to.be.true; - el.size = 'big'; - expect(syncSpy.callCount).to.equal(2); - expect(syncSpy.calledWith({ size: 'big' }, { size: 'tiny' })).to.be.true; - expect(asyncSpy.callCount).to.equal(0); - - await el.updateComplete; - expect(syncSpy.callCount).to.equal(2); - expect(asyncSpy.callCount).to.equal(1); - expect(asyncSpy.calledWith({ size: 'big' }, { size: undefined })).to.be.true; - - el.size = 'medium'; - await el.updateComplete; - expect(syncSpy.callCount).to.equal(3); - expect(syncSpy.calledWith({ size: 'medium' }, { size: 'big' })).to.be.true; - expect(asyncSpy.callCount).to.equal(2); - expect(asyncSpy.calledWith({ size: 'medium' }, { size: 'big' })).to.be.true; - - // we expect to NOT use an "internal" property - // TODO: double check if we test the right thing here - expect(el.constructor._classProperties.get('size')).to.be.undefined; - }); }); diff --git a/packages/core/test/DomHelpersMixin.test.js b/packages/core/test/DomHelpersMixin.test.js deleted file mode 100644 index 745b7b5e1..000000000 --- a/packages/core/test/DomHelpersMixin.test.js +++ /dev/null @@ -1,285 +0,0 @@ -import { expect, fixture, defineCE } from '@open-wc/testing'; -import { LionLitElement, html } from '../src/LionLitElement.js'; - -describe('DomHelpersMixin', () => { - describe('$id()', () => { - it('provides access to element in Shadom DOM with "id" attribute', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` -
my element
- `; - } - }, - ); - const element = await fixture(`<${tag}>`); - expect(element.$id('myId').innerText).to.equal('my element'); - }); - - it('memoizes element reference in cache', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` -
my element
- `; - } - }, - ); - const element = await fixture(`<${tag}>`); - element.$id('myId'); - element.shadowRoot.innerHTML = ''; - expect(element.$id('myId').innerText).to.equal('my element'); - }); - - it('can be removed from cache (individually or completely) via _clearDomCache()', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` -
my element
- `; - } - }, - ); - - const element = await fixture(`<${tag}>`); - element.$id('myId'); - element.shadowRoot.innerHTML = ''; - element._clearDomCache('$id', 'myId'); - expect(element.$id('myId')).to.equal(undefined); - - const element2 = await fixture(`<${tag}>`); - element.$id('myId'); - element2.shadowRoot.innerHTML = ''; - element2._clearDomCache(); - expect(element2.$id('myId')).to.equal(undefined); - }); - }); - - describe('$name()', () => { - it('provides access to element in Shadom DOM with "name" attribute', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` -
my element
- `; - } - }, - ); - const element = await fixture(`<${tag}>`); - expect(element.$name('myName').innerText).to.equal('my element'); - }); - - it('memoizes element reference in cache', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` -
my element
- `; - } - }, - ); - const element = await fixture(`<${tag}>`); - element.$name('myName'); - element.shadowRoot.innerHTML = ''; - expect(element.$name('myName').innerText).to.equal('my element'); - }); - - it('can be removed from cache (individually or completely) via _clearDomCache()', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` -
my element
- `; - } - }, - ); - - const element = await fixture(`<${tag}>`); - element.$name('myName'); - element.shadowRoot.innerHTML = ''; - element._clearDomCache('$name', 'myName'); - expect(element.$name('myName')).to.equal(undefined); - - const element2 = await fixture(`<${tag}>`); - element.$name('myName'); - element2.shadowRoot.innerHTML = ''; - element2._clearDomCache(); - expect(element2.$name('myName')).to.equal(undefined); - }); - }); - - describe('$$id()', () => { - it('provides access to element in Light DOM with "id" attribute', async () => { - const tag = defineCE( - class extends LionLitElement { - createRenderRoot() { - return this; - } - - render() { - return html` -
my element
- `; - } - }, - ); - const element = await fixture(`<${tag}>`); - expect(element.$$id('myId').innerText).to.equal('my element'); - }); - - it('memoizes element reference in cache', async () => { - const tag = defineCE( - class extends LionLitElement { - createRenderRoot() { - return this; - } - - render() { - return html` -
my element
- `; - } - }, - ); - const element = await fixture(`<${tag}>`); - element.$$id('myId'); - element.innerHTML = ''; - expect(element.$$id('myId').innerText).to.equal('my element'); - }); - - it('can be removed from cache (individually or completely) via _clearDomCache()', async () => { - const tag = defineCE( - class extends LionLitElement { - createRenderRoot() { - return this; - } - - render() { - return html` -
my element
- `; - } - }, - ); - - const element = await fixture(`<${tag}>`); - element.$$id('myId'); - element.innerHTML = ''; - element._clearDomCache('$$id', 'myId'); - expect(element.$$id('myId')).to.equal(undefined); - - const element2 = await fixture(`<${tag}>`); - element.$$id('myId'); - element2.innerHTML = ''; - element2._clearDomCache(); - expect(element2.$$id('myId')).to.equal(undefined); - }); - }); - - describe('$$slot()', () => { - it('provides access to element in Light DOM with "slot" attribute', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` - - `; - } - }, - ); - const element = await fixture(`<${tag}> -
my element
- `); - expect(element.$$slot('mySlot').innerText).to.equal('my element'); - }); - - it('retrieves the first named slot that is a direct child of the element', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` - - - - - - - `; - } - }, - ); - const tagNested = tag; // reuse the same component for nested slots with same slot names - const fieldsetNested = await fixture(` - <${tag}> -
Get this slotA
- <${tagNested}> -
Don't get this slotA
-
Don't get this slotB
-
Don't get this slotC
- -
Get this slotB (2nd in dom, but belongs to 'upper tag')
-
- Don't get this slotB either - (it only should get the first slot that is a direct child) -
- `); - - expect(fieldsetNested.$$slot('slotA').textContent).to.equal('Get this slotA'); - expect(fieldsetNested.$$slot('slotB').textContent).to.equal( - "Get this slotB (2nd in dom, but belongs to 'upper tag')", - ); - expect(fieldsetNested.$$slot('slotC')).to.equal(undefined); - }); - - it('memoizes element reference in cache', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` - - `; - } - }, - ); - const element = await fixture(`<${tag}> -
my element
- `); - element.$$slot('mySlot'); - element.innerHTML = ''; - expect(element.$$slot('mySlot').innerText).to.equal('my element'); - }); - - it('can be removed from cache (individually or completely) via _clearDomCache()', async () => { - const tag = defineCE( - class extends LionLitElement { - render() { - return html` - - `; - } - }, - ); - const element = await fixture(`<${tag}> -
my element
- `); - element.$$slot('mySlot'); - element.innerHTML = ''; - element._clearDomCache('$$slot', 'mySlot'); - expect(element.$$slot('mySlot')).to.equal(undefined); - - const element2 = await fixture(`<${tag}> -
my element
- `); - element2.$$slot('mySlot'); - element2.innerHTML = ''; - element2._clearDomCache(); - expect(element2.$$slot('mySlot')).to.equal(undefined); - }); - }); -}); diff --git a/packages/core/test/EventMixin.test.js b/packages/core/test/EventMixin.test.js deleted file mode 100644 index 4e709e3cc..000000000 --- a/packages/core/test/EventMixin.test.js +++ /dev/null @@ -1,237 +0,0 @@ -import { expect, fixture, html, defineCE } from '@open-wc/testing'; -import { LionLitElement } from '../src/LionLitElement.js'; - -import { EventMixin } from '../src/EventMixin.js'; - -describe('EventMixin', () => { - before(() => { - class EventMixinSub extends LionLitElement { - static get properties() { - return { - value: { - type: 'String', - asAttribute: 'value', - }, - }; - } - - _requestUpdate(propName) { - super._requestUpdate(); - if (propName === 'value') { - this.dispatchEvent(new CustomEvent('value-changed', { bubbles: true, composed: true })); - } - } - } - customElements.define('event-mixin-sub', EventMixinSub); - - /* global */ - class EventMixinTag extends EventMixin(LionLitElement) { - get events() { - return { - ...super.events, - _onSelfClick: [() => this, 'click'], - _onButton1Click: [() => this.$id('button1'), 'click'], - _onSub1Click: [() => this.$id('sub1'), 'click'], - _onSub1Input: [() => this.$id('sub1'), 'value-changed'], - }; - } - - render() { - return html` - - - - `; - } - - constructor() { - super(); - this.button1ClickCount = 0; - this.sub1ValueChangeCount = 0; - this.selfClick = 0; - } - - _onButton1Click() { - this.button1ClickCount += 1; - } - - _onSub1Click() { - this.$id('sub1').value = 'you clicked me'; - } - - _onSub1Input() { - this.sub1ValueChangeCount += 1; - } - - _onSelfClick() { - this.selfClick += 1; - } - } - customElements.define('event-mixin', EventMixinTag); - }); - - it('can adding an event that gets triggered', async () => { - const element = await fixture(``); - element.$id('button1').click(); - expect(element.button1ClickCount).to.equal(1); - }); - - it('can add events to itself', async () => { - const element = await fixture(``); - element.click(); - expect(element.selfClick).to.equal(1); - }); - - it('can add events to window', async () => { - const tag = defineCE( - class extends EventMixin(HTMLElement) { - get events() { - return { - _onMyCustomEvent: [() => window, 'my-custom-event'], - }; - } - - _onMyCustomEvent() { - this.fired = true; - } - }, - ); - - const element = await fixture(`<${tag}>`); - expect(element.fired).to.equal(undefined); - - window.dispatchEvent(new Event('my-custom-event')); - await element.updateComplete; - expect(element.fired).to.equal(true); - - // this will clear it's state on window - delete window.__eventMixinProcessed; - }); - - it('supports multiple different events for a single node', async () => { - const element = await fixture(``); - element.$id('sub1').click(); - await element.updateComplete; - expect(element.$id('sub1').value).to.equal('you clicked me'); - - element.$id('sub1').value = 'new'; - await element.updateComplete; - expect(element.$id('sub1').value).to.equal('new'); - expect(element.sub1ValueChangeCount).to.equal(2); - }); - - it('supports multiple different events with a single function', async () => { - class MultiEvents extends EventMixin(HTMLElement) { - get events() { - return { - doIt: [() => this.lightDomInput, ['click', 'change']], - }; - } - - constructor() { - super(); - this.doItCounter = 0; - } - - doIt() { - this.doItCounter += 1; - } - - connectedCallback() { - this.lightDomInput = this.querySelector('input'); - this.__registerEvents(); - } - } - customElements.define('multi-events', MultiEvents); - const element = await fixture(``); - - expect(element.doItCounter).to.equal(0); - - element.lightDomInput.click(); - element.lightDomInput.value = 'foo'; - element.lightDomInput.dispatchEvent(new Event('change')); - - await element.updateComplete; - expect(element.doItCounter).to.equal(2); - }); - - it('supports multiple functions per event for a single node', async () => { - class MultiFunctions extends EventMixin(HTMLElement) { - get events() { - return { - _onClick: [() => this.lightDomButton, 'click'], - _loggging: [() => this.lightDomButton, 'click'], - }; - } - - _onClick() { - this.clicked = true; - } - - _loggging() { - this.logged = true; - } - - connectedCallback() { - this.lightDomButton = this.querySelector('button'); - this.__registerEvents(); - } - } - customElements.define('multi-functions', MultiFunctions); - - const element = await fixture(``); - expect(element.clicked).to.equal(undefined); - expect(element.logged).to.equal(undefined); - - element.lightDomButton.click(); - await element.updateComplete; - expect(element.clicked).to.equal(true); - expect(element.logged).to.equal(true); - }); - - it('will not add same event/function multiple times to a node', async () => { - const element = await fixture(``); - element.__registerEvents(); - element.__registerEvents(); - expect(element.__eventsCache.length).to.equal(4); - }); - - it('will cleanup events', async () => { - // we can't test this properly as according to spec there is no way to get - // a list of attached event listeners - // in dev tools you can use getEventListeners but that is not available globally - // so we are at least testing our implementation - const element = await fixture(``); - element.__unregisterEvents(); - expect(element.__eventsCache.length).to.equal(0); - }); - - it('reregisters events if dom node is moved', async () => { - const tag = defineCE( - class extends EventMixin(HTMLElement) { - get events() { - return { - _onClick: [() => this.querySelector('button'), 'click'], - }; - } - - constructor() { - super(); - this.myCallCount = 0; - } - - _onClick() { - this.myCallCount += 1; - } - }, - ); - const el = await fixture(`<${tag}>`); - el.querySelector('button').click(); - expect(el.myCallCount).to.equal(1); - - const wrapper = await fixture(`
`); - wrapper.appendChild(el); - el.querySelector('button').click(); - expect(el.myCallCount).to.equal(2); - }); -}); diff --git a/packages/core/test/LionLitElement.test.js b/packages/core/test/LionLitElement.test.js deleted file mode 100644 index a89b39d14..000000000 --- a/packages/core/test/LionLitElement.test.js +++ /dev/null @@ -1,88 +0,0 @@ -import { expect, fixture, defineCE } from '@open-wc/testing'; - -import { LionLitElement, html, css } from '../src/LionLitElement.js'; - -describe('LionLitElement', () => { - describe('updateStyles()', () => { - it('handles css variables && direct e.g. host css properties correctly', async () => { - const elementName = defineCE( - class extends LionLitElement { - static get styles() { - return [ - css` - :host { - text-align: right; - - --color: rgb(128, 128, 128); - } - - h1 { - color: var(--color); - } - `, - ]; - } - - render() { - return html` -

hey

- `; - } - }, - ); - const shadowLion = await fixture(`<${elementName}>`); - expect(window.getComputedStyle(shadowLion.$id('header')).color).to.equal( - 'rgb(128, 128, 128)', - ); - expect(window.getComputedStyle(shadowLion).textAlign).to.equal('right'); - shadowLion.updateStyles({ - '--color': 'rgb(255, 0, 0)', - 'text-align': 'center', - }); - - await elementName.updateComplete; - expect(window.getComputedStyle(shadowLion.$id('header')).color).to.equal('rgb(255, 0, 0)'); - expect(window.getComputedStyle(shadowLion).textAlign).to.equal('center'); - }); - - it('preserves existing styles', async () => { - const elementName = defineCE( - class extends LionLitElement { - static get styles() { - return [ - css` - :host { - --color: rgb(128, 128, 128); - } - - h1 { - color: var(--color); - } - `, - ]; - } - - render() { - return html` -

hey

- `; - } - }, - ); - const shadowLion = await fixture(`<${elementName}>`); - expect(window.getComputedStyle(shadowLion.$id('header')).color).to.equal( - 'rgb(128, 128, 128)', - ); - shadowLion.updateStyles({ '--color': 'rgb(255, 0, 0)' }); - - await elementName.updateComplete; - expect(window.getComputedStyle(shadowLion.$id('header')).color).to.equal('rgb(255, 0, 0)'); - shadowLion.updateStyles({ 'text-align': 'left' }); - - await elementName.updateComplete; - const styles = window.getComputedStyle(shadowLion.$id('header')); - expect(styles.color).to.equal('rgb(255, 0, 0)'); - expect(styles.textAlign).to.equal('left'); - }); - }); -}); diff --git a/packages/core/test/ObserverMixin.test.js b/packages/core/test/ObserverMixin.test.js deleted file mode 100644 index c66ce7a1b..000000000 --- a/packages/core/test/ObserverMixin.test.js +++ /dev/null @@ -1,240 +0,0 @@ -import { expect, fixture, defineCE } from '@open-wc/testing'; -import sinon from 'sinon'; -import { LionLitElement } from '../src/LionLitElement.js'; - -import { ObserverMixin } from '../src/ObserverMixin.js'; - -describe('ObserverMixin', () => { - afterEach(() => { - sinon.restore(); - }); - - it('throws if a syncObserver function is not found', async () => { - class SyncTest extends ObserverMixin(class {}) { - static get properties() { - return { size: { type: 'String' } }; - } - - static get syncObservers() { - return { _onSyncMissingFunction: ['size'] }; - } - - get localName() { - return 'SyncTest'; - } - } - - let error = false; - try { - new SyncTest(); // eslint-disable-line no-new - } catch (err) { - error = err; - } - expect(error).to.be.instanceOf(Error); - expect(error.message).to.equal( - 'SyncTest does not have a function called _onSyncMissingFunction', - ); - }); - - it('throws if a asyncObserver function is not found', async () => { - class AsyncTest extends ObserverMixin(class {}) { - static get properties() { - return { size: { type: 'String' } }; - } - - static get asyncObservers() { - return { _onAsyncMissingFunction: ['size'] }; - } - - get localName() { - return 'AsyncTest'; - } - } - - let error = false; - try { - new AsyncTest(); // eslint-disable-line no-new - } catch (err) { - error = err; - } - expect(error).to.be.instanceOf(Error); - expect(error.message).to.equal( - 'AsyncTest does not have a function called _onAsyncMissingFunction', - ); - }); - - // TODO: replace with requestUpdate()? - it('provides triggerObserversFor() which can be used within a setter to hook into the observer system', async () => { - const tag = defineCE( - class extends ObserverMixin(LionLitElement) { - static get syncObservers() { - return { _onSyncSizeChanged: ['size'] }; - } - - static get asyncObservers() { - return { _onAsyncSizeChanged: ['size'] }; - } - - set size(newValue) { - const oldValue = this.__mySize; - this.__mySize = newValue; - this.triggerObserversFor('size', newValue, oldValue); - } - - get size() { - return this.__mySize; - } - - _onSyncSizeChanged() {} - - _onAsyncSizeChanged() {} - }, - ); - const el = await fixture(`<${tag}>`); - const asyncSpy = sinon.spy(el, '_onAsyncSizeChanged'); - const syncSpy = sinon.spy(el, '_onSyncSizeChanged'); - - el.size = 'tiny'; - expect(syncSpy.callCount).to.equal(1); - expect(syncSpy.calledWith({ size: 'tiny' }, { size: undefined })).to.be.true; - el.size = 'big'; - expect(syncSpy.callCount).to.equal(2); - expect(syncSpy.calledWith({ size: 'big' }, { size: 'tiny' })).to.be.true; - - expect(asyncSpy.callCount).to.equal(0); - - await el.updateComplete; - expect(syncSpy.callCount).to.equal(2); - expect(asyncSpy.callCount).to.equal(1); - expect(asyncSpy.calledWith({ size: 'big' }, { size: undefined })).to.be.true; - - el.size = 'medium'; - await el.updateComplete; - expect(syncSpy.callCount).to.equal(3); - expect(syncSpy.calledWith({ size: 'medium' }, { size: 'big' })).to.be.true; - expect(asyncSpy.calledWith({ size: 'medium' }, { size: 'big' })).to.be.true; - }); - - describe('syncObservers', () => { - it('calls observers immediately when the observed property is changed (newValue !== oldValue)', async () => { - const tag = defineCE( - class extends ObserverMixin(LionLitElement) { - static get properties() { - return { size: { type: String } }; - } - - static get syncObservers() { - return { _onSizeChanged: ['size'] }; - } - - _onSizeChanged() {} - }, - ); - const el = await fixture(`<${tag}>`); - const observerSpy = sinon.spy(el, '_onSizeChanged'); - expect(observerSpy.callCount).to.equal(0); - el.size = 'tiny'; - expect(observerSpy.callCount).to.equal(1); - el.size = 'tiny'; - expect(observerSpy.callCount).to.equal(1); - el.size = 'big'; - expect(observerSpy.callCount).to.equal(2); - }); - - it('makes call to observer for every observed property change', async () => { - const tag = defineCE( - class extends ObserverMixin(LionLitElement) { - static get properties() { - return { - size: { type: String }, - speed: { type: Number }, - }; - } - - static get syncObservers() { - return { - _onSpeedOrTypeChanged: ['size', 'speed'], - }; - } - - _onSpeedOrTypeChanged() { - this.__testSize = this.size; - } - }, - ); - const el = await fixture(`<${tag}>`); - const observerSpy = sinon.spy(el, '_onSpeedOrTypeChanged'); - - el.size = 'big'; - expect(observerSpy.callCount).to.equal(1); - expect(el.__testSize).to.equal('big'); - - el.speed = 3; - expect(observerSpy.callCount).to.equal(2); - expect( - observerSpy.calledWith( - { size: 'big', speed: undefined }, - { size: undefined, speed: undefined }, - ), - ).to.be.true; - expect(observerSpy.calledWith({ size: 'big', speed: 3 }, { size: 'big', speed: undefined })) - .to.be.true; - }); - }); - - describe('asyncObservers', () => { - it('calls observer patched when the observed property is changed', async () => { - const tag = defineCE( - class extends ObserverMixin(LionLitElement) { - static get properties() { - return { size: { type: 'String' } }; - } - - static get asyncObservers() { - return { _onAsyncSizeChanged: ['size'] }; - } - - _onAsyncSizeChanged() {} - }, - ); - const el = await fixture(`<${tag}>`); - const observerSpy = sinon.spy(el, '_onAsyncSizeChanged'); - el.size = 'tiny'; - expect(observerSpy.callCount).to.equal(0); - await el.updateComplete; - expect(observerSpy.callCount).to.equal(1); - }); - - it('makes only one call to observer even if multiple observed attributes changed', async () => { - const tag = defineCE( - class extends ObserverMixin(LionLitElement) { - static get properties() { - return { - size: { type: 'String' }, - speed: { type: 'Number' }, - }; - } - - static get asyncObservers() { - return { - _onAsyncSpeedOrTypeChanged: ['size', 'speed'], - }; - } - - _onAsyncSpeedOrTypeChanged() {} - }, - ); - const el = await fixture(`<${tag}>`); - const observerSpy = sinon.spy(el, '_onAsyncSpeedOrTypeChanged'); - el.size = 'big'; - el.speed = 3; - expect(observerSpy.callCount).to.equal(0); - await el.updateComplete; - expect(observerSpy.callCount).to.equal(1); - - expect( - observerSpy.calledWith({ size: 'big', speed: 3 }, { size: undefined, speed: undefined }), - ).to.be.true; - }); - }); -}); diff --git a/packages/core/test/UpdateStylesMixin.test.js b/packages/core/test/UpdateStylesMixin.test.js new file mode 100644 index 000000000..1f16d5a5b --- /dev/null +++ b/packages/core/test/UpdateStylesMixin.test.js @@ -0,0 +1,91 @@ +import { expect, fixture, defineCE, html } from '@open-wc/testing'; +import { css, LitElement } from '../index.js'; + +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; + + --color: rgb(128, 128, 128); + } + + 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)', + ); + 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)', + ); + 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); + } + + 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)', + ); + el.updateStyles({ '--color': 'rgb(255, 0, 0)' }); + + await tag.updateComplete; + expect(window.getComputedStyle(el.shadowRoot.getElementById('header')).color).to.equal( + 'rgb(255, 0, 0)', + ); + el.updateStyles({ 'text-align': 'left' }); + + await tag.updateComplete; + const styles = window.getComputedStyle(el.shadowRoot.getElementById('header')); + expect(styles.color).to.equal('rgb(255, 0, 0)'); + expect(styles.textAlign).to.equal('left'); + }); +}); diff --git a/packages/core/test/lit-html.test.js b/packages/core/test/lit-html.test.js deleted file mode 100644 index 858f427d6..000000000 --- a/packages/core/test/lit-html.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { expect, fixture } from '@open-wc/testing'; - -import { html } from '../src/lit-html.js'; - -describe('lit-html', () => { - it('binds values when parent has shadow root', async () => { - class ComponentWithShadowDom extends HTMLElement { - constructor() { - super(); - this.attachShadow({ mode: 'open' }); - } - } - customElements.define('component-with-shadow-dom', ComponentWithShadowDom); - - const myNumber = 10; - const myFunction = () => {}; - const element = await fixture(html` - - - - `); - expect(element.children[0].propNumber).to.equal(myNumber); - expect(element.children[0].propFunction).to.equal(myFunction); - }); -}); diff --git a/packages/field/docs/CustomFieldsTutorial.md b/packages/field/docs/CustomFieldsTutorial.md index 6591ab799..6796dd7d1 100644 --- a/packages/field/docs/CustomFieldsTutorial.md +++ b/packages/field/docs/CustomFieldsTutorial.md @@ -61,18 +61,18 @@ export class LionSlider extends LionField { } _proxyChangeEvent() { - this.inputElement.dispatchEvent( + this._inputNode.dispatchEvent( new CustomEvent('user-input-changed', { bubbles: true, composed: true }), ); } // 3. Proxy property `.mySliderValue` to `.value` get value() { -   return this.$$slot('input').mySliderValue; +   return this.querySelector('[slot=input]').mySliderValue; } set value(newV) { -   this.$$slot('input').mySliderValue = newV; +   this.querySelector('[slot=input]').mySliderValue = newV; } } ``` diff --git a/packages/field/docs/FormatMixin.md b/packages/field/docs/FormatMixin.md index bb9cace50..687f8d152 100644 --- a/packages/field/docs/FormatMixin.md +++ b/packages/field/docs/FormatMixin.md @@ -22,7 +22,7 @@ Examples: The view value is the result of the formatter function. It will be stored as `.formattedValue` and synchronized to `.value` (a viewValue setter that -allows to synchronize to `.inputElement`). +allows to synchronize to `._inputNode`). Synchronization happens conditionally and is (by default) the result of a blur. Other conditions (like error state/validity and whether the a model value was set programatically) also play a role. @@ -45,7 +45,7 @@ Examples: - For a number input this would be the String representation of a float ('1234.56' instead of 1234.56) -When no parser is available, the value is usually the same as the formattedValue (being inputElement.value) +When no parser is available, the value is usually the same as the formattedValue (being \_inputNode.value) ## Formatters, parsers and (de)serializers diff --git a/packages/field/docs/FormattingAndParsing.md b/packages/field/docs/FormattingAndParsing.md index 0d7f932dd..fafb5b380 100644 --- a/packages/field/docs/FormattingAndParsing.md +++ b/packages/field/docs/FormattingAndParsing.md @@ -21,7 +21,7 @@ Examples: ### formattedValue The view value is the result of the formatter function (when available). -The result will be stored in the native inputElement (usually an input[type=text]). +The result will be stored in the native \_inputNode (usually an input[type=text]). Examples: @@ -42,7 +42,7 @@ Examples: - For a number input this would be the String representation of a float ('1234.56' instead of 1234.56) -When no parser is available, the value is usually the same as the formattedValue (being inputElement.value) +When no parser is available, the value is usually the same as the formattedValue (being \_inputNode.value) ## Formatters, parsers and (de)serializers diff --git a/packages/field/src/FocusMixin.js b/packages/field/src/FocusMixin.js index f16b4e6b2..97e8a909a 100644 --- a/packages/field/src/FocusMixin.js +++ b/packages/field/src/FocusMixin.js @@ -33,47 +33,29 @@ export const FocusMixin = dedupeMixin( } focus() { - const native = this.inputElement; + const native = this._inputNode; if (native) { native.focus(); } } blur() { - const native = this.inputElement; + const native = this._inputNode; if (native) { native.blur(); } } - updated(changedProperties) { - super.updated(changedProperties); - // 'state-focused' css classes are deprecated - if (changedProperties.has('focused')) { - this.classList[this.focused ? 'add' : 'remove']('state-focused'); - } - } - - /** - * Functions should be private - * - * @deprecated - */ - _onFocus() { - if (super._onFocus) { - super._onFocus(); + __onFocus() { + if (super.__onFocus) { + super.__onFocus(); } this.focused = true; } - /** - * Functions should be private - * - * @deprecated - */ - _onBlur() { - if (super._onBlur) { - super._onBlur(); + __onBlur() { + if (super.__onBlur) { + super.__onBlur(); } this.focused = false; } @@ -84,37 +66,37 @@ export const FocusMixin = dedupeMixin( ev.stopPropagation(); this.dispatchEvent(new Event('focus')); }; - this.inputElement.addEventListener('focus', this.__redispatchFocus); + this._inputNode.addEventListener('focus', this.__redispatchFocus); // blur this.__redispatchBlur = ev => { ev.stopPropagation(); this.dispatchEvent(new Event('blur')); }; - this.inputElement.addEventListener('blur', this.__redispatchBlur); + this._inputNode.addEventListener('blur', this.__redispatchBlur); // focusin this.__redispatchFocusin = ev => { ev.stopPropagation(); - this._onFocus(ev); + this.__onFocus(ev); this.dispatchEvent(new Event('focusin', { bubbles: true, composed: true })); }; - this.inputElement.addEventListener('focusin', this.__redispatchFocusin); + this._inputNode.addEventListener('focusin', this.__redispatchFocusin); // focusout this.__redispatchFocusout = ev => { ev.stopPropagation(); - this._onBlur(); + this.__onBlur(); this.dispatchEvent(new Event('focusout', { bubbles: true, composed: true })); }; - this.inputElement.addEventListener('focusout', this.__redispatchFocusout); + this._inputNode.addEventListener('focusout', this.__redispatchFocusout); } __teardownEventsForFocusMixin() { - this.inputElement.removeEventListener('focus', this.__redispatchFocus); - this.inputElement.removeEventListener('blur', this.__redispatchBlur); - this.inputElement.removeEventListener('focusin', this.__redispatchFocusin); - this.inputElement.removeEventListener('focusout', this.__redispatchFocusout); + this._inputNode.removeEventListener('focus', this.__redispatchFocus); + this._inputNode.removeEventListener('blur', this.__redispatchBlur); + this._inputNode.removeEventListener('focusin', this.__redispatchFocusin); + this._inputNode.removeEventListener('focusout', this.__redispatchFocusout); } }, ); diff --git a/packages/field/src/FormControlMixin.js b/packages/field/src/FormControlMixin.js index 86562cb6c..952fbedcd 100644 --- a/packages/field/src/FormControlMixin.js +++ b/packages/field/src/FormControlMixin.js @@ -1,5 +1,4 @@ import { html, css, nothing, dedupeMixin, SlotMixin } from '@lion/core'; -import { ObserverMixin } from '@lion/core/src/ObserverMixin.js'; import { FormRegisteringMixin } from './FormRegisteringMixin.js'; /** @@ -15,18 +14,18 @@ import { FormRegisteringMixin } from './FormRegisteringMixin.js'; export const FormControlMixin = dedupeMixin( superclass => // eslint-disable-next-line no-shadow, no-unused-vars - class FormControlMixin extends FormRegisteringMixin(ObserverMixin(SlotMixin(superclass))) { + class FormControlMixin extends FormRegisteringMixin(SlotMixin(superclass)) { static get properties() { return { /** - * A list of ids that will be put on the inputElement as a serialized string + * A list of ids that will be put on the _inputNode as a serialized string */ _ariaDescribedby: { type: String, }, /** - * A list of ids that will be put on the inputElement as a serialized string + * A list of ids that will be put on the _inputNode as a serialized string */ _ariaLabelledby: { type: String, @@ -65,18 +64,27 @@ export const FormControlMixin = dedupeMixin( }; } - static get asyncObservers() { - return { - ...super.asyncObservers, - _onAriaLabelledbyChanged: ['_ariaLabelledby'], - _onAriaDescribedbyChanged: ['_ariaDescribedby'], - _onLabelChanged: ['label'], - _onHelpTextChanged: ['helpText'], - }; + updated(changedProps) { + super.updated(changedProps); + + if (changedProps.has('_ariaLabelledby')) { + this._onAriaLabelledbyChanged({ _ariaLabelledby: this._ariaLabelledby }); + } + + if (changedProps.has('_ariaDescribedby')) { + this._onAriaDescribedbyChanged({ _ariaDescribedby: this._ariaDescribedby }); + } + + if (changedProps.has('label')) { + this._onLabelChanged({ label: this.label }); + } + + if (changedProps.has('helpText')) { + this._onHelpTextChanged({ helpText: this.helpText }); + } } - /** @deprecated will be this._inputNode in next breaking release */ - get inputElement() { + get _inputNode() { return this.__getDirectSlotChild('input'); } @@ -112,16 +120,16 @@ export const FormControlMixin = dedupeMixin( */ _enhanceLightDomClasses() { - if (this.inputElement) { - this.inputElement.classList.add('form-control'); + if (this._inputNode) { + this._inputNode.classList.add('form-control'); } } _enhanceLightDomA11y() { - const { inputElement, _labelNode, _helpTextNode, _feedbackNode } = this; + const { _inputNode, _labelNode, _helpTextNode, _feedbackNode } = this; - if (inputElement) { - inputElement.id = inputElement.id || this._inputId; + if (_inputNode) { + _inputNode.id = _inputNode.id || this._inputId; } if (_labelNode) { _labelNode.setAttribute('for', this._inputId); @@ -178,8 +186,8 @@ export const FormControlMixin = dedupeMixin( * from an external context, will be read by a screen reader. */ _onAriaLabelledbyChanged({ _ariaLabelledby }) { - if (this.inputElement) { - this.inputElement.setAttribute('aria-labelledby', _ariaLabelledby); + if (this._inputNode) { + this._inputNode.setAttribute('aria-labelledby', _ariaLabelledby); } } @@ -190,8 +198,8 @@ export const FormControlMixin = dedupeMixin( * from an external context, will be read by a screen reader. */ _onAriaDescribedbyChanged({ _ariaDescribedby }) { - if (this.inputElement) { - this.inputElement.setAttribute('aria-describedby', _ariaDescribedby); + if (this._inputNode) { + this._inputNode.setAttribute('aria-describedby', _ariaDescribedby); } } @@ -287,7 +295,7 @@ export const FormControlMixin = dedupeMixin( } inputGroupPrefixTemplate() { - return !this.$$slot('prefix') + return !this.querySelector('[slot=prefix]') ? nothing : html`
@@ -306,7 +314,7 @@ export const FormControlMixin = dedupeMixin( } inputGroupSuffixTemplate() { - return !this.$$slot('suffix') + return !this.querySelector('[slot=suffix]') ? nothing : html`
diff --git a/packages/field/src/FormatMixin.js b/packages/field/src/FormatMixin.js index 6f92e60b8..635320f79 100644 --- a/packages/field/src/FormatMixin.js +++ b/packages/field/src/FormatMixin.js @@ -23,10 +23,10 @@ import { Unparseable } from '@lion/validate'; * ## Flows * FormatMixin supports these two main flows: * [1] Application Developer sets `.modelValue`: - * Flow: `.modelValue` (formatter) -> `.formattedValue` -> `.inputElement.value` + * Flow: `.modelValue` (formatter) -> `.formattedValue` -> `._inputNode.value` * (serializer) -> `.serializedValue` * [2] End user interacts with field: - * Flow: `@user-input-changed` (parser) -> `.modelValue` (formatter) -> `.formattedValue` - (debounce till reflect condition (formatOn) is met) -> `.inputElement.value` + * Flow: `@user-input-changed` (parser) -> `.modelValue` (formatter) -> `.formattedValue` - (debounce till reflect condition (formatOn) is met) -> `._inputNode.value` * (serializer) -> `.serializedValue` * * For backwards compatibility with the platform, we also support `.value` as an api. In that case @@ -40,11 +40,11 @@ import { Unparseable } from '@lion/validate'; * The `.formattedValue` should be seen as the 'scheduled' viewValue. It is computed realtime and * stores the output of formatter. It will replace viewValue. once condition `formatOn` is met. * Another difference is that formattedValue lives on `LionField`, whereas viewValue is shared - * across `LionField` and `.inputElement`. + * across `LionField` and `._inputNode`. * * For restoring serialized values fetched from a server, we could consider one extra flow: * [3] Application Developer sets `.serializedValue`: - * Flow: serializedValue (deserializer) -> `.modelValue` (formatter) -> `.formattedValue` -> `.inputElement.value` + * Flow: serializedValue (deserializer) -> `.modelValue` (formatter) -> `.formattedValue` -> `._inputNode.value` */ export const FormatMixin = dedupeMixin( superclass => @@ -70,7 +70,7 @@ export const FormatMixin = dedupeMixin( /** * The view value is the result of the formatter function (when available). - * The result will be stored in the native inputElement (usually an input[type=text]). + * The result will be stored in the native _inputNode (usually an input[type=text]). * * Examples: * - For a date input, this would be '20/01/1999' (dependent on locale). @@ -95,7 +95,7 @@ export const FormatMixin = dedupeMixin( * instead of 1234.56) * * When no parser is available, the value is usually the same as the formattedValue - * (being inputElement.value) + * (being _inputNode.value) * */ serializedValue: { @@ -127,16 +127,10 @@ export const FormatMixin = dedupeMixin( this._onModelValueChanged({ modelValue: this.modelValue }, { modelValue: oldVal }); } if (name === 'serializedValue' && this.serializedValue !== oldVal) { - this._onSerializedValueChanged( - { serializedValue: this.serializedValue }, - { serializedValue: oldVal }, - ); + this._calculateValues({ source: 'serialized' }); } if (name === 'formattedValue' && this.formattedValue !== oldVal) { - this._onFormattedValueChanged( - { formattedValue: this.formattedValue }, - { formattedValue: oldVal }, - ); + this._calculateValues({ source: 'formatted' }); } } @@ -152,7 +146,7 @@ export const FormatMixin = dedupeMixin( /** * Converts modelValue to formattedValue (formattedValue will be synced with - * `.inputElement.value`) + * `._inputNode.value`) * For instance, a Date object to a localized date. * @param {Object} value - modelValue: can be an Object, Number, String depending on the * input type(date, number, email etc) @@ -230,7 +224,7 @@ export const FormatMixin = dedupeMixin( } // A.2) Handle edge cases We might have no view value yet, for instance because - // inputElement.value was not available yet + // _inputNode.value was not available yet if (typeof value !== 'string') { // This means there is nothing to find inside the view that can be of // interest to the Application Developer or needed to store for future @@ -260,10 +254,10 @@ export const FormatMixin = dedupeMixin( // the value, no matter what. // This means, whenever we are in errorState and modelValue is set // imperatively, we DO want to format a value (it is the only way to get meaningful - // input into `.inputElement` with modelValue as input) + // input into `._inputNode` with modelValue as input) - if (this.__isHandlingUserInput && this.errorState && this.inputElement) { - return this.inputElement ? this.value : undefined; + if (this.__isHandlingUserInput && this.errorState && this._inputNode) { + return this._inputNode ? this.value : undefined; } if (this.modelValue instanceof Unparseable) { @@ -293,30 +287,8 @@ export const FormatMixin = dedupeMixin( ); } - _onFormattedValueChanged() { - /** @deprecated */ - this.dispatchEvent( - new CustomEvent('formatted-value-changed', { - bubbles: true, - composed: true, - }), - ); - this._calculateValues({ source: 'formatted' }); - } - - _onSerializedValueChanged() { - /** @deprecated */ - this.dispatchEvent( - new CustomEvent('serialized-value-changed', { - bubbles: true, - composed: true, - }), - ); - this._calculateValues({ source: 'serialized' }); - } - /** - * Synchronization from `.inputElement.value` to `LionField` (flow [2]) + * Synchronization from `._inputNode.value` to `LionField` (flow [2]) */ _syncValueUpwards() { // Downwards syncing should only happen for `LionField`.value changes from 'above' @@ -326,7 +298,7 @@ export const FormatMixin = dedupeMixin( } /** - * Synchronization from `LionField.value` to `.inputElement.value` + * Synchronization from `LionField.value` to `._inputNode.value` * - flow [1] will always be reflected back * - flow [2] will not be reflected back when this flow was triggered via * `@user-input-changed` (this will happen later, when `formatOn` condition is met) @@ -370,7 +342,7 @@ export const FormatMixin = dedupeMixin( this._reflectBackFormattedValueToUser = this._reflectBackFormattedValueToUser.bind(this); this._reflectBackFormattedValueDebounced = () => { - // Make sure this is fired after the change event of inputElement, so that formattedValue + // Make sure this is fired after the change event of _inputNode, so that formattedValue // is guaranteed to be calculated setTimeout(this._reflectBackFormattedValueToUser); }; @@ -385,21 +357,18 @@ export const FormatMixin = dedupeMixin( } this._reflectBackFormattedValueToUser(); - if (this.inputElement) { - this.inputElement.addEventListener( - this.formatOn, - this._reflectBackFormattedValueDebounced, - ); - this.inputElement.addEventListener('input', this._proxyInputEvent); + if (this._inputNode) { + this._inputNode.addEventListener(this.formatOn, this._reflectBackFormattedValueDebounced); + this._inputNode.addEventListener('input', this._proxyInputEvent); } } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener('user-input-changed', this._onUserInputChanged); - if (this.inputElement) { - this.inputElement.removeEventListener('input', this._proxyInputEvent); - this.inputElement.removeEventListener( + if (this._inputNode) { + this._inputNode.removeEventListener('input', this._proxyInputEvent); + this._inputNode.removeEventListener( this.formatOn, this._reflectBackFormattedValueDebounced, ); diff --git a/packages/field/src/InteractionStateMixin.js b/packages/field/src/InteractionStateMixin.js index e50e2798d..945ab2d88 100644 --- a/packages/field/src/InteractionStateMixin.js +++ b/packages/field/src/InteractionStateMixin.js @@ -104,17 +104,6 @@ export const InteractionStateMixin = dedupeMixin( this.removeEventListener(this._valueChangedEvent, this._iStateOnValueChange); } - updated(changedProperties) { - super.updated(changedProperties); - // classes are added only for backward compatibility - they are deprecated - if (changedProperties.has('touched')) { - this.classList[this.touched ? 'add' : 'remove']('state-touched'); - } - if (changedProperties.has('dirty')) { - this.classList[this.dirty ? 'add' : 'remove']('state-dirty'); - } - } - /** * Evaluations performed on connectedCallback. Since some components can be out of sync * (due to interdependence on light children that can only be processed @@ -163,19 +152,5 @@ export const InteractionStateMixin = dedupeMixin( _onDirtyChanged() { this.dispatchEvent(new CustomEvent('dirty-changed', { bubbles: true, composed: true })); } - - /** - * @deprecated - */ - get leaveEvent() { - return this._leaveEvent; - } - - /** - * @deprecated - */ - set leaveEvent(eventName) { - this._leaveEvent = eventName; - } }, ); diff --git a/packages/field/src/LionField.js b/packages/field/src/LionField.js index c87118525..9afc700f2 100644 --- a/packages/field/src/LionField.js +++ b/packages/field/src/LionField.js @@ -1,7 +1,5 @@ import { SlotMixin, LitElement } from '@lion/core'; -import { ElementMixin } from '@lion/core/src/ElementMixin.js'; import { DisabledMixin } from '@lion/core/src/DisabledMixin.js'; -import { ObserverMixin } from '@lion/core/src/ObserverMixin.js'; import { ValidateMixin } from '@lion/validate'; import { FormControlMixin } from './FormControlMixin.js'; import { InteractionStateMixin } from './InteractionStateMixin.js'; // applies FocusMixin @@ -30,13 +28,11 @@ import { FocusMixin } from './FocusMixin.js'; * * * - * @customElement + * @customElement lion-field */ export class LionField extends FormControlMixin( InteractionStateMixin( - FocusMixin( - FormatMixin(ValidateMixin(DisabledMixin(ElementMixin(SlotMixin(ObserverMixin(LitElement)))))), - ), + FocusMixin(FormatMixin(ValidateMixin(DisabledMixin(SlotMixin(LitElement))))), ), ) { static get properties() { @@ -57,7 +53,7 @@ export class LionField extends FormControlMixin( } get selectionStart() { - const native = this.inputElement; + const native = this._inputNode; if (native && native.selectionStart) { return native.selectionStart; } @@ -65,14 +61,14 @@ export class LionField extends FormControlMixin( } set selectionStart(value) { - const native = this.inputElement; + const native = this._inputNode; if (native && native.selectionStart) { native.selectionStart = value; } } get selectionEnd() { - const native = this.inputElement; + const native = this._inputNode; if (native && native.selectionEnd) { return native.selectionEnd; } @@ -80,7 +76,7 @@ export class LionField extends FormControlMixin( } set selectionEnd(value) { - const native = this.inputElement; + const native = this._inputNode; if (native && native.selectionEnd) { native.selectionEnd = value; } @@ -89,14 +85,14 @@ export class LionField extends FormControlMixin( // We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret set value(value) { // if not yet connected to dom can't change the value - if (this.inputElement) { + if (this._inputNode) { this._setValueAndPreserveCaret(value); } this._onValueChanged({ value }); } get value() { - return (this.inputElement && this.inputElement.value) || ''; + return (this._inputNode && this._inputNode.value) || ''; } constructor() { @@ -120,13 +116,13 @@ export class LionField extends FormControlMixin( super.connectedCallback(); this._onChange = this._onChange.bind(this); - this.inputElement.addEventListener('change', this._onChange); + this._inputNode.addEventListener('change', this._onChange); this.classList.add('form-field'); // eslint-disable-line } disconnectedCallback() { super.disconnectedCallback(); - this.inputElement.removeEventListener('change', this._onChange); + this._inputNode.removeEventListener('change', this._onChange); } updated(changedProps) { @@ -134,25 +130,25 @@ export class LionField extends FormControlMixin( if (changedProps.has('disabled')) { if (this.disabled) { - this.inputElement.disabled = true; + this._inputNode.disabled = true; this.classList.add('state-disabled'); // eslint-disable-line wc/no-self-class } else { - this.inputElement.disabled = false; + this._inputNode.disabled = false; this.classList.remove('state-disabled'); // eslint-disable-line wc/no-self-class } } if (changedProps.has('name')) { - this.inputElement.name = this.name; + this._inputNode.name = this.name; } if (changedProps.has('autocomplete')) { - this.inputElement.autocomplete = this.autocomplete; + this._inputNode.autocomplete = this.autocomplete; } } /** - * This is not done via 'get delegations', because this.inputElement.setAttribute('value') + * This is not done via 'get delegations', because this._inputNode.setAttribute('value') * does not trigger a value change */ _delegateInitialValueAttr() { @@ -214,18 +210,18 @@ export class LionField extends FormControlMixin( // right properties, accessing them might throw an exception (like for // ) try { - const start = this.inputElement.selectionStart; - this.inputElement.value = newValue; + const start = this._inputNode.selectionStart; + this._inputNode.value = newValue; // The cursor automatically jumps to the end after re-setting the value, // so restore it to its original position. - this.inputElement.selectionStart = start; - this.inputElement.selectionEnd = start; + this._inputNode.selectionStart = start; + this._inputNode.selectionEnd = start; } catch (error) { // Just set the value and give up on the caret. - this.inputElement.value = newValue; + this._inputNode.value = newValue; } } else { - this.inputElement.value = newValue; + this._inputNode.value = newValue; } } diff --git a/packages/field/test-suites/FormatMixin.suite.js b/packages/field/test-suites/FormatMixin.suite.js index dc54013e6..7a21b19a7 100644 --- a/packages/field/test-suites/FormatMixin.suite.js +++ b/packages/field/test-suites/FormatMixin.suite.js @@ -7,7 +7,7 @@ import { FormatMixin } from '../src/FormatMixin.js'; function mimicUserInput(formControl, newViewValue) { formControl.value = newViewValue; // eslint-disable-line no-param-reassign - formControl.inputElement.dispatchEvent(new CustomEvent('input', { bubbles: true })); + formControl._inputNode.dispatchEvent(new CustomEvent('input', { bubbles: true })); } export function runFormatMixinSuite(customConfig) { @@ -74,14 +74,14 @@ export function runFormatMixinSuite(customConfig) { } set value(newValue) { - this.inputElement.value = newValue; + this._inputNode.value = newValue; } get value() { - return this.inputElement.value; + return this._inputNode.value; } - get inputElement() { + get _inputNode() { return this.querySelector('input'); } }, @@ -164,7 +164,7 @@ export function runFormatMixinSuite(customConfig) { fooFormat.modelValue = 'string'; expect(fooFormat.formattedValue).to.equal('foo: string'); expect(fooFormat.value).to.equal('foo: string'); - expect(fooFormat.inputElement.value).to.equal('foo: string'); + expect(fooFormat._inputNode.value).to.equal('foo: string'); }); it('converts modelValue => formattedValue (via this.formatter)', async () => { @@ -188,7 +188,7 @@ export function runFormatMixinSuite(customConfig) { expect(fooFormat.modelValue).to.equal('string'); }); - it('synchronizes inputElement.value as a fallback mechanism', async () => { + it('synchronizes _inputNode.value as a fallback mechanism', async () => { // Note that in lion-field, the attribute would be put on , not on const formatElem = await fixture(html` <${elem} @@ -202,7 +202,7 @@ export function runFormatMixinSuite(customConfig) { await formatElem.updateComplete; expect(formatElem.formattedValue).to.equal('foo: string'); - expect(formatElem.inputElement.value).to.equal('foo: string'); + expect(formatElem._inputNode.value).to.equal('foo: string'); expect(formatElem.serializedValue).to.equal('[foo] string'); expect(formatElem.modelValue).to.equal('string'); @@ -218,12 +218,12 @@ export function runFormatMixinSuite(customConfig) { const generatedViewValue = generateValueBasedOnType({ viewValue: true }); const generatedModelValue = generateValueBasedOnType(); mimicUserInput(formatEl, generatedViewValue); - expect(formatEl.inputElement.value).to.not.equal(`foo: ${generatedModelValue}`); + expect(formatEl._inputNode.value).to.not.equal(`foo: ${generatedModelValue}`); // user leaves field - formatEl.inputElement.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true })); + formatEl._inputNode.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true })); await aTimeout(); - expect(formatEl.inputElement.value).to.equal(`foo: ${generatedModelValue}`); + expect(formatEl._inputNode.value).to.equal(`foo: ${generatedModelValue}`); }); it('reflects back .formattedValue immediately when .modelValue changed imperatively', async () => { @@ -238,14 +238,14 @@ export function runFormatMixinSuite(customConfig) { // users types value 'test' mimicUserInput(el, 'test'); - expect(el.inputElement.value).to.not.equal('foo: test'); + expect(el._inputNode.value).to.not.equal('foo: test'); // Now see the difference for an imperative change el.modelValue = 'test2'; - expect(el.inputElement.value).to.equal('foo: test2'); + expect(el._inputNode.value).to.equal('foo: test2'); }); - it('works if there is no underlying inputElement', async () => { + it('works if there is no underlying _inputNode', async () => { const tagNoInputString = defineCE(class extends FormatMixin(LitElement) {}); const tagNoInput = unsafeStatic(tagNoInputString); expect(async () => { diff --git a/packages/field/test-suites/InteractionStateMixin.suite.js b/packages/field/test-suites/InteractionStateMixin.suite.js index 2c7b3b7b4..10c0b3f77 100644 --- a/packages/field/test-suites/InteractionStateMixin.suite.js +++ b/packages/field/test-suites/InteractionStateMixin.suite.js @@ -67,18 +67,6 @@ export function runInteractionStateMixinSuite(customConfig) { expect(el.touched).to.be.true; }); - // classes are added only for backward compatibility - they are deprecated - it('sets a class "state-(touched|dirty)"', async () => { - const el = await fixture(html`<${tag}>`); - el.touched = true; - await el.updateComplete; - expect(el.classList.contains('state-touched')).to.equal(true, 'has class "state-touched"'); - - el.dirty = true; - await el.updateComplete; - expect(el.classList.contains('state-dirty')).to.equal(true, 'has class "state-dirty"'); - }); - it('sets an attribute "touched', async () => { const el = await fixture(html`<${tag}>`); el.touched = true; @@ -125,7 +113,7 @@ export function runInteractionStateMixinSuite(customConfig) { const el = await fixture(html`<${tag}>`); const changeModelValueAndLeave = modelValue => { - const targetEl = el.inputElement || el; + const targetEl = el._inputNode || el; targetEl.dispatchEvent(new Event('focus', { bubbles: true })); el.modelValue = modelValue; targetEl.dispatchEvent(new Event(el._leaveEvent, { bubbles: true })); @@ -201,11 +189,6 @@ export function runInteractionStateMixinSuite(customConfig) { el.dispatchEvent(new Event('custom-blur')); expect(el.touched).to.be.true; }); - - it('can override the deprecated `leaveEvent`', async () => { - const el = await fixture(html`<${tag} .leaveEvent=${'custom-blur'}>`); - expect(el._leaveEvent).to.equal('custom-blur'); - }); }); }); } diff --git a/packages/field/test/FieldCustomMixin.test.js b/packages/field/test/FieldCustomMixin.test.js index 369f1f0da..b23da7f1a 100644 --- a/packages/field/test/FieldCustomMixin.test.js +++ b/packages/field/test/FieldCustomMixin.test.js @@ -24,6 +24,6 @@ describe('FieldCustomMixin', () => { const lionField = await fixture(` <${elem} disable-help-text>${inputSlot} `); - expect(lionField.$$slot('help-text')).to.equal(undefined); + expect(lionField.querySelector('[slot=help-text]')).to.equal(null); }); }); diff --git a/packages/field/test/FocusMixin.test.js b/packages/field/test/FocusMixin.test.js index 8f5eb8b4f..329c9ff19 100644 --- a/packages/field/test/FocusMixin.test.js +++ b/packages/field/test/FocusMixin.test.js @@ -15,7 +15,7 @@ describe('FocusMixin', () => { `; } - get inputElement() { + get _inputNode() { return this.querySelector('input'); } }, @@ -29,9 +29,9 @@ describe('FocusMixin', () => { <${tag}> `); el.focus(); - expect(document.activeElement === el.inputElement).to.be.true; + expect(document.activeElement === el._inputNode).to.be.true; el.blur(); - expect(document.activeElement === el.inputElement).to.be.false; + expect(document.activeElement === el._inputNode).to.be.false; }); it('has an attribute focused when focused', async () => { @@ -52,25 +52,12 @@ describe('FocusMixin', () => { <${tag}> `); expect(el.focused).to.be.false; - el.inputElement.focus(); + el._inputNode.focus(); expect(el.focused).to.be.true; - el.inputElement.blur(); + el._inputNode.blur(); expect(el.focused).to.be.false; }); - it('has a deprecated "state-focused" css class when focused', async () => { - const el = await fixture(html` - <${tag}> - `); - el.focus(); - await el.updateComplete; - expect(el.classList.contains('state-focused')).to.be.true; - - el.blur(); - await el.updateComplete; - expect(el.classList.contains('state-focused')).to.be.false; - }); - it('dispatches [focus, blur] events', async () => { const el = await fixture(html` <${tag}> diff --git a/packages/field/test/FormControlMixin.test.js b/packages/field/test/FormControlMixin.test.js index ea280bcc3..abf4218c4 100644 --- a/packages/field/test/FormControlMixin.test.js +++ b/packages/field/test/FormControlMixin.test.js @@ -1,6 +1,5 @@ import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing'; -import { SlotMixin } from '@lion/core'; -import { LionLitElement } from '@lion/core/src/LionLitElement.js'; +import { LitElement, SlotMixin } from '@lion/core'; import { FormControlMixin } from '../src/FormControlMixin.js'; @@ -10,7 +9,7 @@ describe('FormControlMixin', () => { let tag; before(async () => { - const FormControlMixinClass = class extends FormControlMixin(SlotMixin(LionLitElement)) { + const FormControlMixinClass = class extends FormControlMixin(SlotMixin(LitElement)) { static get properties() { return { modelValue: { @@ -28,7 +27,7 @@ describe('FormControlMixin', () => { const lionFieldAttr = await fixture(html` <${tag} help-text="This email address is already taken">${inputSlot} `); - expect(lionFieldAttr.$$slot('help-text').textContent).to.contain( + expect(lionFieldAttr.querySelector('[slot=help-text]').textContent).to.contain( 'This email address is already taken', ); const lionFieldProp = await fixture(html` @@ -37,7 +36,7 @@ describe('FormControlMixin', () => { >${inputSlot} `); - expect(lionFieldProp.$$slot('help-text').textContent).to.contain( + expect(lionFieldProp.querySelector('[slot=help-text]').textContent).to.contain( 'This email address is already taken', ); }); @@ -54,7 +53,7 @@ describe('FormControlMixin', () => { ['aria-describedby', 'aria-labelledby'].forEach(ariaAttributeName => { const ariaAttribute = lionField - .$$slot('input') + .querySelector('[slot=input]') .getAttribute(ariaAttributeName) .trim() .split(' '); @@ -71,6 +70,6 @@ describe('FormControlMixin', () => { `); - expect(lionField.$$slot('feedback').getAttribute('aria-live')).to.equal('polite'); + expect(lionField.querySelector('[slot=feedback]').getAttribute('aria-live')).to.equal('polite'); }); }); diff --git a/packages/field/test/field-integrations.test.js b/packages/field/test/field-integrations.test.js index 5395b40c2..c6fb7bb8e 100644 --- a/packages/field/test/field-integrations.test.js +++ b/packages/field/test/field-integrations.test.js @@ -8,7 +8,7 @@ const fieldTagString = defineCE( get slots() { return { ...super.slots, - // LionField needs to have an inputElement defined in order to work... + // LionField needs to have an _inputNode defined in order to work... input: () => document.createElement('input'), }; } diff --git a/packages/field/test/lion-field.test.js b/packages/field/test/lion-field.test.js index 7b0f05e98..13ccb96c8 100644 --- a/packages/field/test/lion-field.test.js +++ b/packages/field/test/lion-field.test.js @@ -22,7 +22,7 @@ const inputSlot = unsafeHTML(inputSlotString); function mimicUserInput(formControl, newViewValue) { formControl.value = newViewValue; // eslint-disable-line no-param-reassign - formControl.inputElement.dispatchEvent(new CustomEvent('input', { bubbles: true })); + formControl._inputNode.dispatchEvent(new CustomEvent('input', { bubbles: true })); } beforeEach(() => { @@ -32,7 +32,7 @@ beforeEach(() => { describe('', () => { it(`puts a unique id "${tagString}-[hash]" on the native input`, async () => { const el = await fixture(html`<${tag}>${inputSlot}`); - expect(el.$$slot('input').id).to.equal(el._inputId); + expect(el.querySelector('[slot=input]').id).to.equal(el._inputId); }); it('fires focus/blur event on host and native input if focused/blurred', async () => { @@ -40,15 +40,15 @@ describe('', () => { const cbFocusHost = sinon.spy(); el.addEventListener('focus', cbFocusHost); const cbFocusNativeInput = sinon.spy(); - el.inputElement.addEventListener('focus', cbFocusNativeInput); + el._inputNode.addEventListener('focus', cbFocusNativeInput); const cbBlurHost = sinon.spy(); el.addEventListener('blur', cbBlurHost); const cbBlurNativeInput = sinon.spy(); - el.inputElement.addEventListener('blur', cbBlurNativeInput); + el._inputNode.addEventListener('blur', cbBlurNativeInput); await triggerFocusFor(el); - expect(document.activeElement).to.equal(el.inputElement); + expect(document.activeElement).to.equal(el._inputNode); expect(cbFocusHost.callCount).to.equal(1); expect(cbFocusNativeInput.callCount).to.equal(1); expect(cbBlurHost.callCount).to.equal(0); @@ -59,7 +59,7 @@ describe('', () => { expect(cbBlurNativeInput.callCount).to.equal(1); await triggerFocusFor(el); - expect(document.activeElement).to.equal(el.inputElement); + expect(document.activeElement).to.equal(el._inputNode); expect(cbFocusHost.callCount).to.equal(2); expect(cbFocusNativeInput.callCount).to.equal(2); @@ -80,14 +80,14 @@ describe('', () => { it('can be disabled via attribute', async () => { const elDisabled = await fixture(html`<${tag} disabled>${inputSlot}`); expect(elDisabled.disabled).to.equal(true); - expect(elDisabled.inputElement.disabled).to.equal(true); + expect(elDisabled._inputNode.disabled).to.equal(true); }); it('can be disabled via property', async () => { const el = await fixture(html`<${tag}>${inputSlot}`); el.disabled = true; await el.updateComplete; - expect(el.inputElement.disabled).to.equal(true); + expect(el._inputNode.disabled).to.equal(true); }); it('can be cleared which erases value, validation and interaction states', async () => { @@ -113,29 +113,29 @@ describe('', () => { it('reads initial value from attribute value', async () => { const el = await fixture(html`<${tag} value="one">${inputSlot}`); - expect(el.$$slot('input').value).to.equal('one'); + expect(el.querySelector('[slot=input]').value).to.equal('one'); }); it('delegates value property', async () => { const el = await fixture(html`<${tag}>${inputSlot}`); - expect(el.$$slot('input').value).to.equal(''); + expect(el.querySelector('[slot=input]').value).to.equal(''); el.value = 'one'; expect(el.value).to.equal('one'); - expect(el.$$slot('input').value).to.equal('one'); + expect(el.querySelector('[slot=input]').value).to.equal('one'); }); - // This is necessary for security, so that inputElements autocomplete can be set to 'off' + // This is necessary for security, so that _inputNodes autocomplete can be set to 'off' it('delegates autocomplete property', async () => { const el = await fixture(html`<${tag}>${inputSlot}`); - expect(el.inputElement.autocomplete).to.equal(''); - expect(el.inputElement.hasAttribute('autocomplete')).to.be.false; + expect(el._inputNode.autocomplete).to.equal(''); + expect(el._inputNode.hasAttribute('autocomplete')).to.be.false; el.autocomplete = 'off'; await el.updateComplete; - expect(el.inputElement.autocomplete).to.equal('off'); - expect(el.inputElement.getAttribute('autocomplete')).to.equal('off'); + expect(el._inputNode.autocomplete).to.equal('off'); + expect(el._inputNode.getAttribute('autocomplete')).to.equal('off'); }); - // TODO: find out if we could put all listeners on this.value (instead of this.inputElement.value) + // TODO: find out if we could put all listeners on this.value (instead of this._inputNode.value) // and make it act on this.value again it('has a class "state-filled" if this.value is filled', async () => { const el = await fixture(html`<${tag} value="filled">${inputSlot}`); @@ -152,30 +152,30 @@ describe('', () => { const el = await fixture(html`<${tag}>${inputSlot}`); await triggerFocusFor(el); await el.updateComplete; - el.inputElement.value = 'hello world'; - el.inputElement.selectionStart = 2; - el.inputElement.selectionEnd = 2; + el._inputNode.value = 'hello world'; + el._inputNode.selectionStart = 2; + el._inputNode.selectionEnd = 2; el.value = 'hey there universe'; - expect(el.inputElement.selectionStart).to.equal(2); - expect(el.inputElement.selectionEnd).to.equal(2); + expect(el._inputNode.selectionStart).to.equal(2); + expect(el._inputNode.selectionEnd).to.equal(2); }); // TODO: add pointerEvents test for disabled it('has a class "state-disabled"', async () => { const el = await fixture(html`<${tag}>${inputSlot}`); expect(el.classList.contains('state-disabled')).to.equal(false); - expect(el.inputElement.hasAttribute('disabled')).to.equal(false); + expect(el._inputNode.hasAttribute('disabled')).to.equal(false); el.disabled = true; await el.updateComplete; await aTimeout(); expect(el.classList.contains('state-disabled')).to.equal(true); - expect(el.inputElement.hasAttribute('disabled')).to.equal(true); + expect(el._inputNode.hasAttribute('disabled')).to.equal(true); const disabledel = await fixture(html`<${tag} disabled>${inputSlot}`); expect(disabledel.classList.contains('state-disabled')).to.equal(true); - expect(disabledel.inputElement.hasAttribute('disabled')).to.equal(true); + expect(disabledel._inputNode.hasAttribute('disabled')).to.equal(true); }); describe(`A11y${nameSuffix}`, () => { @@ -200,7 +200,7 @@ describe('', () => { No name entered `); - const nativeInput = el.$$slot('input'); + const nativeInput = el.querySelector('[slot=input]'); expect(nativeInput.getAttribute('aria-labelledby')).to.equal(` label-${el._inputId}`); expect(nativeInput.getAttribute('aria-describedby')).to.contain(` help-text-${el._inputId}`); @@ -218,7 +218,7 @@ describe('', () => { `); - const nativeInput = el.$$slot('input'); + const nativeInput = el.querySelector('[slot=input]'); expect(nativeInput.getAttribute('aria-labelledby')).to.contain( ` before-${el._inputId} after-${el._inputId}`, ); @@ -245,30 +245,30 @@ describe('', () => { await el.updateComplete; await el.updateComplete; - const { inputElement } = el; + const { _inputNode } = el; // 1. addToAriaLabel() // Check if the aria attr is filled initially - expect(inputElement.getAttribute('aria-labelledby')).to.contain(`label-${el._inputId}`); + expect(_inputNode.getAttribute('aria-labelledby')).to.contain(`label-${el._inputId}`); el.addToAriaLabel('additionalLabel'); // Now check if ids are added to the end (not overridden) - expect(inputElement.getAttribute('aria-labelledby')).to.contain(`label-${el._inputId}`); + expect(_inputNode.getAttribute('aria-labelledby')).to.contain(`label-${el._inputId}`); // Should be placed in the end expect( - inputElement.getAttribute('aria-labelledby').indexOf(`label-${el._inputId}`) < - inputElement.getAttribute('aria-labelledby').indexOf('additionalLabel'), + _inputNode.getAttribute('aria-labelledby').indexOf(`label-${el._inputId}`) < + _inputNode.getAttribute('aria-labelledby').indexOf('additionalLabel'), ); // 2. addToAriaDescription() // Check if the aria attr is filled initially - expect(inputElement.getAttribute('aria-describedby')).to.contain(`feedback-${el._inputId}`); + expect(_inputNode.getAttribute('aria-describedby')).to.contain(`feedback-${el._inputId}`); el.addToAriaDescription('additionalDescription'); // Now check if ids are added to the end (not overridden) - expect(inputElement.getAttribute('aria-describedby')).to.contain(`feedback-${el._inputId}`); + expect(_inputNode.getAttribute('aria-describedby')).to.contain(`feedback-${el._inputId}`); // Should be placed in the end expect( - inputElement.getAttribute('aria-describedby').indexOf(`feedback-${el._inputId}`) < - inputElement.getAttribute('aria-describedby').indexOf('additionalDescription'), + _inputNode.getAttribute('aria-describedby').indexOf(`feedback-${el._inputId}`) < + _inputNode.getAttribute('aria-describedby').indexOf('additionalDescription'), ); }); }); @@ -398,10 +398,10 @@ describe('', () => { describe('Delegation', () => { it('delegates property value', async () => { const el = await fixture(html`<${tag}>${inputSlot}`); - expect(el.inputElement.value).to.equal(''); + expect(el._inputNode.value).to.equal(''); el.value = 'one'; expect(el.value).to.equal('one'); - expect(el.inputElement.value).to.equal('one'); + expect(el._inputNode.value).to.equal('one'); }); it('delegates property selectionStart and selectionEnd', async () => { @@ -413,8 +413,8 @@ describe('', () => { el.selectionStart = 5; el.selectionEnd = 12; - expect(el.inputElement.selectionStart).to.equal(5); - expect(el.inputElement.selectionEnd).to.equal(12); + expect(el._inputNode.selectionStart).to.equal(5); + expect(el._inputNode.selectionEnd).to.equal(12); }); }); }); diff --git a/packages/fieldset/src/LionFieldset.js b/packages/fieldset/src/LionFieldset.js index 6e04af64b..87a39102b 100644 --- a/packages/fieldset/src/LionFieldset.js +++ b/packages/fieldset/src/LionFieldset.js @@ -1,6 +1,5 @@ import { SlotMixin, html, LitElement } from '@lion/core'; import { DisabledMixin } from '@lion/core/src/DisabledMixin.js'; -import { ObserverMixin } from '@lion/core/src/ObserverMixin.js'; import { ValidateMixin } from '@lion/validate'; import { FormControlMixin, FormRegistrarMixin } from '@lion/field'; @@ -11,10 +10,10 @@ const pascalCase = str => str.charAt(0).toUpperCase() + str.slice(1); * LionFieldset: fieldset wrapper providing extra features and integration with lion-field elements. * * @customElement lion-fieldset - * @extends LionLitElement + * @extends {LitElement} */ export class LionFieldset extends FormRegistrarMixin( - FormControlMixin(ValidateMixin(DisabledMixin(SlotMixin(ObserverMixin(LitElement))))), + FormControlMixin(ValidateMixin(DisabledMixin(SlotMixin(LitElement)))), ) { static get properties() { return { @@ -50,7 +49,7 @@ export class LionFieldset extends FormRegistrarMixin( this.requestUpdate('touched', oldVal); } - get inputElement() { + get _inputNode() { return this; } @@ -128,27 +127,12 @@ export class LionFieldset extends FormRegistrarMixin( if (changedProps.has('disabled')) { if (this.disabled) { this.__requestChildrenToBeDisabled(); - /** @deprecated use disabled attribute instead */ - this.classList.add('state-disabled'); // eslint-disable-line wc/no-self-class } else { this.__retractRequestChildrenToBeDisabled(); - /** @deprecated use disabled attribute instead */ - this.classList.remove('state-disabled'); // eslint-disable-line wc/no-self-class } } - if (changedProps.has('touched')) { - /** @deprecated use touched attribute instead */ - this.classList[this.touched ? 'add' : 'remove']('state-touched'); - } - - if (changedProps.has('dirty')) { - /** @deprecated use dirty attribute instead */ - this.classList[this.dirty ? 'add' : 'remove']('state-dirty'); - } if (changedProps.has('focused')) { - /** @deprecated use touched attribute instead */ - this.classList[this.focused ? 'add' : 'remove']('state-focused'); if (this.focused === true) { this.__setupOutsideClickHandling(); } @@ -424,11 +408,6 @@ export class LionFieldset extends FormRegistrarMixin( return this._getFromAllFormElements('_initialModelValue'); } - /** @deprecated */ - get resetModelValue() { - return this._initialModelValue; - } - /** * Add aria-describedby to child element(field), so that it points to feedback/help-text of * parent(fieldset) diff --git a/packages/fieldset/test/lion-fieldset.test.js b/packages/fieldset/test/lion-fieldset.test.js index d07ca0aae..566dcd740 100644 --- a/packages/fieldset/test/lion-fieldset.test.js +++ b/packages/fieldset/test/lion-fieldset.test.js @@ -132,7 +132,7 @@ describe('', () => { fieldset.appendChild(newField); expect(Object.keys(fieldset.formElements).length).to.equal(4); - fieldset.inputElement.removeChild(newField); + fieldset._inputNode.removeChild(newField); expect(Object.keys(fieldset.formElements).length).to.equal(3); }); @@ -227,16 +227,6 @@ describe('', () => { expect(el.formElements.sub.formElements['hobbies[]'][1].disabled).to.equal(true); }); - // classes are added only for backward compatibility - they are deprecated - it('sets a state-disabled class when disabled', async () => { - const el = await fixture(html`<${tag} disabled>${inputSlots}`); - await nextFrame(); - expect(el.classList.contains('state-disabled')).to.equal(true); - el.disabled = false; - await nextFrame(); - expect(el.classList.contains('state-disabled')).to.equal(false); - }); - describe('validation', () => { it('validates on init', async () => { function isCat(value) { @@ -334,10 +324,9 @@ describe('', () => { it('sets touched when last field in fieldset left after focus', async () => { const fieldset = await fixture(html`<${tag}>${inputSlots}`); - await triggerFocusFor(fieldset.formElements['hobbies[]'][0].inputElement); + await triggerFocusFor(fieldset.formElements['hobbies[]'][0]._inputNode); await triggerFocusFor( - fieldset.formElements['hobbies[]'][fieldset.formElements['gender[]'].length - 1] - .inputElement, + fieldset.formElements['hobbies[]'][fieldset.formElements['gender[]'].length - 1]._inputNode, ); const el = await fixture(html` @@ -358,17 +347,6 @@ describe('', () => { expect(el).to.have.attribute('dirty'); }); - it('[deprecated] sets a class "state-(touched|dirty)"', async () => { - const el = await fixture(html`<${tag}>`); - el.touched = true; - await el.updateComplete; - expect(el.classList.contains('state-touched')).to.equal(true, 'has class "state-touched"'); - - el.dirty = true; - await el.updateComplete; - expect(el.classList.contains('state-dirty')).to.equal(true, 'has class "state-dirty"'); - }); - it('becomes prefilled if all form elements are prefilled', async () => { const el = await fixture(html` <${tag}> diff --git a/packages/form-system/stories/helper-wc/h-output.js b/packages/form-system/stories/helper-wc/h-output.js index dc1874592..c8354ab93 100644 --- a/packages/form-system/stories/helper-wc/h-output.js +++ b/packages/form-system/stories/helper-wc/h-output.js @@ -53,8 +53,8 @@ export class HelperOutput extends LitElement { this.field.addEventListener('focusin', this.__rerender); this.field.addEventListener('focusout', this.__rerender); - if (this.field.inputElement.form) { - this.field.inputElement.form.addEventListener('submit', this.__rerender); + if (this.field._inputNode.form) { + this.field._inputNode.form.addEventListener('submit', this.__rerender); } } diff --git a/packages/form/src/LionForm.js b/packages/form/src/LionForm.js index 9c3b084c6..ffcbf3a9c 100644 --- a/packages/form/src/LionForm.js +++ b/packages/form/src/LionForm.js @@ -3,8 +3,8 @@ import { LionFieldset } from '@lion/fieldset'; /** * LionForm: form wrapper providing extra features and integration with lion-field elements. * - * @customElement - * @extends LionFieldset + * @customElement lion-form + * @extends {LionFieldset} */ // eslint-disable-next-line no-unused-vars export class LionForm extends LionFieldset { diff --git a/packages/input-amount/src/LionInputAmount.js b/packages/input-amount/src/LionInputAmount.js index 75550ae89..6c58e2fa6 100644 --- a/packages/input-amount/src/LionInputAmount.js +++ b/packages/input-amount/src/LionInputAmount.js @@ -1,6 +1,5 @@ import { css } from '@lion/core'; import { LocalizeMixin } from '@lion/localize'; -import { ObserverMixin } from '@lion/core/src/ObserverMixin.js'; import { LionInput } from '@lion/input'; import { FieldCustomMixin } from '@lion/field'; import { isNumberValidator } from '@lion/validate'; @@ -10,10 +9,10 @@ import { formatAmount } from './formatters.js'; /** * `LionInputAmount` is a class for an amount custom form element (``). * - * @customElement + * @customElement lion-input-amount * @extends {LionInput} */ -export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(ObserverMixin(LionInput))) { +export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(LionInput)) { static get properties() { return { currency: { @@ -22,11 +21,11 @@ export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(ObserverMixi }; } - static get asyncObservers() { - return { - ...super.asyncObservers, - _onCurrencyChanged: ['currency'], - }; + updated(changedProps) { + super.updated(changedProps); + if (changedProps.has('currency')) { + this._onCurrencyChanged({ currency: this.currency }); + } } get slots() { @@ -57,7 +56,7 @@ export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(ObserverMixi _onCurrencyChanged({ currency }) { if (this._isPrivateSlot('after')) { - this.$$slot('after').textContent = currency; + this.querySelector('[slot=after]').textContent = currency; } this.formatOptions.currency = currency; this._calculateValues(); diff --git a/packages/input-amount/test/lion-input-amount.test.js b/packages/input-amount/test/lion-input-amount.test.js index fda30ec43..50a203b0b 100644 --- a/packages/input-amount/test/lion-input-amount.test.js +++ b/packages/input-amount/test/lion-input-amount.test.js @@ -66,33 +66,33 @@ describe('', () => { it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => { const el = await fixture(``); - expect(el.inputElement.type).to.equal('text'); + expect(el._inputNode.type).to.equal('text'); }); it('shows no currency', async () => { const el = await fixture(``); - expect(el.$$slot('suffix')).to.be.undefined; + expect(el.querySelector('[slot=suffix]')).to.be.null; }); it('displays currency if provided', async () => { const el = await fixture(``); - expect(el.$$slot('after').innerText).to.equal('EUR'); + expect(el.querySelector('[slot=after]').innerText).to.equal('EUR'); }); it('can update currency', async () => { const el = await fixture(``); el.currency = 'USD'; await el.updateComplete; - expect(el.$$slot('after').innerText).to.equal('USD'); + expect(el.querySelector('[slot=after]').innerText).to.equal('USD'); }); it('ignores currency if a suffix is already present', async () => { const el = await fixture( `my-currency`, ); - expect(el.$$slot('suffix').innerText).to.equal('my-currency'); + expect(el.querySelector('[slot=suffix]').innerText).to.equal('my-currency'); el.currency = 'EUR'; await el.updateComplete; - expect(el.$$slot('suffix').innerText).to.equal('my-currency'); + expect(el.querySelector('[slot=suffix]').innerText).to.equal('my-currency'); }); }); diff --git a/packages/input-date/src/LionInputDate.js b/packages/input-date/src/LionInputDate.js index a75fc533e..04e66bd6e 100644 --- a/packages/input-date/src/LionInputDate.js +++ b/packages/input-date/src/LionInputDate.js @@ -6,7 +6,7 @@ import { isDateValidator } from '@lion/validate'; /** * `LionInputDate` is a class for a date custom form element (``). * - * @customElement + * @customElement lion-input-date * @extends {LionInput} */ export class LionInputDate extends FieldCustomMixin(LocalizeMixin(LionInput)) { diff --git a/packages/input-date/test/lion-input-date.test.js b/packages/input-date/test/lion-input-date.test.js index 7307a272a..e0733a0bc 100644 --- a/packages/input-date/test/lion-input-date.test.js +++ b/packages/input-date/test/lion-input-date.test.js @@ -19,7 +19,7 @@ describe('', () => { it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => { const el = await fixture(``); - expect(el.inputElement.type).to.equal('text'); + expect(el._inputNode.type).to.equal('text'); }); it('has validator "isDate" applied by default', async () => { diff --git a/packages/input-datepicker/src/LionCalendarOverlayFrame.js b/packages/input-datepicker/src/LionCalendarOverlayFrame.js index d06c52add..3ec585e68 100644 --- a/packages/input-datepicker/src/LionCalendarOverlayFrame.js +++ b/packages/input-datepicker/src/LionCalendarOverlayFrame.js @@ -1,7 +1,7 @@ -import { html, css, LitElement, DomHelpersMixin } from '@lion/core'; +import { html, css, LitElement } from '@lion/core'; import { LocalizeMixin } from '@lion/localize'; -export class LionCalendarOverlayFrame extends LocalizeMixin(DomHelpersMixin(LitElement)) { +export class LionCalendarOverlayFrame extends LocalizeMixin(LitElement) { static get styles() { return [ css` @@ -43,16 +43,10 @@ export class LionCalendarOverlayFrame extends LocalizeMixin(DomHelpersMixin(LitE switch (locale) { case 'bg-BG': return import('@lion/overlays/translations/bg-BG.js'); - case 'bg': - return import('@lion/overlays/translations/bg.js'); case 'cs-CZ': return import('@lion/overlays/translations/cs-CZ.js'); - case 'cs': - return import('@lion/overlays/translations/cs.js'); case 'de-DE': return import('@lion/overlays/translations/de-DE.js'); - case 'de': - return import('@lion/overlays/translations/de.js'); case 'en-AU': return import('@lion/overlays/translations/en-AU.js'); case 'en-GB': @@ -60,54 +54,32 @@ export class LionCalendarOverlayFrame extends LocalizeMixin(DomHelpersMixin(LitE case 'en-US': return import('@lion/overlays/translations/en-US.js'); case 'en-PH': - case 'en': return import('@lion/overlays/translations/en.js'); case 'es-ES': return import('@lion/overlays/translations/es-ES.js'); - case 'es': - return import('@lion/overlays/translations/es.js'); case 'fr-FR': return import('@lion/overlays/translations/fr-FR.js'); case 'fr-BE': return import('@lion/overlays/translations/fr-BE.js'); - case 'fr': - return import('@lion/overlays/translations/fr.js'); case 'hu-HU': return import('@lion/overlays/translations/hu-HU.js'); - case 'hu': - return import('@lion/overlays/translations/hu.js'); case 'it-IT': return import('@lion/overlays/translations/it-IT.js'); - case 'it': - return import('@lion/overlays/translations/it.js'); case 'nl-BE': return import('@lion/overlays/translations/nl-BE.js'); case 'nl-NL': return import('@lion/overlays/translations/nl-NL.js'); - case 'nl': - return import('@lion/overlays/translations/nl.js'); case 'pl-PL': return import('@lion/overlays/translations/pl-PL.js'); - case 'pl': - return import('@lion/overlays/translations/pl.js'); case 'ro-RO': return import('@lion/overlays/translations/ro-RO.js'); - case 'ro': - return import('@lion/overlays/translations/ro.js'); case 'ru-RU': return import('@lion/overlays/translations/ru-RU.js'); - case 'ru': - return import('@lion/overlays/translations/ru.js'); case 'sk-SK': return import('@lion/overlays/translations/sk-SK.js'); - case 'sk': - return import('@lion/overlays/translations/sk.js'); case 'uk-UA': return import('@lion/overlays/translations/uk-UA.js'); - case 'uk': - return import('@lion/overlays/translations/uk.js'); case 'zh-CN': - case 'zh': return import('@lion/overlays/translations/zh.js'); default: return import(`@lion/overlays/translations/${locale}.js`); diff --git a/packages/input-email/src/LionInputEmail.js b/packages/input-email/src/LionInputEmail.js index 99a0a4f59..7a92730d1 100644 --- a/packages/input-email/src/LionInputEmail.js +++ b/packages/input-email/src/LionInputEmail.js @@ -6,8 +6,8 @@ import { isEmailValidator } from '@lion/validate'; /** * LionInputEmail: extension of lion-input * - * @customElement - * @extends LionInput + * @customElement lion-input-email + * @extends {LionInput} */ export class LionInputEmail extends FieldCustomMixin(LocalizeMixin(LionInput)) { getValidatorsForType(type) { diff --git a/packages/input-email/test/lion-input-email.test.js b/packages/input-email/test/lion-input-email.test.js index a8911bacc..5bc6cd56d 100644 --- a/packages/input-email/test/lion-input-email.test.js +++ b/packages/input-email/test/lion-input-email.test.js @@ -5,7 +5,7 @@ import '../lion-input-email.js'; describe('', () => { it('has a type = text', async () => { const lionInputEmail = await fixture(``); - expect(lionInputEmail.inputElement.type).to.equal('text'); + expect(lionInputEmail._inputNode.type).to.equal('text'); }); it('has validator "isEmail" applied by default', async () => { diff --git a/packages/input-iban/test/lion-input-iban.test.js b/packages/input-iban/test/lion-input-iban.test.js index bfa578cdc..3935f1b22 100644 --- a/packages/input-iban/test/lion-input-iban.test.js +++ b/packages/input-iban/test/lion-input-iban.test.js @@ -20,7 +20,7 @@ describe('', () => { it('has a type = text', async () => { const el = await fixture(``); - expect(el.inputElement.type).to.equal('text'); + expect(el._inputNode.type).to.equal('text'); }); it('has validator "isIBAN" applied by default', async () => { diff --git a/packages/input/src/LionInput.js b/packages/input/src/LionInput.js index 9db0a427c..1140de38a 100644 --- a/packages/input/src/LionInput.js +++ b/packages/input/src/LionInput.js @@ -3,8 +3,8 @@ import { LionField } from '@lion/field'; /** * LionInput: extension of lion-field with native input element in place and user friendly API * - * @customElement - * @extends LionField + * @customElement lion-input + * @extends {LionField} */ export class LionInput extends LionField { static get properties() { @@ -26,10 +26,6 @@ export class LionInput extends LionField { type: String, reflect: true, }, - step: { - type: Number, - reflect: true, - }, placeholder: { type: String, reflect: true, @@ -56,12 +52,6 @@ export class LionInput extends LionField { super(); this.readOnly = false; this.type = 'text'; - /** - * Only application to type="amount" & type="range" - * - * @deprecated - */ - this.step = undefined; } _requestUpdate(name, oldValue) { @@ -79,19 +69,16 @@ export class LionInput extends LionField { updated(changedProps) { super.updated(changedProps); if (changedProps.has('type')) { - this.inputElement.type = this.type; - } - if (changedProps.has('step')) { - this.inputElement.step = this.step; + this._inputNode.type = this.type; } if (changedProps.has('placeholder')) { - this.inputElement.placeholder = this.placeholder; + this._inputNode.placeholder = this.placeholder; } } __delegateReadOnly() { - if (this.inputElement) { - this.inputElement.readOnly = this.readOnly; + if (this._inputNode) { + this._inputNode.readOnly = this.readOnly; } } } diff --git a/packages/input/test/lion-input.test.js b/packages/input/test/lion-input.test.js index aefbd6f39..9a1f74836 100644 --- a/packages/input/test/lion-input.test.js +++ b/packages/input/test/lion-input.test.js @@ -7,46 +7,38 @@ describe('', () => { const el = await fixture( ``, ); - expect(el.inputElement.readOnly).to.equal(true); + expect(el._inputNode.readOnly).to.equal(true); el.readOnly = false; await el.updateComplete; expect(el.readOnly).to.equal(false); - expect(el.inputElement.readOnly).to.equal(false); - }); - - it('delegates "step" attribute and property', async () => { - const el = await fixture(``); - expect(el.inputElement.step).to.equal('0.01'); - // TODO: activate when DelegateMixin is refactored - // const el2 = await fixture(``); - // expect(el2.inputElement.step).to.equal('0.02'); + expect(el._inputNode.readOnly).to.equal(false); }); it('automatically creates an element if not provided by user', async () => { const el = await fixture(``); - expect(el.querySelector('input')).to.equal(el.inputElement); + expect(el.querySelector('input')).to.equal(el._inputNode); }); it('has a type which is reflected to an attribute and is synced down to the native input', async () => { const el = await fixture(``); expect(el.type).to.equal('text'); expect(el.getAttribute('type')).to.equal('text'); - expect(el.inputElement.getAttribute('type')).to.equal('text'); + expect(el._inputNode.getAttribute('type')).to.equal('text'); el.type = 'foo'; await el.updateComplete; expect(el.getAttribute('type')).to.equal('foo'); - expect(el.inputElement.getAttribute('type')).to.equal('foo'); + expect(el._inputNode.getAttribute('type')).to.equal('foo'); }); it('has an attribute that can be used to set the placeholder text of the input', async () => { const el = await fixture(``); expect(el.getAttribute('placeholder')).to.equal('text'); - expect(el.inputElement.getAttribute('placeholder')).to.equal('text'); + expect(el._inputNode.getAttribute('placeholder')).to.equal('text'); el.placeholder = 'foo'; await el.updateComplete; expect(el.getAttribute('placeholder')).to.equal('foo'); - expect(el.inputElement.getAttribute('placeholder')).to.equal('foo'); + expect(el._inputNode.getAttribute('placeholder')).to.equal('foo'); }); }); diff --git a/packages/localize/RATIONALE.md b/packages/localize/RATIONALE.md index dbd19d3fe..aa5818f96 100644 --- a/packages/localize/RATIONALE.md +++ b/packages/localize/RATIONALE.md @@ -46,7 +46,7 @@ We chose `Intl MessageFormat` as a format for translation parts because: ### Fallbacks -> Important: language-only locales are now deprecated, and cause a warning. This is because language only locales cause bugs with date and number formatting. It also makes writing locale based tooling harder and more cumbersome. Usage is highly discouraged. +> Important: language-only locales are now removed, and cause an error if you try to use it. This is because language only locales cause bugs with date and number formatting. It also makes writing locale based tooling harder and more cumbersome. We decided to have a fallback mechanism in case a dialect (e.g. `nl-NL.js`) is not defined, but generic language (e.g. `nl.js`) is, because we wanted to support legacy applications which used [Polymer's AppLocalizeBehavior](https://polymer-library.polymer-project.org/3.0/docs/apps/localize) and could not instantly switch to using full dialects. diff --git a/packages/localize/docs/message.md b/packages/localize/docs/message.md index 7778d4ff5..a3f247b2a 100644 --- a/packages/localize/docs/message.md +++ b/packages/localize/docs/message.md @@ -29,7 +29,7 @@ Below, we show a common and easy example, it assumes you have your translation f ```js import { LocalizeMixin } from '@lion/localize'; -class MyHelloComponent extends LocalizeMixin(LionLitElement) { +class MyHelloComponent extends LocalizeMixin(LitElement) { static get localizeNamespaces() { // using an explicit loader function return [ @@ -63,7 +63,7 @@ static get localizeNamespaces() { If you don't want your rendering to wait for localize namespaces to have loaded, this is how you override it. If you use `performUpdate()`, this will now also not wait for localize namespaces to have loaded. ```js -class MyHelloComponent extends LocalizeMixin(LionLitElement) { +class MyHelloComponent extends LocalizeMixin(LitElement) { static get localizeNamespaces() { // using an explicit loader function return [ diff --git a/packages/localize/src/LocalizeManager.js b/packages/localize/src/LocalizeManager.js index 938322c20..64f932f10 100644 --- a/packages/localize/src/LocalizeManager.js +++ b/packages/localize/src/LocalizeManager.js @@ -43,16 +43,21 @@ export class LocalizeManager extends LionSingleton { this._setupHtmlLangAttributeObserver(); if (!value.includes('-')) { - console.warn(` - Locale was set to ${value}. - Language only locales are deprecated, please use the full language locale e.g. 'en-GB' instead of 'en'. - See https://github.com/ing-bank/lion/issues/187 for more information. - `); + this.__handleLanguageOnly(value); } this._onLocaleChanged(value, oldLocale); } + // eslint-disable-next-line class-methods-use-this + __handleLanguageOnly(value) { + throw new Error(` + Locale was set to ${value}. + Language only locales are not allowed, please use the full language locale e.g. 'en-GB' instead of 'en'. + See https://github.com/ing-bank/lion/issues/187 for more information. + `); + } + get loadingComplete() { return Promise.all(Object.values(this.__namespaceLoaderPromisesCache[this.locale])); } diff --git a/packages/localize/stories/message.stories.js b/packages/localize/stories/message.stories.js index 44f821762..8d509d043 100644 --- a/packages/localize/stories/message.stories.js +++ b/packages/localize/stories/message.stories.js @@ -1,9 +1,9 @@ import { storiesOf, html } from '@open-wc/demoing-storybook'; -import { LionLitElement } from '@lion/core/src/LionLitElement.js'; +import { LitElement } from '@lion/core'; import { localize, LocalizeMixin } from '../index.js'; storiesOf('Localize System|Message', module).add('locale', () => { - class messageExample extends LocalizeMixin(LionLitElement) { + class messageExample extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [ { 'lit-html-example': locale => import(`./translations/${locale}.js`) }, diff --git a/packages/localize/test/LocalizeManager.test.js b/packages/localize/test/LocalizeManager.test.js index 48fef29c5..fe2048baa 100644 --- a/packages/localize/test/LocalizeManager.test.js +++ b/packages/localize/test/LocalizeManager.test.js @@ -303,23 +303,22 @@ describe('LocalizeManager', () => { throw new Error('did not throw'); }); - it('throws a warning if the locale set by the user is not a full language locale', async () => { - const spy = sinon.spy(console, 'warn'); + it('throws an error if the locale set by the user is not a full language locale', async () => { manager = new LocalizeManager(); - manager.locale = 'nl'; - - expect(spy.callCount).to.equal(1); - console.warn.restore(); + expect(() => { + manager.locale = 'nl'; + }).to.throw(` + Locale was set to nl. + Language only locales are not allowed, please use the full language locale e.g. 'en-GB' instead of 'en'. + See https://github.com/ing-bank/lion/issues/187 for more information. + `); }); - it('does not throw a warning if locale was set through the html lang attribute', async () => { - const spy = sinon.spy(console, 'warn'); + it('does not throw an error if locale was set through the html lang attribute', async () => { manager = new LocalizeManager(); - document.documentElement.lang = 'nl'; - await aTimeout(50); // wait for mutation observer to be called - - expect(spy.callCount).to.equal(0); - console.warn.restore(); + expect(() => { + document.documentElement.lang = 'nl'; + }).to.not.throw(); }); }); }); diff --git a/packages/localize/test/LocalizeMixin.test.js b/packages/localize/test/LocalizeMixin.test.js index 8bf2c714b..b2705f60e 100644 --- a/packages/localize/test/LocalizeMixin.test.js +++ b/packages/localize/test/LocalizeMixin.test.js @@ -8,8 +8,7 @@ import { html, } from '@open-wc/testing'; import sinon from 'sinon'; -import { isDirective } from '@lion/core'; -import { LionLitElement } from '@lion/core/src/LionLitElement.js'; +import { LitElement, isDirective } from '@lion/core'; import { localizeTearDown, setupEmptyFakeImportsFor, @@ -295,7 +294,7 @@ describe('LocalizeMixin', () => { }); const tag = defineCE( - class extends LocalizeMixin(LionLitElement) { + class extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; } @@ -328,7 +327,7 @@ describe('LocalizeMixin', () => { }); const tag = defineCE( - class TestPromise extends LocalizeMixin(LionLitElement) { + class TestPromise extends LocalizeMixin(LitElement) { static get localizeNamespaces() { return [myElementNs, ...super.localizeNamespaces]; } @@ -360,7 +359,7 @@ describe('LocalizeMixin', () => { }); const tag = defineCE( - class extends LocalizeMixin(LionLitElement) { + class extends LocalizeMixin(LitElement) { static get waitForLocalizeNamespaces() { return false; } diff --git a/packages/radio-group/src/LionRadioGroup.js b/packages/radio-group/src/LionRadioGroup.js index 5be3cba18..4a6506995 100644 --- a/packages/radio-group/src/LionRadioGroup.js +++ b/packages/radio-group/src/LionRadioGroup.js @@ -20,8 +20,8 @@ import { LionFieldset } from '@lion/fieldset'; * It extends LionFieldset so it inherits it's features. * * - * @customElement - * @extends LionFieldset + * @customElement lion-radio-group + * @extends {LionFieldset} */ export class LionRadioGroup extends LionFieldset { @@ -65,28 +65,28 @@ export class LionRadioGroup extends LionFieldset { _checkRadioElements(ev) { const { target } = ev; - if (target.type !== 'radio' || target.choiceChecked === false) return; + if (target.type !== 'radio' || target.checked === false) return; const groupName = target.name; this.formElementsArray .filter(i => i.name === groupName) .forEach(radio => { if (radio !== target) { - radio.choiceChecked = false; // eslint-disable-line no-param-reassign + radio.checked = false; // eslint-disable-line no-param-reassign } }); this.__triggerCheckedValueChanged(); } _getCheckedRadioElement() { - const filtered = this.formElementsArray.filter(el => el.choiceChecked === true); + const filtered = this.formElementsArray.filter(el => el.checked === true); return filtered.length > 0 ? filtered[0] : undefined; } _setCheckedRadioElement(value, check) { for (let i = 0; i < this.formElementsArray.length; i += 1) { if (check(this.formElementsArray[i], value)) { - this.formElementsArray[i].choiceChecked = true; + this.formElementsArray[i].checked = true; return; } } diff --git a/packages/radio-group/test/lion-radio-group.test.js b/packages/radio-group/test/lion-radio-group.test.js index 57a16ffa2..34a0e0fb9 100644 --- a/packages/radio-group/test/lion-radio-group.test.js +++ b/packages/radio-group/test/lion-radio-group.test.js @@ -8,15 +8,15 @@ describe('', () => { const el = await fixture(html` - + `); await nextFrame(); expect(el.checkedValue).to.equal('female'); - el.formElementsArray[0].choiceChecked = true; + el.formElementsArray[0].checked = true; expect(el.checkedValue).to.equal('male'); - el.formElementsArray[2].choiceChecked = true; + el.formElementsArray[2].checked = true; expect(el.checkedValue).to.equal('alien'); }); @@ -26,27 +26,27 @@ describe('', () => { const el = await fixture(html` - + `); await nextFrame(); expect(el.checkedValue).to.equal(date); - el.formElementsArray[0].choiceChecked = true; + el.formElementsArray[0].checked = true; expect(el.checkedValue).to.deep.equal({ some: 'data' }); }); it('can handle 0 and empty string as valid values ', async () => { const el = await fixture(html` - + `); await nextFrame(); expect(el.checkedValue).to.equal(0); - el.formElementsArray[1].choiceChecked = true; + el.formElementsArray[1].checked = true; expect(el.checkedValue).to.equal(''); }); @@ -54,7 +54,7 @@ describe('', () => { const el = await fixture(html` - + `); @@ -85,7 +85,7 @@ describe('', () => { await nextFrame(); expect(el.checkedValue).to.equal('female'); el.checkedValue = 'alien'; - expect(el.formElementsArray[2].choiceChecked).to.be.true; + expect(el.formElementsArray[2].checked).to.be.true; }); it('fires checked-value-changed event only once per checked change', async () => { @@ -106,14 +106,14 @@ describe('', () => { /* eslint-enable indent */ expect(counter).to.equal(0); - el.formElementsArray[0].choiceChecked = true; + el.formElementsArray[0].checked = true; expect(counter).to.equal(1); // not changed values trigger no event - el.formElementsArray[0].choiceChecked = true; + el.formElementsArray[0].checked = true; expect(counter).to.equal(1); - el.formElementsArray[2].choiceChecked = true; + el.formElementsArray[2].checked = true; expect(counter).to.equal(2); // not found values trigger no event @@ -142,14 +142,14 @@ describe('', () => { /* eslint-enable indent */ counter = 0; // reset after setup which may result in different results - el.formElementsArray[0].choiceChecked = true; + el.formElementsArray[0].checked = true; expect(counter).to.equal(2); // male becomes checked, female becomes unchecked // not changed values trigger no event - el.formElementsArray[0].choiceChecked = true; + el.formElementsArray[0].checked = true; expect(counter).to.equal(2); - el.formElementsArray[2].choiceChecked = true; + el.formElementsArray[2].checked = true; expect(counter).to.equal(4); // alien becomes checked, male becomes unchecked // not found values trigger no event @@ -176,24 +176,18 @@ describe('', () => { const female = el.formElements['gender[]'][1]; const femaleInput = female.querySelector('input'); - expect(male.choiceChecked).to.equal(false); - expect(female.choiceChecked).to.equal(false); + expect(male.checked).to.equal(false); + expect(female.checked).to.equal(false); maleInput.focus(); maleInput.click(); - expect(male.choiceChecked).to.equal(true); - expect(female.choiceChecked).to.equal(false); - await el.updateComplete; - expect(Array.from(male.classList)).to.contain('state-checked'); - expect(Array.from(female.classList)).to.not.contain('state-checked'); + expect(male.checked).to.equal(true); + expect(female.checked).to.equal(false); femaleInput.focus(); femaleInput.click(); - expect(male.choiceChecked).to.equal(false); - expect(female.choiceChecked).to.equal(true); - await el.updateComplete; - expect(Array.from(male.classList)).to.not.contain('state-checked'); - expect(Array.from(female.classList)).to.contain('state-checked'); + expect(male.checked).to.equal(false); + expect(female.checked).to.equal(true); }); it('should have role = radiogroup', async () => { @@ -222,7 +216,7 @@ describe('', () => { await nextFrame(); expect(el.error.required).to.be.true; - el.formElements['gender[]'][0].choiceChecked = true; + el.formElements['gender[]'][0].checked = true; expect(el.error.required).to.be.undefined; }); @@ -234,9 +228,7 @@ describe('', () => { `); - await nextFrame(); - group.formElements['gender[]'][0].choiceChecked = true; - + group.formElements['gender[]'][0].checked = true; expect(group.serializedValue).to.deep.equal({ checked: true, value: 'male' }); }); diff --git a/packages/radio/README.md b/packages/radio/README.md index 63dec6509..d7a6c48c6 100644 --- a/packages/radio/README.md +++ b/packages/radio/README.md @@ -6,9 +6,9 @@ ## Features -- Get or set the checked state (boolean) - `choiceChecked()` -- Get or set the value of the choice - `choiceValue()` +- Get the checked state (boolean) - `checked` boolean attribute - Pre-select an option by setting the `checked` boolean attribute +- Get or set the value of the choice - `choiceValue()` ## How to use diff --git a/packages/radio/src/LionRadio.js b/packages/radio/src/LionRadio.js index 0eef3df75..f9bab516e 100644 --- a/packages/radio/src/LionRadio.js +++ b/packages/radio/src/LionRadio.js @@ -19,8 +19,8 @@ import { ChoiceInputMixin } from '@lion/choice-input'; * * * - * @customElement - * @extends ChoiceInputMixin(LionInput) + * @customElement lion-radio + * @extends {LionInput} */ export class LionRadio extends ChoiceInputMixin(LionInput) { connectedCallback() { diff --git a/packages/radio/test/lion-radio.test.js b/packages/radio/test/lion-radio.test.js index e0694a6d8..fcf69b19e 100644 --- a/packages/radio/test/lion-radio.test.js +++ b/packages/radio/test/lion-radio.test.js @@ -16,6 +16,6 @@ describe('', () => { `); await nextFrame(); - expect(el.children[1].inputElement.getAttribute('type')).to.equal('radio'); + expect(el.children[1]._inputNode.getAttribute('type')).to.equal('radio'); }); }); diff --git a/packages/select-rich/src/LionOptions.js b/packages/select-rich/src/LionOptions.js index 3dc34b897..b0830b47b 100644 --- a/packages/select-rich/src/LionOptions.js +++ b/packages/select-rich/src/LionOptions.js @@ -5,7 +5,7 @@ import { FormRegistrarPortalMixin } from '@lion/field'; * LionOptions * * @customElement lion-options - * @extends LitElement + * @extends {LitElement} */ export class LionOptions extends FormRegistrarPortalMixin(LitElement) { static get properties() { diff --git a/packages/select-rich/src/LionSelectInvoker.js b/packages/select-rich/src/LionSelectInvoker.js index 2a4bb80dc..fcc01c144 100644 --- a/packages/select-rich/src/LionSelectInvoker.js +++ b/packages/select-rich/src/LionSelectInvoker.js @@ -5,7 +5,7 @@ import { html } from '@lion/core'; * LionSelectInvoker: invoker button consuming a selected element * * @customElement lion-select-invoker - * @extends LionButton + * @extends {LionButton} */ export class LionSelectInvoker extends LionButton { static get properties() { diff --git a/packages/select-rich/src/LionSelectRich.js b/packages/select-rich/src/LionSelectRich.js index 1eb11b9cb..17cfe3b48 100644 --- a/packages/select-rich/src/LionSelectRich.js +++ b/packages/select-rich/src/LionSelectRich.js @@ -23,7 +23,7 @@ function detectInteractionMode() { * LionSelectRich: wraps the element * * @customElement lion-select-rich - * @extends LionField + * @extends {LitElement} */ export class LionSelectRich extends OverlayMixin( FormRegistrarMixin(InteractionStateMixin(ValidateMixin(FormControlMixin(SlotMixin(LitElement))))), @@ -272,12 +272,12 @@ export class LionSelectRich extends OverlayMixin( } /** - * add same aria-label to invokerNode as inputElement + * add same aria-label to invokerNode as _inputNode * @override */ _onAriaLabelledbyChanged({ _ariaLabelledby }) { - if (this.inputElement) { - this.inputElement.setAttribute('aria-labelledby', _ariaLabelledby); + if (this._inputNode) { + this._inputNode.setAttribute('aria-labelledby', _ariaLabelledby); } if (this._invokerNode) { this._invokerNode.setAttribute( @@ -288,12 +288,12 @@ export class LionSelectRich extends OverlayMixin( } /** - * add same aria-label to invokerNode as inputElement + * add same aria-label to invokerNode as _inputNode * @override */ _onAriaDescribedbyChanged({ _ariaDescribedby }) { - if (this.inputElement) { - this.inputElement.setAttribute('aria-describedby', _ariaDescribedby); + if (this._inputNode) { + this._inputNode.setAttribute('aria-describedby', _ariaDescribedby); } if (this._invokerNode) { this._invokerNode.setAttribute('aria-describedby', _ariaDescribedby); diff --git a/packages/select/src/LionSelect.js b/packages/select/src/LionSelect.js index 9eaf2b5fd..96708eddf 100644 --- a/packages/select/src/LionSelect.js +++ b/packages/select/src/LionSelect.js @@ -24,8 +24,8 @@ import { LionField } from '@lion/field'; * where each option name starts with the same word or phrase can also significantly degrade * usability for keyboard and screen reader users. * - * @customElement - * @extends LionField + * @customElement lion-select + * @extends {LionField} */ // eslint-disable-next-line no-unused-vars @@ -41,7 +41,7 @@ export class LionSelect extends LionField { } _proxyChangeEvent() { - this.inputElement.dispatchEvent( + this._inputNode.dispatchEvent( new CustomEvent('user-input-changed', { bubbles: true, composed: true, diff --git a/packages/steps/README.md b/packages/steps/README.md index 5f85acde4..7b2bcfd71 100644 --- a/packages/steps/README.md +++ b/packages/steps/README.md @@ -50,11 +50,11 @@ The first step needs to be explicitely set via `initial-step` so that it get sta ```js ... next() { - return this.$id('steps').next(); + return this.shadowRoot.getElementById('steps').next(); } previous() { - return this.$id('steps').previous(); + return this.shadowRoot.getElementById('steps').previous(); } ... ``` diff --git a/packages/steps/src/LionStep.js b/packages/steps/src/LionStep.js index 176e03c80..3db30564d 100644 --- a/packages/steps/src/LionStep.js +++ b/packages/steps/src/LionStep.js @@ -1,12 +1,12 @@ -import { html, css } from '@lion/core'; -import { LionLitElement } from '@lion/core/src/LionLitElement.js'; +import { LitElement, html, css } from '@lion/core'; /** * `LionStep` is one of many in a LionSteps Controller * - * @customElement + * @customElement lion-step + * @extends {LitElement} */ -export class LionStep extends LionLitElement { +export class LionStep extends LitElement { static get properties() { /** * Fired when the step is entered. diff --git a/packages/steps/src/LionSteps.js b/packages/steps/src/LionSteps.js index cb98119db..fafa064c9 100644 --- a/packages/steps/src/LionSteps.js +++ b/packages/steps/src/LionSteps.js @@ -1,13 +1,12 @@ -import { html, css } from '@lion/core'; -import { LionLitElement } from '@lion/core/src/LionLitElement.js'; -import { ObserverMixin } from '@lion/core/src/ObserverMixin.js'; +import { LitElement, html, css } from '@lion/core'; /** * `LionSteps` is a controller for a multi step system. * - * @customElement + * @customElement lion-steps + * @extends {LitElement} */ -export class LionSteps extends ObserverMixin(LionLitElement) { +export class LionSteps extends LitElement { static get properties() { /** * Fired when a transition between steps happens. @@ -32,10 +31,11 @@ export class LionSteps extends ObserverMixin(LionLitElement) { }; } - static get asyncObservers() { - return { - _onCurrentChanged: ['current'], - }; + updated(changedProps) { + super.updated(changedProps); + if (changedProps.has('current')) { + this._onCurrentChanged({ current: this.current }, { current: changedProps.get('current') }); + } } constructor() { diff --git a/packages/switch/README.md b/packages/switch/README.md index 65783bbef..f21d61818 100644 --- a/packages/switch/README.md +++ b/packages/switch/README.md @@ -6,9 +6,9 @@ ## Features -- Get or set the checked state (boolean) - `choiceChecked()` -- Get or set the value of the choice - `choiceValue()` +- Get or set the checked state (boolean) - `checked` boolean attribute - Pre-select an option by setting the `checked` boolean attribute +- Get or set the value of the choice - `choiceValue()` ## How to use diff --git a/packages/switch/src/LionSwitch.js b/packages/switch/src/LionSwitch.js index 0e7284ec4..b4c5a9463 100644 --- a/packages/switch/src/LionSwitch.js +++ b/packages/switch/src/LionSwitch.js @@ -34,7 +34,7 @@ export class LionSwitch extends ChoiceInputMixin(LionField) { connectedCallback() { super.connectedCallback(); - this.inputElement.addEventListener( + this._inputNode.addEventListener( 'checked-changed', this.__handleButtonSwitchCheckedChanged.bind(this), ); @@ -49,11 +49,11 @@ export class LionSwitch extends ChoiceInputMixin(LionField) { __handleButtonSwitchCheckedChanged() { // TODO: should be replaced by "_inputNode" after the next breaking change // https://github.com/ing-bank/lion/blob/master/packages/field/src/FormControlMixin.js#L78 - this.checked = this.inputElement.checked; + this.checked = this._inputNode.checked; } _syncButtonSwitch() { - this.inputElement.checked = this.checked; - this.inputElement.disabled = this.disabled; + this._inputNode.checked = this.checked; + this._inputNode.disabled = this.disabled; } } diff --git a/packages/switch/test/lion-switch.test.js b/packages/switch/test/lion-switch.test.js index 3ad32b028..fc1e31558 100644 --- a/packages/switch/test/lion-switch.test.js +++ b/packages/switch/test/lion-switch.test.js @@ -14,12 +14,12 @@ describe('lion-switch', () => { const el = await fixture(html` `); - expect(el.inputElement.disabled).to.be.true; - expect(el.inputElement.hasAttribute('disabled')).to.be.true; + expect(el._inputNode.disabled).to.be.true; + expect(el._inputNode.hasAttribute('disabled')).to.be.true; el.disabled = false; await el.updateComplete; - expect(el.inputElement.disabled).to.be.false; - expect(el.inputElement.hasAttribute('disabled')).to.be.false; + expect(el._inputNode.disabled).to.be.false; + expect(el._inputNode.hasAttribute('disabled')).to.be.false; }); it('should sync its "checked" state to child button', async () => { @@ -29,21 +29,21 @@ describe('lion-switch', () => { const checkedEl = await fixture(html` `); - expect(uncheckedEl.inputElement.checked).to.be.false; - expect(checkedEl.inputElement.checked).to.be.true; + expect(uncheckedEl._inputNode.checked).to.be.false; + expect(checkedEl._inputNode.checked).to.be.true; uncheckedEl.checked = true; checkedEl.checked = false; await uncheckedEl.updateComplete; await checkedEl.updateComplete; - expect(uncheckedEl.inputElement.checked).to.be.true; - expect(checkedEl.inputElement.checked).to.be.false; + expect(uncheckedEl._inputNode.checked).to.be.true; + expect(checkedEl._inputNode.checked).to.be.false; }); it('should sync "checked" state received from child button', async () => { const el = await fixture(html` `); - const button = el.inputElement; + const button = el._inputNode; expect(el.checked).to.be.false; button.click(); expect(el.checked).to.be.true; diff --git a/packages/textarea/src/LionTextarea.js b/packages/textarea/src/LionTextarea.js index ab9315200..1e5f36bd5 100644 --- a/packages/textarea/src/LionTextarea.js +++ b/packages/textarea/src/LionTextarea.js @@ -5,8 +5,8 @@ import { css } from '@lion/core'; /** * LionTextarea: extension of lion-field with native input element in place and user friendly API * - * @customElement - * @extends LionInput + * @customElement lion-textarea + * @extends {LionField} */ export class LionTextarea extends LionField { static get properties() { @@ -57,20 +57,20 @@ export class LionTextarea extends LionField { } disconnectedCallback() { - autosize.destroy(this.inputElement); + autosize.destroy(this._inputNode); } updated(changedProperties) { super.updated(changedProperties); if (changedProperties.has('rows')) { - const native = this.inputElement; + const native = this._inputNode; if (native) { native.rows = this.rows; } } if (changedProperties.has('readOnly')) { - const native = this.inputElement; + const native = this._inputNode; if (native) { native.readOnly = this.readOnly; } @@ -89,19 +89,19 @@ export class LionTextarea extends LionField { * To support maxRows we need to set max-height of the textarea */ setTextareaMaxHeight() { - const { value } = this.inputElement; - this.inputElement.value = ''; + const { value } = this._inputNode; + this._inputNode.value = ''; this.resizeTextarea(); - const cs = window.getComputedStyle(this.inputElement, null); + const cs = window.getComputedStyle(this._inputNode, null); const lineHeight = parseFloat(cs.lineHeight) || parseFloat(cs.height) / this.rows; const paddingOffset = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom); const borderOffset = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth); const offset = cs.boxSizing === 'border-box' ? paddingOffset + borderOffset : 0; - this.inputElement.style.maxHeight = `${lineHeight * this.maxRows + offset}px`; + this._inputNode.style.maxHeight = `${lineHeight * this.maxRows + offset}px`; - this.inputElement.value = value; + this._inputNode.value = value; this.resizeTextarea(); } @@ -124,7 +124,7 @@ export class LionTextarea extends LionField { } resizeTextarea() { - autosize.update(this.inputElement); + autosize.update(this._inputNode); } __initializeAutoresize() { @@ -140,7 +140,7 @@ export class LionTextarea extends LionField { async __waitForTextareaRenderedInRealDOM() { let count = 3; // max tasks to wait for - while (count !== 0 && !this.__shady_native_contains(this.inputElement)) { + while (count !== 0 && !this.__shady_native_contains(this._inputNode)) { // eslint-disable-next-line no-await-in-loop await new Promise(resolve => setTimeout(resolve)); count -= 1; @@ -148,7 +148,7 @@ export class LionTextarea extends LionField { } __startAutoresize() { - autosize(this.inputElement); + autosize(this._inputNode); this.setTextareaMaxHeight(); } } diff --git a/packages/textarea/test/lion-input-iban-integrations.test.js b/packages/textarea/test/lion-textarea-integrations.test.js similarity index 100% rename from packages/textarea/test/lion-input-iban-integrations.test.js rename to packages/textarea/test/lion-textarea-integrations.test.js diff --git a/packages/textarea/test/lion-textarea.test.js b/packages/textarea/test/lion-textarea.test.js index 4cc716897..72e244c46 100644 --- a/packages/textarea/test/lion-textarea.test.js +++ b/packages/textarea/test/lion-textarea.test.js @@ -26,18 +26,18 @@ describe('', () => { const el = await fixture(`foo`); expect(el.rows).to.equal(2); expect(el.getAttribute('rows')).to.be.equal('2'); - expect(el.inputElement.rows).to.equal(2); - expect(el.inputElement.getAttribute('rows')).to.be.equal('2'); + expect(el._inputNode.rows).to.equal(2); + expect(el._inputNode.getAttribute('rows')).to.be.equal('2'); expect(el.readOnly).to.be.false; - expect(el.inputElement.hasAttribute('readonly')).to.be.false; + expect(el._inputNode.hasAttribute('readonly')).to.be.false; }); it('sync rows down to the native textarea', async () => { const el = await fixture(`foo`); expect(el.rows).to.equal(8); expect(el.getAttribute('rows')).to.be.equal('8'); - expect(el.inputElement.rows).to.equal(8); - expect(el.inputElement.getAttribute('rows')).to.be.equal('8'); + expect(el._inputNode.rows).to.equal(8); + expect(el._inputNode.getAttribute('rows')).to.be.equal('8'); }); it('sync readOnly to the native textarea', async () => { @@ -52,7 +52,7 @@ describe('', () => { } const el = await fixture(``); - const computedStyle = window.getComputedStyle(el.inputElement); + const computedStyle = window.getComputedStyle(el._inputNode); expect(computedStyle.resize).to.equal('none'); }); diff --git a/packages/validate/src/ValidateMixin.js b/packages/validate/src/ValidateMixin.js index a46c77d9a..3b61e7722 100644 --- a/packages/validate/src/ValidateMixin.js +++ b/packages/validate/src/ValidateMixin.js @@ -294,12 +294,12 @@ export const ValidateMixin = dedupeMixin( } get _feedbackElement() { - return (this.$$slot && this.$$slot('feedback')) || this.querySelector('[slot="feedback"]'); + return this.querySelector('[slot="feedback"]'); } getFieldName(validatorParams) { - const label = - this.label || (this.$$slot && this.$$slot('label') && this.$$slot('label').textContent); + const labelEl = this.querySelector('[slot=label]'); + const label = this.label || (labelEl && labelEl.textContent); if (validatorParams && validatorParams.fieldName) { return validatorParams.fieldName; @@ -413,6 +413,8 @@ export const ValidateMixin = dedupeMixin( /** * Can be overridden by sub classers + * Note that it's important to always render your feedback to the _feedbackElement textContent! + * This is necessary because it is allocated as the feedback slot, which is what the mixin renders feedback to. */ renderFeedback() { if (this._feedbackElement) { @@ -422,10 +424,10 @@ export const ValidateMixin = dedupeMixin( _onErrorShowChangedAsync() { // Screen reader output should be in sync with visibility of error messages - if (this.inputElement) { - this.inputElement.setAttribute('aria-invalid', this.errorShow); + if (this._inputNode) { + this._inputNode.setAttribute('aria-invalid', this.errorShow); // TODO: test and see if needed for a11y - // this.inputElement.setCustomValidity(this._validationMessage || ''); + // this._inputNode.setCustomValidity(this._validationMessage || ''); } } diff --git a/packages/validate/test/ValidateMixin.test.js b/packages/validate/test/ValidateMixin.test.js index 326e747d0..7f3381ffc 100644 --- a/packages/validate/test/ValidateMixin.test.js +++ b/packages/validate/test/ValidateMixin.test.js @@ -1,7 +1,7 @@ /* eslint-disable no-unused-vars, no-param-reassign */ import { expect, fixture, html, unsafeStatic, defineCE, aTimeout } from '@open-wc/testing'; import sinon from 'sinon'; -import { LionLitElement } from '@lion/core/src/LionLitElement.js'; +import { LitElement } from '@lion/core'; import { localizeTearDown } from '@lion/localize/test-helpers.js'; import { localize } from '@lion/localize'; @@ -16,7 +16,7 @@ const suffixName = ''; const lightDom = ''; const tagString = defineCE( - class extends ValidateMixin(LionLitElement) { + class extends ValidateMixin(LitElement) { static get properties() { return { modelValue: { @@ -47,7 +47,7 @@ describe('ValidateMixin', () => { * The element ('this') the ValidateMixin is applied on. * * - *input-element* - * The 'this.inputElement' property (usually a getter) that returns/contains a reference to an + * The 'this._inputNode' property (usually a getter) that returns/contains a reference to an * interaction element that receives focus, displays the input value, interaction states are * derived from, aria properties are put on and setCustomValidity (if applicable) is called on. * Can be input, textarea, my-custom-slider etc. @@ -654,7 +654,7 @@ describe('ValidateMixin', () => { } const defaultElement = defineCE( - class extends ValidateMixin(LionLitElement) { + class extends ValidateMixin(LitElement) { static get properties() { return { modelValue: { @@ -707,13 +707,15 @@ describe('ValidateMixin', () => { >${lightDom} `); - expect(el.$$slot('feedback').innerText).to.equal(''); + expect(el.querySelector('[slot=feedback]').innerText).to.equal(''); showErrors = true; el.validate(); await el.updateComplete; - expect(el.$$slot('feedback').innerText).to.equal('This is error message for alwaysFalse'); + expect(el.querySelector('[slot=feedback]').innerText).to.equal( + 'This is error message for alwaysFalse', + ); }); it('writes validation outcome to *feedback-element*, if present', async () => { @@ -723,7 +725,7 @@ describe('ValidateMixin', () => { .errorValidators=${[[alwaysFalse]]} >${lightDom} `); - expect(feedbackResult.$$slot('feedback').innerText).to.equal( + expect(feedbackResult.querySelector('[slot=feedback]').innerText).to.equal( 'This is error message for alwaysFalse', ); }); @@ -737,7 +739,7 @@ describe('ValidateMixin', () => { >${lightDom} `); - expect(feedbackResult.$$slot('feedback').innerText).to.equal(''); + expect(feedbackResult.querySelector('[slot=feedback]').innerText).to.equal(''); // locale changed or smth localize.reset(); localize.addData('en-GB', 'lion-validate', { @@ -745,7 +747,9 @@ describe('ValidateMixin', () => { }); feedbackResult.onLocaleUpdated(); - expect(feedbackResult.$$slot('feedback').innerText).to.equal('error:alwaysFalseAsyncTransl'); + expect(feedbackResult.querySelector('[slot=feedback]').innerText).to.equal( + 'error:alwaysFalseAsyncTransl', + ); }); it('allows to overwrite the way messages are translated', async () => { @@ -766,18 +770,20 @@ describe('ValidateMixin', () => { >${lightDom} `); - expect(customTranslations.$$slot('feedback').innerText).to.equal( + expect(customTranslations.querySelector('[slot=feedback]').innerText).to.equal( 'You should have a lowercase a', ); customTranslations.modelValue = 'cat'; await customTranslations.updateComplete; - expect(customTranslations.$$slot('feedback').innerText).to.equal('You can not pass'); + expect(customTranslations.querySelector('[slot=feedback]').innerText).to.equal( + 'You can not pass', + ); }); it('allows to overwrite the way messages are rendered/added to dom', async () => { const element = defineCE( - class extends ValidateMixin(LionLitElement) { + class extends ValidateMixin(LitElement) { static get properties() { return { modelValue: { @@ -789,7 +795,9 @@ describe('ValidateMixin', () => { renderFeedback(validationStates, message) { const validator = message.list[0].data.validatorName; const showError = validationStates.error; - this.innerHTML = showError ? `ERROR on ${validator}` : ''; + this.innerHTML = showError + ? `
ERROR on ${validator}
` + : '
'; } }, ); @@ -801,11 +809,13 @@ describe('ValidateMixin', () => { >${lightDom} `); await ownTranslations.updateComplete; - expect(ownTranslations.innerHTML).to.equal('ERROR on containsLowercaseA'); + expect(ownTranslations.innerHTML).to.equal( + '
ERROR on containsLowercaseA
', + ); ownTranslations.modelValue = 'cat'; await ownTranslations.updateComplete; - expect(ownTranslations.innerHTML).to.equal('ERROR on alwaysFalse'); + expect(ownTranslations.innerHTML).to.equal('
ERROR on alwaysFalse
'); }); it('supports custom element to render feedback', async () => { @@ -832,11 +842,13 @@ describe('ValidateMixin', () => { element.modelValue = 'dog'; await element.updateComplete; - expect(element.$$slot('feedback').innerText).to.equal('ERROR on containsLowercaseA'); + expect(element.querySelector('[slot=feedback]').innerText).to.equal( + 'ERROR on containsLowercaseA', + ); element.modelValue = 'cat'; await element.updateComplete; - expect(element.$$slot('feedback').innerText).to.equal('ERROR on alwaysFalse'); + expect(element.querySelector('[slot=feedback]').innerText).to.equal('ERROR on alwaysFalse'); }); it('allows to create a custom feedback renderer via the template [to-be-implemented]', async () => { @@ -856,19 +868,19 @@ describe('ValidateMixin', () => { validityFeedback.modelValue = 'a'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'This is error message for minLength', ); validityFeedback.modelValue = 'abc'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'This is warning message for minLength', ); validityFeedback.modelValue = 'abcde'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'This is info message for minLength', ); }); @@ -884,13 +896,13 @@ describe('ValidateMixin', () => { validityFeedback.modelValue = 'a'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'This is error message for minLength', ); validityFeedback.modelValue = 'abcd'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'This is success message for alwaysFalse', ); }); @@ -905,30 +917,30 @@ describe('ValidateMixin', () => { `); validityFeedback.modelValue = 'dog and dog'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'This is error message for containsCat', ); validityFeedback.modelValue = 'dog'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'This is error message for containsCat', ); validityFeedback.modelValue = 'cat'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'This is error message for minLength', ); validityFeedback.modelValue = 'dog and cat'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal(''); + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(''); }); it('supports randomized selection of multiple messages for the same validator', async () => { const randomTranslationsElement = defineCE( - class extends ValidateMixin(LionLitElement) { + class extends ValidateMixin(LitElement) { static get properties() { return { modelValue: { @@ -987,13 +999,13 @@ describe('ValidateMixin', () => { 'Good job!', ); - expect(randomTranslations.$$slot('feedback').innerText).to.equal( + expect(randomTranslations.querySelector('[slot=feedback]').innerText).to.equal( 'You should have a lowercase a', ); randomTranslations.modelValue = 'cat'; await randomTranslations.updateComplete; - expect(randomTranslations.$$slot('feedback').innerText).to.equal('Good job!'); + expect(randomTranslations.querySelector('[slot=feedback]').innerText).to.equal('Good job!'); Math.random = () => 0.25; randomTranslations.__lastGetSuccessResult = false; @@ -1001,7 +1013,9 @@ describe('ValidateMixin', () => { randomTranslations.modelValue = 'cat'; await randomTranslations.updateComplete; - expect(randomTranslations.$$slot('feedback').innerText).to.equal('You did great!'); + expect(randomTranslations.querySelector('[slot=feedback]').innerText).to.equal( + 'You did great!', + ); Math.random = mathRandom; // manually restore }); @@ -1029,13 +1043,13 @@ describe('ValidateMixin', () => { errorValidators: [[minLength, { min: 4 }]], }), ); - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'You need to enter at least 4 characters.', ); localize.locale = 'de-DE'; await validityFeedback.updateComplete; - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'Es müssen mindestens 4 Zeichen eingegeben werden.', ); }); @@ -1056,7 +1070,9 @@ describe('ValidateMixin', () => { .modelValue=${'cat'} >${lightDom} `); - expect(el.$$slot('feedback').innerText).to.equal('myField needs more characters'); + expect(el.querySelector('[slot=feedback]').innerText).to.equal( + 'myField needs more characters', + ); }); it('allows to configure field name for every validator message', async () => { @@ -1068,7 +1084,7 @@ describe('ValidateMixin', () => { ]} .modelValue=${'cat'} >${lightDom} `); - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'overrideName needs more characters', ); }); @@ -1082,7 +1098,7 @@ describe('ValidateMixin', () => { .errorValidators=${[[minLength, { min: 4 }]]} .modelValue=${'cat'} >${lightDom} `); - expect(validityFeedback.$$slot('feedback').innerText).to.equal( + expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal( 'myField needs more characters', ); @@ -1091,7 +1107,7 @@ describe('ValidateMixin', () => { .errorValidators=${[[minLength, { min: 4 }]]} .modelValue=${'cat'} >${lightDom} `); - expect(validityFeedback2.$$slot('feedback').innerText).to.equal( + expect(validityFeedback2.querySelector('[slot=feedback]').innerText).to.equal( 'myName needs more characters', ); }); @@ -1134,11 +1150,13 @@ describe('ValidateMixin', () => { element.modelValue = 'dog'; await element.updateComplete; - expect(element.$$slot('feedback').innerText).to.equal('ERROR on containsLowercaseA'); + expect(element.querySelector('[slot=feedback]').innerText).to.equal( + 'ERROR on containsLowercaseA', + ); element.modelValue = 'cat'; await element.updateComplete; - expect(element.$$slot('feedback').innerText).to.equal(''); + expect(element.querySelector('[slot=feedback]').innerText).to.equal(''); }); }); @@ -1191,7 +1209,9 @@ describe('ValidateMixin', () => { }, }); el._createMessageAndRenderFeedback(); - expect(el.$$slot('feedback').innerText).to.equal('lion-validate : orderValidator'); + expect(el.querySelector('[slot=feedback]').innerText).to.equal( + 'lion-validate : orderValidator', + ); // 1. lion-validate+orderValidator localize.addData('en-GB', 'lion-validate+orderValidator', { @@ -1200,7 +1220,7 @@ describe('ValidateMixin', () => { }, }); el._createMessageAndRenderFeedback(); - expect(el.$$slot('feedback').innerText).to.equal( + expect(el.querySelector('[slot=feedback]').innerText).to.equal( 'lion-validate+orderValidator : orderValidator', ); }); @@ -1214,7 +1234,7 @@ describe('ValidateMixin', () => { // Tests are in 'reversed order', so we can increase prio by filling up localize storage const is12Validator = () => [modelValue => ({ is12Validator: modelValue === 12 })]; const orderName = defineCE( - class extends ValidateMixin(LionLitElement) { + class extends ValidateMixin(LitElement) { static get properties() { return { modelValue: { type: String } }; } @@ -1249,7 +1269,9 @@ describe('ValidateMixin', () => { }, }); el._createMessageAndRenderFeedback(); - expect(el.$$slot('feedback').innerText).to.equal('lion-validate : is12Validator'); + expect(el.querySelector('[slot=feedback]').innerText).to.equal( + 'lion-validate : is12Validator', + ); // 3. lion-validate+is12Validator localize.addData('en-GB', 'lion-validate+is12Validator', { @@ -1258,7 +1280,7 @@ describe('ValidateMixin', () => { }, }); el._createMessageAndRenderFeedback(); - expect(el.$$slot('feedback').innerText).to.equal( + expect(el.querySelector('[slot=feedback]').innerText).to.equal( 'lion-validate+is12Validator : is12Validator', ); @@ -1269,7 +1291,9 @@ describe('ValidateMixin', () => { }, }); el._createMessageAndRenderFeedback(); - expect(el.$$slot('feedback').innerText).to.equal('my-custom-namespace : is12Validator'); + expect(el.querySelector('[slot=feedback]').innerText).to.equal( + 'my-custom-namespace : is12Validator', + ); // 1. my-custom-namespace+is12Validator localize.addData('en-GB', 'my-custom-namespace+is12Validator', { @@ -1278,7 +1302,7 @@ describe('ValidateMixin', () => { }, }); el._createMessageAndRenderFeedback(); - expect(el.$$slot('feedback').innerText).to.equal( + expect(el.querySelector('[slot=feedback]').innerText).to.equal( 'my-custom-namespace+is12Validator : is12Validator', ); });