fix: better support when options change dynamically

This commit is contained in:
Matthieu 2021-06-11 12:44:54 -04:00
parent 7e6c91f335
commit 159d6839c8
4 changed files with 87 additions and 7 deletions

View file

@ -0,0 +1,5 @@
---
'@lion/combobox': minor
---
Better support when options change dynamically

View file

@ -266,7 +266,7 @@ function fetchMyData(val) {
if (rejectPrev) {
rejectPrev();
}
const results = comboboxData.filter(item => item.toLowerCase().includes(val.toLowerCase()));
const results = listboxData.filter(item => item.toLowerCase().includes(val.toLowerCase()));
return new Promise((resolve, reject) => {
rejectPrev = reject;
setTimeout(() => {

View file

@ -286,6 +286,16 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
* @private
*/
this.__prevCboxValue = '';
/**
* @type {boolean}
* @private
*/
this.__hadUserIntendsInlineAutoFill = false;
/**
* @type {boolean}
* @private
*/
this.__listboxContentChanged = false;
/** @type {EventListener}
* @private
@ -386,6 +396,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
// Only update list in render cycle
this._handleAutocompletion();
this.__shouldAutocompleteNextUpdate = false;
this.__listboxContentChanged = false;
}
if (typeof this._selectionDisplayNode?.onComboboxElementUpdated === 'function') {
@ -479,6 +490,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
_onListboxContentChanged() {
super._onListboxContentChanged();
this.__shouldAutocompleteNextUpdate = true;
this.__listboxContentChanged = true;
}
// eslint-disable-next-line no-unused-vars
@ -609,7 +621,11 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
prevValue.length &&
curValue.length &&
prevValue[0].toLowerCase() !== curValue[0].toLowerCase();
return userIsAddingChars || userStartsNewWord;
return (
userIsAddingChars ||
userStartsNewWord ||
(this.__listboxContentChanged && this.__hadUserIntendsInlineAutoFill)
);
}
/* eslint-enable no-param-reassign, class-methods-use-this */
@ -629,7 +645,11 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
_handleAutocompletion() {
const hasSelection = this._inputNode.value.length !== this._inputNode.selectionStart;
const curValue = this._inputNode.value;
const inputValue = this._inputNode.value;
const inputSelectionStart = this._inputNode.selectionStart;
const curValue =
hasSelection && inputSelectionStart ? inputValue.slice(0, inputSelectionStart) : inputValue;
const prevValue =
hasSelection || this.__hadSelectionLastAutofill
? this.__prevCboxValueNonSelected
@ -734,6 +754,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
this.__prevCboxValue = this._inputNode.value;
this.__hadSelectionLastAutofill =
this._inputNode.value.length !== this._inputNode.selectionStart;
this.__hadUserIntendsInlineAutoFill = userIntendsInlineAutoFill;
// [9]. Reposition overlay
if (this._overlayCtrl && this._overlayCtrl._popper) {
@ -745,11 +766,16 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
* @private
*/
__textboxInlineComplete(option = this.formElements[this.activeIndex]) {
const newValue = this._getTextboxValueFromOption(option);
// Make sure that we don't lose inputNode.selectionStart and inputNode.selectionEnd
if (this._inputNode.value !== newValue) {
const prevLen = this._inputNode.value.length;
this._inputNode.value = this._getTextboxValueFromOption(option);
this._inputNode.value = newValue;
this._inputNode.selectionStart = prevLen;
this._inputNode.selectionEnd = this._inputNode.value.length;
}
}
/**
* When this condition is false, an end user will have to manually select a suggested

View file

@ -1457,6 +1457,11 @@ describe('lion-combobox', () => {
this.requestUpdate();
}
fillAllOptions() {
this.options = [...listboxData];
this.requestUpdate();
}
get combobox() {
return /** @type {LionCombobox} */ (this.shadowRoot?.querySelector('#combobox'));
}
@ -1488,6 +1493,50 @@ describe('lion-combobox', () => {
await el.updateComplete;
expect(spy).to.have.been.calledTwice;
});
it('should handle dynamic options', async () => {
// Arrange
const el = /** @type {MyEl} */ (await fixture(html`<${wrappingTag}></${wrappingTag}>`));
await el.combobox.registrationComplete;
// Act (start typing)
mimicUserTyping(el.combobox, 'l');
// simulate fetching data from server
el.clearOptions();
await el.updateComplete;
await el.updateComplete;
el.fillAllOptions();
await el.updateComplete;
await el.updateComplete;
// Assert
const { _inputNode } = getComboboxMembers(el.combobox);
expect(_inputNode.value).to.equal('lorem');
expect(_inputNode.selectionStart).to.equal(1);
expect(_inputNode.selectionEnd).to.equal(_inputNode.value.length);
expect(getFilteredOptionValues(el.combobox)).to.eql(['lorem', 'dolor']);
// Act (continue typing)
mimicUserTyping(el.combobox, 'lo');
// simulate fetching data from server
el.clearOptions();
await el.updateComplete;
await el.updateComplete;
el.fillAllOptions();
await el.updateComplete;
await el.updateComplete;
// Assert
expect(_inputNode.value).to.equal('lorem');
expect(_inputNode.selectionStart).to.equal(2);
expect(_inputNode.selectionEnd).to.equal(_inputNode.value.length);
expect(getFilteredOptionValues(el.combobox)).to.eql(['lorem', 'dolor']);
// We don't autocomplete when characters are removed
mimicUserTyping(el.combobox, 'l'); // The user pressed backspace (number of chars decreased)
expect(_inputNode.value).to.equal('l');
expect(_inputNode.selectionStart).to.equal(_inputNode.value.length);
});
});
describe('Subclassers', () => {