Skip to content

Commit

Permalink
fix: better support when options change dynamically
Browse files Browse the repository at this point in the history
  • Loading branch information
MatthieuLebigre committed Jun 11, 2021
1 parent 7e6c91f commit 159d683
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-students-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lion/combobox': minor
---

Better support when options change dynamically
2 changes: 1 addition & 1 deletion docs/components/inputs/combobox/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
38 changes: 32 additions & 6 deletions packages/combobox/src/LionCombobox.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 */
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -745,10 +766,15 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
* @private
*/
__textboxInlineComplete(option = this.formElements[this.activeIndex]) {
const prevLen = this._inputNode.value.length;
this._inputNode.value = this._getTextboxValueFromOption(option);
this._inputNode.selectionStart = prevLen;
this._inputNode.selectionEnd = this._inputNode.value.length;
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 = newValue;
this._inputNode.selectionStart = prevLen;
this._inputNode.selectionEnd = this._inputNode.value.length;
}
}

/**
Expand Down
49 changes: 49 additions & 0 deletions packages/combobox/test/lion-combobox.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,11 @@ describe('lion-combobox', () => {
this.requestUpdate();
}

fillAllOptions() {
this.options = [...listboxData];
this.requestUpdate();
}

get combobox() {
return /** @type {LionCombobox} */ (this.shadowRoot?.querySelector('#combobox'));
}
Expand Down Expand Up @@ -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', () => {
Expand Down

0 comments on commit 159d683

Please sign in to comment.