chore(field): small cleanup LionField
This commit is contained in:
parent
a5bc33072f
commit
d7f7ffa221
2 changed files with 65 additions and 63 deletions
|
|
@ -1,39 +1,43 @@
|
||||||
import { DelegateMixin, SlotMixin } from '@lion/core';
|
import { DelegateMixin, SlotMixin, LitElement } from '@lion/core';
|
||||||
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
|
||||||
import { ElementMixin } from '@lion/core/src/ElementMixin.js';
|
import { ElementMixin } from '@lion/core/src/ElementMixin.js';
|
||||||
import { CssClassMixin } from '@lion/core/src/CssClassMixin.js';
|
import { CssClassMixin } from '@lion/core/src/CssClassMixin.js';
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.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
|
||||||
import { FormatMixin } from './FormatMixin.js';
|
import { FormatMixin } from './FormatMixin.js';
|
||||||
import { FocusMixin } from './FocusMixin.js';
|
import { FocusMixin } from './FocusMixin.js';
|
||||||
|
|
||||||
|
/* eslint-disable wc/guard-super-call */
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Consider exporting as FieldMixin
|
||||||
|
// - Add submitted prop to InteractionStateMixin
|
||||||
|
// - Find a better way to do value delegation via attr
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LionField: wraps components input, textarea and select and potentially others
|
* `LionField`: wraps <input>, <textarea>, <select> and other interactable elements.
|
||||||
* (checkbox group, radio group)
|
|
||||||
* Also it would follow a nice hierarchy: lion-form -> lion-fieldset -> lion-field
|
* Also it would follow a nice hierarchy: lion-form -> lion-fieldset -> lion-field
|
||||||
*
|
*
|
||||||
|
* Note: We don't support placeholders, because we have a helper text and
|
||||||
|
* placeholders confuse the user with accessibility needs.
|
||||||
|
*
|
||||||
|
* Please see the docs for in depth information.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
* <lion-field name="myName">
|
* <lion-field name="myName">
|
||||||
* <label slot="label">My Input</label>
|
* <label slot="label">My Input</label>
|
||||||
* <input type="text" slot="input">
|
* <input type="text" slot="input">
|
||||||
* </lion-field>
|
* </lion-field>
|
||||||
*
|
*
|
||||||
* Note: We do not support placeholders, because we have a helper text and
|
|
||||||
* placeholders confuse the user with accessibility needs.
|
|
||||||
*
|
|
||||||
* @customElement
|
* @customElement
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: Consider exporting as FieldMixin
|
|
||||||
// eslint-disable-next-line max-len, no-unused-vars
|
|
||||||
export class LionField extends FormControlMixin(
|
export class LionField extends FormControlMixin(
|
||||||
InteractionStateMixin(
|
InteractionStateMixin(
|
||||||
FocusMixin(
|
FocusMixin(
|
||||||
FormatMixin(
|
FormatMixin(
|
||||||
ValidateMixin(
|
ValidateMixin(
|
||||||
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LionLitElement))))),
|
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LitElement))))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -65,14 +69,7 @@ export class LionField extends FormControlMixin(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static get asyncObservers() {
|
// We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret
|
||||||
return {
|
|
||||||
...super.asyncObservers,
|
|
||||||
_setDisabledClass: ['disabled'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't delegate, because we want to 'preprocess' 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.inputElement) {
|
||||||
|
|
@ -85,29 +82,26 @@ export class LionField extends FormControlMixin(
|
||||||
return (this.inputElement && this.inputElement.value) || '';
|
return (this.inputElement && this.inputElement.value) || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
_setDisabledClass() {
|
static get asyncObservers() {
|
||||||
this.classList[this.disabled ? 'add' : 'remove']('state-disabled');
|
return {
|
||||||
|
...super.asyncObservers,
|
||||||
|
_setDisabledClass: ['disabled'],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
resetInteractionState() {
|
|
||||||
if (super.resetInteractionState) super.resetInteractionState();
|
|
||||||
// TODO: add submitted prop to InteractionStateMixin ?
|
|
||||||
this.submitted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* * * * * * * *
|
|
||||||
Lifecycle */
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
this._onChange = this._onChange.bind(this);
|
this._onChange = this._onChange.bind(this);
|
||||||
this.inputElement.addEventListener('change', this._onChange);
|
this.inputElement.addEventListener('change', this._onChange);
|
||||||
this._delegateInitialValueAttr(); // TODO: find a better way to do this
|
this._delegateInitialValueAttr();
|
||||||
this._setDisabledClass();
|
this._setDisabledClass();
|
||||||
this.classList.add('form-field');
|
this.classList.add('form-field'); // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
|
|
||||||
if (this.__parentFormGroup) {
|
if (this.__parentFormGroup) {
|
||||||
const event = new CustomEvent('form-element-unregister', {
|
const event = new CustomEvent('form-element-unregister', {
|
||||||
detail: { element: this },
|
detail: { element: this },
|
||||||
|
|
@ -118,6 +112,10 @@ export class LionField extends FormControlMixin(
|
||||||
this.inputElement.removeEventListener('change', this._onChange);
|
this.inputElement.removeEventListener('change', this._onChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setDisabledClass() {
|
||||||
|
this.classList[this.disabled ? 'add' : 'remove']('state-disabled');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is not done via 'get delegations', because this.inputElement.setAttribute('value')
|
* This is not done via 'get delegations', because this.inputElement.setAttribute('value')
|
||||||
* does not trigger a value change
|
* does not trigger a value change
|
||||||
|
|
@ -129,33 +127,37 @@ export class LionField extends FormControlMixin(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
resetInteractionState() {
|
||||||
Public Methods (also notice delegated methods that are available on host) */
|
if (super.resetInteractionState) {
|
||||||
|
super.resetInteractionState();
|
||||||
|
}
|
||||||
|
this.submitted = false;
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
// Let validationMixin and interactionStateMixin clear their invalid and dirty/touched states
|
if (super.clear) {
|
||||||
// respectively
|
// Let validationMixin and interactionStateMixin clear their
|
||||||
if (super.clear) super.clear();
|
// invalid and dirty/touched states respectively
|
||||||
|
super.clear();
|
||||||
|
}
|
||||||
this.value = ''; // can't set null here, because IE11 treats it as a string
|
this.value = ''; // can't set null here, because IE11 treats it as a string
|
||||||
}
|
}
|
||||||
|
|
||||||
/* * * * * * * * * *
|
|
||||||
Event Handlers */
|
|
||||||
|
|
||||||
_onChange() {
|
_onChange() {
|
||||||
if (super._onChange) super._onChange();
|
if (super._onChange) {
|
||||||
|
super._onChange();
|
||||||
|
}
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent('user-input-changed', {
|
new CustomEvent('user-input-changed', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
this.modelValue = this.parser(this.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* * * * * * * * * * * *
|
|
||||||
Observer Handlers */
|
|
||||||
_onValueChanged({ value }) {
|
_onValueChanged({ value }) {
|
||||||
if (super._onValueChanged) super._onValueChanged();
|
if (super._onValueChanged) {
|
||||||
|
super._onValueChanged();
|
||||||
|
}
|
||||||
// For styling purposes, make it known the input field is not empty
|
// For styling purposes, make it known the input field is not empty
|
||||||
this.classList[value ? 'add' : 'remove']('state-filled');
|
this.classList[value ? 'add' : 'remove']('state-filled');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import {
|
||||||
unsafeStatic,
|
unsafeStatic,
|
||||||
triggerFocusFor,
|
triggerFocusFor,
|
||||||
triggerBlurFor,
|
triggerBlurFor,
|
||||||
|
aTimeout,
|
||||||
} from '@open-wc/testing';
|
} from '@open-wc/testing';
|
||||||
import { unsafeHTML } from '@lion/core';
|
import { unsafeHTML } from '@lion/core';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
@ -152,25 +153,24 @@ describe('<lion-field>', () => {
|
||||||
expect(lionField.inputElement.selectionEnd).to.equal(2);
|
expect(lionField.inputElement.selectionEnd).to.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: add pointerEvents test
|
// TODO: add pointerEvents test for disabled
|
||||||
// TODO: why is this a describe?
|
it('has a class "state-disabled"', async () => {
|
||||||
describe(`<lion-field> with <input disabled>${nameSuffix}`, () => {
|
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
|
||||||
it('has a class "state-disabled"', async () => {
|
expect(lionField.classList.contains('state-disabled')).to.equal(false);
|
||||||
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
|
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(false);
|
||||||
expect(lionField.classList.contains('state-disabled')).to.equal(false);
|
|
||||||
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(false);
|
|
||||||
|
|
||||||
lionField.disabled = true;
|
lionField.disabled = true;
|
||||||
await lionField.updateComplete;
|
await lionField.updateComplete;
|
||||||
expect(lionField.classList.contains('state-disabled')).to.equal(true);
|
await aTimeout();
|
||||||
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(true);
|
|
||||||
|
|
||||||
const disabledlionField = await fixture(
|
expect(lionField.classList.contains('state-disabled')).to.equal(true);
|
||||||
`<${tagString} disabled>${inputSlotString}</${tagString}>`,
|
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(true);
|
||||||
);
|
|
||||||
expect(disabledlionField.classList.contains('state-disabled')).to.equal(true);
|
const disabledlionField = await fixture(
|
||||||
expect(disabledlionField.inputElement.hasAttribute('disabled')).to.equal(true);
|
`<${tagString} disabled>${inputSlotString}</${tagString}>`,
|
||||||
});
|
);
|
||||||
|
expect(disabledlionField.classList.contains('state-disabled')).to.equal(true);
|
||||||
|
expect(disabledlionField.inputElement.hasAttribute('disabled')).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`A11y${nameSuffix}`, () => {
|
describe(`A11y${nameSuffix}`, () => {
|
||||||
|
|
@ -355,7 +355,7 @@ describe('<lion-field>', () => {
|
||||||
|
|
||||||
mimicUserInput(lionField, 'foo');
|
mimicUserInput(lionField, 'foo');
|
||||||
expect(formatterSpy.callCount).to.equal(1);
|
expect(formatterSpy.callCount).to.equal(1);
|
||||||
expect(lionField.formattedValue).to.equal('foo');
|
expect(lionField.value).to.equal('foo');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue