diff --git a/.changeset/hungry-files-lay.md b/.changeset/hungry-files-lay.md new file mode 100644 index 000000000..c720f0d2f --- /dev/null +++ b/.changeset/hungry-files-lay.md @@ -0,0 +1,5 @@ +--- +'@lion/form-core': patch +--- + +Properly update formElements when the name attribute changes, in order to get an updated serializedValue. diff --git a/packages/form-core/src/FormControlMixin.js b/packages/form-core/src/FormControlMixin.js index b764a22cc..2488c650c 100644 --- a/packages/form-core/src/FormControlMixin.js +++ b/packages/form-core/src/FormControlMixin.js @@ -23,8 +23,8 @@ function uuid(prefix) { * @typedef {import('@lion/core').nothing} nothing * @typedef {import('@lion/core/types/SlotMixinTypes').SlotsMap} SlotsMap * @typedef {import('../types/FormControlMixinTypes.js').FormControlMixin} FormControlMixin - * @type {FormControlMixin} * @param {import('@open-wc/dedupe-mixin').Constructor} superclass + * @type {FormControlMixin} */ const FormControlMixinImplementation = superclass => // eslint-disable-next-line no-shadow, no-unused-vars @@ -144,8 +144,7 @@ const FormControlMixinImplementation = superclass => * @return {string} */ get fieldName() { - // @ts-expect-error - return this.__fieldName || this.label || this.name; // FIXME: when LionField is typed we can inherit this prop + return this.__fieldName || this.label || this.name || ''; } /** @@ -195,6 +194,8 @@ const FormControlMixinImplementation = superclass => constructor() { super(); + /** @type {string | undefined} */ + this.name = undefined; /** @type {string} */ this._inputId = uuid(this.localName); /** @type {HTMLElement[]} */ @@ -257,6 +258,15 @@ const FormControlMixinImplementation = superclass => if (changedProperties.has('helpText') && this._helpTextNode) { this._helpTextNode.textContent = this.helpText; } + + if (changedProperties.has('name')) { + this.dispatchEvent( + new CustomEvent('form-element-name-changed', { + detail: { oldName: changedProperties.get('name'), newName: this.name }, + bubbles: true, + }), + ); + } } _triggerInitialModelValueChangedEvent() { diff --git a/packages/form-core/src/choice-group/ChoiceGroupMixin.js b/packages/form-core/src/choice-group/ChoiceGroupMixin.js index 0d4fe415a..2212a0676 100644 --- a/packages/form-core/src/choice-group/ChoiceGroupMixin.js +++ b/packages/form-core/src/choice-group/ChoiceGroupMixin.js @@ -15,7 +15,6 @@ import { InteractionStateMixin } from '../InteractionStateMixin.js'; * @param {import('@open-wc/dedupe-mixin').Constructor} superclass */ const ChoiceGroupMixinImplementation = superclass => - // @ts-expect-error class ChoiceGroupMixin extends FormRegistrarMixin(InteractionStateMixin(superclass)) { static get properties() { return { @@ -121,6 +120,7 @@ const ChoiceGroupMixinImplementation = superclass => constructor() { super(); this.multipleChoice = false; + /** @type {'child'|'choice-group'|'fieldset'} */ this._repropagationRole = 'choice-group'; // configures event propagation logic of FormControlMixin this.__isInitialModelValue = true; diff --git a/packages/form-core/src/registration/FormRegistrarMixin.js b/packages/form-core/src/registration/FormRegistrarMixin.js index c28f03aad..74d7717fb 100644 --- a/packages/form-core/src/registration/FormRegistrarMixin.js +++ b/packages/form-core/src/registration/FormRegistrarMixin.js @@ -50,11 +50,16 @@ const FormRegistrarMixinImplementation = superclass => this._isFormOrFieldset = false; this._onRequestToAddFormElement = this._onRequestToAddFormElement.bind(this); + this._onRequestToChangeFormElementName = this._onRequestToChangeFormElementName.bind(this); this.addEventListener( 'form-element-register', /** @type {EventListenerOrEventListenerObject} */ (this._onRequestToAddFormElement), ); + this.addEventListener( + 'form-element-name-changed', + /** @type {EventListenerOrEventListenerObject} */ (this._onRequestToChangeFormElementName), + ); } /** @@ -163,6 +168,17 @@ const FormRegistrarMixinImplementation = superclass => this.addFormElement(child, indexToInsertAt); } + /** + * @param {CustomEvent} ev + */ + _onRequestToChangeFormElementName(ev) { + const element = this.formElements[ev.detail.oldName]; + if (element) { + this.formElements[ev.detail.newName] = element; + delete this.formElements[ev.detail.oldName]; + } + } + /** * @param {CustomEvent} ev */ diff --git a/packages/form-core/test-suites/ValidateMixinFeedbackPart.suite.js b/packages/form-core/test-suites/ValidateMixinFeedbackPart.suite.js index a634a98ed..e4fc39e0b 100644 --- a/packages/form-core/test-suites/ValidateMixinFeedbackPart.suite.js +++ b/packages/form-core/test-suites/ValidateMixinFeedbackPart.suite.js @@ -390,7 +390,7 @@ export function runValidateMixinFeedbackPart() { params: 4, modelValue: 'cat', formControl: el, - fieldName: undefined, + fieldName: '', type: 'x', name: 'MinLength', }); @@ -414,7 +414,7 @@ export function runValidateMixinFeedbackPart() { params: 4, modelValue: 'cat', formControl: el, - fieldName: undefined, + fieldName: '', type: 'error', name: 'MinLength', }); diff --git a/packages/form-core/test-suites/form-group/FormGroupMixin.suite.js b/packages/form-core/test-suites/form-group/FormGroupMixin.suite.js index dbe8451cc..d2a681723 100644 --- a/packages/form-core/test-suites/form-group/FormGroupMixin.suite.js +++ b/packages/form-core/test-suites/form-group/FormGroupMixin.suite.js @@ -18,26 +18,27 @@ import { FormGroupMixin } from '../../src/form-group/FormGroupMixin.js'; * @param {{ tagString?: string, childTagString?:string }} [cfg] */ export function runFormGroupMixinSuite(cfg = {}) { - const FormChild = class extends LionField { + class FormChild extends LionField { get slots() { return { ...super.slots, input: () => document.createElement('input'), }; } - }; + } const childTagString = cfg.childTagString || defineCE(FormChild); - // @ts-expect-error - const FormGroup = class extends FormGroupMixin(LitElement) { + // @ts-expect-error base constructors same return type + class FormGroup extends FormGroupMixin(LitElement) { constructor() { super(); /** @override from FormRegistrarMixin */ this._isFormOrFieldset = true; + /** @type {'child'|'choice-group'|'fieldset'} */ this._repropagationRole = 'fieldset'; // configures FormControlMixin } - }; + } const tagString = cfg.tagString || defineCE(FormGroup); const tag = unsafeStatic(tagString); @@ -804,6 +805,19 @@ export function runFormGroupMixinSuite(cfg = {}) { }, }); }); + + it('updates the formElements keys when a name attribute changes', async () => { + const fieldset = /** @type {FormGroup} */ (await fixture(html` + <${tag}> + <${childTag} name="foo" .modelValue=${'qux'}> + + `)); + expect(fieldset.serializedValue.foo).to.equal('qux'); + fieldset.formElements[0].name = 'bar'; + await fieldset.updateComplete; + await fieldset.formElements[0].updateComplete; + expect(fieldset.serializedValue.bar).to.equal('qux'); + }); }); describe('Reset', () => { diff --git a/packages/form-core/types/FormControlMixinTypes.d.ts b/packages/form-core/types/FormControlMixinTypes.d.ts index 6af5a4e5f..0a6095597 100644 --- a/packages/form-core/types/FormControlMixinTypes.d.ts +++ b/packages/form-core/types/FormControlMixinTypes.d.ts @@ -10,7 +10,7 @@ declare interface HTMLElementWithValue extends HTMLElement { value: string; } -export class FormControlHost { +export declare class FormControlHost { static get styles(): CSSResult | CSSResult[]; /** * A Boolean attribute which, if present, indicates that the user should not be able to edit @@ -129,7 +129,7 @@ export declare function FormControlImplementation & - FormControlHost & + typeof FormControlHost & Constructor & typeof FormRegisteringHost & Constructor & diff --git a/packages/form-core/types/form-group/FormGroupMixinTypes.d.ts b/packages/form-core/types/form-group/FormGroupMixinTypes.d.ts index f27a6ba55..7afaa1c4d 100644 --- a/packages/form-core/types/form-group/FormGroupMixinTypes.d.ts +++ b/packages/form-core/types/form-group/FormGroupMixinTypes.d.ts @@ -15,7 +15,7 @@ export declare class FormGroupHost { touched: boolean; dirty: boolean; submitted: boolean; - serializedValue: string; + serializedValue: { [key: string]: any }; modelValue: { [x: string]: any }; formattedValue: string; children: Array;