fix(form-core): validate on child change + {feedbackType}StateChanged
This commit is contained in:
parent
a100fb43df
commit
53167fd205
16 changed files with 245 additions and 32 deletions
6
.changeset/fair-geese-drum.md
Normal file
6
.changeset/fair-geese-drum.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'@lion/form-core': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
- validation of form groups when child fires 'model-value-changed'
|
||||||
|
- fire {feedbackType}StateChanged event when feedback type (like 'error'/'warning') changed
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
import { html, render } from '@lion/core';
|
import { html, render } from '@lion/core';
|
||||||
import { renderLitAsNode } from '@lion/helpers';
|
import { renderLitAsNode } from '@lion/helpers';
|
||||||
import '@lion/input/define';
|
import '@lion/input/define';
|
||||||
import { Validator } from '@lion/form-core';
|
import '@lion/input-date/define';
|
||||||
|
import { Validator, Unparseable, MinDate, Required } from '@lion/form-core';
|
||||||
import './assets/h-output.js';
|
import './assets/h-output.js';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -118,7 +119,7 @@ export const feedbackCondition = () => {
|
||||||
></lion-input>
|
></lion-input>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// 4. When checkboxes change, set the showFeedbackConditionFor method to a new method that checks
|
// 4. When checkboxes change, set the feedbackCondition method to a new method that checks
|
||||||
// whether every condition that is checked, is true on the field. Otherwise, don't show the feedback.
|
// whether every condition that is checked, is true on the field. Otherwise, don't show the feedback.
|
||||||
const fetchConditionsAndReevaluate = ({ currentTarget: { modelValue } }) => {
|
const fetchConditionsAndReevaluate = ({ currentTarget: { modelValue } }) => {
|
||||||
fieldElement._showFeedbackConditionFor = type => {
|
fieldElement._showFeedbackConditionFor = type => {
|
||||||
|
|
@ -142,3 +143,35 @@ export const feedbackCondition = () => {
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Changing the feedback show condition (Application Developers)
|
||||||
|
|
||||||
|
In some situations, the default condition for showing feedback messages might not apply.
|
||||||
|
The conditions as described in 'When is feedback shown to the user' can be overidden via
|
||||||
|
the `feedbackCondition` method.
|
||||||
|
In this example, we want to implement the following situation:
|
||||||
|
|
||||||
|
- for an `input-date`, we have a MinDate condition
|
||||||
|
- we want to send feedback as soon as we know the user intentionally typed a full Date
|
||||||
|
(we know if modelValue is not Unparseable)
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
export const feedbackVisibility = () => html`
|
||||||
|
<lion-input-date
|
||||||
|
.validators="${[
|
||||||
|
new Required(),
|
||||||
|
new MinDate(new Date('2000/10/10'), {
|
||||||
|
getMessage: () => `You provided a correctly formatted date, but it's below MinData`,
|
||||||
|
}),
|
||||||
|
]}"
|
||||||
|
.feedbackCondition="${(type, meta, originalCondition) => {
|
||||||
|
if (meta.modelValue && !(meta.modelValue instanceof Unparseable)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return originalCondition(type, meta);
|
||||||
|
}}"
|
||||||
|
help-text="Error appears as soon as a Parseable date before 10/10/2000 is typed"
|
||||||
|
label="Custom feedback visibility"
|
||||||
|
></lion-input-date>
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,11 @@ import {
|
||||||
Required,
|
Required,
|
||||||
Validator,
|
Validator,
|
||||||
Pattern,
|
Pattern,
|
||||||
|
Unparseable,
|
||||||
} from '@lion/form-core';
|
} from '@lion/form-core';
|
||||||
import { localize } from '@lion/localize';
|
import { localize } from '@lion/localize';
|
||||||
import { loadDefaultFeedbackMessages } from '@lion/validate-messages';
|
import { loadDefaultFeedbackMessages } from '@lion/validate-messages';
|
||||||
|
import { renderLitAsNode } from '@lion/helpers';
|
||||||
```
|
```
|
||||||
|
|
||||||
## When validation happens
|
## When validation happens
|
||||||
|
|
@ -804,3 +806,92 @@ export const backendValidation = () => {
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Multiple field validation
|
||||||
|
|
||||||
|
When validation is dependent on muliple fields, two approaches can be considered:
|
||||||
|
|
||||||
|
- Fieldset validation
|
||||||
|
- Validators with knowledge about context
|
||||||
|
|
||||||
|
### Fieldset validation
|
||||||
|
|
||||||
|
Assume we have a field `startDate` and `endDate` field, with condition `startDate` < `endDate`.
|
||||||
|
The easiest way to achieve this, is by adding a Validator to a fieldset wrapping those fields.
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
const isInterpretable = mv => mv && !(mv instanceof Unparseable);
|
||||||
|
|
||||||
|
class Interval extends Validator {
|
||||||
|
static get validatorName() {
|
||||||
|
return 'Interval';
|
||||||
|
}
|
||||||
|
|
||||||
|
execute({ startDate, endDate }) {
|
||||||
|
if (isInterpretable(startDate) && isInterpretable(endDate)) {
|
||||||
|
return !(startDate < endDate);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getMessage() {
|
||||||
|
return `The start date should be before the end date`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const fieldsetValidation = () => html`
|
||||||
|
<lion-fieldset .validators="${[new Interval()]}">
|
||||||
|
<lion-input-date name="startDate" label="Start date"></lion-input-date>
|
||||||
|
<lion-input-date name="endDate" label="End date"></lion-input-date>
|
||||||
|
</lion-fieldset>
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validators with knowledge about context
|
||||||
|
|
||||||
|
Assume we want to create password validation with a confirmation field.
|
||||||
|
We don't want to show feedback on group level, but right beneath the fields.
|
||||||
|
|
||||||
|
```js preview-story
|
||||||
|
const isInterpretableValue = mv => mv && !(mv instanceof Unparseable);
|
||||||
|
|
||||||
|
class PasswordMatch extends Validator {
|
||||||
|
static get validatorName() {
|
||||||
|
return 'PasswordsMatch';
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(modelValue, { first, second }) {
|
||||||
|
if (isInterpretableValue(first.modelValue) && isInterpretableValue(second.modelValue)) {
|
||||||
|
return first.modelValue !== second.modelValue;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: use ref directive once on Lit-element 3
|
||||||
|
const first = renderLitAsNode(html`
|
||||||
|
<lion-input
|
||||||
|
.feedbackCondition="${(type, meta) => meta.focused}"
|
||||||
|
type="password"
|
||||||
|
name="initialPassword"
|
||||||
|
label="New password"
|
||||||
|
></lion-input>
|
||||||
|
`);
|
||||||
|
const second = renderLitAsNode(html`
|
||||||
|
<lion-input
|
||||||
|
.feedbackCondition="${(type, meta) => meta.focused}"
|
||||||
|
type="password"
|
||||||
|
name="confirmPassword"
|
||||||
|
label="Confirm password"
|
||||||
|
></lion-input>
|
||||||
|
`);
|
||||||
|
first.validators = [
|
||||||
|
new PasswordMatch(
|
||||||
|
{ first, second },
|
||||||
|
{ getMessage: () => 'Please match with confirmation field' },
|
||||||
|
),
|
||||||
|
];
|
||||||
|
second.validators = [
|
||||||
|
new PasswordMatch({ first, second }, { getMessage: () => 'Please match with initial field' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
export const contextValidation = () => html` ${first}${second} `;
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ const InteractionStateMixinImplementation = superclass =>
|
||||||
|
|
||||||
get _feedbackConditionMeta() {
|
get _feedbackConditionMeta() {
|
||||||
return {
|
return {
|
||||||
// @ts-ignore
|
// @ts-ignore to fix, InteractionStateMixin needs to depend on ValidateMixin
|
||||||
...super._feedbackConditionMeta,
|
...super._feedbackConditionMeta,
|
||||||
submitted: this.submitted,
|
submitted: this.submitted,
|
||||||
touched: this.touched,
|
touched: this.touched,
|
||||||
|
|
|
||||||
|
|
@ -99,4 +99,11 @@ export class LionField extends FormControlMixin(
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @configure InteractionStateMixin, ValidateMixin
|
||||||
|
*/
|
||||||
|
get _feedbackConditionMeta() {
|
||||||
|
return { ...super._feedbackConditionMeta, focused: this.focused };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
* By default, just like the platform, only one message (with highest prio) is visible.
|
* By default, just like the platform, only one message (with highest prio) is visible.
|
||||||
*/
|
*/
|
||||||
_visibleMessagesAmount: { attribute: false },
|
_visibleMessagesAmount: { attribute: false },
|
||||||
|
|
||||||
|
__childModelValueChanged: { attribute: false },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,6 +183,12 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
this.__onValidatorUpdated = this.__onValidatorUpdated.bind(this);
|
this.__onValidatorUpdated = this.__onValidatorUpdated.bind(this);
|
||||||
/** @protected */
|
/** @protected */
|
||||||
this._updateFeedbackComponent = this._updateFeedbackComponent.bind(this);
|
this._updateFeedbackComponent = this._updateFeedbackComponent.bind(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will be used for FormGroups that listen for `model-value-changed` of children
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.__childModelValueChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
|
@ -200,6 +208,11 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProperties);
|
||||||
this.__validateInitialized = true;
|
this.__validateInitialized = true;
|
||||||
this.validate();
|
this.validate();
|
||||||
|
if (this._repropagationRole !== 'child') {
|
||||||
|
this.addEventListener('model-value-changed', () => {
|
||||||
|
this.__childModelValueChanged = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -627,8 +640,8 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default showFeedbackConditionFor condition that will be used when the
|
* The default feedbackCondition condition that will be used when the
|
||||||
* showFeedbackConditionFor is not overridden.
|
* feedbackCondition is not overridden.
|
||||||
* Show the validity feedback when returning true, don't show when false
|
* Show the validity feedback when returning true, don't show when false
|
||||||
* @param {string} type could be 'error', 'warning', 'info', 'success' or any other custom
|
* @param {string} type could be 'error', 'warning', 'info', 'success' or any other custom
|
||||||
* Validator type
|
* Validator type
|
||||||
|
|
@ -641,17 +654,17 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows super classes to add meta info for showFeedbackConditionFor
|
* Allows super classes to add meta info for feedbackCondition
|
||||||
* @configurable
|
* @configurable
|
||||||
*/
|
*/
|
||||||
get _feedbackConditionMeta() {
|
get _feedbackConditionMeta() {
|
||||||
return {};
|
return { modelValue: this.modelValue, el: this };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows the end user to specify when a feedback message should be shown
|
* Allows the end user to specify when a feedback message should be shown
|
||||||
* @example
|
* @example
|
||||||
* showFeedbackConditionFor(type, meta, defaultCondition) {
|
* feedbackCondition(type, meta, defaultCondition) {
|
||||||
* if (type === 'info') {
|
* if (type === 'info') {
|
||||||
* return return;
|
* return return;
|
||||||
* } else if (type === 'prefilledOnly') {
|
* } else if (type === 'prefilledOnly') {
|
||||||
|
|
@ -668,7 +681,7 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
* for other types
|
* for other types
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
showFeedbackConditionFor(
|
feedbackCondition(
|
||||||
type,
|
type,
|
||||||
meta = this._feedbackConditionMeta,
|
meta = this._feedbackConditionMeta,
|
||||||
currentCondition = this._showFeedbackConditionFor.bind(this),
|
currentCondition = this._showFeedbackConditionFor.bind(this),
|
||||||
|
|
@ -702,9 +715,30 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
// Necessary typecast because types aren't smart enough to understand that we filter out undefined
|
// Necessary typecast because types aren't smart enough to understand that we filter out undefined
|
||||||
this.showsFeedbackFor = /** @type {string[]} */ (ctor.validationTypes
|
this.showsFeedbackFor = /** @type {string[]} */ (ctor.validationTypes
|
||||||
.map(type => (this._hasFeedbackVisibleFor(type) ? type : undefined))
|
.map(type => (this._hasFeedbackVisibleFor(type) ? type : undefined))
|
||||||
.filter(_ => !!_));
|
.filter(Boolean));
|
||||||
this._updateFeedbackComponent();
|
this._updateFeedbackComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('__childModelValueChanged') && this.__childModelValueChanged) {
|
||||||
|
this.validate({ clearCurrentResult: true });
|
||||||
|
this.__childModelValueChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('validationStates')) {
|
||||||
|
const prevStates = /** @type {{[key: string]: object;}} */ (changedProperties.get(
|
||||||
|
'validationStates',
|
||||||
|
));
|
||||||
|
if (prevStates) {
|
||||||
|
Object.entries(this.validationStates).forEach(([type, feedbackObj]) => {
|
||||||
|
if (
|
||||||
|
prevStates[type] &&
|
||||||
|
JSON.stringify(feedbackObj) !== JSON.stringify(prevStates[type])
|
||||||
|
) {
|
||||||
|
this.dispatchEvent(new CustomEvent(`${type}StateChanged`, { detail: feedbackObj }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -717,7 +751,7 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
// Necessary typecast because types aren't smart enough to understand that we filter out undefined
|
// Necessary typecast because types aren't smart enough to understand that we filter out undefined
|
||||||
const newShouldShowFeedbackFor = /** @type {string[]} */ (ctor.validationTypes
|
const newShouldShowFeedbackFor = /** @type {string[]} */ (ctor.validationTypes
|
||||||
.map(type =>
|
.map(type =>
|
||||||
this.showFeedbackConditionFor(
|
this.feedbackCondition(
|
||||||
type,
|
type,
|
||||||
this._feedbackConditionMeta,
|
this._feedbackConditionMeta,
|
||||||
this._showFeedbackConditionFor.bind(this),
|
this._showFeedbackConditionFor.bind(this),
|
||||||
|
|
@ -725,7 +759,7 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
? type
|
? type
|
||||||
: undefined,
|
: undefined,
|
||||||
)
|
)
|
||||||
.filter(_ => !!_));
|
.filter(Boolean));
|
||||||
|
|
||||||
if (JSON.stringify(this.shouldShowFeedbackFor) !== JSON.stringify(newShouldShowFeedbackFor)) {
|
if (JSON.stringify(this.shouldShowFeedbackFor) !== JSON.stringify(newShouldShowFeedbackFor)) {
|
||||||
this.shouldShowFeedbackFor = newShouldShowFeedbackFor;
|
this.shouldShowFeedbackFor = newShouldShowFeedbackFor;
|
||||||
|
|
@ -748,7 +782,7 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
// Sort all validators based on the type provided.
|
// Sort all validators based on the type provided.
|
||||||
const res = validationResult
|
const res = validationResult
|
||||||
.filter(v =>
|
.filter(v =>
|
||||||
this.showFeedbackConditionFor(
|
this.feedbackCondition(
|
||||||
v.type,
|
v.type,
|
||||||
this._feedbackConditionMeta,
|
this._feedbackConditionMeta,
|
||||||
this._showFeedbackConditionFor.bind(this),
|
this._showFeedbackConditionFor.bind(this),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ import {
|
||||||
AsyncAlwaysInvalid,
|
AsyncAlwaysInvalid,
|
||||||
AsyncAlwaysValid,
|
AsyncAlwaysValid,
|
||||||
} from '../test-helpers/index.js';
|
} from '../test-helpers/index.js';
|
||||||
|
import '../define.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('../').LionField} LionField
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{tagString?: string | null, lightDom?: string}} [customConfig]
|
* @param {{tagString?: string | null, lightDom?: string}} [customConfig]
|
||||||
|
|
@ -133,6 +138,20 @@ export function runValidateMixinSuite(customConfig) {
|
||||||
expect(validateSpy.callCount).to.equal(1);
|
expect(validateSpy.callCount).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('revalidates when child ".modelValue" changes', async () => {
|
||||||
|
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||||
|
<${tag}
|
||||||
|
._repropagationRole="${'fieldset'}"
|
||||||
|
.validators=${[new AlwaysValid()]}
|
||||||
|
.modelValue=${'myValue'}
|
||||||
|
><lion-field id="child"><input slot="input"></lion-field></${tag}>
|
||||||
|
`));
|
||||||
|
const validateSpy = sinon.spy(el, 'validate');
|
||||||
|
/** @type {LionField} */ (el.querySelector('#child')).modelValue = 'test';
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(validateSpy.callCount).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('revalidates when ".validators" changes', async () => {
|
it('revalidates when ".validators" changes', async () => {
|
||||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||||
<${tag}
|
<${tag}
|
||||||
|
|
@ -867,7 +886,7 @@ export function runValidateMixinSuite(customConfig) {
|
||||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||||
<${tag}
|
<${tag}
|
||||||
.validators="${[new Required({}, { type: 'error' })]}"
|
.validators="${[new Required({}, { type: 'error' })]}"
|
||||||
.showFeedbackConditionFor="${(
|
.feedbackCondition="${(
|
||||||
/** @type {string} */ type,
|
/** @type {string} */ type,
|
||||||
/** @type {object} */ meta,
|
/** @type {object} */ meta,
|
||||||
/** @type {(type: string) => any} */ defaultCondition,
|
/** @type {(type: string) => any} */ defaultCondition,
|
||||||
|
|
@ -927,6 +946,30 @@ export function runValidateMixinSuite(customConfig) {
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(spy).to.have.callCount(2);
|
expect(spy).to.have.callCount(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fires "{type}StateChanged" event async when type validity changed', async () => {
|
||||||
|
const spy = sinon.spy();
|
||||||
|
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||||
|
<${tag}
|
||||||
|
.submitted=${true}
|
||||||
|
.validators=${[new MinLength(7)]}
|
||||||
|
@errorStateChanged=${spy};
|
||||||
|
>${lightDom}</${tag}>
|
||||||
|
`));
|
||||||
|
expect(spy).to.have.callCount(0);
|
||||||
|
|
||||||
|
el.modelValue = 'a';
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(spy).to.have.callCount(1);
|
||||||
|
|
||||||
|
el.modelValue = 'abc';
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(spy).to.have.callCount(1);
|
||||||
|
|
||||||
|
el.modelValue = 'abcdefg';
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(spy).to.have.callCount(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,18 +83,18 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
||||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||||
<${tag} label="foo" .fieldName="${'bar'}">${inputSlots}</${tag}>
|
<${tag} label="foo" .fieldName="${'bar'}">${inputSlots}</${tag}>
|
||||||
`));
|
`));
|
||||||
// @ts-ignore [allow-proteced] in test
|
// @ts-ignore [allow-protected] in test
|
||||||
expect(el.__fieldName).to.equal(el.fieldName);
|
expect(el.__fieldName).to.equal(el.fieldName);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Tests below belong to FormRegistrarMixin. Preferably run suite integration test
|
// TODO: Tests below belong to FormRegistrarMixin. Preferably run suite integration test
|
||||||
it(`${tagString} has an up to date list of every form element in .formElements`, async () => {
|
it(`${tagString} has an up to date list of every form element in .formElements`, async () => {
|
||||||
const el = /** @type {FormGroup} */ (await fixture(html`<${tag}>${inputSlots}</${tag}>`));
|
const el = /** @type {FormGroup} */ (await fixture(html`<${tag}>${inputSlots}</${tag}>`));
|
||||||
// @ts-ignore [allow-proteced] in test
|
// @ts-ignore [allow-protected] in test
|
||||||
expect(el.formElements._keys().length).to.equal(3);
|
expect(el.formElements._keys().length).to.equal(3);
|
||||||
expect(el.formElements['hobbies[]'].length).to.equal(2);
|
expect(el.formElements['hobbies[]'].length).to.equal(2);
|
||||||
el.removeChild(el.formElements['hobbies[]'][0]);
|
el.removeChild(el.formElements['hobbies[]'][0]);
|
||||||
// @ts-ignore [allow-proteced] in test
|
// @ts-ignore [allow-protected] in test
|
||||||
expect(el.formElements._keys().length).to.equal(3);
|
expect(el.formElements._keys().length).to.equal(3);
|
||||||
expect(el.formElements['hobbies[]'].length).to.equal(1);
|
expect(el.formElements['hobbies[]'].length).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ describe('<lion-field>', () => {
|
||||||
const el = /** @type {LionField} */ (await fixture(
|
const el = /** @type {LionField} */ (await fixture(
|
||||||
html`<${tag} label="foo" .fieldName="${'bar'}">${inputSlot}</${tag}>`,
|
html`<${tag} label="foo" .fieldName="${'bar'}">${inputSlot}</${tag}>`,
|
||||||
));
|
));
|
||||||
// @ts-ignore [allow-proteced] in test
|
// @ts-ignore [allow-protected] in test
|
||||||
expect(el.__fieldName).to.equal(el.fieldName);
|
expect(el.__fieldName).to.equal(el.fieldName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
import { Constructor } from '@open-wc/dedupe-mixin';
|
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||||
// import { LionField } from '@lion/form-core/src/LionField';
|
|
||||||
import { LitElement } from '@lion/core';
|
import { LitElement } from '@lion/core';
|
||||||
import { FocusHost } from '@lion/form-core/types/FocusMixinTypes';
|
import { FocusHost } from '@lion/form-core/types/FocusMixinTypes';
|
||||||
import { FormControlHost } from '@lion/form-core/types/FormControlMixinTypes';
|
import { FormControlHost } from '@lion/form-core/types/FormControlMixinTypes';
|
||||||
|
|
||||||
// export declare class NativeTextField extends LionField {
|
|
||||||
// protected get _inputNode(): HTMLTextAreaElement | HTMLInputElement;
|
|
||||||
// }
|
|
||||||
|
|
||||||
export declare class NativeTextFieldHost {
|
export declare class NativeTextFieldHost {
|
||||||
// protected get _inputNode(): HTMLTextAreaElement | HTMLInputElement;
|
|
||||||
|
|
||||||
get selectionStart(): number;
|
get selectionStart(): number;
|
||||||
set selectionStart(value: number);
|
set selectionStart(value: number);
|
||||||
get selectionEnd(): number;
|
get selectionEnd(): number;
|
||||||
|
|
@ -26,7 +19,5 @@ export declare function NativeTextFieldImplementation<T extends Constructor<LitE
|
||||||
Pick<typeof FocusHost, keyof typeof FocusHost> &
|
Pick<typeof FocusHost, keyof typeof FocusHost> &
|
||||||
Constructor<FormControlHost> &
|
Constructor<FormControlHost> &
|
||||||
Pick<typeof FormControlHost, keyof typeof FormControlHost>;
|
Pick<typeof FormControlHost, keyof typeof FormControlHost>;
|
||||||
// &
|
|
||||||
// Pick<typeof NativeTextField, keyof typeof NativeTextField>;
|
|
||||||
|
|
||||||
export type NativeTextFieldMixin = typeof NativeTextFieldImplementation;
|
export type NativeTextFieldMixin = typeof NativeTextFieldImplementation;
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ export declare class FormGroupHost {
|
||||||
protected _initialModelValue: { [x: string]: any };
|
protected _initialModelValue: { [x: string]: any };
|
||||||
protected get _inputNode(): HTMLElement;
|
protected get _inputNode(): HTMLElement;
|
||||||
protected static _addDescriptionElementIdsToField(): void;
|
protected static _addDescriptionElementIdsToField(): void;
|
||||||
|
|
||||||
protected _setValueForAllFormElements(property: string, value: any): void;
|
protected _setValueForAllFormElements(property: string, value: any): void;
|
||||||
|
|
||||||
private __descriptionElementsInParentChain: Set<HTMLElement>;
|
private __descriptionElementsInParentChain: Set<HTMLElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { LitElement } from '@lion/core';
|
import { LitElement } from '@lion/core';
|
||||||
import { Constructor } from '@open-wc/dedupe-mixin';
|
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||||
import { PropertyValues } from '@lion/core';
|
|
||||||
|
|
||||||
export declare interface SyncUpdatableNamespace {
|
export declare interface SyncUpdatableNamespace {
|
||||||
connected?: boolean;
|
connected?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export declare class ValidateHost {
|
||||||
|
|
||||||
protected _visibleMessagesAmount: number;
|
protected _visibleMessagesAmount: number;
|
||||||
protected _allValidators: Validator[];
|
protected _allValidators: Validator[];
|
||||||
|
protected get _feedbackConditionMeta(): object;
|
||||||
protected get _feedbackNode(): LionValidationFeedback;
|
protected get _feedbackNode(): LionValidationFeedback;
|
||||||
|
|
||||||
protected _updateFeedbackComponent(): void;
|
protected _updateFeedbackComponent(): void;
|
||||||
|
|
@ -50,6 +51,7 @@ export declare class ValidateHost {
|
||||||
private __validationResult: Validator[];
|
private __validationResult: Validator[];
|
||||||
private __prevValidationResult: Validator[];
|
private __prevValidationResult: Validator[];
|
||||||
private __prevShownValidationResult: Validator[];
|
private __prevShownValidationResult: Validator[];
|
||||||
|
private __childModelValueChanged: boolean;
|
||||||
|
|
||||||
private __storePrevResult(): void;
|
private __storePrevResult(): void;
|
||||||
private __executeValidators(): void;
|
private __executeValidators(): void;
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,11 @@ import { ListboxMixin } from './ListboxMixin.js';
|
||||||
*/
|
*/
|
||||||
export class LionListbox extends ListboxMixin(
|
export class LionListbox extends ListboxMixin(
|
||||||
FocusMixin(InteractionStateMixin(ValidateMixin(LitElement))),
|
FocusMixin(InteractionStateMixin(ValidateMixin(LitElement))),
|
||||||
) {}
|
) {
|
||||||
|
/**
|
||||||
|
* @configure InteractionStateMixin, ValidateMixin
|
||||||
|
*/
|
||||||
|
get _feedbackConditionMeta() {
|
||||||
|
return { ...super._feedbackConditionMeta, focused: this.focused };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,7 @@ export function runListboxMixinSuite(customConfig = {}) {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<${tag} label="foo" .fieldName="${'bar'}"></${tag}>
|
<${tag} label="foo" .fieldName="${'bar'}"></${tag}>
|
||||||
`);
|
`);
|
||||||
// @ts-ignore [allow-proteced] in test
|
// @ts-ignore [allow-protected] in test
|
||||||
expect(el.__fieldName).to.equal(el.fieldName);
|
expect(el.__fieldName).to.equal(el.fieldName);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ describe('lion-switch', () => {
|
||||||
const el = await fixture(
|
const el = await fixture(
|
||||||
html`<custom-switch
|
html`<custom-switch
|
||||||
.validators="${[new IsTrue({}, { type: 'info' })]}"
|
.validators="${[new IsTrue({}, { type: 'info' })]}"
|
||||||
.showFeedbackConditionFor="${(
|
.feedbackCondition="${(
|
||||||
/** @type {string} */ type,
|
/** @type {string} */ type,
|
||||||
/** @type {object} */ meta,
|
/** @type {object} */ meta,
|
||||||
/** @type {(type: string, meta: object) => any} */ defaultCondition,
|
/** @type {(type: string, meta: object) => any} */ defaultCondition,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue