fix: better support when options change dynamically
This commit is contained in:
parent
7e6c91f335
commit
159d6839c8
4 changed files with 87 additions and 7 deletions
5
.changeset/beige-students-vanish.md
Normal file
5
.changeset/beige-students-vanish.md
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@lion/combobox': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Better support when options change dynamically
|
||||||
|
|
@ -266,7 +266,7 @@ function fetchMyData(val) {
|
||||||
if (rejectPrev) {
|
if (rejectPrev) {
|
||||||
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
rejectPrev = reject;
|
rejectPrev = reject;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,16 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.__prevCboxValue = '';
|
this.__prevCboxValue = '';
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.__hadUserIntendsInlineAutoFill = false;
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.__listboxContentChanged = false;
|
||||||
|
|
||||||
/** @type {EventListener}
|
/** @type {EventListener}
|
||||||
* @private
|
* @private
|
||||||
|
|
@ -386,6 +396,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
||||||
// Only update list in render cycle
|
// Only update list in render cycle
|
||||||
this._handleAutocompletion();
|
this._handleAutocompletion();
|
||||||
this.__shouldAutocompleteNextUpdate = false;
|
this.__shouldAutocompleteNextUpdate = false;
|
||||||
|
this.__listboxContentChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this._selectionDisplayNode?.onComboboxElementUpdated === 'function') {
|
if (typeof this._selectionDisplayNode?.onComboboxElementUpdated === 'function') {
|
||||||
|
|
@ -479,6 +490,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
||||||
_onListboxContentChanged() {
|
_onListboxContentChanged() {
|
||||||
super._onListboxContentChanged();
|
super._onListboxContentChanged();
|
||||||
this.__shouldAutocompleteNextUpdate = true;
|
this.__shouldAutocompleteNextUpdate = true;
|
||||||
|
this.__listboxContentChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
|
@ -609,7 +621,11 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
||||||
prevValue.length &&
|
prevValue.length &&
|
||||||
curValue.length &&
|
curValue.length &&
|
||||||
prevValue[0].toLowerCase() !== curValue[0].toLowerCase();
|
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 */
|
/* eslint-enable no-param-reassign, class-methods-use-this */
|
||||||
|
|
@ -629,7 +645,11 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
||||||
_handleAutocompletion() {
|
_handleAutocompletion() {
|
||||||
const hasSelection = this._inputNode.value.length !== this._inputNode.selectionStart;
|
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 =
|
const prevValue =
|
||||||
hasSelection || this.__hadSelectionLastAutofill
|
hasSelection || this.__hadSelectionLastAutofill
|
||||||
? this.__prevCboxValueNonSelected
|
? this.__prevCboxValueNonSelected
|
||||||
|
|
@ -734,6 +754,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
||||||
this.__prevCboxValue = this._inputNode.value;
|
this.__prevCboxValue = this._inputNode.value;
|
||||||
this.__hadSelectionLastAutofill =
|
this.__hadSelectionLastAutofill =
|
||||||
this._inputNode.value.length !== this._inputNode.selectionStart;
|
this._inputNode.value.length !== this._inputNode.selectionStart;
|
||||||
|
this.__hadUserIntendsInlineAutoFill = userIntendsInlineAutoFill;
|
||||||
|
|
||||||
// [9]. Reposition overlay
|
// [9]. Reposition overlay
|
||||||
if (this._overlayCtrl && this._overlayCtrl._popper) {
|
if (this._overlayCtrl && this._overlayCtrl._popper) {
|
||||||
|
|
@ -745,11 +766,16 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
__textboxInlineComplete(option = this.formElements[this.activeIndex]) {
|
__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;
|
const prevLen = this._inputNode.value.length;
|
||||||
this._inputNode.value = this._getTextboxValueFromOption(option);
|
this._inputNode.value = newValue;
|
||||||
this._inputNode.selectionStart = prevLen;
|
this._inputNode.selectionStart = prevLen;
|
||||||
this._inputNode.selectionEnd = this._inputNode.value.length;
|
this._inputNode.selectionEnd = this._inputNode.value.length;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When this condition is false, an end user will have to manually select a suggested
|
* When this condition is false, an end user will have to manually select a suggested
|
||||||
|
|
|
||||||
|
|
@ -1457,6 +1457,11 @@ describe('lion-combobox', () => {
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fillAllOptions() {
|
||||||
|
this.options = [...listboxData];
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
get combobox() {
|
get combobox() {
|
||||||
return /** @type {LionCombobox} */ (this.shadowRoot?.querySelector('#combobox'));
|
return /** @type {LionCombobox} */ (this.shadowRoot?.querySelector('#combobox'));
|
||||||
}
|
}
|
||||||
|
|
@ -1488,6 +1493,50 @@ describe('lion-combobox', () => {
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(spy).to.have.been.calledTwice;
|
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', () => {
|
describe('Subclassers', () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue