From 0e910e3f8065a0ca9c7cd76b888a8679f9d5fd21 Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Thu, 8 Apr 2021 17:00:19 +0200 Subject: [PATCH] feat(form-core): allow developers to add .showFeedbackConditionFor --- .changeset/sharp-ravens-buy.md | 5 ++ .../form-core/src/InteractionStateMixin.js | 20 +++++++- .../form-core/src/validate/ValidateMixin.js | 46 +++++++++++++++++-- .../test-suites/ValidateMixin.suite.js | 20 ++++++++ .../types/InteractionStateMixinTypes.d.ts | 17 +++++++ .../types/validate/ValidateMixinTypes.d.ts | 3 +- 6 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 .changeset/sharp-ravens-buy.md diff --git a/.changeset/sharp-ravens-buy.md b/.changeset/sharp-ravens-buy.md new file mode 100644 index 000000000..1f032e6a6 --- /dev/null +++ b/.changeset/sharp-ravens-buy.md @@ -0,0 +1,5 @@ +--- +'@lion/form-core': minor +--- + +allow fine grained feedback visibility control via `.showFeedConditionFor(type, meta, currentCondition)` for Application Developers diff --git a/packages/form-core/src/InteractionStateMixin.js b/packages/form-core/src/InteractionStateMixin.js index 9428ba9d2..6689630ad 100644 --- a/packages/form-core/src/InteractionStateMixin.js +++ b/packages/form-core/src/InteractionStateMixin.js @@ -3,6 +3,7 @@ import { FormControlMixin } from './FormControlMixin.js'; /** * @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`), * an error message shouldn't be shown either. * @protected + * @param {string} type + * @param {InteractionStates} meta */ - _showFeedbackConditionFor() { - return (this.touched && this.dirty) || this.prefilled || this.submitted; + // eslint-disable-next-line class-methods-use-this, no-unused-vars + _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, + }; } }; diff --git a/packages/form-core/src/validate/ValidateMixin.js b/packages/form-core/src/validate/ValidateMixin.js index ecd8bf008..510829df2 100644 --- a/packages/form-core/src/validate/ValidateMixin.js +++ b/packages/form-core/src/validate/ValidateMixin.js @@ -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 - * @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 */ // eslint-disable-next-line no-unused-vars - _showFeedbackConditionFor(type) { + _showFeedbackConditionFor(type, meta) { 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 * @protected @@ -677,7 +715,7 @@ export const ValidateMixinImplementation = superclass => // Necessary typecast because types aren't smart enough to understand that we filter out undefined const newShouldShowFeedbackFor = /** @type {string[]} */ (ctor.validationTypes - .map(type => (this._showFeedbackConditionFor(type) ? type : undefined)) + .map(type => (this.showFeedbackConditionFor(type) ? type : undefined)) .filter(_ => !!_)); if (JSON.stringify(this.shouldShowFeedbackFor) !== JSON.stringify(newShouldShowFeedbackFor)) { @@ -700,7 +738,7 @@ export const ValidateMixinImplementation = superclass => const types = ctor.validationTypes; // Sort all validators based on the type provided. 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)); return res.slice(0, this._visibleMessagesAmount); } diff --git a/packages/form-core/test-suites/ValidateMixin.suite.js b/packages/form-core/test-suites/ValidateMixin.suite.js index 73e044f0e..b728e9558 100644 --- a/packages/form-core/test-suites/ValidateMixin.suite.js +++ b/packages/form-core/test-suites/ValidateMixin.suite.js @@ -841,6 +841,26 @@ export function runValidateMixinSuite(customConfig) { 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} + `)); + + expect(el.showsFeedbackFor).to.eql(['error']); + }); + describe('Events', () => { it('fires "showsFeedbackForChanged" event async after feedbackData got synced to feedbackElement', async () => { const spy = sinon.spy(); diff --git a/packages/form-core/types/InteractionStateMixinTypes.d.ts b/packages/form-core/types/InteractionStateMixinTypes.d.ts index bd1949f96..2dbf81a0c 100644 --- a/packages/form-core/types/InteractionStateMixinTypes.d.ts +++ b/packages/form-core/types/InteractionStateMixinTypes.d.ts @@ -2,6 +2,17 @@ import { Constructor } from '@open-wc/dedupe-mixin'; import { LitElement } from '@lion/core'; 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 { prefilled: boolean; filled: boolean; @@ -20,6 +31,12 @@ export declare class InteractionStateHost { _iStateOnValueChange(): void; _onTouchedChanged(): void; _onDirtyChanged(): void; + + showFeedbackConditionFor( + type: string, + meta: InteractionStates, + currentCondition: Function, + ): boolean; } export declare function InteractionStateImplementation>( diff --git a/packages/form-core/types/validate/ValidateMixinTypes.d.ts b/packages/form-core/types/validate/ValidateMixinTypes.d.ts index 56a567b72..4e433a91c 100644 --- a/packages/form-core/types/validate/ValidateMixinTypes.d.ts +++ b/packages/form-core/types/validate/ValidateMixinTypes.d.ts @@ -66,7 +66,8 @@ export declare class ValidateHost { __isEmpty(v: unknown): boolean; __getFeedbackMessages(validators: Validator[]): Promise; _updateFeedbackComponent(): void; - _showFeedbackConditionFor(type: string): boolean; + _showFeedbackConditionFor(type: string, meta: object): boolean; + showFeedbackConditionFor(type: string, meta: object, currentCondition: Function): boolean; _hasFeedbackVisibleFor(type: string): boolean; _updateShouldShowFeedbackFor(): void; _prioritizeAndFilterFeedback(opts: { validationResult: Validator[] }): Validator[];