feat: remove all deprecations from lion
This commit is contained in:
parent
6e4c8e188e
commit
66d3d390ae
80 changed files with 569 additions and 2268 deletions
|
|
@ -17,7 +17,7 @@ import { calendarStyle } from './calendarStyle.js';
|
||||||
import { createDay } from './utils/createDay.js';
|
import { createDay } from './utils/createDay.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @customElement
|
* @customElement lion-calendar
|
||||||
*/
|
*/
|
||||||
export class LionCalendar extends LocalizeMixin(LitElement) {
|
export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
|
|
@ -26,55 +26,40 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
||||||
'lion-calendar': locale => {
|
'lion-calendar': locale => {
|
||||||
switch (locale) {
|
switch (locale) {
|
||||||
case 'bg-BG':
|
case 'bg-BG':
|
||||||
case 'bg':
|
|
||||||
return import('../translations/bg.js');
|
return import('../translations/bg.js');
|
||||||
case 'cs-CZ':
|
case 'cs-CZ':
|
||||||
case 'cs':
|
|
||||||
return import('../translations/cs.js');
|
return import('../translations/cs.js');
|
||||||
case 'de-AT':
|
case 'de-AT':
|
||||||
case 'de-DE':
|
case 'de-DE':
|
||||||
case 'de':
|
|
||||||
return import('../translations/de.js');
|
return import('../translations/de.js');
|
||||||
case 'en-AU':
|
case 'en-AU':
|
||||||
case 'en-GB':
|
case 'en-GB':
|
||||||
case 'en-PH':
|
case 'en-PH':
|
||||||
case 'en-US':
|
case 'en-US':
|
||||||
case 'en':
|
|
||||||
return import('../translations/en.js');
|
return import('../translations/en.js');
|
||||||
case 'es-ES':
|
case 'es-ES':
|
||||||
case 'es':
|
|
||||||
return import('../translations/es.js');
|
return import('../translations/es.js');
|
||||||
case 'fr-FR':
|
case 'fr-FR':
|
||||||
case 'fr-BE':
|
case 'fr-BE':
|
||||||
case 'fr':
|
|
||||||
return import('../translations/fr.js');
|
return import('../translations/fr.js');
|
||||||
case 'hu-HU':
|
case 'hu-HU':
|
||||||
case 'hu':
|
|
||||||
return import('../translations/hu.js');
|
return import('../translations/hu.js');
|
||||||
case 'it-IT':
|
case 'it-IT':
|
||||||
case 'it':
|
|
||||||
return import('../translations/it.js');
|
return import('../translations/it.js');
|
||||||
case 'nl-BE':
|
case 'nl-BE':
|
||||||
case 'nl-NL':
|
case 'nl-NL':
|
||||||
case 'nl':
|
|
||||||
return import('../translations/nl.js');
|
return import('../translations/nl.js');
|
||||||
case 'pl-PL':
|
case 'pl-PL':
|
||||||
case 'pl':
|
|
||||||
return import('../translations/pl.js');
|
return import('../translations/pl.js');
|
||||||
case 'ro-RO':
|
case 'ro-RO':
|
||||||
case 'ro':
|
|
||||||
return import('../translations/ro.js');
|
return import('../translations/ro.js');
|
||||||
case 'ru-RU':
|
case 'ru-RU':
|
||||||
case 'ru':
|
|
||||||
return import('../translations/ru.js');
|
return import('../translations/ru.js');
|
||||||
case 'sk-SK':
|
case 'sk-SK':
|
||||||
case 'sk':
|
|
||||||
return import('../translations/sk.js');
|
return import('../translations/sk.js');
|
||||||
case 'uk-UA':
|
case 'uk-UA':
|
||||||
case 'uk':
|
|
||||||
return import('../translations/uk.js');
|
return import('../translations/uk.js');
|
||||||
case 'zh-CN':
|
case 'zh-CN':
|
||||||
case 'zh':
|
|
||||||
return import('../translations/zh.js');
|
return import('../translations/zh.js');
|
||||||
default:
|
default:
|
||||||
return import(`../translations/${locale}.js`);
|
return import(`../translations/${locale}.js`);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ storiesOf('Forms|Checkbox Group', module)
|
||||||
name="scientists[]"
|
name="scientists[]"
|
||||||
label="Francis Bacon"
|
label="Francis Bacon"
|
||||||
.choiceValue=${'Francis Bacon'}
|
.choiceValue=${'Francis Bacon'}
|
||||||
.choiceChecked=${true}
|
checked
|
||||||
></lion-checkbox>
|
></lion-checkbox>
|
||||||
<lion-checkbox
|
<lion-checkbox
|
||||||
name="scientists[]"
|
name="scientists[]"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ describe('<lion-checkbox-group>', () => {
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
|
|
||||||
expect(el.error.required).to.be.true;
|
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;
|
expect(el.error.required).to.be.undefined;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Get or set the checked state (boolean) - `choiceChecked()`
|
- Get the checked state (boolean) - `checked` boolean attribute
|
||||||
- Get or set the value of the choice - `choiceValue()`
|
|
||||||
- Pre-select an option by setting the `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
|
## How to use
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,6 @@ export const ChoiceInputMixin = superclass =>
|
||||||
updated(c) {
|
updated(c) {
|
||||||
super.updated(c);
|
super.updated(c);
|
||||||
if (c.has('modelValue')) {
|
if (c.has('modelValue')) {
|
||||||
this._reflectCheckedToCssClass({ modelValue: this.modelValue });
|
|
||||||
this.__syncCheckedToInputElement();
|
this.__syncCheckedToInputElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +124,6 @@ export const ChoiceInputMixin = superclass =>
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.addEventListener('user-input-changed', this.__toggleChecked);
|
this.addEventListener('user-input-changed', this.__toggleChecked);
|
||||||
this._reflectCheckedToCssClass();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
|
|
@ -146,10 +144,10 @@ export const ChoiceInputMixin = superclass =>
|
||||||
}
|
}
|
||||||
|
|
||||||
__syncCheckedToInputElement() {
|
__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])
|
// or at all (no reliance on platform construct, in case of [role=option])
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.checked = this.checked;
|
this._inputNode.checked = this.checked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,23 +215,4 @@ export const ChoiceInputMixin = superclass =>
|
||||||
* Synchronization from user input is already arranged in this Mixin.
|
* Synchronization from user input is already arranged in this Mixin.
|
||||||
*/
|
*/
|
||||||
_syncValueUpwards() {}
|
_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');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ describe('ChoiceInputMixin', () => {
|
||||||
expect(el.modelValue.value).to.equal(date);
|
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;
|
let counter = 0;
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<choice-input
|
<choice-input
|
||||||
|
|
@ -77,7 +77,7 @@ describe('ChoiceInputMixin', () => {
|
||||||
`);
|
`);
|
||||||
expect(counter).to.equal(0);
|
expect(counter).to.equal(0);
|
||||||
// Here we try to mimic user interaction by firing browser events
|
// 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
|
nativeInput.dispatchEvent(new CustomEvent('input', { bubbles: true })); // fired by (at least) Chrome
|
||||||
expect(counter).to.equal(0);
|
expect(counter).to.equal(0);
|
||||||
nativeInput.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
nativeInput.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||||
|
|
@ -112,14 +112,14 @@ describe('ChoiceInputMixin', () => {
|
||||||
expect(el.checked).to.be.true;
|
expect(el.checked).to.be.true;
|
||||||
|
|
||||||
await el.updateComplete;
|
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 () => {
|
it('can be checked and unchecked via user interaction', async () => {
|
||||||
const el = await fixture(`<choice-input></choice-input>`);
|
const el = await fixture(`<choice-input></choice-input>`);
|
||||||
el.inputElement.click();
|
el._inputNode.click();
|
||||||
expect(el.checked).to.be.true;
|
expect(el.checked).to.be.true;
|
||||||
el.inputElement.click();
|
el._inputNode.click();
|
||||||
expect(el.checked).to.be.false;
|
expect(el.checked).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -193,8 +193,8 @@ describe('ChoiceInputMixin', () => {
|
||||||
elChecked.checked = true;
|
elChecked.checked = true;
|
||||||
|
|
||||||
// Via user interaction
|
// Via user interaction
|
||||||
el.inputElement.click();
|
el._inputNode.click();
|
||||||
elChecked.inputElement.click();
|
elChecked._inputNode.click();
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(hasAttr(el)).to.equal(true, 'user click checked');
|
expect(hasAttr(el)).to.equal(true, 'user click checked');
|
||||||
expect(hasAttr(elChecked)).to.equal(false, 'user click unchecked');
|
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(el)).to.equal(true, 'modelValue checked');
|
||||||
expect(hasAttr(elChecked)).to.equal(false, 'modelValue unchecked');
|
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(`<choice-input></choice-input>`);
|
|
||||||
const elChecked = await fixture(html`
|
|
||||||
<choice-input .checked=${true}>
|
|
||||||
<input slot="input" />
|
|
||||||
</choice-input>
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 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`
|
|
||||||
<choice-input .choiceValue=${'foo'}></choice-input>
|
|
||||||
`);
|
|
||||||
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', () => {
|
describe('Format/parse/serialize loop', () => {
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,7 @@
|
||||||
|
|
||||||
## Deprecations
|
## Deprecations
|
||||||
|
|
||||||
The following files/features are deprecated
|
Currently all deprecations are removed due to alpha state.
|
||||||
|
|
||||||
- CssClassMixin
|
|
||||||
- DomHelpersMixin (only $$id, $$slot is deprecated)
|
|
||||||
- ElementMixin
|
|
||||||
- EventMixin
|
|
||||||
- ObserverMixin
|
|
||||||
- lit-html.js
|
|
||||||
|
|
||||||
## Deduping of mixins
|
## Deduping of mixins
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ export {
|
||||||
// ours
|
// ours
|
||||||
export { dedupeMixin } from './src/dedupeMixin.js';
|
export { dedupeMixin } from './src/dedupeMixin.js';
|
||||||
export { DelegateMixin } from './src/DelegateMixin.js';
|
export { DelegateMixin } from './src/DelegateMixin.js';
|
||||||
export { DomHelpersMixin } from './src/DomHelpersMixin.js';
|
|
||||||
export { LionSingleton } from './src/LionSingleton.js';
|
export { LionSingleton } from './src/LionSingleton.js';
|
||||||
export { SlotMixin } from './src/SlotMixin.js';
|
export { SlotMixin } from './src/SlotMixin.js';
|
||||||
export { DisabledMixin } from './src/DisabledMixin.js';
|
export { DisabledMixin } from './src/DisabledMixin.js';
|
||||||
export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js';
|
export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js';
|
||||||
|
export { UpdateStylesMixin } from './src/UpdateStylesMixin.js';
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { dedupeMixin } from './dedupeMixin.js';
|
||||||
* get delegations() {
|
* get delegations() {
|
||||||
* return {
|
* return {
|
||||||
* ...super.delegations,
|
* ...super.delegations,
|
||||||
* target: () => this.$id('button1'),
|
* target: () => this.shadowRoot.getElementById('button1'),
|
||||||
* events: ['click'],
|
* events: ['click'],
|
||||||
* methods: ['click'],
|
* methods: ['click'],
|
||||||
* properties: ['disabled'],
|
* properties: ['disabled'],
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
@ -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`
|
|
||||||
* <button id="button1">with click event</button>
|
|
||||||
* <button id="button2">with focus + blur event</button>
|
|
||||||
* `;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
@ -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) {}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
/* eslint-disable class-methods-use-this */
|
/* eslint-disable class-methods-use-this */
|
||||||
|
|
||||||
import { dedupeMixin } from './dedupeMixin.js';
|
import { dedupeMixin } from './dedupeMixin.js';
|
||||||
import { DomHelpersMixin } from './DomHelpersMixin.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # SlotMixin
|
* # SlotMixin
|
||||||
|
|
@ -28,7 +27,7 @@ import { DomHelpersMixin } from './DomHelpersMixin.js';
|
||||||
export const SlotMixin = dedupeMixin(
|
export const SlotMixin = dedupeMixin(
|
||||||
superclass =>
|
superclass =>
|
||||||
// eslint-disable-next-line no-unused-vars, no-shadow
|
// eslint-disable-next-line no-unused-vars, no-shadow
|
||||||
class SlotMixin extends DomHelpersMixin(superclass) {
|
class SlotMixin extends superclass {
|
||||||
/**
|
/**
|
||||||
* @returns {{}}
|
* @returns {{}}
|
||||||
*/
|
*/
|
||||||
|
|
@ -54,7 +53,7 @@ export const SlotMixin = dedupeMixin(
|
||||||
_connectSlotMixin() {
|
_connectSlotMixin() {
|
||||||
if (!this.__isConnectedSlotMixin) {
|
if (!this.__isConnectedSlotMixin) {
|
||||||
Object.keys(this.slots).forEach(slotName => {
|
Object.keys(this.slots).forEach(slotName => {
|
||||||
if (!this.$$slot(slotName)) {
|
if (!this.querySelector(`[slot=${slotName}]`)) {
|
||||||
const slotFactory = this.slots[slotName];
|
const slotFactory = this.slots[slotName];
|
||||||
const slotContent = slotFactory();
|
const slotContent = slotFactory();
|
||||||
if (slotContent instanceof Element) {
|
if (slotContent instanceof Element) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
/* global ShadyCSS */
|
/* global ShadyCSS */
|
||||||
import { dedupeMixin } from './dedupeMixin.js';
|
import { dedupeMixin } from './dedupeMixin.js';
|
||||||
import { DomHelpersMixin } from './DomHelpersMixin.js';
|
|
||||||
|
|
||||||
/**
|
export const UpdateStylesMixin = dedupeMixin(
|
||||||
* @deprecated please apply DomHelpersMixin and UpdateStylesMixin if needed yourself
|
|
||||||
*/
|
|
||||||
export const ElementMixin = dedupeMixin(
|
|
||||||
superclass =>
|
superclass =>
|
||||||
// eslint-disable-next-line no-shadow
|
// eslint-disable-next-line no-shadow
|
||||||
class ElementMixin extends DomHelpersMixin(superclass) {
|
class UpdateStylesMixin extends superclass {
|
||||||
/**
|
/**
|
||||||
* @example
|
* @example
|
||||||
* <my-element>
|
* <my-element>
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
// deprecated!
|
|
||||||
export { html, render } from 'lit-html';
|
|
||||||
export { render as renderShady } from 'lit-html/lib/shady-render.js';
|
|
||||||
|
|
@ -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(`<to-class-element><to-class-element/>`);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { expect, fixture, defineCE, unsafeStatic, html } from '@open-wc/testing';
|
import { expect, fixture, defineCE, unsafeStatic, html } from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { LionLitElement } from '../src/LionLitElement.js';
|
import { LitElement } from '../index.js';
|
||||||
import { ObserverMixin } from '../src/ObserverMixin.js';
|
|
||||||
|
|
||||||
import { DelegateMixin } from '../src/DelegateMixin.js';
|
import { DelegateMixin } from '../src/DelegateMixin.js';
|
||||||
|
|
||||||
|
|
@ -12,11 +11,11 @@ describe('DelegateMixin', () => {
|
||||||
|
|
||||||
it('delegates events', async () => {
|
it('delegates events', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
events: ['click'],
|
events: ['click'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -31,17 +30,17 @@ describe('DelegateMixin', () => {
|
||||||
const element = await fixture(`<${tag}></${tag}>`);
|
const element = await fixture(`<${tag}></${tag}>`);
|
||||||
const cb = sinon.spy();
|
const cb = sinon.spy();
|
||||||
element.addEventListener('click', cb);
|
element.addEventListener('click', cb);
|
||||||
element.$id('button1').click();
|
element.shadowRoot.getElementById('button1').click();
|
||||||
expect(cb.callCount).to.equal(1);
|
expect(cb.callCount).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('delegates events before delegation target is attached to DOM', async () => {
|
it('delegates events before delegation target is attached to DOM', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
events: ['click'],
|
events: ['click'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +57,7 @@ describe('DelegateMixin', () => {
|
||||||
element.addEventListener('click', cb);
|
element.addEventListener('click', cb);
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
await element.updateComplete;
|
await element.updateComplete;
|
||||||
element.$id('button1').click();
|
element.shadowRoot.getElementById('button1').click();
|
||||||
expect(cb.callCount).to.equal(1);
|
expect(cb.callCount).to.equal(1);
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
|
|
@ -67,11 +66,11 @@ describe('DelegateMixin', () => {
|
||||||
|
|
||||||
it('delegates if light and shadow dom is used at the same time', async () => {
|
it('delegates if light and shadow dom is used at the same time', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$$slot('button'),
|
target: () => this.querySelector('[slot=button]'),
|
||||||
events: ['click'],
|
events: ['click'],
|
||||||
methods: ['click'],
|
methods: ['click'],
|
||||||
};
|
};
|
||||||
|
|
@ -90,17 +89,17 @@ describe('DelegateMixin', () => {
|
||||||
<${tag}><button slot="button">click me</button></${tag}>`);
|
<${tag}><button slot="button">click me</button></${tag}>`);
|
||||||
const cb = sinon.spy();
|
const cb = sinon.spy();
|
||||||
element.addEventListener('click', cb);
|
element.addEventListener('click', cb);
|
||||||
element.$$slot('button').click();
|
element.querySelector('[slot=button]').click();
|
||||||
expect(cb.callCount).to.equal(1);
|
expect(cb.callCount).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('will still support other events', async () => {
|
it('will still support other events', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
events: ['click'],
|
events: ['click'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -125,11 +124,11 @@ describe('DelegateMixin', () => {
|
||||||
|
|
||||||
it('will call delegated methods', async () => {
|
it('will call delegated methods', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
methods: ['click'],
|
methods: ['click'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -143,13 +142,13 @@ describe('DelegateMixin', () => {
|
||||||
);
|
);
|
||||||
const element = await fixture(`<${tag}></${tag}>`);
|
const element = await fixture(`<${tag}></${tag}>`);
|
||||||
const cb = sinon.spy();
|
const cb = sinon.spy();
|
||||||
element.$id('button1').addEventListener('click', cb);
|
element.shadowRoot.getElementById('button1').addEventListener('click', cb);
|
||||||
element.click();
|
element.click();
|
||||||
expect(cb.callCount).to.equal(1);
|
expect(cb.callCount).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports arguments for delegated methods', async () => {
|
it('supports arguments for delegated methods', async () => {
|
||||||
class DelegateArgumentSub extends LionLitElement {
|
class DelegateArgumentSub extends LitElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.foo = { a: 'a', b: 'b' };
|
this.foo = { a: 'a', b: 'b' };
|
||||||
|
|
@ -162,11 +161,11 @@ describe('DelegateMixin', () => {
|
||||||
}
|
}
|
||||||
customElements.define('delegate-argument-sub', DelegateArgumentSub);
|
customElements.define('delegate-argument-sub', DelegateArgumentSub);
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('sub'),
|
target: () => this.shadowRoot.getElementById('sub'),
|
||||||
methods: ['setFooAandB'],
|
methods: ['setFooAandB'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -182,17 +181,17 @@ describe('DelegateMixin', () => {
|
||||||
const element = await fixture(`<${tag}></${tag}>`);
|
const element = await fixture(`<${tag}></${tag}>`);
|
||||||
element.disabled = true;
|
element.disabled = true;
|
||||||
element.setFooAandB('newA', 'newB');
|
element.setFooAandB('newA', 'newB');
|
||||||
expect(element.$id('sub').foo.a).to.equal('newA');
|
expect(element.shadowRoot.getElementById('sub').foo.a).to.equal('newA');
|
||||||
expect(element.$id('sub').foo.b).to.equal('newB');
|
expect(element.shadowRoot.getElementById('sub').foo.b).to.equal('newB');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('will set delegated properties', async () => {
|
it('will set delegated properties', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
properties: ['disabled'],
|
properties: ['disabled'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -207,17 +206,17 @@ describe('DelegateMixin', () => {
|
||||||
const element = await fixture(`<${tag}></${tag}>`);
|
const element = await fixture(`<${tag}></${tag}>`);
|
||||||
element.disabled = true;
|
element.disabled = true;
|
||||||
await element.updateComplete;
|
await element.updateComplete;
|
||||||
expect(element.$id('button1').disabled).to.equal(true);
|
expect(element.shadowRoot.getElementById('button1').disabled).to.equal(true);
|
||||||
expect(element.$id('button1').hasAttribute('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 () => {
|
it('delegates properties before delegation target is attached to DOM', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
properties: ['disabled'],
|
properties: ['disabled'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -233,7 +232,7 @@ describe('DelegateMixin', () => {
|
||||||
element.disabled = true;
|
element.disabled = true;
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
await element.updateComplete;
|
await element.updateComplete;
|
||||||
expect(element.$id('button1').disabled).to.equal(true);
|
expect(element.shadowRoot.getElementById('button1').disabled).to.equal(true);
|
||||||
|
|
||||||
// cleanup
|
// cleanup
|
||||||
document.body.removeChild(element);
|
document.body.removeChild(element);
|
||||||
|
|
@ -241,11 +240,11 @@ describe('DelegateMixin', () => {
|
||||||
|
|
||||||
it('will delegate setAttribute', async () => {
|
it('will delegate setAttribute', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
attributes: ['disabled'],
|
attributes: ['disabled'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -261,16 +260,16 @@ describe('DelegateMixin', () => {
|
||||||
element.setAttribute('disabled', '');
|
element.setAttribute('disabled', '');
|
||||||
await element.updateComplete;
|
await element.updateComplete;
|
||||||
expect(element.hasAttribute('disabled')).to.equal(false);
|
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 () => {
|
it('will read inital attributes', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
attributes: ['disabled'],
|
attributes: ['disabled'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -284,16 +283,16 @@ describe('DelegateMixin', () => {
|
||||||
);
|
);
|
||||||
const element = await fixture(`<${tag} disabled></${tag}>`);
|
const element = await fixture(`<${tag} disabled></${tag}>`);
|
||||||
expect(element.hasAttribute('disabled')).to.equal(false);
|
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 () => {
|
it('will delegate removeAttribute', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
target: () => this.$id('button1'),
|
target: () => this.shadowRoot.getElementById('button1'),
|
||||||
attributes: ['disabled'],
|
attributes: ['disabled'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -309,12 +308,12 @@ describe('DelegateMixin', () => {
|
||||||
element.removeAttribute('disabled', '');
|
element.removeAttribute('disabled', '');
|
||||||
await element.updateComplete;
|
await element.updateComplete;
|
||||||
expect(element.hasAttribute('disabled')).to.equal(false);
|
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 () => {
|
it('respects user defined values for delegated attributes and properties', async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
|
|
@ -353,7 +352,7 @@ describe('DelegateMixin', () => {
|
||||||
it(`uses attribute value as a fallback for delegated property getter
|
it(`uses attribute value as a fallback for delegated property getter
|
||||||
when property not set by user and delegationTarget not ready`, async () => {
|
when property not set by user and delegationTarget not ready`, async () => {
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
|
|
@ -393,7 +392,7 @@ describe('DelegateMixin', () => {
|
||||||
|
|
||||||
it('works with shadow dom', async () => {
|
it('works with shadow dom', async () => {
|
||||||
const tag = await defineCE(
|
const tag = await defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
|
|
@ -416,7 +415,7 @@ describe('DelegateMixin', () => {
|
||||||
|
|
||||||
it('works with light dom', async () => {
|
it('works with light dom', async () => {
|
||||||
const tag = await defineCE(
|
const tag = await defineCE(
|
||||||
class extends DelegateMixin(LionLitElement) {
|
class extends DelegateMixin(LitElement) {
|
||||||
get delegations() {
|
get delegations() {
|
||||||
return {
|
return {
|
||||||
...super.delegations,
|
...super.delegations,
|
||||||
|
|
@ -440,67 +439,4 @@ describe('DelegateMixin', () => {
|
||||||
element.foo = 'new';
|
element.foo = 'new';
|
||||||
expect(element.querySelector('div').foo).to.equal('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`
|
|
||||||
<div></div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
createRenderRoot() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onSyncSizeChanged() {}
|
|
||||||
|
|
||||||
_onAsyncSizeChanged() {}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const el = await fixture(`<${tag}><div></div></${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;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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`
|
|
||||||
<div id="myId">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}></${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`
|
|
||||||
<div id="myId">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}></${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`
|
|
||||||
<div id="myId">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const element = await fixture(`<${tag}></${tag}>`);
|
|
||||||
element.$id('myId');
|
|
||||||
element.shadowRoot.innerHTML = '';
|
|
||||||
element._clearDomCache('$id', 'myId');
|
|
||||||
expect(element.$id('myId')).to.equal(undefined);
|
|
||||||
|
|
||||||
const element2 = await fixture(`<${tag}></${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`
|
|
||||||
<div name="myName">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}></${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`
|
|
||||||
<div name="myName">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}></${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`
|
|
||||||
<div name="myName">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const element = await fixture(`<${tag}></${tag}>`);
|
|
||||||
element.$name('myName');
|
|
||||||
element.shadowRoot.innerHTML = '';
|
|
||||||
element._clearDomCache('$name', 'myName');
|
|
||||||
expect(element.$name('myName')).to.equal(undefined);
|
|
||||||
|
|
||||||
const element2 = await fixture(`<${tag}></${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`
|
|
||||||
<div id="myId">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}></${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`
|
|
||||||
<div id="myId">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}></${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`
|
|
||||||
<div id="myId">my element</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const element = await fixture(`<${tag}></${tag}>`);
|
|
||||||
element.$$id('myId');
|
|
||||||
element.innerHTML = '';
|
|
||||||
element._clearDomCache('$$id', 'myId');
|
|
||||||
expect(element.$$id('myId')).to.equal(undefined);
|
|
||||||
|
|
||||||
const element2 = await fixture(`<${tag}></${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`
|
|
||||||
<slot name="mySlot"></slot>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}>
|
|
||||||
<div slot="mySlot">my element</div>
|
|
||||||
</${tag}>`);
|
|
||||||
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`
|
|
||||||
<!-- here the nested slots will be plotted -->
|
|
||||||
<slot></slot>
|
|
||||||
<!-- here the slots belonging to this component are plotted -->
|
|
||||||
<slot name="slotA"></slot>
|
|
||||||
<slot name="slotB"></slot>
|
|
||||||
<slot name="slotC"></slot>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const tagNested = tag; // reuse the same component for nested slots with same slot names
|
|
||||||
const fieldsetNested = await fixture(`
|
|
||||||
<${tag}>
|
|
||||||
<div slot="slotA">Get this slotA</div>
|
|
||||||
<${tagNested}>
|
|
||||||
<div slot="slotA">Don't get this slotA</div>
|
|
||||||
<div slot="slotB">Don't get this slotB</div>
|
|
||||||
<div slot="slotC">Don't get this slotC</div>
|
|
||||||
</${tagNested}>
|
|
||||||
<div slot="slotB">Get this slotB (2nd in dom, but belongs to 'upper tag')</div>
|
|
||||||
<div slot="slotB">
|
|
||||||
Don't get this slotB either
|
|
||||||
(it only should get the first slot that is a direct child)
|
|
||||||
</div>
|
|
||||||
</${tag}>`);
|
|
||||||
|
|
||||||
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`
|
|
||||||
<slot name="mySlot"></slot>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}>
|
|
||||||
<div slot="mySlot">my element</div>
|
|
||||||
</${tag}>`);
|
|
||||||
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`
|
|
||||||
<slot name="mySlot"></slot>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const element = await fixture(`<${tag}>
|
|
||||||
<div slot="mySlot">my element</div>
|
|
||||||
</${tag}>`);
|
|
||||||
element.$$slot('mySlot');
|
|
||||||
element.innerHTML = '';
|
|
||||||
element._clearDomCache('$$slot', 'mySlot');
|
|
||||||
expect(element.$$slot('mySlot')).to.equal(undefined);
|
|
||||||
|
|
||||||
const element2 = await fixture(`<${tag}>
|
|
||||||
<div slot="mySlot">my element</div>
|
|
||||||
</${tag}>`);
|
|
||||||
element2.$$slot('mySlot');
|
|
||||||
element2.innerHTML = '';
|
|
||||||
element2._clearDomCache();
|
|
||||||
expect(element2.$$slot('mySlot')).to.equal(undefined);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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`
|
|
||||||
<button id="button1">with click event</button>
|
|
||||||
<event-mixin-sub type="text" id="sub1"></event-mixin-sub>
|
|
||||||
<button id="button3">no event</button>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(`<event-mixin></event-mixin>`);
|
|
||||||
element.$id('button1').click();
|
|
||||||
expect(element.button1ClickCount).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can add events to itself', async () => {
|
|
||||||
const element = await fixture(`<event-mixin></event-mixin>`);
|
|
||||||
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}></${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(`<event-mixin></event-mixin>`);
|
|
||||||
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(`<multi-events><input /></multi-events>`);
|
|
||||||
|
|
||||||
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(`<multi-functions><button>click</button></multi-functions>`);
|
|
||||||
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(`<event-mixin></event-mixin>`);
|
|
||||||
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(`<event-mixin></event-mixin>`);
|
|
||||||
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}><button>click</button></${tag}>`);
|
|
||||||
el.querySelector('button').click();
|
|
||||||
expect(el.myCallCount).to.equal(1);
|
|
||||||
|
|
||||||
const wrapper = await fixture(`<div></div>`);
|
|
||||||
wrapper.appendChild(el);
|
|
||||||
el.querySelector('button').click();
|
|
||||||
expect(el.myCallCount).to.equal(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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`
|
|
||||||
<h1 id="header">hey</h1>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const shadowLion = await fixture(`<${elementName}></${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`
|
|
||||||
<h1 id="header">hey</h1>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const shadowLion = await fixture(`<${elementName}></${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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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}></${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}></${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}></${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}></${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}></${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;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
91
packages/core/test/UpdateStylesMixin.test.js
Normal file
91
packages/core/test/UpdateStylesMixin.test.js
Normal file
|
|
@ -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`
|
||||||
|
<h1 id="header">hey</h1>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const el = await fixture(`<${tag}></${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`
|
||||||
|
<h1 id="header">hey</h1>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const el = await fixture(`<${tag}></${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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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`
|
|
||||||
<component-with-shadow-dom>
|
|
||||||
<any-element .propNumber=${myNumber} .propFunction=${myFunction}></any-element>
|
|
||||||
</component-with-shadow-dom>
|
|
||||||
`);
|
|
||||||
expect(element.children[0].propNumber).to.equal(myNumber);
|
|
||||||
expect(element.children[0].propFunction).to.equal(myFunction);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -61,18 +61,18 @@ export class LionSlider extends LionField {
|
||||||
}
|
}
|
||||||
|
|
||||||
_proxyChangeEvent() {
|
_proxyChangeEvent() {
|
||||||
this.inputElement.dispatchEvent(
|
this._inputNode.dispatchEvent(
|
||||||
new CustomEvent('user-input-changed', { bubbles: true, composed: true }),
|
new CustomEvent('user-input-changed', { bubbles: true, composed: true }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Proxy property `<my-slider>.mySliderValue` to `<lion-slider>.value`
|
// 3. Proxy property `<my-slider>.mySliderValue` to `<lion-slider>.value`
|
||||||
get value() {
|
get value() {
|
||||||
return this.$$slot('input').mySliderValue;
|
return this.querySelector('[slot=input]').mySliderValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
set value(newV) {
|
set value(newV) {
|
||||||
this.$$slot('input').mySliderValue = newV;
|
this.querySelector('[slot=input]').mySliderValue = newV;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Examples:
|
||||||
|
|
||||||
The view value is the result of the formatter function.
|
The view value is the result of the formatter function.
|
||||||
It will be stored as `.formattedValue` and synchronized to `.value` (a viewValue setter that
|
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
|
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.
|
(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
|
- For a number input this would be the String representation of a float ('1234.56' instead
|
||||||
of 1234.56)
|
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
|
## Formatters, parsers and (de)serializers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ Examples:
|
||||||
### formattedValue
|
### formattedValue
|
||||||
|
|
||||||
The view value is the result of the formatter function (when available).
|
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:
|
Examples:
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ Examples:
|
||||||
- For a number input this would be the String representation of a float ('1234.56' instead
|
- For a number input this would be the String representation of a float ('1234.56' instead
|
||||||
of 1234.56)
|
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
|
## Formatters, parsers and (de)serializers
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,47 +33,29 @@ export const FocusMixin = dedupeMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
focus() {
|
focus() {
|
||||||
const native = this.inputElement;
|
const native = this._inputNode;
|
||||||
if (native) {
|
if (native) {
|
||||||
native.focus();
|
native.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blur() {
|
blur() {
|
||||||
const native = this.inputElement;
|
const native = this._inputNode;
|
||||||
if (native) {
|
if (native) {
|
||||||
native.blur();
|
native.blur();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties) {
|
__onFocus() {
|
||||||
super.updated(changedProperties);
|
if (super.__onFocus) {
|
||||||
// 'state-focused' css classes are deprecated
|
super.__onFocus();
|
||||||
if (changedProperties.has('focused')) {
|
|
||||||
this.classList[this.focused ? 'add' : 'remove']('state-focused');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functions should be private
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
_onFocus() {
|
|
||||||
if (super._onFocus) {
|
|
||||||
super._onFocus();
|
|
||||||
}
|
}
|
||||||
this.focused = true;
|
this.focused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
__onBlur() {
|
||||||
* Functions should be private
|
if (super.__onBlur) {
|
||||||
*
|
super.__onBlur();
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
_onBlur() {
|
|
||||||
if (super._onBlur) {
|
|
||||||
super._onBlur();
|
|
||||||
}
|
}
|
||||||
this.focused = false;
|
this.focused = false;
|
||||||
}
|
}
|
||||||
|
|
@ -84,37 +66,37 @@ export const FocusMixin = dedupeMixin(
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.dispatchEvent(new Event('focus'));
|
this.dispatchEvent(new Event('focus'));
|
||||||
};
|
};
|
||||||
this.inputElement.addEventListener('focus', this.__redispatchFocus);
|
this._inputNode.addEventListener('focus', this.__redispatchFocus);
|
||||||
|
|
||||||
// blur
|
// blur
|
||||||
this.__redispatchBlur = ev => {
|
this.__redispatchBlur = ev => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.dispatchEvent(new Event('blur'));
|
this.dispatchEvent(new Event('blur'));
|
||||||
};
|
};
|
||||||
this.inputElement.addEventListener('blur', this.__redispatchBlur);
|
this._inputNode.addEventListener('blur', this.__redispatchBlur);
|
||||||
|
|
||||||
// focusin
|
// focusin
|
||||||
this.__redispatchFocusin = ev => {
|
this.__redispatchFocusin = ev => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._onFocus(ev);
|
this.__onFocus(ev);
|
||||||
this.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
this.dispatchEvent(new Event('focusin', { bubbles: true, composed: true }));
|
||||||
};
|
};
|
||||||
this.inputElement.addEventListener('focusin', this.__redispatchFocusin);
|
this._inputNode.addEventListener('focusin', this.__redispatchFocusin);
|
||||||
|
|
||||||
// focusout
|
// focusout
|
||||||
this.__redispatchFocusout = ev => {
|
this.__redispatchFocusout = ev => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._onBlur();
|
this.__onBlur();
|
||||||
this.dispatchEvent(new Event('focusout', { bubbles: true, composed: true }));
|
this.dispatchEvent(new Event('focusout', { bubbles: true, composed: true }));
|
||||||
};
|
};
|
||||||
this.inputElement.addEventListener('focusout', this.__redispatchFocusout);
|
this._inputNode.addEventListener('focusout', this.__redispatchFocusout);
|
||||||
}
|
}
|
||||||
|
|
||||||
__teardownEventsForFocusMixin() {
|
__teardownEventsForFocusMixin() {
|
||||||
this.inputElement.removeEventListener('focus', this.__redispatchFocus);
|
this._inputNode.removeEventListener('focus', this.__redispatchFocus);
|
||||||
this.inputElement.removeEventListener('blur', this.__redispatchBlur);
|
this._inputNode.removeEventListener('blur', this.__redispatchBlur);
|
||||||
this.inputElement.removeEventListener('focusin', this.__redispatchFocusin);
|
this._inputNode.removeEventListener('focusin', this.__redispatchFocusin);
|
||||||
this.inputElement.removeEventListener('focusout', this.__redispatchFocusout);
|
this._inputNode.removeEventListener('focusout', this.__redispatchFocusout);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { html, css, nothing, dedupeMixin, SlotMixin } from '@lion/core';
|
import { html, css, nothing, dedupeMixin, SlotMixin } from '@lion/core';
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
|
||||||
import { FormRegisteringMixin } from './FormRegisteringMixin.js';
|
import { FormRegisteringMixin } from './FormRegisteringMixin.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -15,18 +14,18 @@ import { FormRegisteringMixin } from './FormRegisteringMixin.js';
|
||||||
export const FormControlMixin = dedupeMixin(
|
export const FormControlMixin = dedupeMixin(
|
||||||
superclass =>
|
superclass =>
|
||||||
// eslint-disable-next-line no-shadow, no-unused-vars
|
// 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() {
|
static get properties() {
|
||||||
return {
|
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: {
|
_ariaDescribedby: {
|
||||||
type: String,
|
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: {
|
_ariaLabelledby: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -65,18 +64,27 @@ export const FormControlMixin = dedupeMixin(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get asyncObservers() {
|
updated(changedProps) {
|
||||||
return {
|
super.updated(changedProps);
|
||||||
...super.asyncObservers,
|
|
||||||
_onAriaLabelledbyChanged: ['_ariaLabelledby'],
|
if (changedProps.has('_ariaLabelledby')) {
|
||||||
_onAriaDescribedbyChanged: ['_ariaDescribedby'],
|
this._onAriaLabelledbyChanged({ _ariaLabelledby: this._ariaLabelledby });
|
||||||
_onLabelChanged: ['label'],
|
}
|
||||||
_onHelpTextChanged: ['helpText'],
|
|
||||||
};
|
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 _inputNode() {
|
||||||
get inputElement() {
|
|
||||||
return this.__getDirectSlotChild('input');
|
return this.__getDirectSlotChild('input');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,16 +120,16 @@ export const FormControlMixin = dedupeMixin(
|
||||||
*/
|
*/
|
||||||
|
|
||||||
_enhanceLightDomClasses() {
|
_enhanceLightDomClasses() {
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.classList.add('form-control');
|
this._inputNode.classList.add('form-control');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_enhanceLightDomA11y() {
|
_enhanceLightDomA11y() {
|
||||||
const { inputElement, _labelNode, _helpTextNode, _feedbackNode } = this;
|
const { _inputNode, _labelNode, _helpTextNode, _feedbackNode } = this;
|
||||||
|
|
||||||
if (inputElement) {
|
if (_inputNode) {
|
||||||
inputElement.id = inputElement.id || this._inputId;
|
_inputNode.id = _inputNode.id || this._inputId;
|
||||||
}
|
}
|
||||||
if (_labelNode) {
|
if (_labelNode) {
|
||||||
_labelNode.setAttribute('for', this._inputId);
|
_labelNode.setAttribute('for', this._inputId);
|
||||||
|
|
@ -178,8 +186,8 @@ export const FormControlMixin = dedupeMixin(
|
||||||
* from an external context, will be read by a screen reader.
|
* from an external context, will be read by a screen reader.
|
||||||
*/
|
*/
|
||||||
_onAriaLabelledbyChanged({ _ariaLabelledby }) {
|
_onAriaLabelledbyChanged({ _ariaLabelledby }) {
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.setAttribute('aria-labelledby', _ariaLabelledby);
|
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.
|
* from an external context, will be read by a screen reader.
|
||||||
*/
|
*/
|
||||||
_onAriaDescribedbyChanged({ _ariaDescribedby }) {
|
_onAriaDescribedbyChanged({ _ariaDescribedby }) {
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.setAttribute('aria-describedby', _ariaDescribedby);
|
this._inputNode.setAttribute('aria-describedby', _ariaDescribedby);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -287,7 +295,7 @@ export const FormControlMixin = dedupeMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
inputGroupPrefixTemplate() {
|
inputGroupPrefixTemplate() {
|
||||||
return !this.$$slot('prefix')
|
return !this.querySelector('[slot=prefix]')
|
||||||
? nothing
|
? nothing
|
||||||
: html`
|
: html`
|
||||||
<div class="input-group__prefix">
|
<div class="input-group__prefix">
|
||||||
|
|
@ -306,7 +314,7 @@ export const FormControlMixin = dedupeMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
inputGroupSuffixTemplate() {
|
inputGroupSuffixTemplate() {
|
||||||
return !this.$$slot('suffix')
|
return !this.querySelector('[slot=suffix]')
|
||||||
? nothing
|
? nothing
|
||||||
: html`
|
: html`
|
||||||
<div class="input-group__suffix">
|
<div class="input-group__suffix">
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ import { Unparseable } from '@lion/validate';
|
||||||
* ## Flows
|
* ## Flows
|
||||||
* FormatMixin supports these two main flows:
|
* FormatMixin supports these two main flows:
|
||||||
* [1] Application Developer sets `.modelValue`:
|
* [1] Application Developer sets `.modelValue`:
|
||||||
* Flow: `.modelValue` (formatter) -> `.formattedValue` -> `.inputElement.value`
|
* Flow: `.modelValue` (formatter) -> `.formattedValue` -> `._inputNode.value`
|
||||||
* (serializer) -> `.serializedValue`
|
* (serializer) -> `.serializedValue`
|
||||||
* [2] End user interacts with field:
|
* [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`
|
* (serializer) -> `.serializedValue`
|
||||||
*
|
*
|
||||||
* For backwards compatibility with the platform, we also support `.value` as an api. In that case
|
* 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
|
* 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.
|
* 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
|
* 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:
|
* For restoring serialized values fetched from a server, we could consider one extra flow:
|
||||||
* [3] Application Developer sets `.serializedValue`:
|
* [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(
|
export const FormatMixin = dedupeMixin(
|
||||||
superclass =>
|
superclass =>
|
||||||
|
|
@ -70,7 +70,7 @@ export const FormatMixin = dedupeMixin(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The view value is the result of the formatter function (when available).
|
* 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:
|
* Examples:
|
||||||
* - For a date input, this would be '20/01/1999' (dependent on locale).
|
* - 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)
|
* instead of 1234.56)
|
||||||
*
|
*
|
||||||
* When no parser is available, the value is usually the same as the formattedValue
|
* When no parser is available, the value is usually the same as the formattedValue
|
||||||
* (being inputElement.value)
|
* (being _inputNode.value)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
serializedValue: {
|
serializedValue: {
|
||||||
|
|
@ -127,16 +127,10 @@ export const FormatMixin = dedupeMixin(
|
||||||
this._onModelValueChanged({ modelValue: this.modelValue }, { modelValue: oldVal });
|
this._onModelValueChanged({ modelValue: this.modelValue }, { modelValue: oldVal });
|
||||||
}
|
}
|
||||||
if (name === 'serializedValue' && this.serializedValue !== oldVal) {
|
if (name === 'serializedValue' && this.serializedValue !== oldVal) {
|
||||||
this._onSerializedValueChanged(
|
this._calculateValues({ source: 'serialized' });
|
||||||
{ serializedValue: this.serializedValue },
|
|
||||||
{ serializedValue: oldVal },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (name === 'formattedValue' && this.formattedValue !== oldVal) {
|
if (name === 'formattedValue' && this.formattedValue !== oldVal) {
|
||||||
this._onFormattedValueChanged(
|
this._calculateValues({ source: 'formatted' });
|
||||||
{ formattedValue: this.formattedValue },
|
|
||||||
{ formattedValue: oldVal },
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,7 +146,7 @@ export const FormatMixin = dedupeMixin(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts modelValue to formattedValue (formattedValue will be synced with
|
* Converts modelValue to formattedValue (formattedValue will be synced with
|
||||||
* `.inputElement.value`)
|
* `._inputNode.value`)
|
||||||
* For instance, a Date object to a localized date.
|
* For instance, a Date object to a localized date.
|
||||||
* @param {Object} value - modelValue: can be an Object, Number, String depending on the
|
* @param {Object} value - modelValue: can be an Object, Number, String depending on the
|
||||||
* input type(date, number, email etc)
|
* 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
|
// 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') {
|
if (typeof value !== 'string') {
|
||||||
// This means there is nothing to find inside the view that can be of
|
// 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
|
// interest to the Application Developer or needed to store for future
|
||||||
|
|
@ -260,10 +254,10 @@ export const FormatMixin = dedupeMixin(
|
||||||
// the value, no matter what.
|
// the value, no matter what.
|
||||||
// This means, whenever we are in errorState and modelValue is set
|
// 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
|
// 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) {
|
if (this.__isHandlingUserInput && this.errorState && this._inputNode) {
|
||||||
return this.inputElement ? this.value : undefined;
|
return this._inputNode ? this.value : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.modelValue instanceof Unparseable) {
|
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() {
|
_syncValueUpwards() {
|
||||||
// Downwards syncing should only happen for `LionField`.value changes from 'above'
|
// 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 [1] will always be reflected back
|
||||||
* - flow [2] will not be reflected back when this flow was triggered via
|
* - 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)
|
* `@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._reflectBackFormattedValueToUser = this._reflectBackFormattedValueToUser.bind(this);
|
||||||
|
|
||||||
this._reflectBackFormattedValueDebounced = () => {
|
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
|
// is guaranteed to be calculated
|
||||||
setTimeout(this._reflectBackFormattedValueToUser);
|
setTimeout(this._reflectBackFormattedValueToUser);
|
||||||
};
|
};
|
||||||
|
|
@ -385,21 +357,18 @@ export const FormatMixin = dedupeMixin(
|
||||||
}
|
}
|
||||||
this._reflectBackFormattedValueToUser();
|
this._reflectBackFormattedValueToUser();
|
||||||
|
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.addEventListener(
|
this._inputNode.addEventListener(this.formatOn, this._reflectBackFormattedValueDebounced);
|
||||||
this.formatOn,
|
this._inputNode.addEventListener('input', this._proxyInputEvent);
|
||||||
this._reflectBackFormattedValueDebounced,
|
|
||||||
);
|
|
||||||
this.inputElement.addEventListener('input', this._proxyInputEvent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this.removeEventListener('user-input-changed', this._onUserInputChanged);
|
this.removeEventListener('user-input-changed', this._onUserInputChanged);
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.removeEventListener('input', this._proxyInputEvent);
|
this._inputNode.removeEventListener('input', this._proxyInputEvent);
|
||||||
this.inputElement.removeEventListener(
|
this._inputNode.removeEventListener(
|
||||||
this.formatOn,
|
this.formatOn,
|
||||||
this._reflectBackFormattedValueDebounced,
|
this._reflectBackFormattedValueDebounced,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -104,17 +104,6 @@ export const InteractionStateMixin = dedupeMixin(
|
||||||
this.removeEventListener(this._valueChangedEvent, this._iStateOnValueChange);
|
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
|
* Evaluations performed on connectedCallback. Since some components can be out of sync
|
||||||
* (due to interdependence on light children that can only be processed
|
* (due to interdependence on light children that can only be processed
|
||||||
|
|
@ -163,19 +152,5 @@ export const InteractionStateMixin = dedupeMixin(
|
||||||
_onDirtyChanged() {
|
_onDirtyChanged() {
|
||||||
this.dispatchEvent(new CustomEvent('dirty-changed', { bubbles: true, composed: true }));
|
this.dispatchEvent(new CustomEvent('dirty-changed', { bubbles: true, composed: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
get leaveEvent() {
|
|
||||||
return this._leaveEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
set leaveEvent(eventName) {
|
|
||||||
this._leaveEvent = eventName;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import { SlotMixin, LitElement } from '@lion/core';
|
import { SlotMixin, LitElement } from '@lion/core';
|
||||||
import { ElementMixin } from '@lion/core/src/ElementMixin.js';
|
|
||||||
import { DisabledMixin } from '@lion/core/src/DisabledMixin.js';
|
import { DisabledMixin } from '@lion/core/src/DisabledMixin.js';
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
|
||||||
import { ValidateMixin } from '@lion/validate';
|
import { ValidateMixin } from '@lion/validate';
|
||||||
import { FormControlMixin } from './FormControlMixin.js';
|
import { FormControlMixin } from './FormControlMixin.js';
|
||||||
import { InteractionStateMixin } from './InteractionStateMixin.js'; // applies FocusMixin
|
import { InteractionStateMixin } from './InteractionStateMixin.js'; // applies FocusMixin
|
||||||
|
|
@ -30,13 +28,11 @@ import { FocusMixin } from './FocusMixin.js';
|
||||||
* <input type="text" slot="input">
|
* <input type="text" slot="input">
|
||||||
* </lion-field>
|
* </lion-field>
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-field
|
||||||
*/
|
*/
|
||||||
export class LionField extends FormControlMixin(
|
export class LionField extends FormControlMixin(
|
||||||
InteractionStateMixin(
|
InteractionStateMixin(
|
||||||
FocusMixin(
|
FocusMixin(FormatMixin(ValidateMixin(DisabledMixin(SlotMixin(LitElement))))),
|
||||||
FormatMixin(ValidateMixin(DisabledMixin(ElementMixin(SlotMixin(ObserverMixin(LitElement)))))),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
|
|
@ -57,7 +53,7 @@ export class LionField extends FormControlMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectionStart() {
|
get selectionStart() {
|
||||||
const native = this.inputElement;
|
const native = this._inputNode;
|
||||||
if (native && native.selectionStart) {
|
if (native && native.selectionStart) {
|
||||||
return native.selectionStart;
|
return native.selectionStart;
|
||||||
}
|
}
|
||||||
|
|
@ -65,14 +61,14 @@ export class LionField extends FormControlMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
set selectionStart(value) {
|
set selectionStart(value) {
|
||||||
const native = this.inputElement;
|
const native = this._inputNode;
|
||||||
if (native && native.selectionStart) {
|
if (native && native.selectionStart) {
|
||||||
native.selectionStart = value;
|
native.selectionStart = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectionEnd() {
|
get selectionEnd() {
|
||||||
const native = this.inputElement;
|
const native = this._inputNode;
|
||||||
if (native && native.selectionEnd) {
|
if (native && native.selectionEnd) {
|
||||||
return native.selectionEnd;
|
return native.selectionEnd;
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +76,7 @@ export class LionField extends FormControlMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
set selectionEnd(value) {
|
set selectionEnd(value) {
|
||||||
const native = this.inputElement;
|
const native = this._inputNode;
|
||||||
if (native && native.selectionEnd) {
|
if (native && native.selectionEnd) {
|
||||||
native.selectionEnd = value;
|
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
|
// We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret
|
||||||
set value(value) {
|
set value(value) {
|
||||||
// if not yet connected to dom can't change the value
|
// if not yet connected to dom can't change the value
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this._setValueAndPreserveCaret(value);
|
this._setValueAndPreserveCaret(value);
|
||||||
}
|
}
|
||||||
this._onValueChanged({ value });
|
this._onValueChanged({ value });
|
||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return (this.inputElement && this.inputElement.value) || '';
|
return (this._inputNode && this._inputNode.value) || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -120,13 +116,13 @@ export class LionField extends FormControlMixin(
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
this._onChange = this._onChange.bind(this);
|
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
|
this.classList.add('form-field'); // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this.inputElement.removeEventListener('change', this._onChange);
|
this._inputNode.removeEventListener('change', this._onChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProps) {
|
updated(changedProps) {
|
||||||
|
|
@ -134,25 +130,25 @@ export class LionField extends FormControlMixin(
|
||||||
|
|
||||||
if (changedProps.has('disabled')) {
|
if (changedProps.has('disabled')) {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
this.inputElement.disabled = true;
|
this._inputNode.disabled = true;
|
||||||
this.classList.add('state-disabled'); // eslint-disable-line wc/no-self-class
|
this.classList.add('state-disabled'); // eslint-disable-line wc/no-self-class
|
||||||
} else {
|
} else {
|
||||||
this.inputElement.disabled = false;
|
this._inputNode.disabled = false;
|
||||||
this.classList.remove('state-disabled'); // eslint-disable-line wc/no-self-class
|
this.classList.remove('state-disabled'); // eslint-disable-line wc/no-self-class
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProps.has('name')) {
|
if (changedProps.has('name')) {
|
||||||
this.inputElement.name = this.name;
|
this._inputNode.name = this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProps.has('autocomplete')) {
|
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
|
* does not trigger a value change
|
||||||
*/
|
*/
|
||||||
_delegateInitialValueAttr() {
|
_delegateInitialValueAttr() {
|
||||||
|
|
@ -214,18 +210,18 @@ export class LionField extends FormControlMixin(
|
||||||
// right properties, accessing them might throw an exception (like for
|
// right properties, accessing them might throw an exception (like for
|
||||||
// <input type=number>)
|
// <input type=number>)
|
||||||
try {
|
try {
|
||||||
const start = this.inputElement.selectionStart;
|
const start = this._inputNode.selectionStart;
|
||||||
this.inputElement.value = newValue;
|
this._inputNode.value = newValue;
|
||||||
// The cursor automatically jumps to the end after re-setting the value,
|
// The cursor automatically jumps to the end after re-setting the value,
|
||||||
// so restore it to its original position.
|
// so restore it to its original position.
|
||||||
this.inputElement.selectionStart = start;
|
this._inputNode.selectionStart = start;
|
||||||
this.inputElement.selectionEnd = start;
|
this._inputNode.selectionEnd = start;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Just set the value and give up on the caret.
|
// Just set the value and give up on the caret.
|
||||||
this.inputElement.value = newValue;
|
this._inputNode.value = newValue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.inputElement.value = newValue;
|
this._inputNode.value = newValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { FormatMixin } from '../src/FormatMixin.js';
|
||||||
|
|
||||||
function mimicUserInput(formControl, newViewValue) {
|
function mimicUserInput(formControl, newViewValue) {
|
||||||
formControl.value = newViewValue; // eslint-disable-line no-param-reassign
|
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) {
|
export function runFormatMixinSuite(customConfig) {
|
||||||
|
|
@ -74,14 +74,14 @@ export function runFormatMixinSuite(customConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
set value(newValue) {
|
set value(newValue) {
|
||||||
this.inputElement.value = newValue;
|
this._inputNode.value = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this.inputElement.value;
|
return this._inputNode.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
get inputElement() {
|
get _inputNode() {
|
||||||
return this.querySelector('input');
|
return this.querySelector('input');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -164,7 +164,7 @@ export function runFormatMixinSuite(customConfig) {
|
||||||
fooFormat.modelValue = 'string';
|
fooFormat.modelValue = 'string';
|
||||||
expect(fooFormat.formattedValue).to.equal('foo: string');
|
expect(fooFormat.formattedValue).to.equal('foo: string');
|
||||||
expect(fooFormat.value).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 () => {
|
it('converts modelValue => formattedValue (via this.formatter)', async () => {
|
||||||
|
|
@ -188,7 +188,7 @@ export function runFormatMixinSuite(customConfig) {
|
||||||
expect(fooFormat.modelValue).to.equal('string');
|
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 <lion-field>, not on <input>
|
// Note that in lion-field, the attribute would be put on <lion-field>, not on <input>
|
||||||
const formatElem = await fixture(html`
|
const formatElem = await fixture(html`
|
||||||
<${elem}
|
<${elem}
|
||||||
|
|
@ -202,7 +202,7 @@ export function runFormatMixinSuite(customConfig) {
|
||||||
await formatElem.updateComplete;
|
await formatElem.updateComplete;
|
||||||
expect(formatElem.formattedValue).to.equal('foo: string');
|
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.serializedValue).to.equal('[foo] string');
|
||||||
expect(formatElem.modelValue).to.equal('string');
|
expect(formatElem.modelValue).to.equal('string');
|
||||||
|
|
@ -218,12 +218,12 @@ export function runFormatMixinSuite(customConfig) {
|
||||||
const generatedViewValue = generateValueBasedOnType({ viewValue: true });
|
const generatedViewValue = generateValueBasedOnType({ viewValue: true });
|
||||||
const generatedModelValue = generateValueBasedOnType();
|
const generatedModelValue = generateValueBasedOnType();
|
||||||
mimicUserInput(formatEl, generatedViewValue);
|
mimicUserInput(formatEl, generatedViewValue);
|
||||||
expect(formatEl.inputElement.value).to.not.equal(`foo: ${generatedModelValue}`);
|
expect(formatEl._inputNode.value).to.not.equal(`foo: ${generatedModelValue}`);
|
||||||
|
|
||||||
// user leaves field
|
// user leaves field
|
||||||
formatEl.inputElement.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true }));
|
formatEl._inputNode.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true }));
|
||||||
await aTimeout();
|
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 () => {
|
it('reflects back .formattedValue immediately when .modelValue changed imperatively', async () => {
|
||||||
|
|
@ -238,14 +238,14 @@ export function runFormatMixinSuite(customConfig) {
|
||||||
|
|
||||||
// users types value 'test'
|
// users types value 'test'
|
||||||
mimicUserInput(el, '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
|
// Now see the difference for an imperative change
|
||||||
el.modelValue = 'test2';
|
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 tagNoInputString = defineCE(class extends FormatMixin(LitElement) {});
|
||||||
const tagNoInput = unsafeStatic(tagNoInputString);
|
const tagNoInput = unsafeStatic(tagNoInputString);
|
||||||
expect(async () => {
|
expect(async () => {
|
||||||
|
|
|
||||||
|
|
@ -67,18 +67,6 @@ export function runInteractionStateMixinSuite(customConfig) {
|
||||||
expect(el.touched).to.be.true;
|
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}></${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 () => {
|
it('sets an attribute "touched', async () => {
|
||||||
const el = await fixture(html`<${tag}></${tag}>`);
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
el.touched = true;
|
el.touched = true;
|
||||||
|
|
@ -125,7 +113,7 @@ export function runInteractionStateMixinSuite(customConfig) {
|
||||||
const el = await fixture(html`<${tag}></${tag}>`);
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
|
|
||||||
const changeModelValueAndLeave = modelValue => {
|
const changeModelValueAndLeave = modelValue => {
|
||||||
const targetEl = el.inputElement || el;
|
const targetEl = el._inputNode || el;
|
||||||
targetEl.dispatchEvent(new Event('focus', { bubbles: true }));
|
targetEl.dispatchEvent(new Event('focus', { bubbles: true }));
|
||||||
el.modelValue = modelValue;
|
el.modelValue = modelValue;
|
||||||
targetEl.dispatchEvent(new Event(el._leaveEvent, { bubbles: true }));
|
targetEl.dispatchEvent(new Event(el._leaveEvent, { bubbles: true }));
|
||||||
|
|
@ -201,11 +189,6 @@ export function runInteractionStateMixinSuite(customConfig) {
|
||||||
el.dispatchEvent(new Event('custom-blur'));
|
el.dispatchEvent(new Event('custom-blur'));
|
||||||
expect(el.touched).to.be.true;
|
expect(el.touched).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can override the deprecated `leaveEvent`', async () => {
|
|
||||||
const el = await fixture(html`<${tag} .leaveEvent=${'custom-blur'}></${tag}>`);
|
|
||||||
expect(el._leaveEvent).to.equal('custom-blur');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,6 @@ describe('FieldCustomMixin', () => {
|
||||||
const lionField = await fixture(`
|
const lionField = await fixture(`
|
||||||
<${elem} disable-help-text>${inputSlot}</${elem}>
|
<${elem} disable-help-text>${inputSlot}</${elem}>
|
||||||
`);
|
`);
|
||||||
expect(lionField.$$slot('help-text')).to.equal(undefined);
|
expect(lionField.querySelector('[slot=help-text]')).to.equal(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ describe('FocusMixin', () => {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get inputElement() {
|
get _inputNode() {
|
||||||
return this.querySelector('input');
|
return this.querySelector('input');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -29,9 +29,9 @@ describe('FocusMixin', () => {
|
||||||
<${tag}><input slot="input"></${tag}>
|
<${tag}><input slot="input"></${tag}>
|
||||||
`);
|
`);
|
||||||
el.focus();
|
el.focus();
|
||||||
expect(document.activeElement === el.inputElement).to.be.true;
|
expect(document.activeElement === el._inputNode).to.be.true;
|
||||||
el.blur();
|
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 () => {
|
it('has an attribute focused when focused', async () => {
|
||||||
|
|
@ -52,25 +52,12 @@ describe('FocusMixin', () => {
|
||||||
<${tag}><input slot="input"></${tag}>
|
<${tag}><input slot="input"></${tag}>
|
||||||
`);
|
`);
|
||||||
expect(el.focused).to.be.false;
|
expect(el.focused).to.be.false;
|
||||||
el.inputElement.focus();
|
el._inputNode.focus();
|
||||||
expect(el.focused).to.be.true;
|
expect(el.focused).to.be.true;
|
||||||
el.inputElement.blur();
|
el._inputNode.blur();
|
||||||
expect(el.focused).to.be.false;
|
expect(el.focused).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has a deprecated "state-focused" css class when focused', async () => {
|
|
||||||
const el = await fixture(html`
|
|
||||||
<${tag}><input slot="input"></${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 () => {
|
it('dispatches [focus, blur] events', async () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<${tag}><input slot="input"></${tag}>
|
<${tag}><input slot="input"></${tag}>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
|
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
|
||||||
import { SlotMixin } from '@lion/core';
|
import { LitElement, SlotMixin } from '@lion/core';
|
||||||
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
|
||||||
|
|
||||||
import { FormControlMixin } from '../src/FormControlMixin.js';
|
import { FormControlMixin } from '../src/FormControlMixin.js';
|
||||||
|
|
||||||
|
|
@ -10,7 +9,7 @@ describe('FormControlMixin', () => {
|
||||||
let tag;
|
let tag;
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const FormControlMixinClass = class extends FormControlMixin(SlotMixin(LionLitElement)) {
|
const FormControlMixinClass = class extends FormControlMixin(SlotMixin(LitElement)) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
@ -28,7 +27,7 @@ describe('FormControlMixin', () => {
|
||||||
const lionFieldAttr = await fixture(html`
|
const lionFieldAttr = await fixture(html`
|
||||||
<${tag} help-text="This email address is already taken">${inputSlot}</${tag}>
|
<${tag} help-text="This email address is already taken">${inputSlot}</${tag}>
|
||||||
`);
|
`);
|
||||||
expect(lionFieldAttr.$$slot('help-text').textContent).to.contain(
|
expect(lionFieldAttr.querySelector('[slot=help-text]').textContent).to.contain(
|
||||||
'This email address is already taken',
|
'This email address is already taken',
|
||||||
);
|
);
|
||||||
const lionFieldProp = await fixture(html`
|
const lionFieldProp = await fixture(html`
|
||||||
|
|
@ -37,7 +36,7 @@ describe('FormControlMixin', () => {
|
||||||
>${inputSlot}
|
>${inputSlot}
|
||||||
</${tag}>`);
|
</${tag}>`);
|
||||||
|
|
||||||
expect(lionFieldProp.$$slot('help-text').textContent).to.contain(
|
expect(lionFieldProp.querySelector('[slot=help-text]').textContent).to.contain(
|
||||||
'This email address is already taken',
|
'This email address is already taken',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -54,7 +53,7 @@ describe('FormControlMixin', () => {
|
||||||
|
|
||||||
['aria-describedby', 'aria-labelledby'].forEach(ariaAttributeName => {
|
['aria-describedby', 'aria-labelledby'].forEach(ariaAttributeName => {
|
||||||
const ariaAttribute = lionField
|
const ariaAttribute = lionField
|
||||||
.$$slot('input')
|
.querySelector('[slot=input]')
|
||||||
.getAttribute(ariaAttributeName)
|
.getAttribute(ariaAttributeName)
|
||||||
.trim()
|
.trim()
|
||||||
.split(' ');
|
.split(' ');
|
||||||
|
|
@ -71,6 +70,6 @@ describe('FormControlMixin', () => {
|
||||||
</${tag}>
|
</${tag}>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(lionField.$$slot('feedback').getAttribute('aria-live')).to.equal('polite');
|
expect(lionField.querySelector('[slot=feedback]').getAttribute('aria-live')).to.equal('polite');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const fieldTagString = defineCE(
|
||||||
get slots() {
|
get slots() {
|
||||||
return {
|
return {
|
||||||
...super.slots,
|
...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'),
|
input: () => document.createElement('input'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ const inputSlot = unsafeHTML(inputSlotString);
|
||||||
|
|
||||||
function mimicUserInput(formControl, newViewValue) {
|
function mimicUserInput(formControl, newViewValue) {
|
||||||
formControl.value = newViewValue; // eslint-disable-line no-param-reassign
|
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(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -32,7 +32,7 @@ beforeEach(() => {
|
||||||
describe('<lion-field>', () => {
|
describe('<lion-field>', () => {
|
||||||
it(`puts a unique id "${tagString}-[hash]" on the native input`, async () => {
|
it(`puts a unique id "${tagString}-[hash]" on the native input`, async () => {
|
||||||
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
||||||
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 () => {
|
it('fires focus/blur event on host and native input if focused/blurred', async () => {
|
||||||
|
|
@ -40,15 +40,15 @@ describe('<lion-field>', () => {
|
||||||
const cbFocusHost = sinon.spy();
|
const cbFocusHost = sinon.spy();
|
||||||
el.addEventListener('focus', cbFocusHost);
|
el.addEventListener('focus', cbFocusHost);
|
||||||
const cbFocusNativeInput = sinon.spy();
|
const cbFocusNativeInput = sinon.spy();
|
||||||
el.inputElement.addEventListener('focus', cbFocusNativeInput);
|
el._inputNode.addEventListener('focus', cbFocusNativeInput);
|
||||||
const cbBlurHost = sinon.spy();
|
const cbBlurHost = sinon.spy();
|
||||||
el.addEventListener('blur', cbBlurHost);
|
el.addEventListener('blur', cbBlurHost);
|
||||||
const cbBlurNativeInput = sinon.spy();
|
const cbBlurNativeInput = sinon.spy();
|
||||||
el.inputElement.addEventListener('blur', cbBlurNativeInput);
|
el._inputNode.addEventListener('blur', cbBlurNativeInput);
|
||||||
|
|
||||||
await triggerFocusFor(el);
|
await triggerFocusFor(el);
|
||||||
|
|
||||||
expect(document.activeElement).to.equal(el.inputElement);
|
expect(document.activeElement).to.equal(el._inputNode);
|
||||||
expect(cbFocusHost.callCount).to.equal(1);
|
expect(cbFocusHost.callCount).to.equal(1);
|
||||||
expect(cbFocusNativeInput.callCount).to.equal(1);
|
expect(cbFocusNativeInput.callCount).to.equal(1);
|
||||||
expect(cbBlurHost.callCount).to.equal(0);
|
expect(cbBlurHost.callCount).to.equal(0);
|
||||||
|
|
@ -59,7 +59,7 @@ describe('<lion-field>', () => {
|
||||||
expect(cbBlurNativeInput.callCount).to.equal(1);
|
expect(cbBlurNativeInput.callCount).to.equal(1);
|
||||||
|
|
||||||
await triggerFocusFor(el);
|
await triggerFocusFor(el);
|
||||||
expect(document.activeElement).to.equal(el.inputElement);
|
expect(document.activeElement).to.equal(el._inputNode);
|
||||||
expect(cbFocusHost.callCount).to.equal(2);
|
expect(cbFocusHost.callCount).to.equal(2);
|
||||||
expect(cbFocusNativeInput.callCount).to.equal(2);
|
expect(cbFocusNativeInput.callCount).to.equal(2);
|
||||||
|
|
||||||
|
|
@ -80,14 +80,14 @@ describe('<lion-field>', () => {
|
||||||
it('can be disabled via attribute', async () => {
|
it('can be disabled via attribute', async () => {
|
||||||
const elDisabled = await fixture(html`<${tag} disabled>${inputSlot}</${tag}>`);
|
const elDisabled = await fixture(html`<${tag} disabled>${inputSlot}</${tag}>`);
|
||||||
expect(elDisabled.disabled).to.equal(true);
|
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 () => {
|
it('can be disabled via property', async () => {
|
||||||
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
||||||
el.disabled = true;
|
el.disabled = true;
|
||||||
await el.updateComplete;
|
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 () => {
|
it('can be cleared which erases value, validation and interaction states', async () => {
|
||||||
|
|
@ -113,29 +113,29 @@ describe('<lion-field>', () => {
|
||||||
|
|
||||||
it('reads initial value from attribute value', async () => {
|
it('reads initial value from attribute value', async () => {
|
||||||
const el = await fixture(html`<${tag} value="one">${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag} value="one">${inputSlot}</${tag}>`);
|
||||||
expect(el.$$slot('input').value).to.equal('one');
|
expect(el.querySelector('[slot=input]').value).to.equal('one');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('delegates value property', async () => {
|
it('delegates value property', async () => {
|
||||||
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
||||||
expect(el.$$slot('input').value).to.equal('');
|
expect(el.querySelector('[slot=input]').value).to.equal('');
|
||||||
el.value = 'one';
|
el.value = 'one';
|
||||||
expect(el.value).to.equal('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 () => {
|
it('delegates autocomplete property', async () => {
|
||||||
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
||||||
expect(el.inputElement.autocomplete).to.equal('');
|
expect(el._inputNode.autocomplete).to.equal('');
|
||||||
expect(el.inputElement.hasAttribute('autocomplete')).to.be.false;
|
expect(el._inputNode.hasAttribute('autocomplete')).to.be.false;
|
||||||
el.autocomplete = 'off';
|
el.autocomplete = 'off';
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.inputElement.autocomplete).to.equal('off');
|
expect(el._inputNode.autocomplete).to.equal('off');
|
||||||
expect(el.inputElement.getAttribute('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
|
// and make it act on this.value again
|
||||||
it('has a class "state-filled" if this.value is filled', async () => {
|
it('has a class "state-filled" if this.value is filled', async () => {
|
||||||
const el = await fixture(html`<${tag} value="filled">${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag} value="filled">${inputSlot}</${tag}>`);
|
||||||
|
|
@ -152,30 +152,30 @@ describe('<lion-field>', () => {
|
||||||
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
||||||
await triggerFocusFor(el);
|
await triggerFocusFor(el);
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
el.inputElement.value = 'hello world';
|
el._inputNode.value = 'hello world';
|
||||||
el.inputElement.selectionStart = 2;
|
el._inputNode.selectionStart = 2;
|
||||||
el.inputElement.selectionEnd = 2;
|
el._inputNode.selectionEnd = 2;
|
||||||
el.value = 'hey there universe';
|
el.value = 'hey there universe';
|
||||||
expect(el.inputElement.selectionStart).to.equal(2);
|
expect(el._inputNode.selectionStart).to.equal(2);
|
||||||
expect(el.inputElement.selectionEnd).to.equal(2);
|
expect(el._inputNode.selectionEnd).to.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: add pointerEvents test for disabled
|
// TODO: add pointerEvents test for disabled
|
||||||
it('has a class "state-disabled"', async () => {
|
it('has a class "state-disabled"', async () => {
|
||||||
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
||||||
expect(el.classList.contains('state-disabled')).to.equal(false);
|
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;
|
el.disabled = true;
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
await aTimeout();
|
await aTimeout();
|
||||||
|
|
||||||
expect(el.classList.contains('state-disabled')).to.equal(true);
|
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}</${tag}>`);
|
const disabledel = await fixture(html`<${tag} disabled>${inputSlot}</${tag}>`);
|
||||||
expect(disabledel.classList.contains('state-disabled')).to.equal(true);
|
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}`, () => {
|
describe(`A11y${nameSuffix}`, () => {
|
||||||
|
|
@ -200,7 +200,7 @@ describe('<lion-field>', () => {
|
||||||
<span slot="feedback">No name entered</span>
|
<span slot="feedback">No name entered</span>
|
||||||
</${tag}>
|
</${tag}>
|
||||||
`);
|
`);
|
||||||
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-labelledby')).to.equal(` label-${el._inputId}`);
|
||||||
expect(nativeInput.getAttribute('aria-describedby')).to.contain(` help-text-${el._inputId}`);
|
expect(nativeInput.getAttribute('aria-describedby')).to.contain(` help-text-${el._inputId}`);
|
||||||
|
|
@ -218,7 +218,7 @@ describe('<lion-field>', () => {
|
||||||
</${tag}>
|
</${tag}>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const nativeInput = el.$$slot('input');
|
const nativeInput = el.querySelector('[slot=input]');
|
||||||
expect(nativeInput.getAttribute('aria-labelledby')).to.contain(
|
expect(nativeInput.getAttribute('aria-labelledby')).to.contain(
|
||||||
` before-${el._inputId} after-${el._inputId}`,
|
` before-${el._inputId} after-${el._inputId}`,
|
||||||
);
|
);
|
||||||
|
|
@ -245,30 +245,30 @@ describe('<lion-field>', () => {
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
const { inputElement } = el;
|
const { _inputNode } = el;
|
||||||
|
|
||||||
// 1. addToAriaLabel()
|
// 1. addToAriaLabel()
|
||||||
// Check if the aria attr is filled initially
|
// 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');
|
el.addToAriaLabel('additionalLabel');
|
||||||
// Now check if ids are added to the end (not overridden)
|
// 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
|
// Should be placed in the end
|
||||||
expect(
|
expect(
|
||||||
inputElement.getAttribute('aria-labelledby').indexOf(`label-${el._inputId}`) <
|
_inputNode.getAttribute('aria-labelledby').indexOf(`label-${el._inputId}`) <
|
||||||
inputElement.getAttribute('aria-labelledby').indexOf('additionalLabel'),
|
_inputNode.getAttribute('aria-labelledby').indexOf('additionalLabel'),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. addToAriaDescription()
|
// 2. addToAriaDescription()
|
||||||
// Check if the aria attr is filled initially
|
// 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');
|
el.addToAriaDescription('additionalDescription');
|
||||||
// Now check if ids are added to the end (not overridden)
|
// 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
|
// Should be placed in the end
|
||||||
expect(
|
expect(
|
||||||
inputElement.getAttribute('aria-describedby').indexOf(`feedback-${el._inputId}`) <
|
_inputNode.getAttribute('aria-describedby').indexOf(`feedback-${el._inputId}`) <
|
||||||
inputElement.getAttribute('aria-describedby').indexOf('additionalDescription'),
|
_inputNode.getAttribute('aria-describedby').indexOf('additionalDescription'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -398,10 +398,10 @@ describe('<lion-field>', () => {
|
||||||
describe('Delegation', () => {
|
describe('Delegation', () => {
|
||||||
it('delegates property value', async () => {
|
it('delegates property value', async () => {
|
||||||
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`);
|
||||||
expect(el.inputElement.value).to.equal('');
|
expect(el._inputNode.value).to.equal('');
|
||||||
el.value = 'one';
|
el.value = 'one';
|
||||||
expect(el.value).to.equal('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 () => {
|
it('delegates property selectionStart and selectionEnd', async () => {
|
||||||
|
|
@ -413,8 +413,8 @@ describe('<lion-field>', () => {
|
||||||
|
|
||||||
el.selectionStart = 5;
|
el.selectionStart = 5;
|
||||||
el.selectionEnd = 12;
|
el.selectionEnd = 12;
|
||||||
expect(el.inputElement.selectionStart).to.equal(5);
|
expect(el._inputNode.selectionStart).to.equal(5);
|
||||||
expect(el.inputElement.selectionEnd).to.equal(12);
|
expect(el._inputNode.selectionEnd).to.equal(12);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { SlotMixin, html, LitElement } from '@lion/core';
|
import { SlotMixin, html, LitElement } from '@lion/core';
|
||||||
import { DisabledMixin } from '@lion/core/src/DisabledMixin.js';
|
import { DisabledMixin } from '@lion/core/src/DisabledMixin.js';
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
|
||||||
import { ValidateMixin } from '@lion/validate';
|
import { ValidateMixin } from '@lion/validate';
|
||||||
import { FormControlMixin, FormRegistrarMixin } from '@lion/field';
|
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.
|
* LionFieldset: fieldset wrapper providing extra features and integration with lion-field elements.
|
||||||
*
|
*
|
||||||
* @customElement lion-fieldset
|
* @customElement lion-fieldset
|
||||||
* @extends LionLitElement
|
* @extends {LitElement}
|
||||||
*/
|
*/
|
||||||
export class LionFieldset extends FormRegistrarMixin(
|
export class LionFieldset extends FormRegistrarMixin(
|
||||||
FormControlMixin(ValidateMixin(DisabledMixin(SlotMixin(ObserverMixin(LitElement))))),
|
FormControlMixin(ValidateMixin(DisabledMixin(SlotMixin(LitElement)))),
|
||||||
) {
|
) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -50,7 +49,7 @@ export class LionFieldset extends FormRegistrarMixin(
|
||||||
this.requestUpdate('touched', oldVal);
|
this.requestUpdate('touched', oldVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
get inputElement() {
|
get _inputNode() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,27 +127,12 @@ export class LionFieldset extends FormRegistrarMixin(
|
||||||
if (changedProps.has('disabled')) {
|
if (changedProps.has('disabled')) {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
this.__requestChildrenToBeDisabled();
|
this.__requestChildrenToBeDisabled();
|
||||||
/** @deprecated use disabled attribute instead */
|
|
||||||
this.classList.add('state-disabled'); // eslint-disable-line wc/no-self-class
|
|
||||||
} else {
|
} else {
|
||||||
this.__retractRequestChildrenToBeDisabled();
|
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')) {
|
if (changedProps.has('focused')) {
|
||||||
/** @deprecated use touched attribute instead */
|
|
||||||
this.classList[this.focused ? 'add' : 'remove']('state-focused');
|
|
||||||
if (this.focused === true) {
|
if (this.focused === true) {
|
||||||
this.__setupOutsideClickHandling();
|
this.__setupOutsideClickHandling();
|
||||||
}
|
}
|
||||||
|
|
@ -424,11 +408,6 @@ export class LionFieldset extends FormRegistrarMixin(
|
||||||
return this._getFromAllFormElements('_initialModelValue');
|
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
|
* Add aria-describedby to child element(field), so that it points to feedback/help-text of
|
||||||
* parent(fieldset)
|
* parent(fieldset)
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ describe('<lion-fieldset>', () => {
|
||||||
fieldset.appendChild(newField);
|
fieldset.appendChild(newField);
|
||||||
expect(Object.keys(fieldset.formElements).length).to.equal(4);
|
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);
|
expect(Object.keys(fieldset.formElements).length).to.equal(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -227,16 +227,6 @@ describe('<lion-fieldset>', () => {
|
||||||
expect(el.formElements.sub.formElements['hobbies[]'][1].disabled).to.equal(true);
|
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}</${tag}>`);
|
|
||||||
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', () => {
|
describe('validation', () => {
|
||||||
it('validates on init', async () => {
|
it('validates on init', async () => {
|
||||||
function isCat(value) {
|
function isCat(value) {
|
||||||
|
|
@ -334,10 +324,9 @@ describe('<lion-fieldset>', () => {
|
||||||
|
|
||||||
it('sets touched when last field in fieldset left after focus', async () => {
|
it('sets touched when last field in fieldset left after focus', async () => {
|
||||||
const fieldset = await fixture(html`<${tag}>${inputSlots}</${tag}>`);
|
const fieldset = await fixture(html`<${tag}>${inputSlots}</${tag}>`);
|
||||||
await triggerFocusFor(fieldset.formElements['hobbies[]'][0].inputElement);
|
await triggerFocusFor(fieldset.formElements['hobbies[]'][0]._inputNode);
|
||||||
await triggerFocusFor(
|
await triggerFocusFor(
|
||||||
fieldset.formElements['hobbies[]'][fieldset.formElements['gender[]'].length - 1]
|
fieldset.formElements['hobbies[]'][fieldset.formElements['gender[]'].length - 1]._inputNode,
|
||||||
.inputElement,
|
|
||||||
);
|
);
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<button></button>
|
<button></button>
|
||||||
|
|
@ -358,17 +347,6 @@ describe('<lion-fieldset>', () => {
|
||||||
expect(el).to.have.attribute('dirty');
|
expect(el).to.have.attribute('dirty');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('[deprecated] sets a class "state-(touched|dirty)"', async () => {
|
|
||||||
const el = await fixture(html`<${tag}></${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 () => {
|
it('becomes prefilled if all form elements are prefilled', async () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<${tag}>
|
<${tag}>
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,8 @@ export class HelperOutput extends LitElement {
|
||||||
this.field.addEventListener('focusin', this.__rerender);
|
this.field.addEventListener('focusin', this.__rerender);
|
||||||
this.field.addEventListener('focusout', this.__rerender);
|
this.field.addEventListener('focusout', this.__rerender);
|
||||||
|
|
||||||
if (this.field.inputElement.form) {
|
if (this.field._inputNode.form) {
|
||||||
this.field.inputElement.form.addEventListener('submit', this.__rerender);
|
this.field._inputNode.form.addEventListener('submit', this.__rerender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { LionFieldset } from '@lion/fieldset';
|
||||||
/**
|
/**
|
||||||
* LionForm: form wrapper providing extra features and integration with lion-field elements.
|
* LionForm: form wrapper providing extra features and integration with lion-field elements.
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-form
|
||||||
* @extends LionFieldset
|
* @extends {LionFieldset}
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
export class LionForm extends LionFieldset {
|
export class LionForm extends LionFieldset {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { css } from '@lion/core';
|
import { css } from '@lion/core';
|
||||||
import { LocalizeMixin } from '@lion/localize';
|
import { LocalizeMixin } from '@lion/localize';
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
|
||||||
import { LionInput } from '@lion/input';
|
import { LionInput } from '@lion/input';
|
||||||
import { FieldCustomMixin } from '@lion/field';
|
import { FieldCustomMixin } from '@lion/field';
|
||||||
import { isNumberValidator } from '@lion/validate';
|
import { isNumberValidator } from '@lion/validate';
|
||||||
|
|
@ -10,10 +9,10 @@ import { formatAmount } from './formatters.js';
|
||||||
/**
|
/**
|
||||||
* `LionInputAmount` is a class for an amount custom form element (`<lion-input-amount>`).
|
* `LionInputAmount` is a class for an amount custom form element (`<lion-input-amount>`).
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-input-amount
|
||||||
* @extends {LionInput}
|
* @extends {LionInput}
|
||||||
*/
|
*/
|
||||||
export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(ObserverMixin(LionInput))) {
|
export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(LionInput)) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
currency: {
|
currency: {
|
||||||
|
|
@ -22,11 +21,11 @@ export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(ObserverMixi
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get asyncObservers() {
|
updated(changedProps) {
|
||||||
return {
|
super.updated(changedProps);
|
||||||
...super.asyncObservers,
|
if (changedProps.has('currency')) {
|
||||||
_onCurrencyChanged: ['currency'],
|
this._onCurrencyChanged({ currency: this.currency });
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get slots() {
|
get slots() {
|
||||||
|
|
@ -57,7 +56,7 @@ export class LionInputAmount extends FieldCustomMixin(LocalizeMixin(ObserverMixi
|
||||||
|
|
||||||
_onCurrencyChanged({ currency }) {
|
_onCurrencyChanged({ currency }) {
|
||||||
if (this._isPrivateSlot('after')) {
|
if (this._isPrivateSlot('after')) {
|
||||||
this.$$slot('after').textContent = currency;
|
this.querySelector('[slot=after]').textContent = currency;
|
||||||
}
|
}
|
||||||
this.formatOptions.currency = currency;
|
this.formatOptions.currency = currency;
|
||||||
this._calculateValues();
|
this._calculateValues();
|
||||||
|
|
|
||||||
|
|
@ -66,33 +66,33 @@ describe('<lion-input-amount>', () => {
|
||||||
|
|
||||||
it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => {
|
it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => {
|
||||||
const el = await fixture(`<lion-input-amount></lion-input-amount>`);
|
const el = await fixture(`<lion-input-amount></lion-input-amount>`);
|
||||||
expect(el.inputElement.type).to.equal('text');
|
expect(el._inputNode.type).to.equal('text');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows no currency', async () => {
|
it('shows no currency', async () => {
|
||||||
const el = await fixture(`<lion-input-amount></lion-input-amount>`);
|
const el = await fixture(`<lion-input-amount></lion-input-amount>`);
|
||||||
expect(el.$$slot('suffix')).to.be.undefined;
|
expect(el.querySelector('[slot=suffix]')).to.be.null;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays currency if provided', async () => {
|
it('displays currency if provided', async () => {
|
||||||
const el = await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`);
|
const el = await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`);
|
||||||
expect(el.$$slot('after').innerText).to.equal('EUR');
|
expect(el.querySelector('[slot=after]').innerText).to.equal('EUR');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can update currency', async () => {
|
it('can update currency', async () => {
|
||||||
const el = await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`);
|
const el = await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`);
|
||||||
el.currency = 'USD';
|
el.currency = 'USD';
|
||||||
await el.updateComplete;
|
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 () => {
|
it('ignores currency if a suffix is already present', async () => {
|
||||||
const el = await fixture(
|
const el = await fixture(
|
||||||
`<lion-input-amount currency="EUR"><span slot="suffix">my-currency</span></lion-input-amount>`,
|
`<lion-input-amount currency="EUR"><span slot="suffix">my-currency</span></lion-input-amount>`,
|
||||||
);
|
);
|
||||||
expect(el.$$slot('suffix').innerText).to.equal('my-currency');
|
expect(el.querySelector('[slot=suffix]').innerText).to.equal('my-currency');
|
||||||
el.currency = 'EUR';
|
el.currency = 'EUR';
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.$$slot('suffix').innerText).to.equal('my-currency');
|
expect(el.querySelector('[slot=suffix]').innerText).to.equal('my-currency');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { isDateValidator } from '@lion/validate';
|
||||||
/**
|
/**
|
||||||
* `LionInputDate` is a class for a date custom form element (`<lion-input-date>`).
|
* `LionInputDate` is a class for a date custom form element (`<lion-input-date>`).
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-input-date
|
||||||
* @extends {LionInput}
|
* @extends {LionInput}
|
||||||
*/
|
*/
|
||||||
export class LionInputDate extends FieldCustomMixin(LocalizeMixin(LionInput)) {
|
export class LionInputDate extends FieldCustomMixin(LocalizeMixin(LionInput)) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ describe('<lion-input-date>', () => {
|
||||||
|
|
||||||
it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => {
|
it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => {
|
||||||
const el = await fixture(`<lion-input-date></lion-input-date>`);
|
const el = await fixture(`<lion-input-date></lion-input-date>`);
|
||||||
expect(el.inputElement.type).to.equal('text');
|
expect(el._inputNode.type).to.equal('text');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has validator "isDate" applied by default', async () => {
|
it('has validator "isDate" applied by default', async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { html, css, LitElement, DomHelpersMixin } from '@lion/core';
|
import { html, css, LitElement } from '@lion/core';
|
||||||
import { LocalizeMixin } from '@lion/localize';
|
import { LocalizeMixin } from '@lion/localize';
|
||||||
|
|
||||||
export class LionCalendarOverlayFrame extends LocalizeMixin(DomHelpersMixin(LitElement)) {
|
export class LionCalendarOverlayFrame extends LocalizeMixin(LitElement) {
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
|
|
@ -43,16 +43,10 @@ export class LionCalendarOverlayFrame extends LocalizeMixin(DomHelpersMixin(LitE
|
||||||
switch (locale) {
|
switch (locale) {
|
||||||
case 'bg-BG':
|
case 'bg-BG':
|
||||||
return import('@lion/overlays/translations/bg-BG.js');
|
return import('@lion/overlays/translations/bg-BG.js');
|
||||||
case 'bg':
|
|
||||||
return import('@lion/overlays/translations/bg.js');
|
|
||||||
case 'cs-CZ':
|
case 'cs-CZ':
|
||||||
return import('@lion/overlays/translations/cs-CZ.js');
|
return import('@lion/overlays/translations/cs-CZ.js');
|
||||||
case 'cs':
|
|
||||||
return import('@lion/overlays/translations/cs.js');
|
|
||||||
case 'de-DE':
|
case 'de-DE':
|
||||||
return import('@lion/overlays/translations/de-DE.js');
|
return import('@lion/overlays/translations/de-DE.js');
|
||||||
case 'de':
|
|
||||||
return import('@lion/overlays/translations/de.js');
|
|
||||||
case 'en-AU':
|
case 'en-AU':
|
||||||
return import('@lion/overlays/translations/en-AU.js');
|
return import('@lion/overlays/translations/en-AU.js');
|
||||||
case 'en-GB':
|
case 'en-GB':
|
||||||
|
|
@ -60,54 +54,32 @@ export class LionCalendarOverlayFrame extends LocalizeMixin(DomHelpersMixin(LitE
|
||||||
case 'en-US':
|
case 'en-US':
|
||||||
return import('@lion/overlays/translations/en-US.js');
|
return import('@lion/overlays/translations/en-US.js');
|
||||||
case 'en-PH':
|
case 'en-PH':
|
||||||
case 'en':
|
|
||||||
return import('@lion/overlays/translations/en.js');
|
return import('@lion/overlays/translations/en.js');
|
||||||
case 'es-ES':
|
case 'es-ES':
|
||||||
return import('@lion/overlays/translations/es-ES.js');
|
return import('@lion/overlays/translations/es-ES.js');
|
||||||
case 'es':
|
|
||||||
return import('@lion/overlays/translations/es.js');
|
|
||||||
case 'fr-FR':
|
case 'fr-FR':
|
||||||
return import('@lion/overlays/translations/fr-FR.js');
|
return import('@lion/overlays/translations/fr-FR.js');
|
||||||
case 'fr-BE':
|
case 'fr-BE':
|
||||||
return import('@lion/overlays/translations/fr-BE.js');
|
return import('@lion/overlays/translations/fr-BE.js');
|
||||||
case 'fr':
|
|
||||||
return import('@lion/overlays/translations/fr.js');
|
|
||||||
case 'hu-HU':
|
case 'hu-HU':
|
||||||
return import('@lion/overlays/translations/hu-HU.js');
|
return import('@lion/overlays/translations/hu-HU.js');
|
||||||
case 'hu':
|
|
||||||
return import('@lion/overlays/translations/hu.js');
|
|
||||||
case 'it-IT':
|
case 'it-IT':
|
||||||
return import('@lion/overlays/translations/it-IT.js');
|
return import('@lion/overlays/translations/it-IT.js');
|
||||||
case 'it':
|
|
||||||
return import('@lion/overlays/translations/it.js');
|
|
||||||
case 'nl-BE':
|
case 'nl-BE':
|
||||||
return import('@lion/overlays/translations/nl-BE.js');
|
return import('@lion/overlays/translations/nl-BE.js');
|
||||||
case 'nl-NL':
|
case 'nl-NL':
|
||||||
return import('@lion/overlays/translations/nl-NL.js');
|
return import('@lion/overlays/translations/nl-NL.js');
|
||||||
case 'nl':
|
|
||||||
return import('@lion/overlays/translations/nl.js');
|
|
||||||
case 'pl-PL':
|
case 'pl-PL':
|
||||||
return import('@lion/overlays/translations/pl-PL.js');
|
return import('@lion/overlays/translations/pl-PL.js');
|
||||||
case 'pl':
|
|
||||||
return import('@lion/overlays/translations/pl.js');
|
|
||||||
case 'ro-RO':
|
case 'ro-RO':
|
||||||
return import('@lion/overlays/translations/ro-RO.js');
|
return import('@lion/overlays/translations/ro-RO.js');
|
||||||
case 'ro':
|
|
||||||
return import('@lion/overlays/translations/ro.js');
|
|
||||||
case 'ru-RU':
|
case 'ru-RU':
|
||||||
return import('@lion/overlays/translations/ru-RU.js');
|
return import('@lion/overlays/translations/ru-RU.js');
|
||||||
case 'ru':
|
|
||||||
return import('@lion/overlays/translations/ru.js');
|
|
||||||
case 'sk-SK':
|
case 'sk-SK':
|
||||||
return import('@lion/overlays/translations/sk-SK.js');
|
return import('@lion/overlays/translations/sk-SK.js');
|
||||||
case 'sk':
|
|
||||||
return import('@lion/overlays/translations/sk.js');
|
|
||||||
case 'uk-UA':
|
case 'uk-UA':
|
||||||
return import('@lion/overlays/translations/uk-UA.js');
|
return import('@lion/overlays/translations/uk-UA.js');
|
||||||
case 'uk':
|
|
||||||
return import('@lion/overlays/translations/uk.js');
|
|
||||||
case 'zh-CN':
|
case 'zh-CN':
|
||||||
case 'zh':
|
|
||||||
return import('@lion/overlays/translations/zh.js');
|
return import('@lion/overlays/translations/zh.js');
|
||||||
default:
|
default:
|
||||||
return import(`@lion/overlays/translations/${locale}.js`);
|
return import(`@lion/overlays/translations/${locale}.js`);
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import { isEmailValidator } from '@lion/validate';
|
||||||
/**
|
/**
|
||||||
* LionInputEmail: extension of lion-input
|
* LionInputEmail: extension of lion-input
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-input-email
|
||||||
* @extends LionInput
|
* @extends {LionInput}
|
||||||
*/
|
*/
|
||||||
export class LionInputEmail extends FieldCustomMixin(LocalizeMixin(LionInput)) {
|
export class LionInputEmail extends FieldCustomMixin(LocalizeMixin(LionInput)) {
|
||||||
getValidatorsForType(type) {
|
getValidatorsForType(type) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import '../lion-input-email.js';
|
||||||
describe('<lion-input-email>', () => {
|
describe('<lion-input-email>', () => {
|
||||||
it('has a type = text', async () => {
|
it('has a type = text', async () => {
|
||||||
const lionInputEmail = await fixture(`<lion-input-email></lion-input-email>`);
|
const lionInputEmail = await fixture(`<lion-input-email></lion-input-email>`);
|
||||||
expect(lionInputEmail.inputElement.type).to.equal('text');
|
expect(lionInputEmail._inputNode.type).to.equal('text');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has validator "isEmail" applied by default', async () => {
|
it('has validator "isEmail" applied by default', async () => {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ describe('<lion-input-iban>', () => {
|
||||||
|
|
||||||
it('has a type = text', async () => {
|
it('has a type = text', async () => {
|
||||||
const el = await fixture(`<lion-input-iban></lion-input-iban>`);
|
const el = await fixture(`<lion-input-iban></lion-input-iban>`);
|
||||||
expect(el.inputElement.type).to.equal('text');
|
expect(el._inputNode.type).to.equal('text');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has validator "isIBAN" applied by default', async () => {
|
it('has validator "isIBAN" applied by default', async () => {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { LionField } from '@lion/field';
|
||||||
/**
|
/**
|
||||||
* LionInput: extension of lion-field with native input element in place and user friendly API
|
* LionInput: extension of lion-field with native input element in place and user friendly API
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-input
|
||||||
* @extends LionField
|
* @extends {LionField}
|
||||||
*/
|
*/
|
||||||
export class LionInput extends LionField {
|
export class LionInput extends LionField {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
|
|
@ -26,10 +26,6 @@ export class LionInput extends LionField {
|
||||||
type: String,
|
type: String,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
},
|
},
|
||||||
step: {
|
|
||||||
type: Number,
|
|
||||||
reflect: true,
|
|
||||||
},
|
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
reflect: true,
|
reflect: true,
|
||||||
|
|
@ -56,12 +52,6 @@ export class LionInput extends LionField {
|
||||||
super();
|
super();
|
||||||
this.readOnly = false;
|
this.readOnly = false;
|
||||||
this.type = 'text';
|
this.type = 'text';
|
||||||
/**
|
|
||||||
* Only application to type="amount" & type="range"
|
|
||||||
*
|
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
this.step = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_requestUpdate(name, oldValue) {
|
_requestUpdate(name, oldValue) {
|
||||||
|
|
@ -79,19 +69,16 @@ export class LionInput extends LionField {
|
||||||
updated(changedProps) {
|
updated(changedProps) {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
if (changedProps.has('type')) {
|
if (changedProps.has('type')) {
|
||||||
this.inputElement.type = this.type;
|
this._inputNode.type = this.type;
|
||||||
}
|
|
||||||
if (changedProps.has('step')) {
|
|
||||||
this.inputElement.step = this.step;
|
|
||||||
}
|
}
|
||||||
if (changedProps.has('placeholder')) {
|
if (changedProps.has('placeholder')) {
|
||||||
this.inputElement.placeholder = this.placeholder;
|
this._inputNode.placeholder = this.placeholder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__delegateReadOnly() {
|
__delegateReadOnly() {
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.readOnly = this.readOnly;
|
this._inputNode.readOnly = this.readOnly;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,46 +7,38 @@ describe('<lion-input>', () => {
|
||||||
const el = await fixture(
|
const el = await fixture(
|
||||||
`<lion-input readonly><label slot="label">Testing readonly</label></lion-input>`,
|
`<lion-input readonly><label slot="label">Testing readonly</label></lion-input>`,
|
||||||
);
|
);
|
||||||
expect(el.inputElement.readOnly).to.equal(true);
|
expect(el._inputNode.readOnly).to.equal(true);
|
||||||
el.readOnly = false;
|
el.readOnly = false;
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.readOnly).to.equal(false);
|
expect(el.readOnly).to.equal(false);
|
||||||
expect(el.inputElement.readOnly).to.equal(false);
|
expect(el._inputNode.readOnly).to.equal(false);
|
||||||
});
|
|
||||||
|
|
||||||
it('delegates "step" attribute and property', async () => {
|
|
||||||
const el = await fixture(`<lion-input step="0.01"></lion-input>`);
|
|
||||||
expect(el.inputElement.step).to.equal('0.01');
|
|
||||||
// TODO: activate when DelegateMixin is refactored
|
|
||||||
// const el2 = await fixture(`<lion-input .step="${'0.02'}"></lion-input>`);
|
|
||||||
// expect(el2.inputElement.step).to.equal('0.02');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('automatically creates an <input> element if not provided by user', async () => {
|
it('automatically creates an <input> element if not provided by user', async () => {
|
||||||
const el = await fixture(`<lion-input></lion-input>`);
|
const el = await fixture(`<lion-input></lion-input>`);
|
||||||
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 () => {
|
it('has a type which is reflected to an attribute and is synced down to the native input', async () => {
|
||||||
const el = await fixture(`<lion-input></lion-input>`);
|
const el = await fixture(`<lion-input></lion-input>`);
|
||||||
expect(el.type).to.equal('text');
|
expect(el.type).to.equal('text');
|
||||||
expect(el.getAttribute('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';
|
el.type = 'foo';
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.getAttribute('type')).to.equal('foo');
|
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 () => {
|
it('has an attribute that can be used to set the placeholder text of the input', async () => {
|
||||||
const el = await fixture(`<lion-input placeholder="text"></lion-input>`);
|
const el = await fixture(`<lion-input placeholder="text"></lion-input>`);
|
||||||
expect(el.getAttribute('placeholder')).to.equal('text');
|
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';
|
el.placeholder = 'foo';
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.getAttribute('placeholder')).to.equal('foo');
|
expect(el.getAttribute('placeholder')).to.equal('foo');
|
||||||
expect(el.inputElement.getAttribute('placeholder')).to.equal('foo');
|
expect(el._inputNode.getAttribute('placeholder')).to.equal('foo');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ We chose `Intl MessageFormat` as a format for translation parts because:
|
||||||
|
|
||||||
### Fallbacks
|
### 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.
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ Below, we show a common and easy example, it assumes you have your translation f
|
||||||
```js
|
```js
|
||||||
import { LocalizeMixin } from '@lion/localize';
|
import { LocalizeMixin } from '@lion/localize';
|
||||||
|
|
||||||
class MyHelloComponent extends LocalizeMixin(LionLitElement) {
|
class MyHelloComponent extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
// using an explicit loader function
|
// using an explicit loader function
|
||||||
return [
|
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.
|
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
|
```js
|
||||||
class MyHelloComponent extends LocalizeMixin(LionLitElement) {
|
class MyHelloComponent extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
// using an explicit loader function
|
// using an explicit loader function
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -43,16 +43,21 @@ export class LocalizeManager extends LionSingleton {
|
||||||
this._setupHtmlLangAttributeObserver();
|
this._setupHtmlLangAttributeObserver();
|
||||||
|
|
||||||
if (!value.includes('-')) {
|
if (!value.includes('-')) {
|
||||||
console.warn(`
|
this.__handleLanguageOnly(value);
|
||||||
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._onLocaleChanged(value, oldLocale);
|
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() {
|
get loadingComplete() {
|
||||||
return Promise.all(Object.values(this.__namespaceLoaderPromisesCache[this.locale]));
|
return Promise.all(Object.values(this.__namespaceLoaderPromisesCache[this.locale]));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { storiesOf, html } from '@open-wc/demoing-storybook';
|
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';
|
import { localize, LocalizeMixin } from '../index.js';
|
||||||
|
|
||||||
storiesOf('Localize System|Message', module).add('locale', () => {
|
storiesOf('Localize System|Message', module).add('locale', () => {
|
||||||
class messageExample extends LocalizeMixin(LionLitElement) {
|
class messageExample extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [
|
return [
|
||||||
{ 'lit-html-example': locale => import(`./translations/${locale}.js`) },
|
{ 'lit-html-example': locale => import(`./translations/${locale}.js`) },
|
||||||
|
|
|
||||||
|
|
@ -303,23 +303,22 @@ describe('LocalizeManager', () => {
|
||||||
throw new Error('did not throw');
|
throw new Error('did not throw');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws a warning if the locale set by the user is not a full language locale', async () => {
|
it('throws an error if the locale set by the user is not a full language locale', async () => {
|
||||||
const spy = sinon.spy(console, 'warn');
|
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
manager.locale = 'nl';
|
expect(() => {
|
||||||
|
manager.locale = 'nl';
|
||||||
expect(spy.callCount).to.equal(1);
|
}).to.throw(`
|
||||||
console.warn.restore();
|
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 () => {
|
it('does not throw an error if locale was set through the html lang attribute', async () => {
|
||||||
const spy = sinon.spy(console, 'warn');
|
|
||||||
manager = new LocalizeManager();
|
manager = new LocalizeManager();
|
||||||
document.documentElement.lang = 'nl';
|
expect(() => {
|
||||||
await aTimeout(50); // wait for mutation observer to be called
|
document.documentElement.lang = 'nl';
|
||||||
|
}).to.not.throw();
|
||||||
expect(spy.callCount).to.equal(0);
|
|
||||||
console.warn.restore();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ import {
|
||||||
html,
|
html,
|
||||||
} from '@open-wc/testing';
|
} from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { isDirective } from '@lion/core';
|
import { LitElement, isDirective } from '@lion/core';
|
||||||
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
|
||||||
import {
|
import {
|
||||||
localizeTearDown,
|
localizeTearDown,
|
||||||
setupEmptyFakeImportsFor,
|
setupEmptyFakeImportsFor,
|
||||||
|
|
@ -295,7 +294,7 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends LocalizeMixin(LionLitElement) {
|
class extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
@ -328,7 +327,7 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class TestPromise extends LocalizeMixin(LionLitElement) {
|
class TestPromise extends LocalizeMixin(LitElement) {
|
||||||
static get localizeNamespaces() {
|
static get localizeNamespaces() {
|
||||||
return [myElementNs, ...super.localizeNamespaces];
|
return [myElementNs, ...super.localizeNamespaces];
|
||||||
}
|
}
|
||||||
|
|
@ -360,7 +359,7 @@ describe('LocalizeMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const tag = defineCE(
|
const tag = defineCE(
|
||||||
class extends LocalizeMixin(LionLitElement) {
|
class extends LocalizeMixin(LitElement) {
|
||||||
static get waitForLocalizeNamespaces() {
|
static get waitForLocalizeNamespaces() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import { LionFieldset } from '@lion/fieldset';
|
||||||
* It extends LionFieldset so it inherits it's features.
|
* It extends LionFieldset so it inherits it's features.
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-radio-group
|
||||||
* @extends LionFieldset
|
* @extends {LionFieldset}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class LionRadioGroup extends LionFieldset {
|
export class LionRadioGroup extends LionFieldset {
|
||||||
|
|
@ -65,28 +65,28 @@ export class LionRadioGroup extends LionFieldset {
|
||||||
|
|
||||||
_checkRadioElements(ev) {
|
_checkRadioElements(ev) {
|
||||||
const { target } = ev;
|
const { target } = ev;
|
||||||
if (target.type !== 'radio' || target.choiceChecked === false) return;
|
if (target.type !== 'radio' || target.checked === false) return;
|
||||||
|
|
||||||
const groupName = target.name;
|
const groupName = target.name;
|
||||||
this.formElementsArray
|
this.formElementsArray
|
||||||
.filter(i => i.name === groupName)
|
.filter(i => i.name === groupName)
|
||||||
.forEach(radio => {
|
.forEach(radio => {
|
||||||
if (radio !== target) {
|
if (radio !== target) {
|
||||||
radio.choiceChecked = false; // eslint-disable-line no-param-reassign
|
radio.checked = false; // eslint-disable-line no-param-reassign
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.__triggerCheckedValueChanged();
|
this.__triggerCheckedValueChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCheckedRadioElement() {
|
_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;
|
return filtered.length > 0 ? filtered[0] : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
_setCheckedRadioElement(value, check) {
|
_setCheckedRadioElement(value, check) {
|
||||||
for (let i = 0; i < this.formElementsArray.length; i += 1) {
|
for (let i = 0; i < this.formElementsArray.length; i += 1) {
|
||||||
if (check(this.formElementsArray[i], value)) {
|
if (check(this.formElementsArray[i], value)) {
|
||||||
this.formElementsArray[i].choiceChecked = true;
|
this.formElementsArray[i].checked = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,15 @@ describe('<lion-radio-group>', () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<lion-radio-group>
|
<lion-radio-group>
|
||||||
<lion-radio name="gender[]" .choiceValue=${'male'}></lion-radio>
|
<lion-radio name="gender[]" .choiceValue=${'male'}></lion-radio>
|
||||||
<lion-radio name="gender[]" .choiceValue=${'female'} .choiceChecked=${true}></lion-radio>
|
<lion-radio name="gender[]" .choiceValue=${'female'} checked></lion-radio>
|
||||||
<lion-radio name="gender[]" .choiceValue=${'alien'}></lion-radio>
|
<lion-radio name="gender[]" .choiceValue=${'alien'}></lion-radio>
|
||||||
</lion-radio-group>
|
</lion-radio-group>
|
||||||
`);
|
`);
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
expect(el.checkedValue).to.equal('female');
|
expect(el.checkedValue).to.equal('female');
|
||||||
el.formElementsArray[0].choiceChecked = true;
|
el.formElementsArray[0].checked = true;
|
||||||
expect(el.checkedValue).to.equal('male');
|
expect(el.checkedValue).to.equal('male');
|
||||||
el.formElementsArray[2].choiceChecked = true;
|
el.formElementsArray[2].checked = true;
|
||||||
expect(el.checkedValue).to.equal('alien');
|
expect(el.checkedValue).to.equal('alien');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -26,27 +26,27 @@ describe('<lion-radio-group>', () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<lion-radio-group>
|
<lion-radio-group>
|
||||||
<lion-radio name="data[]" .choiceValue=${{ some: 'data' }}></lion-radio>
|
<lion-radio name="data[]" .choiceValue=${{ some: 'data' }}></lion-radio>
|
||||||
<lion-radio name="data[]" .choiceValue=${date} .choiceChecked=${true}></lion-radio>
|
<lion-radio name="data[]" .choiceValue=${date} checked></lion-radio>
|
||||||
</lion-radio-group>
|
</lion-radio-group>
|
||||||
`);
|
`);
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
|
|
||||||
expect(el.checkedValue).to.equal(date);
|
expect(el.checkedValue).to.equal(date);
|
||||||
el.formElementsArray[0].choiceChecked = true;
|
el.formElementsArray[0].checked = true;
|
||||||
expect(el.checkedValue).to.deep.equal({ some: 'data' });
|
expect(el.checkedValue).to.deep.equal({ some: 'data' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can handle 0 and empty string as valid values ', async () => {
|
it('can handle 0 and empty string as valid values ', async () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<lion-radio-group>
|
<lion-radio-group>
|
||||||
<lion-radio name="data[]" .choiceValue=${0} .choiceChecked=${true}></lion-radio>
|
<lion-radio name="data[]" .choiceValue=${0} checked></lion-radio>
|
||||||
<lion-radio name="data[]" .choiceValue=${''}></lion-radio>
|
<lion-radio name="data[]" .choiceValue=${''}></lion-radio>
|
||||||
</lion-radio-group>
|
</lion-radio-group>
|
||||||
`);
|
`);
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
|
|
||||||
expect(el.checkedValue).to.equal(0);
|
expect(el.checkedValue).to.equal(0);
|
||||||
el.formElementsArray[1].choiceChecked = true;
|
el.formElementsArray[1].checked = true;
|
||||||
expect(el.checkedValue).to.equal('');
|
expect(el.checkedValue).to.equal('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ describe('<lion-radio-group>', () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<lion-radio-group>
|
<lion-radio-group>
|
||||||
<lion-radio name="gender[]" .choiceValue=${'male'}></lion-radio>
|
<lion-radio name="gender[]" .choiceValue=${'male'}></lion-radio>
|
||||||
<lion-radio name="gender[]" .choiceValue=${'female'} .choiceChecked=${true}></lion-radio>
|
<lion-radio name="gender[]" .choiceValue=${'female'} checked></lion-radio>
|
||||||
<lion-radio name="gender[]" .choiceValue=${'alien'}></lion-radio>
|
<lion-radio name="gender[]" .choiceValue=${'alien'}></lion-radio>
|
||||||
</lion-radio-group>
|
</lion-radio-group>
|
||||||
`);
|
`);
|
||||||
|
|
@ -85,7 +85,7 @@ describe('<lion-radio-group>', () => {
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
expect(el.checkedValue).to.equal('female');
|
expect(el.checkedValue).to.equal('female');
|
||||||
el.checkedValue = 'alien';
|
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 () => {
|
it('fires checked-value-changed event only once per checked change', async () => {
|
||||||
|
|
@ -106,14 +106,14 @@ describe('<lion-radio-group>', () => {
|
||||||
/* eslint-enable indent */
|
/* eslint-enable indent */
|
||||||
expect(counter).to.equal(0);
|
expect(counter).to.equal(0);
|
||||||
|
|
||||||
el.formElementsArray[0].choiceChecked = true;
|
el.formElementsArray[0].checked = true;
|
||||||
expect(counter).to.equal(1);
|
expect(counter).to.equal(1);
|
||||||
|
|
||||||
// not changed values trigger no event
|
// not changed values trigger no event
|
||||||
el.formElementsArray[0].choiceChecked = true;
|
el.formElementsArray[0].checked = true;
|
||||||
expect(counter).to.equal(1);
|
expect(counter).to.equal(1);
|
||||||
|
|
||||||
el.formElementsArray[2].choiceChecked = true;
|
el.formElementsArray[2].checked = true;
|
||||||
expect(counter).to.equal(2);
|
expect(counter).to.equal(2);
|
||||||
|
|
||||||
// not found values trigger no event
|
// not found values trigger no event
|
||||||
|
|
@ -142,14 +142,14 @@ describe('<lion-radio-group>', () => {
|
||||||
/* eslint-enable indent */
|
/* eslint-enable indent */
|
||||||
counter = 0; // reset after setup which may result in different results
|
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
|
expect(counter).to.equal(2); // male becomes checked, female becomes unchecked
|
||||||
|
|
||||||
// not changed values trigger no event
|
// not changed values trigger no event
|
||||||
el.formElementsArray[0].choiceChecked = true;
|
el.formElementsArray[0].checked = true;
|
||||||
expect(counter).to.equal(2);
|
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
|
expect(counter).to.equal(4); // alien becomes checked, male becomes unchecked
|
||||||
|
|
||||||
// not found values trigger no event
|
// not found values trigger no event
|
||||||
|
|
@ -176,24 +176,18 @@ describe('<lion-radio-group>', () => {
|
||||||
const female = el.formElements['gender[]'][1];
|
const female = el.formElements['gender[]'][1];
|
||||||
const femaleInput = female.querySelector('input');
|
const femaleInput = female.querySelector('input');
|
||||||
|
|
||||||
expect(male.choiceChecked).to.equal(false);
|
expect(male.checked).to.equal(false);
|
||||||
expect(female.choiceChecked).to.equal(false);
|
expect(female.checked).to.equal(false);
|
||||||
|
|
||||||
maleInput.focus();
|
maleInput.focus();
|
||||||
maleInput.click();
|
maleInput.click();
|
||||||
expect(male.choiceChecked).to.equal(true);
|
expect(male.checked).to.equal(true);
|
||||||
expect(female.choiceChecked).to.equal(false);
|
expect(female.checked).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');
|
|
||||||
|
|
||||||
femaleInput.focus();
|
femaleInput.focus();
|
||||||
femaleInput.click();
|
femaleInput.click();
|
||||||
expect(male.choiceChecked).to.equal(false);
|
expect(male.checked).to.equal(false);
|
||||||
expect(female.choiceChecked).to.equal(true);
|
expect(female.checked).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');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have role = radiogroup', async () => {
|
it('should have role = radiogroup', async () => {
|
||||||
|
|
@ -222,7 +216,7 @@ describe('<lion-radio-group>', () => {
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
|
|
||||||
expect(el.error.required).to.be.true;
|
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;
|
expect(el.error.required).to.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -234,9 +228,7 @@ describe('<lion-radio-group>', () => {
|
||||||
</lion-radio-group>
|
</lion-radio-group>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
await nextFrame();
|
group.formElements['gender[]'][0].checked = true;
|
||||||
group.formElements['gender[]'][0].choiceChecked = true;
|
|
||||||
|
|
||||||
expect(group.serializedValue).to.deep.equal({ checked: true, value: 'male' });
|
expect(group.serializedValue).to.deep.equal({ checked: true, value: 'male' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Get or set the checked state (boolean) - `choiceChecked()`
|
- Get the checked state (boolean) - `checked` boolean attribute
|
||||||
- Get or set the value of the choice - `choiceValue()`
|
|
||||||
- Pre-select an option by setting the `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
|
## How to use
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import { ChoiceInputMixin } from '@lion/choice-input';
|
||||||
* <lion-radio name="name[]" checked>
|
* <lion-radio name="name[]" checked>
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-radio
|
||||||
* @extends ChoiceInputMixin(LionInput)
|
* @extends {LionInput}
|
||||||
*/
|
*/
|
||||||
export class LionRadio extends ChoiceInputMixin(LionInput) {
|
export class LionRadio extends ChoiceInputMixin(LionInput) {
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,6 @@ describe('<lion-radio>', () => {
|
||||||
</lion-radio-group>
|
</lion-radio-group>
|
||||||
`);
|
`);
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
expect(el.children[1].inputElement.getAttribute('type')).to.equal('radio');
|
expect(el.children[1]._inputNode.getAttribute('type')).to.equal('radio');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { FormRegistrarPortalMixin } from '@lion/field';
|
||||||
* LionOptions
|
* LionOptions
|
||||||
*
|
*
|
||||||
* @customElement lion-options
|
* @customElement lion-options
|
||||||
* @extends LitElement
|
* @extends {LitElement}
|
||||||
*/
|
*/
|
||||||
export class LionOptions extends FormRegistrarPortalMixin(LitElement) {
|
export class LionOptions extends FormRegistrarPortalMixin(LitElement) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { html } from '@lion/core';
|
||||||
* LionSelectInvoker: invoker button consuming a selected element
|
* LionSelectInvoker: invoker button consuming a selected element
|
||||||
*
|
*
|
||||||
* @customElement lion-select-invoker
|
* @customElement lion-select-invoker
|
||||||
* @extends LionButton
|
* @extends {LionButton}
|
||||||
*/
|
*/
|
||||||
export class LionSelectInvoker extends LionButton {
|
export class LionSelectInvoker extends LionButton {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ function detectInteractionMode() {
|
||||||
* LionSelectRich: wraps the <lion-listbox> element
|
* LionSelectRich: wraps the <lion-listbox> element
|
||||||
*
|
*
|
||||||
* @customElement lion-select-rich
|
* @customElement lion-select-rich
|
||||||
* @extends LionField
|
* @extends {LitElement}
|
||||||
*/
|
*/
|
||||||
export class LionSelectRich extends OverlayMixin(
|
export class LionSelectRich extends OverlayMixin(
|
||||||
FormRegistrarMixin(InteractionStateMixin(ValidateMixin(FormControlMixin(SlotMixin(LitElement))))),
|
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
|
* @override
|
||||||
*/
|
*/
|
||||||
_onAriaLabelledbyChanged({ _ariaLabelledby }) {
|
_onAriaLabelledbyChanged({ _ariaLabelledby }) {
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.setAttribute('aria-labelledby', _ariaLabelledby);
|
this._inputNode.setAttribute('aria-labelledby', _ariaLabelledby);
|
||||||
}
|
}
|
||||||
if (this._invokerNode) {
|
if (this._invokerNode) {
|
||||||
this._invokerNode.setAttribute(
|
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
|
* @override
|
||||||
*/
|
*/
|
||||||
_onAriaDescribedbyChanged({ _ariaDescribedby }) {
|
_onAriaDescribedbyChanged({ _ariaDescribedby }) {
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.setAttribute('aria-describedby', _ariaDescribedby);
|
this._inputNode.setAttribute('aria-describedby', _ariaDescribedby);
|
||||||
}
|
}
|
||||||
if (this._invokerNode) {
|
if (this._invokerNode) {
|
||||||
this._invokerNode.setAttribute('aria-describedby', _ariaDescribedby);
|
this._invokerNode.setAttribute('aria-describedby', _ariaDescribedby);
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ import { LionField } from '@lion/field';
|
||||||
* where each option name starts with the same word or phrase can also significantly degrade
|
* where each option name starts with the same word or phrase can also significantly degrade
|
||||||
* usability for keyboard and screen reader users.
|
* usability for keyboard and screen reader users.
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-select
|
||||||
* @extends LionField
|
* @extends {LionField}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
|
@ -41,7 +41,7 @@ export class LionSelect extends LionField {
|
||||||
}
|
}
|
||||||
|
|
||||||
_proxyChangeEvent() {
|
_proxyChangeEvent() {
|
||||||
this.inputElement.dispatchEvent(
|
this._inputNode.dispatchEvent(
|
||||||
new CustomEvent('user-input-changed', {
|
new CustomEvent('user-input-changed', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
composed: true,
|
composed: true,
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,11 @@ The first step needs to be explicitely set via `initial-step` so that it get sta
|
||||||
```js
|
```js
|
||||||
...
|
...
|
||||||
next() {
|
next() {
|
||||||
return this.$id('steps').next();
|
return this.shadowRoot.getElementById('steps').next();
|
||||||
}
|
}
|
||||||
|
|
||||||
previous() {
|
previous() {
|
||||||
return this.$id('steps').previous();
|
return this.shadowRoot.getElementById('steps').previous();
|
||||||
}
|
}
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { html, css } from '@lion/core';
|
import { LitElement, html, css } from '@lion/core';
|
||||||
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `LionStep` is one of many in a LionSteps Controller
|
* `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() {
|
static get properties() {
|
||||||
/**
|
/**
|
||||||
* Fired when the step is entered.
|
* Fired when the step is entered.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { html, css } from '@lion/core';
|
import { LitElement, html, css } from '@lion/core';
|
||||||
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `LionSteps` is a controller for a multi step system.
|
* `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() {
|
static get properties() {
|
||||||
/**
|
/**
|
||||||
* Fired when a transition between steps happens.
|
* Fired when a transition between steps happens.
|
||||||
|
|
@ -32,10 +31,11 @@ export class LionSteps extends ObserverMixin(LionLitElement) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get asyncObservers() {
|
updated(changedProps) {
|
||||||
return {
|
super.updated(changedProps);
|
||||||
_onCurrentChanged: ['current'],
|
if (changedProps.has('current')) {
|
||||||
};
|
this._onCurrentChanged({ current: this.current }, { current: changedProps.get('current') });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Get or set the checked state (boolean) - `choiceChecked()`
|
- Get or set the checked state (boolean) - `checked` boolean attribute
|
||||||
- Get or set the value of the choice - `choiceValue()`
|
|
||||||
- Pre-select an option by setting the `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
|
## How to use
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export class LionSwitch extends ChoiceInputMixin(LionField) {
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this.inputElement.addEventListener(
|
this._inputNode.addEventListener(
|
||||||
'checked-changed',
|
'checked-changed',
|
||||||
this.__handleButtonSwitchCheckedChanged.bind(this),
|
this.__handleButtonSwitchCheckedChanged.bind(this),
|
||||||
);
|
);
|
||||||
|
|
@ -49,11 +49,11 @@ export class LionSwitch extends ChoiceInputMixin(LionField) {
|
||||||
__handleButtonSwitchCheckedChanged() {
|
__handleButtonSwitchCheckedChanged() {
|
||||||
// TODO: should be replaced by "_inputNode" after the next breaking change
|
// 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
|
// 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() {
|
_syncButtonSwitch() {
|
||||||
this.inputElement.checked = this.checked;
|
this._inputNode.checked = this.checked;
|
||||||
this.inputElement.disabled = this.disabled;
|
this._inputNode.disabled = this.disabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ describe('lion-switch', () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<lion-switch disabled></lion-switch>
|
<lion-switch disabled></lion-switch>
|
||||||
`);
|
`);
|
||||||
expect(el.inputElement.disabled).to.be.true;
|
expect(el._inputNode.disabled).to.be.true;
|
||||||
expect(el.inputElement.hasAttribute('disabled')).to.be.true;
|
expect(el._inputNode.hasAttribute('disabled')).to.be.true;
|
||||||
el.disabled = false;
|
el.disabled = false;
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.inputElement.disabled).to.be.false;
|
expect(el._inputNode.disabled).to.be.false;
|
||||||
expect(el.inputElement.hasAttribute('disabled')).to.be.false;
|
expect(el._inputNode.hasAttribute('disabled')).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync its "checked" state to child button', async () => {
|
it('should sync its "checked" state to child button', async () => {
|
||||||
|
|
@ -29,21 +29,21 @@ describe('lion-switch', () => {
|
||||||
const checkedEl = await fixture(html`
|
const checkedEl = await fixture(html`
|
||||||
<lion-switch checked></lion-switch>
|
<lion-switch checked></lion-switch>
|
||||||
`);
|
`);
|
||||||
expect(uncheckedEl.inputElement.checked).to.be.false;
|
expect(uncheckedEl._inputNode.checked).to.be.false;
|
||||||
expect(checkedEl.inputElement.checked).to.be.true;
|
expect(checkedEl._inputNode.checked).to.be.true;
|
||||||
uncheckedEl.checked = true;
|
uncheckedEl.checked = true;
|
||||||
checkedEl.checked = false;
|
checkedEl.checked = false;
|
||||||
await uncheckedEl.updateComplete;
|
await uncheckedEl.updateComplete;
|
||||||
await checkedEl.updateComplete;
|
await checkedEl.updateComplete;
|
||||||
expect(uncheckedEl.inputElement.checked).to.be.true;
|
expect(uncheckedEl._inputNode.checked).to.be.true;
|
||||||
expect(checkedEl.inputElement.checked).to.be.false;
|
expect(checkedEl._inputNode.checked).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync "checked" state received from child button', async () => {
|
it('should sync "checked" state received from child button', async () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<lion-switch></lion-switch>
|
<lion-switch></lion-switch>
|
||||||
`);
|
`);
|
||||||
const button = el.inputElement;
|
const button = el._inputNode;
|
||||||
expect(el.checked).to.be.false;
|
expect(el.checked).to.be.false;
|
||||||
button.click();
|
button.click();
|
||||||
expect(el.checked).to.be.true;
|
expect(el.checked).to.be.true;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import { css } from '@lion/core';
|
||||||
/**
|
/**
|
||||||
* LionTextarea: extension of lion-field with native input element in place and user friendly API
|
* LionTextarea: extension of lion-field with native input element in place and user friendly API
|
||||||
*
|
*
|
||||||
* @customElement
|
* @customElement lion-textarea
|
||||||
* @extends LionInput
|
* @extends {LionField}
|
||||||
*/
|
*/
|
||||||
export class LionTextarea extends LionField {
|
export class LionTextarea extends LionField {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
|
|
@ -57,20 +57,20 @@ export class LionTextarea extends LionField {
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
autosize.destroy(this.inputElement);
|
autosize.destroy(this._inputNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
updated(changedProperties) {
|
updated(changedProperties) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has('rows')) {
|
if (changedProperties.has('rows')) {
|
||||||
const native = this.inputElement;
|
const native = this._inputNode;
|
||||||
if (native) {
|
if (native) {
|
||||||
native.rows = this.rows;
|
native.rows = this.rows;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changedProperties.has('readOnly')) {
|
if (changedProperties.has('readOnly')) {
|
||||||
const native = this.inputElement;
|
const native = this._inputNode;
|
||||||
if (native) {
|
if (native) {
|
||||||
native.readOnly = this.readOnly;
|
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
|
* To support maxRows we need to set max-height of the textarea
|
||||||
*/
|
*/
|
||||||
setTextareaMaxHeight() {
|
setTextareaMaxHeight() {
|
||||||
const { value } = this.inputElement;
|
const { value } = this._inputNode;
|
||||||
this.inputElement.value = '';
|
this._inputNode.value = '';
|
||||||
this.resizeTextarea();
|
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 lineHeight = parseFloat(cs.lineHeight) || parseFloat(cs.height) / this.rows;
|
||||||
const paddingOffset = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
|
const paddingOffset = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
|
||||||
const borderOffset = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
|
const borderOffset = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
|
||||||
const offset = cs.boxSizing === 'border-box' ? paddingOffset + borderOffset : 0;
|
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();
|
this.resizeTextarea();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ export class LionTextarea extends LionField {
|
||||||
}
|
}
|
||||||
|
|
||||||
resizeTextarea() {
|
resizeTextarea() {
|
||||||
autosize.update(this.inputElement);
|
autosize.update(this._inputNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
__initializeAutoresize() {
|
__initializeAutoresize() {
|
||||||
|
|
@ -140,7 +140,7 @@ export class LionTextarea extends LionField {
|
||||||
|
|
||||||
async __waitForTextareaRenderedInRealDOM() {
|
async __waitForTextareaRenderedInRealDOM() {
|
||||||
let count = 3; // max tasks to wait for
|
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
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await new Promise(resolve => setTimeout(resolve));
|
await new Promise(resolve => setTimeout(resolve));
|
||||||
count -= 1;
|
count -= 1;
|
||||||
|
|
@ -148,7 +148,7 @@ export class LionTextarea extends LionField {
|
||||||
}
|
}
|
||||||
|
|
||||||
__startAutoresize() {
|
__startAutoresize() {
|
||||||
autosize(this.inputElement);
|
autosize(this._inputNode);
|
||||||
this.setTextareaMaxHeight();
|
this.setTextareaMaxHeight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,18 +26,18 @@ describe('<lion-textarea>', () => {
|
||||||
const el = await fixture(`<lion-textarea>foo</lion-textarea>`);
|
const el = await fixture(`<lion-textarea>foo</lion-textarea>`);
|
||||||
expect(el.rows).to.equal(2);
|
expect(el.rows).to.equal(2);
|
||||||
expect(el.getAttribute('rows')).to.be.equal('2');
|
expect(el.getAttribute('rows')).to.be.equal('2');
|
||||||
expect(el.inputElement.rows).to.equal(2);
|
expect(el._inputNode.rows).to.equal(2);
|
||||||
expect(el.inputElement.getAttribute('rows')).to.be.equal('2');
|
expect(el._inputNode.getAttribute('rows')).to.be.equal('2');
|
||||||
expect(el.readOnly).to.be.false;
|
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 () => {
|
it('sync rows down to the native textarea', async () => {
|
||||||
const el = await fixture(`<lion-textarea rows="8">foo</lion-textarea>`);
|
const el = await fixture(`<lion-textarea rows="8">foo</lion-textarea>`);
|
||||||
expect(el.rows).to.equal(8);
|
expect(el.rows).to.equal(8);
|
||||||
expect(el.getAttribute('rows')).to.be.equal('8');
|
expect(el.getAttribute('rows')).to.be.equal('8');
|
||||||
expect(el.inputElement.rows).to.equal(8);
|
expect(el._inputNode.rows).to.equal(8);
|
||||||
expect(el.inputElement.getAttribute('rows')).to.be.equal('8');
|
expect(el._inputNode.getAttribute('rows')).to.be.equal('8');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sync readOnly to the native textarea', async () => {
|
it('sync readOnly to the native textarea', async () => {
|
||||||
|
|
@ -52,7 +52,7 @@ describe('<lion-textarea>', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const el = await fixture(`<lion-textarea></lion-textarea>`);
|
const el = await fixture(`<lion-textarea></lion-textarea>`);
|
||||||
const computedStyle = window.getComputedStyle(el.inputElement);
|
const computedStyle = window.getComputedStyle(el._inputNode);
|
||||||
expect(computedStyle.resize).to.equal('none');
|
expect(computedStyle.resize).to.equal('none');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -294,12 +294,12 @@ export const ValidateMixin = dedupeMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
get _feedbackElement() {
|
get _feedbackElement() {
|
||||||
return (this.$$slot && this.$$slot('feedback')) || this.querySelector('[slot="feedback"]');
|
return this.querySelector('[slot="feedback"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
getFieldName(validatorParams) {
|
getFieldName(validatorParams) {
|
||||||
const label =
|
const labelEl = this.querySelector('[slot=label]');
|
||||||
this.label || (this.$$slot && this.$$slot('label') && this.$$slot('label').textContent);
|
const label = this.label || (labelEl && labelEl.textContent);
|
||||||
|
|
||||||
if (validatorParams && validatorParams.fieldName) {
|
if (validatorParams && validatorParams.fieldName) {
|
||||||
return validatorParams.fieldName;
|
return validatorParams.fieldName;
|
||||||
|
|
@ -413,6 +413,8 @@ export const ValidateMixin = dedupeMixin(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can be overridden by sub classers
|
* 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() {
|
renderFeedback() {
|
||||||
if (this._feedbackElement) {
|
if (this._feedbackElement) {
|
||||||
|
|
@ -422,10 +424,10 @@ export const ValidateMixin = dedupeMixin(
|
||||||
|
|
||||||
_onErrorShowChangedAsync() {
|
_onErrorShowChangedAsync() {
|
||||||
// Screen reader output should be in sync with visibility of error messages
|
// Screen reader output should be in sync with visibility of error messages
|
||||||
if (this.inputElement) {
|
if (this._inputNode) {
|
||||||
this.inputElement.setAttribute('aria-invalid', this.errorShow);
|
this._inputNode.setAttribute('aria-invalid', this.errorShow);
|
||||||
// TODO: test and see if needed for a11y
|
// TODO: test and see if needed for a11y
|
||||||
// this.inputElement.setCustomValidity(this._validationMessage || '');
|
// this._inputNode.setCustomValidity(this._validationMessage || '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable no-unused-vars, no-param-reassign */
|
/* eslint-disable no-unused-vars, no-param-reassign */
|
||||||
import { expect, fixture, html, unsafeStatic, defineCE, aTimeout } from '@open-wc/testing';
|
import { expect, fixture, html, unsafeStatic, defineCE, aTimeout } from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
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 { localizeTearDown } from '@lion/localize/test-helpers.js';
|
||||||
import { localize } from '@lion/localize';
|
import { localize } from '@lion/localize';
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ const suffixName = '';
|
||||||
const lightDom = '';
|
const lightDom = '';
|
||||||
|
|
||||||
const tagString = defineCE(
|
const tagString = defineCE(
|
||||||
class extends ValidateMixin(LionLitElement) {
|
class extends ValidateMixin(LitElement) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
@ -47,7 +47,7 @@ describe('ValidateMixin', () => {
|
||||||
* The element ('this') the ValidateMixin is applied on.
|
* The element ('this') the ValidateMixin is applied on.
|
||||||
*
|
*
|
||||||
* - *input-element*
|
* - *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
|
* 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.
|
* derived from, aria properties are put on and setCustomValidity (if applicable) is called on.
|
||||||
* Can be input, textarea, my-custom-slider etc.
|
* Can be input, textarea, my-custom-slider etc.
|
||||||
|
|
@ -654,7 +654,7 @@ describe('ValidateMixin', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultElement = defineCE(
|
const defaultElement = defineCE(
|
||||||
class extends ValidateMixin(LionLitElement) {
|
class extends ValidateMixin(LitElement) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
@ -707,13 +707,15 @@ describe('ValidateMixin', () => {
|
||||||
>${lightDom}</${tag}>
|
>${lightDom}</${tag}>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(el.$$slot('feedback').innerText).to.equal('');
|
expect(el.querySelector('[slot=feedback]').innerText).to.equal('');
|
||||||
|
|
||||||
showErrors = true;
|
showErrors = true;
|
||||||
el.validate();
|
el.validate();
|
||||||
await el.updateComplete;
|
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 () => {
|
it('writes validation outcome to *feedback-element*, if present', async () => {
|
||||||
|
|
@ -723,7 +725,7 @@ describe('ValidateMixin', () => {
|
||||||
.errorValidators=${[[alwaysFalse]]}
|
.errorValidators=${[[alwaysFalse]]}
|
||||||
>${lightDom}</${tag}>
|
>${lightDom}</${tag}>
|
||||||
`);
|
`);
|
||||||
expect(feedbackResult.$$slot('feedback').innerText).to.equal(
|
expect(feedbackResult.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is error message for alwaysFalse',
|
'This is error message for alwaysFalse',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -737,7 +739,7 @@ describe('ValidateMixin', () => {
|
||||||
>${lightDom}</${tag}>
|
>${lightDom}</${tag}>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(feedbackResult.$$slot('feedback').innerText).to.equal('');
|
expect(feedbackResult.querySelector('[slot=feedback]').innerText).to.equal('');
|
||||||
// locale changed or smth
|
// locale changed or smth
|
||||||
localize.reset();
|
localize.reset();
|
||||||
localize.addData('en-GB', 'lion-validate', {
|
localize.addData('en-GB', 'lion-validate', {
|
||||||
|
|
@ -745,7 +747,9 @@ describe('ValidateMixin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
feedbackResult.onLocaleUpdated();
|
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 () => {
|
it('allows to overwrite the way messages are translated', async () => {
|
||||||
|
|
@ -766,18 +770,20 @@ describe('ValidateMixin', () => {
|
||||||
>${lightDom}</${tag}>
|
>${lightDom}</${tag}>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(customTranslations.$$slot('feedback').innerText).to.equal(
|
expect(customTranslations.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'You should have a lowercase a',
|
'You should have a lowercase a',
|
||||||
);
|
);
|
||||||
|
|
||||||
customTranslations.modelValue = 'cat';
|
customTranslations.modelValue = 'cat';
|
||||||
await customTranslations.updateComplete;
|
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 () => {
|
it('allows to overwrite the way messages are rendered/added to dom', async () => {
|
||||||
const element = defineCE(
|
const element = defineCE(
|
||||||
class extends ValidateMixin(LionLitElement) {
|
class extends ValidateMixin(LitElement) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
@ -789,7 +795,9 @@ describe('ValidateMixin', () => {
|
||||||
renderFeedback(validationStates, message) {
|
renderFeedback(validationStates, message) {
|
||||||
const validator = message.list[0].data.validatorName;
|
const validator = message.list[0].data.validatorName;
|
||||||
const showError = validationStates.error;
|
const showError = validationStates.error;
|
||||||
this.innerHTML = showError ? `ERROR on ${validator}` : '';
|
this.innerHTML = showError
|
||||||
|
? `<div slot="feedback">ERROR on ${validator}</div>`
|
||||||
|
: '<div slot="feedback"></div>';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -801,11 +809,13 @@ describe('ValidateMixin', () => {
|
||||||
>${lightDom}</${elem}>
|
>${lightDom}</${elem}>
|
||||||
`);
|
`);
|
||||||
await ownTranslations.updateComplete;
|
await ownTranslations.updateComplete;
|
||||||
expect(ownTranslations.innerHTML).to.equal('ERROR on containsLowercaseA');
|
expect(ownTranslations.innerHTML).to.equal(
|
||||||
|
'<div slot="feedback">ERROR on containsLowercaseA</div>',
|
||||||
|
);
|
||||||
|
|
||||||
ownTranslations.modelValue = 'cat';
|
ownTranslations.modelValue = 'cat';
|
||||||
await ownTranslations.updateComplete;
|
await ownTranslations.updateComplete;
|
||||||
expect(ownTranslations.innerHTML).to.equal('ERROR on alwaysFalse');
|
expect(ownTranslations.innerHTML).to.equal('<div slot="feedback">ERROR on alwaysFalse</div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('supports custom element to render feedback', async () => {
|
it('supports custom element to render feedback', async () => {
|
||||||
|
|
@ -832,11 +842,13 @@ describe('ValidateMixin', () => {
|
||||||
|
|
||||||
element.modelValue = 'dog';
|
element.modelValue = 'dog';
|
||||||
await element.updateComplete;
|
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';
|
element.modelValue = 'cat';
|
||||||
await element.updateComplete;
|
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 () => {
|
it('allows to create a custom feedback renderer via the template [to-be-implemented]', async () => {
|
||||||
|
|
@ -856,19 +868,19 @@ describe('ValidateMixin', () => {
|
||||||
|
|
||||||
validityFeedback.modelValue = 'a';
|
validityFeedback.modelValue = 'a';
|
||||||
await validityFeedback.updateComplete;
|
await validityFeedback.updateComplete;
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is error message for minLength',
|
'This is error message for minLength',
|
||||||
);
|
);
|
||||||
|
|
||||||
validityFeedback.modelValue = 'abc';
|
validityFeedback.modelValue = 'abc';
|
||||||
await validityFeedback.updateComplete;
|
await validityFeedback.updateComplete;
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is warning message for minLength',
|
'This is warning message for minLength',
|
||||||
);
|
);
|
||||||
|
|
||||||
validityFeedback.modelValue = 'abcde';
|
validityFeedback.modelValue = 'abcde';
|
||||||
await validityFeedback.updateComplete;
|
await validityFeedback.updateComplete;
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is info message for minLength',
|
'This is info message for minLength',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -884,13 +896,13 @@ describe('ValidateMixin', () => {
|
||||||
|
|
||||||
validityFeedback.modelValue = 'a';
|
validityFeedback.modelValue = 'a';
|
||||||
await validityFeedback.updateComplete;
|
await validityFeedback.updateComplete;
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is error message for minLength',
|
'This is error message for minLength',
|
||||||
);
|
);
|
||||||
|
|
||||||
validityFeedback.modelValue = 'abcd';
|
validityFeedback.modelValue = 'abcd';
|
||||||
await validityFeedback.updateComplete;
|
await validityFeedback.updateComplete;
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is success message for alwaysFalse',
|
'This is success message for alwaysFalse',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -905,30 +917,30 @@ describe('ValidateMixin', () => {
|
||||||
`);
|
`);
|
||||||
validityFeedback.modelValue = 'dog and dog';
|
validityFeedback.modelValue = 'dog and dog';
|
||||||
await validityFeedback.updateComplete;
|
await validityFeedback.updateComplete;
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is error message for containsCat',
|
'This is error message for containsCat',
|
||||||
);
|
);
|
||||||
|
|
||||||
validityFeedback.modelValue = 'dog';
|
validityFeedback.modelValue = 'dog';
|
||||||
await validityFeedback.updateComplete;
|
await validityFeedback.updateComplete;
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is error message for containsCat',
|
'This is error message for containsCat',
|
||||||
);
|
);
|
||||||
|
|
||||||
validityFeedback.modelValue = 'cat';
|
validityFeedback.modelValue = 'cat';
|
||||||
await validityFeedback.updateComplete;
|
await validityFeedback.updateComplete;
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'This is error message for minLength',
|
'This is error message for minLength',
|
||||||
);
|
);
|
||||||
|
|
||||||
validityFeedback.modelValue = 'dog and cat';
|
validityFeedback.modelValue = 'dog and cat';
|
||||||
await validityFeedback.updateComplete;
|
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 () => {
|
it('supports randomized selection of multiple messages for the same validator', async () => {
|
||||||
const randomTranslationsElement = defineCE(
|
const randomTranslationsElement = defineCE(
|
||||||
class extends ValidateMixin(LionLitElement) {
|
class extends ValidateMixin(LitElement) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
@ -987,13 +999,13 @@ describe('ValidateMixin', () => {
|
||||||
'Good job!',
|
'Good job!',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(randomTranslations.$$slot('feedback').innerText).to.equal(
|
expect(randomTranslations.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'You should have a lowercase a',
|
'You should have a lowercase a',
|
||||||
);
|
);
|
||||||
|
|
||||||
randomTranslations.modelValue = 'cat';
|
randomTranslations.modelValue = 'cat';
|
||||||
await randomTranslations.updateComplete;
|
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;
|
Math.random = () => 0.25;
|
||||||
randomTranslations.__lastGetSuccessResult = false;
|
randomTranslations.__lastGetSuccessResult = false;
|
||||||
|
|
@ -1001,7 +1013,9 @@ describe('ValidateMixin', () => {
|
||||||
randomTranslations.modelValue = 'cat';
|
randomTranslations.modelValue = 'cat';
|
||||||
await randomTranslations.updateComplete;
|
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
|
Math.random = mathRandom; // manually restore
|
||||||
});
|
});
|
||||||
|
|
@ -1029,13 +1043,13 @@ describe('ValidateMixin', () => {
|
||||||
errorValidators: [[minLength, { min: 4 }]],
|
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.',
|
'You need to enter at least 4 characters.',
|
||||||
);
|
);
|
||||||
|
|
||||||
localize.locale = 'de-DE';
|
localize.locale = 'de-DE';
|
||||||
await validityFeedback.updateComplete;
|
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.',
|
'Es müssen mindestens 4 Zeichen eingegeben werden.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -1056,7 +1070,9 @@ describe('ValidateMixin', () => {
|
||||||
.modelValue=${'cat'}
|
.modelValue=${'cat'}
|
||||||
>${lightDom}</${tag}>
|
>${lightDom}</${tag}>
|
||||||
`);
|
`);
|
||||||
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 () => {
|
it('allows to configure field name for every validator message', async () => {
|
||||||
|
|
@ -1068,7 +1084,7 @@ describe('ValidateMixin', () => {
|
||||||
]} .modelValue=${'cat'}
|
]} .modelValue=${'cat'}
|
||||||
>${lightDom}
|
>${lightDom}
|
||||||
</${elNameStatic}>`);
|
</${elNameStatic}>`);
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'overrideName needs more characters',
|
'overrideName needs more characters',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -1082,7 +1098,7 @@ describe('ValidateMixin', () => {
|
||||||
.errorValidators=${[[minLength, { min: 4 }]]} .modelValue=${'cat'}
|
.errorValidators=${[[minLength, { min: 4 }]]} .modelValue=${'cat'}
|
||||||
>${lightDom}
|
>${lightDom}
|
||||||
</${elNameStatic}>`);
|
</${elNameStatic}>`);
|
||||||
expect(validityFeedback.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'myField needs more characters',
|
'myField needs more characters',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1091,7 +1107,7 @@ describe('ValidateMixin', () => {
|
||||||
.errorValidators=${[[minLength, { min: 4 }]]} .modelValue=${'cat'}
|
.errorValidators=${[[minLength, { min: 4 }]]} .modelValue=${'cat'}
|
||||||
>${lightDom}
|
>${lightDom}
|
||||||
</${elNameStatic}>`);
|
</${elNameStatic}>`);
|
||||||
expect(validityFeedback2.$$slot('feedback').innerText).to.equal(
|
expect(validityFeedback2.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'myName needs more characters',
|
'myName needs more characters',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -1134,11 +1150,13 @@ describe('ValidateMixin', () => {
|
||||||
|
|
||||||
element.modelValue = 'dog';
|
element.modelValue = 'dog';
|
||||||
await element.updateComplete;
|
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';
|
element.modelValue = 'cat';
|
||||||
await element.updateComplete;
|
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();
|
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
|
// 1. lion-validate+orderValidator
|
||||||
localize.addData('en-GB', 'lion-validate+orderValidator', {
|
localize.addData('en-GB', 'lion-validate+orderValidator', {
|
||||||
|
|
@ -1200,7 +1220,7 @@ describe('ValidateMixin', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
el._createMessageAndRenderFeedback();
|
el._createMessageAndRenderFeedback();
|
||||||
expect(el.$$slot('feedback').innerText).to.equal(
|
expect(el.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'lion-validate+orderValidator : orderValidator',
|
'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
|
// Tests are in 'reversed order', so we can increase prio by filling up localize storage
|
||||||
const is12Validator = () => [modelValue => ({ is12Validator: modelValue === 12 })];
|
const is12Validator = () => [modelValue => ({ is12Validator: modelValue === 12 })];
|
||||||
const orderName = defineCE(
|
const orderName = defineCE(
|
||||||
class extends ValidateMixin(LionLitElement) {
|
class extends ValidateMixin(LitElement) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return { modelValue: { type: String } };
|
return { modelValue: { type: String } };
|
||||||
}
|
}
|
||||||
|
|
@ -1249,7 +1269,9 @@ describe('ValidateMixin', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
el._createMessageAndRenderFeedback();
|
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
|
// 3. lion-validate+is12Validator
|
||||||
localize.addData('en-GB', 'lion-validate+is12Validator', {
|
localize.addData('en-GB', 'lion-validate+is12Validator', {
|
||||||
|
|
@ -1258,7 +1280,7 @@ describe('ValidateMixin', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
el._createMessageAndRenderFeedback();
|
el._createMessageAndRenderFeedback();
|
||||||
expect(el.$$slot('feedback').innerText).to.equal(
|
expect(el.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'lion-validate+is12Validator : is12Validator',
|
'lion-validate+is12Validator : is12Validator',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -1269,7 +1291,9 @@ describe('ValidateMixin', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
el._createMessageAndRenderFeedback();
|
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
|
// 1. my-custom-namespace+is12Validator
|
||||||
localize.addData('en-GB', 'my-custom-namespace+is12Validator', {
|
localize.addData('en-GB', 'my-custom-namespace+is12Validator', {
|
||||||
|
|
@ -1278,7 +1302,7 @@ describe('ValidateMixin', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
el._createMessageAndRenderFeedback();
|
el._createMessageAndRenderFeedback();
|
||||||
expect(el.$$slot('feedback').innerText).to.equal(
|
expect(el.querySelector('[slot=feedback]').innerText).to.equal(
|
||||||
'my-custom-namespace+is12Validator : is12Validator',
|
'my-custom-namespace+is12Validator : is12Validator',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue