working on dynamic options

This commit is contained in:
konnorrogers
2024-09-25 18:18:04 -04:00
parent f9b3f1e01d
commit 634a796841
3 changed files with 111 additions and 27 deletions

View File

@@ -299,4 +299,16 @@ Remember that custom tags are rendered in a shadow root. To style them, you can
:::warning
Be sure you trust the content you are outputting! Passing unsanitized user input to `getTag()` can result in XSS vulnerabilities.
:::
:::
## Lazy loading options
Lazy loading options is very hard to get right. `<wa-select>` largely follows how a native `<select>` works.
Here are the following conditions:
- If a `<wa-select>` is created without any options, but is given a `value` attribute, its `value` will be `""`, and then when options are added, if any of the options have a value equal to the `<wa-select>` value, the value of the `<wa-select>` will be that of the option.
- If a `<wa-select>` with an initial value has multiple values, but only some of the options are present, it will only respect the loaded options, and if a selected option is loaded in later, *AND* the value of the select has not changed via user interaction, it will add the selected option.
This can be hard to conceptualize, so heres a fairly large example showing how lazy loaded options work with `<wa-select>`

View File

@@ -614,6 +614,88 @@ describe('<wa-select>', () => {
expect(tag.hasAttribute('pill')).to.be.true;
});
describe.only("With lazily loaded options", () => {
describe("With no existing options", () => {
it("Should wait to select the option when the option exists for multiple select", async () => {
const form = await fixture<HTMLFormElement>(html`<form><wa-select value="option-1"></wa-select></form>`)
const el = form.querySelector<WaSelect>("wa-select")!
expect(el.value).to.equal("")
const option = document.createElement("wa-option")
option.value = "option-1"
option.innerText = "Option 1"
el.append(option)
await el.updateComplete
expect(el.value).to.equal("option-1")
})
it("Should wait to select the option when the option exists for multiple select", async () => {
const form = await fixture<HTMLFormElement>(html`<form><wa-select value="option-1" multiple></wa-select></form>`)
const el = form.querySelector<WaSelect>("wa-select")!
expect(el.value).to.equal("")
const option = document.createElement("wa-option")
option.value = "option-1"
option.innerText = "Option 1"
el.append(option)
await el.updateComplete
expect(el.value).to.equal(["option-1"])
})
})
describe("With existing options", () => {
it("Should not select the option if options already exist for single select", async () => {
const form = await fixture<HTMLFormElement>(html`
<form>
<wa-select value="option-1">
<wa-option value="bar">Bar</wa-option>
<wa-option value="baz">Baz</wa-option>
</wa-select>
</form>`
)
const el = form.querySelector<WaSelect>("wa-select")!
expect(el.value).to.equal("")
const option = document.createElement("wa-option")
option.value = "option-1"
option.innerText = "Option 1"
el.append(option)
await aTimeout(10)
await el.updateComplete
expect(el.value).to.equal("option-1")
})
it("Should not select the option if options already exists for multiple select", async () => {
const form = await fixture<HTMLFormElement>(html`
<form>
<wa-select value="option-1" multiple>
<wa-option value="bar">Bar</wa-option>
<wa-option value="baz">Baz</wa-option>
</wa-select>
</form>`
)
const el = form.querySelector<WaSelect>("wa-select")!
expect(el.value).to.equal("")
const option = document.createElement("wa-option")
option.value = "option-1"
option.innerText = "Option 1"
el.append(option)
await aTimeout(10)
await el.updateComplete
expect(el.value).to.equal([""])
})
})
})
});
}
});

View File

@@ -183,11 +183,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
@property({ attribute: false })
set value(val: string | string[] | null) {
if (this._value === val) {
return;
}
this.valueHasChanged = true;
this._value = val;
}
@@ -290,11 +285,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
connectedCallback() {
super.connectedCallback();
this.updateComplete.then(() => {
if (!this.hasInteracted) {
this.value = this.defaultValue;
}
});
// Because this is a form control, it shouldn't be opened initially
this.open = false;
}
@@ -545,6 +535,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
const oldValue = this.value;
if (option && !option.disabled) {
this.valueHasChanged = true
if (this.multiple) {
this.toggleOptionSelection(option);
} else {
@@ -570,20 +561,21 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
}
private handleDefaultSlotChange() {
if (!customElements.get("wa-option")) {
customElements.whenDefined('wa-option').then(() => this.handleDefaultSlotChange());
return
}
const allOptions = this.getAllOptions();
const value = Array.isArray(this.value) ? this.value : [this.value];
const val = this.valueHasChanged ? this.value : this.defaultValue
const value = Array.isArray(val) ? val : [val];
const values: string[] = [];
// Check for duplicate values in menu items
if (customElements.get('wa-option')) {
allOptions.forEach(option => values.push(option.value));
allOptions.forEach(option => values.push(option.value));
// Select only the options that match the new value
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
} else {
// Rerun this handler when `<wa-option>` is registered
customElements.whenDefined('wa-option').then(() => this.handleDefaultSlotChange());
}
// Select only the options that match the new value
this.setSelectedOptions(allOptions.filter(el => value.includes(el.value)));
}
private handleTagRemove(event: WaRemoveEvent, option: WaOption) {
@@ -661,13 +653,10 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// This method must be called whenever the selection changes. It will update the selected options cache, the current
// value, and the display value
private selectionChanged() {
// if (!customElements.get('wa-option')) {
// customElements.whenDefined('wa-option').then(() => this.selectionChanged());
// return;
// }
const options = this.getAllOptions()
// Update selected options cache
this.selectedOptions = this.getAllOptions().filter(el => el.selected);
this.selectedOptions = options.filter(el => el.selected);
// Update the value and display label
if (this.multiple) {
@@ -680,8 +669,9 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
this.displayLabel = this.localize.term('numOptionsSelected', this.selectedOptions.length);
}
} else {
this.value = this.selectedOptions[0]?.value ?? '';
this.displayLabel = this.selectedOptions[0]?.getTextLabel?.() ?? '';
const selectedOption = this.selectedOptions[0]
this.value = selectedOption?.value ?? '';
this.displayLabel = selectedOption?.getTextLabel?.() ?? '';
}
// Update validity