diff --git a/dev/vscode-single-select/combobox-mode/custom-controller.html b/dev/vscode-single-select/combobox-mode/custom-controller.html
new file mode 100644
index 000000000..e937bc53e
--- /dev/null
+++ b/dev/vscode-single-select/combobox-mode/custom-controller.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ VSCode Elements — Single Select — Combobox — Custom Controller
+
+
+
+
+
+
+
+
+
+ Lorem
+ Ipsum
+ Dolor
+ Sit (disabled)
+ Amet
+
+
+
+
+
+ Lorem
+ Ipsum
+ Dolor
+ Sit (disabled)
+ Amet
+
+
+
+
+
+
+
diff --git a/dev/vscode-single-select/combobox-mode/npm-search-controller.html b/dev/vscode-single-select/combobox-mode/npm-search-controller.html
new file mode 100644
index 000000000..91f7634f9
--- /dev/null
+++ b/dev/vscode-single-select/combobox-mode/npm-search-controller.html
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+ VSCode Elements — Single Select — Combobox — npm Search (Custom
+ Controller)
+
+
+
+
+
+
+
+
+
+ Type at least 2 characters…
+
+
+
+
+
+
+
diff --git a/dev/vscode-single-select/select-mode/custom-controller.html b/dev/vscode-single-select/select-mode/custom-controller.html
new file mode 100644
index 000000000..07f038e48
--- /dev/null
+++ b/dev/vscode-single-select/select-mode/custom-controller.html
@@ -0,0 +1,84 @@
+
+
+
+
+
+ VSCode Elements — Single Select — Custom Controller
+
+
+
+
+
+
+
+
+ Lorem
+ Ipsum
+ Dolor
+ Sit (disabled)
+ Amet
+
+
+
+
+
+ Lorem
+ Ipsum
+ Dolor
+ Sit (disabled)
+ Amet
+
+
+
+
+
+
+
diff --git a/src/includes/vscode-select/vscode-select-base.ts b/src/includes/vscode-select/vscode-select-base.ts
index a4ac6d423..faae9c9be 100644
--- a/src/includes/vscode-select/vscode-select-base.ts
+++ b/src/includes/vscode-select/vscode-select-base.ts
@@ -1,4 +1,5 @@
import {html, render, nothing, TemplateResult, PropertyValues} from 'lit';
+import type {ReactiveController} from 'lit';
import {property, query, queryAssignedElements, state} from 'lit/decorators.js';
import {classMap} from 'lit/directives/class-map.js';
import {ifDefined} from 'lit/directives/if-defined.js';
@@ -156,7 +157,59 @@ export class VscodeSelectBase extends VscElement {
@query('.dropdown', true)
private _dropdownEl!: HTMLDivElement;
- protected _opts = new OptionListController(this);
+ protected _opts: OptionListController = this.createOptionListController();
+
+ /**
+ * Factory method to create the option list controller. Subclasses can override
+ * this to provide a custom controller implementation that maintains the same API.
+ */
+ protected createOptionListController(): OptionListController {
+ return new OptionListController(this);
+ }
+
+ /**
+ * Replace the current OptionListController with a new instance. Detaches the
+ * previous controller and optionally transfers common state to the new one.
+ */
+ protected replaceOptionListController(
+ next: OptionListController,
+ {transferState = true}: {transferState?: boolean} = {}
+ ) {
+ const prev = this._opts;
+
+ if (prev === next) return;
+
+ // Detach the previous controller to avoid duplicate lifecycle hooks
+ try {
+ this.removeController(prev as ReactiveController);
+ } catch {
+ // Ignore if prev was not registered as a controller
+ }
+
+ if (transferState) {
+ // Best-effort transfer of options and key fields
+ const prevOptions = this.options;
+ try {
+ next.clear();
+ next.populate(prevOptions);
+ } catch {
+ // Ignore if custom controller uses different API
+ }
+
+ try {
+ // Propagate common fields used by the component logic
+ next.comboboxMode = prev.comboboxMode ?? next.comboboxMode;
+ next.filterMethod = prev.filterMethod ?? next.filterMethod;
+ next.filterPattern = prev.filterPattern ?? next.filterPattern;
+ next.activeIndex = prev.activeIndex ?? next.activeIndex;
+ } catch {
+ // Ignore if setters are guarded
+ }
+ }
+
+ this._opts = next;
+ this.requestUpdate();
+ }
//#region lifecycle callbacks
@@ -534,24 +587,22 @@ export class VscodeSelectBase extends VscElement {
event.preventDefault();
}
- if (event.key === 'Enter') {
- this._onEnterKeyDown(event);
- }
-
- if (event.key === ' ') {
- this._onSpaceKeyDown();
- }
-
- if (event.key === 'Escape') {
- this._onEscapeKeyDown();
- }
-
- if (event.key === 'ArrowUp') {
- this._onArrowUpKeyDown();
- }
-
- if (event.key === 'ArrowDown') {
- this._onArrowDownKeyDown();
+ switch (event.key) {
+ case 'Enter':
+ this._onEnterKeyDown(event);
+ break;
+ case ' ':
+ this._onSpaceKeyDown();
+ break;
+ case 'Escape':
+ this._onEscapeKeyDown();
+ break;
+ case 'ArrowUp':
+ this._onArrowUpKeyDown();
+ break;
+ case 'ArrowDown':
+ this._onArrowDownKeyDown();
+ break;
}
};
diff --git a/src/vscode-single-select/vscode-single-select.test.ts b/src/vscode-single-select/vscode-single-select.test.ts
index 5818e1f72..e751ff226 100644
--- a/src/vscode-single-select/vscode-single-select.test.ts
+++ b/src/vscode-single-select/vscode-single-select.test.ts
@@ -182,9 +182,11 @@ describe('vscode-single-select', () => {
await el.updateComplete;
expect(face).lightDom.to.eq(`
+
Ipsum
+
`);
diff --git a/src/vscode-single-select/vscode-single-select.ts b/src/vscode-single-select/vscode-single-select.ts
index 39a7f5dac..af8e223f2 100644
--- a/src/vscode-single-select/vscode-single-select.ts
+++ b/src/vscode-single-select/vscode-single-select.ts
@@ -353,7 +353,10 @@ export class VscodeSingleSelect
role="combobox"
tabindex="0"
>
- ${label} ${chevronDownIcon}
+
+ ${label}
+
+ ${chevronDownIcon}
`;
}
@@ -374,6 +377,7 @@ export class VscodeSingleSelect
return html`
+
+