feat(form-core): allow developers to add .showFeedbackConditionFor
This commit is contained in:
parent
d4dcb7c1fb
commit
0e910e3f80
6 changed files with 104 additions and 7 deletions
5
.changeset/sharp-ravens-buy.md
Normal file
5
.changeset/sharp-ravens-buy.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/form-core': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
allow fine grained feedback visibility control via `.showFeedConditionFor(type, meta, currentCondition)` for Application Developers
|
||||||
|
|
@ -3,6 +3,7 @@ import { FormControlMixin } from './FormControlMixin.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('../types/InteractionStateMixinTypes').InteractionStateMixin} InteractionStateMixin
|
* @typedef {import('../types/InteractionStateMixinTypes').InteractionStateMixin} InteractionStateMixin
|
||||||
|
* @typedef {import('../types/InteractionStateMixinTypes').InteractionStates} InteractionStates
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -190,9 +191,24 @@ const InteractionStateMixinImplementation = superclass =>
|
||||||
* When a user enters a field without altering the value(making it `dirty`),
|
* When a user enters a field without altering the value(making it `dirty`),
|
||||||
* an error message shouldn't be shown either.
|
* an error message shouldn't be shown either.
|
||||||
* @protected
|
* @protected
|
||||||
|
* @param {string} type
|
||||||
|
* @param {InteractionStates} meta
|
||||||
*/
|
*/
|
||||||
_showFeedbackConditionFor() {
|
// eslint-disable-next-line class-methods-use-this, no-unused-vars
|
||||||
return (this.touched && this.dirty) || this.prefilled || this.submitted;
|
_showFeedbackConditionFor(type, meta) {
|
||||||
|
const { touched, dirty, prefilled, submitted } = meta;
|
||||||
|
return (touched && dirty) || prefilled || submitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _feedbackConditionMeta() {
|
||||||
|
return {
|
||||||
|
...super._feedbackConditionMeta,
|
||||||
|
submitted: this.submitted,
|
||||||
|
touched: this.touched,
|
||||||
|
dirty: this.dirty,
|
||||||
|
filled: this.filled,
|
||||||
|
prefilled: this.prefilled,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -628,15 +628,53 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The default showFeedbackConditionFor condition that will be used when the
|
||||||
|
* showFeedbackConditionFor 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
|
* @param {string} type could be 'error', 'warning', 'info', 'success' or any other custom
|
||||||
|
* Validator type
|
||||||
|
* @param {object} meta meta info (interaction states etc)
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
_showFeedbackConditionFor(type) {
|
_showFeedbackConditionFor(type, meta) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows super classes to add meta info for showFeedbackConditionFor
|
||||||
|
* @configurable
|
||||||
|
*/
|
||||||
|
get _feedbackConditionMeta() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the end user to specify when a feedback message should be shown
|
||||||
|
* @example
|
||||||
|
* showFeedbackConditionFor(type, defaultCondition) {
|
||||||
|
* if (type === 'info') {
|
||||||
|
* return true;
|
||||||
|
* }
|
||||||
|
* return defaultCondition(type);
|
||||||
|
* }
|
||||||
|
* @overridable
|
||||||
|
* @param {string} type could be 'error', 'warning', 'info', 'success' or any other custom
|
||||||
|
* Validator type
|
||||||
|
* @param {object} meta meta info (interaction states etc)
|
||||||
|
* @param {((type: string, meta: object) => boolean)} currentCondition this is the _showFeedbackConditionFor
|
||||||
|
* that can be used if a developer wants to override for a certain type, but wants to fallback
|
||||||
|
* for other types
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
showFeedbackConditionFor(
|
||||||
|
type,
|
||||||
|
meta = this._feedbackConditionMeta,
|
||||||
|
currentCondition = this._showFeedbackConditionFor.bind(this),
|
||||||
|
) {
|
||||||
|
return currentCondition(type, meta);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} type
|
* @param {string} type
|
||||||
* @protected
|
* @protected
|
||||||
|
|
@ -677,7 +715,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 => (this._showFeedbackConditionFor(type) ? type : undefined))
|
.map(type => (this.showFeedbackConditionFor(type) ? type : undefined))
|
||||||
.filter(_ => !!_));
|
.filter(_ => !!_));
|
||||||
|
|
||||||
if (JSON.stringify(this.shouldShowFeedbackFor) !== JSON.stringify(newShouldShowFeedbackFor)) {
|
if (JSON.stringify(this.shouldShowFeedbackFor) !== JSON.stringify(newShouldShowFeedbackFor)) {
|
||||||
|
|
@ -700,7 +738,7 @@ export const ValidateMixinImplementation = superclass =>
|
||||||
const types = ctor.validationTypes;
|
const types = ctor.validationTypes;
|
||||||
// Sort all validators based on the type provided.
|
// Sort all validators based on the type provided.
|
||||||
const res = validationResult
|
const res = validationResult
|
||||||
.filter(v => this._showFeedbackConditionFor(v.type))
|
.filter(v => this.showFeedbackConditionFor(v.type))
|
||||||
.sort((a, b) => types.indexOf(a.type) - types.indexOf(b.type));
|
.sort((a, b) => types.indexOf(a.type) - types.indexOf(b.type));
|
||||||
return res.slice(0, this._visibleMessagesAmount);
|
return res.slice(0, this._visibleMessagesAmount);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -841,6 +841,26 @@ export function runValidateMixinSuite(customConfig) {
|
||||||
expect(el.validationStates.error).to.not.eql({});
|
expect(el.validationStates.error).to.not.eql({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can be configured to change visibility conditions per type', async () => {
|
||||||
|
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||||
|
<${tag}
|
||||||
|
.validators="${[new Required({}, { type: 'error' })]}"
|
||||||
|
.showFeedbackConditionFor="${(
|
||||||
|
/** @type {string} */ type,
|
||||||
|
/** @type {object} */ meta,
|
||||||
|
/** @type {(type: string) => any} */ defaultCondition,
|
||||||
|
) => {
|
||||||
|
if (type === 'error') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return defaultCondition(type);
|
||||||
|
}}"
|
||||||
|
>${lightDom}</${tag}>
|
||||||
|
`));
|
||||||
|
|
||||||
|
expect(el.showsFeedbackFor).to.eql(['error']);
|
||||||
|
});
|
||||||
|
|
||||||
describe('Events', () => {
|
describe('Events', () => {
|
||||||
it('fires "showsFeedbackForChanged" event async after feedbackData got synced to feedbackElement', async () => {
|
it('fires "showsFeedbackForChanged" event async after feedbackData got synced to feedbackElement', async () => {
|
||||||
const spy = sinon.spy();
|
const spy = sinon.spy();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,17 @@ import { Constructor } from '@open-wc/dedupe-mixin';
|
||||||
import { LitElement } from '@lion/core';
|
import { LitElement } from '@lion/core';
|
||||||
import { FormControlHost } from './FormControlMixinTypes';
|
import { FormControlHost } from './FormControlMixinTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of meta info about a FormControl that helps in the context of determining validation
|
||||||
|
* feedback visibility
|
||||||
|
*/
|
||||||
|
type InteractionStates = {
|
||||||
|
submitted: boolean;
|
||||||
|
touched: boolean;
|
||||||
|
dirty: boolean;
|
||||||
|
filled: boolean;
|
||||||
|
prefilled: boolean;
|
||||||
|
};
|
||||||
export declare class InteractionStateHost {
|
export declare class InteractionStateHost {
|
||||||
prefilled: boolean;
|
prefilled: boolean;
|
||||||
filled: boolean;
|
filled: boolean;
|
||||||
|
|
@ -20,6 +31,12 @@ export declare class InteractionStateHost {
|
||||||
_iStateOnValueChange(): void;
|
_iStateOnValueChange(): void;
|
||||||
_onTouchedChanged(): void;
|
_onTouchedChanged(): void;
|
||||||
_onDirtyChanged(): void;
|
_onDirtyChanged(): void;
|
||||||
|
|
||||||
|
showFeedbackConditionFor(
|
||||||
|
type: string,
|
||||||
|
meta: InteractionStates,
|
||||||
|
currentCondition: Function,
|
||||||
|
): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare function InteractionStateImplementation<T extends Constructor<LitElement>>(
|
export declare function InteractionStateImplementation<T extends Constructor<LitElement>>(
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,8 @@ export declare class ValidateHost {
|
||||||
__isEmpty(v: unknown): boolean;
|
__isEmpty(v: unknown): boolean;
|
||||||
__getFeedbackMessages(validators: Validator[]): Promise<FeedbackMessage[]>;
|
__getFeedbackMessages(validators: Validator[]): Promise<FeedbackMessage[]>;
|
||||||
_updateFeedbackComponent(): void;
|
_updateFeedbackComponent(): void;
|
||||||
_showFeedbackConditionFor(type: string): boolean;
|
_showFeedbackConditionFor(type: string, meta: object): boolean;
|
||||||
|
showFeedbackConditionFor(type: string, meta: object, currentCondition: Function): boolean;
|
||||||
_hasFeedbackVisibleFor(type: string): boolean;
|
_hasFeedbackVisibleFor(type: string): boolean;
|
||||||
_updateShouldShowFeedbackFor(): void;
|
_updateShouldShowFeedbackFor(): void;
|
||||||
_prioritizeAndFilterFeedback(opts: { validationResult: Validator[] }): Validator[];
|
_prioritizeAndFilterFeedback(opts: { validationResult: Validator[] }): Validator[];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue