chore(field): small cleanup LionField

This commit is contained in:
Thijs Louisse 2019-07-15 15:00:02 +02:00
parent a5bc33072f
commit d7f7ffa221
2 changed files with 65 additions and 63 deletions

View file

@ -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');
} }

View file

@ -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');
}); });
}); });