From 86f1ba4a526b4907b8903b1b6e2a217bfcee23a4 Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Thu, 25 Mar 2021 00:20:21 +0100 Subject: [PATCH 1/2] chore: docs for model-value-changed meta --- docs/docs/systems/form/model-value.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/systems/form/model-value.md b/docs/docs/systems/form/model-value.md index 4e8d62a94..ae471a5db 100644 --- a/docs/docs/systems/form/model-value.md +++ b/docs/docs/systems/form/model-value.md @@ -32,3 +32,4 @@ For more information about parsing and the Unparseable type, see [Formatting and - `formPath`: an array of FormControls. It contains the path an event follows to go from a 'leaf element' (for instance a lion-input) to a top element (for instance lion-form). An example path could be [lionForm, lionFieldset, lionInput] +- `initialize`: whether this is the first time the event is fired (on first render of the FormControl) From ca15374a12436888fe5ee720753d5989bc496b9f Mon Sep 17 00:00:00 2001 From: Thijs Louisse Date: Thu, 25 Mar 2021 09:59:50 +0100 Subject: [PATCH 2/2] fix: 'isTriggeredByUser' support for listbox (descendants) --- .changeset/wise-moles-teach.md | 5 +++ .../test/model-value-consistency.test.js | 33 +++++++++++++++---- packages/listbox/src/ListboxMixin.js | 17 +++++++++- 3 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 .changeset/wise-moles-teach.md diff --git a/.changeset/wise-moles-teach.md b/.changeset/wise-moles-teach.md new file mode 100644 index 000000000..cf9f61713 --- /dev/null +++ b/.changeset/wise-moles-teach.md @@ -0,0 +1,5 @@ +--- +'@lion/listbox': patch +--- + +listbox (descendants) add `isTriggeredByUser` meta data to `model-value-changed` event on keyboard interaction diff --git a/packages/form-integrations/test/model-value-consistency.test.js b/packages/form-integrations/test/model-value-consistency.test.js index f8a39cee8..2cfeb0d05 100644 --- a/packages/form-integrations/test/model-value-consistency.test.js +++ b/packages/form-integrations/test/model-value-consistency.test.js @@ -396,10 +396,11 @@ describe('detail.isTriggeredByUser', () => { } /** - * @param {FormControl & {value: string}} el + * @param {FormControl & {value: string;}} el * @param {string} newViewValue + * @param {string | undefined} [triggerType] */ - function mimicUserInput(el, newViewValue) { + function mimicUserInput(el, newViewValue, triggerType) { const type = detectType(el); let userInputEv; if (type === 'RegularField') { @@ -409,7 +410,14 @@ describe('detail.isTriggeredByUser', () => { } else if (type === 'ChoiceField') { el._inputNode.dispatchEvent(new Event('change', { bubbles: true })); } else if (type === 'OptionChoiceField') { - el.dispatchEvent(new Event('click', { bubbles: true })); + if (!triggerType) { + el.dispatchEvent(new Event('click', { bubbles: true })); + } else if (triggerType === 'keypress') { + el.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })); + el.dispatchEvent(new KeyboardEvent('keyup', { key: 'ArrowDown', bubbles: true })); + el.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true })); + el.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', bubbles: true })); + } } } @@ -459,9 +467,12 @@ describe('detail.isTriggeredByUser', () => { } /** - * @param {FormControl & {value: string}} formControl + * @param {FormControl & {value: string;}} formControl + * @param {boolean | undefined} [testKeyboardBehavior] */ - function expectCorrectEventMetaChoiceField(formControl) { + function expectCorrectEventMetaChoiceField(formControl, testKeyboardBehavior) { + const type = detectType(formControl); + resetChoiceFieldToForceRepropagation(formControl); mimicUserInput(formControl, 'userValue'); expect(spy.firstCall.args[0].detail.isTriggeredByUser).to.be.true; @@ -474,6 +485,16 @@ describe('detail.isTriggeredByUser', () => { // eslint-disable-next-line no-param-reassign formControl.modelValue = { value: 'programmaticValue', checked: false }; expect(spy.secondCall.args[0].detail.isTriggeredByUser).to.be.false; + + if (type === 'OptionChoiceField' && testKeyboardBehavior) { + resetChoiceFieldToForceRepropagation(formControl); + mimicUserInput(formControl, 'userValue', 'keypress'); + try { + expect(spy.firstCall.args[0].detail.isTriggeredByUser).to.be.true; + } catch (e) { + console.log(formControl); + } + } } // 1. Derive the type of field we're dealing with @@ -494,7 +515,7 @@ describe('detail.isTriggeredByUser', () => { ); el.appendChild(childrenEls); await el.registrationComplete; - expectCorrectEventMetaChoiceField(el.formElements[0]); + expectCorrectEventMetaChoiceField(el.formElements[0], true); } else if (type === 'FormOrFieldset') { const childrenEls = await fixture( html`
`, diff --git a/packages/listbox/src/ListboxMixin.js b/packages/listbox/src/ListboxMixin.js index 3450af3dd..61972bdc2 100644 --- a/packages/listbox/src/ListboxMixin.js +++ b/packages/listbox/src/ListboxMixin.js @@ -486,6 +486,13 @@ const ListboxMixinImplementation = superclass => return; } + this.__isHandlingUserInput = true; + setTimeout(() => { + // Since we can't control when subclasses are done handling keyboard input, we + // schedule a timeout to reset __isHandlingUserInput + this.__isHandlingUserInput = false; + }); + const { key } = ev; switch (key) { @@ -586,6 +593,14 @@ const ListboxMixinImplementation = superclass => if (this.disabled) { return; } + + this.__isHandlingUserInput = true; + setTimeout(() => { + // Since we can't control when subclasses are done handling keyboard input, we + // schedule a timeout to reset __isHandlingUserInput + this.__isHandlingUserInput = false; + }); + const { key } = ev; // eslint-disable-next-line default-case switch (key) { @@ -702,7 +717,7 @@ const ListboxMixinImplementation = superclass => new CustomEvent('model-value-changed', { detail: /** @type {ModelValueEventDetails} */ ({ formPath: ev.detail.formPath, - isTriggeredByUser: ev.detail.isTriggeredByUser, + isTriggeredByUser: ev.detail.isTriggeredByUser || this.__isHandlingUserInput, element: ev.target, }), }),