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`
+ +