From a943dd0ebdcb71b8bdc0a90df9bcdb78267f0735 Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Wed, 10 Jun 2026 19:09:00 -0400 Subject: [PATCH] SEV-392: combobox setOptions() for reactive parent-fed option lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Passing a parent component's reactive array into the nested combobox x-data needs x-effect="setOptions(vpicMakes)" — a plain closure over the parent scope doesn't track Alpine updates. Falls back to config.options for static lists. 18/18 tests pass. Co-Authored-By: Claude Fable 5 --- src/combobox.js | 14 +++++++++++++- src/combobox.test.js | 10 ++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/combobox.js b/src/combobox.js index da0121b..6efe67a 100644 --- a/src/combobox.js +++ b/src/combobox.js @@ -53,6 +53,7 @@ export function comboboxData(config = {}) { isOpen: false, highlighted: -1, placeholder: config.placeholder || '', + _opts: null, // set reactively via setOptions(); see _options getter init() { this.syncQueryFromValue(); @@ -66,8 +67,19 @@ export function comboboxData(config = {}) { } }, + // setOptions lets a parent feed reactive option lists in via x-effect — + // e.g. x-effect="setOptions(vpicMakes)" — which is the reliable way to + // pass a parent component's reactive array into this nested x-data (a + // plain closure over the parent scope would NOT track updates). Falls back + // to config.options when never called (static lists). + setOptions(raw) { + this._opts = normalizeOptions(raw); + // a shrinking list shouldn't leave a stale highlight selected + if (this.highlighted >= this._opts.length) this.highlighted = -1; + }, + get _options() { - return normalizeOptions(config.options); + return this._opts != null ? this._opts : normalizeOptions(config.options); }, syncQueryFromValue() { diff --git a/src/combobox.test.js b/src/combobox.test.js index 2dceb0d..9571557 100644 --- a/src/combobox.test.js +++ b/src/combobox.test.js @@ -54,3 +54,13 @@ test('empty query lists all options (capped) (SEV-392)', () => { c.query = ''; assert.equal(c.filtered.length, 3); }); + +test('setOptions feeds reactive option lists (SEV-392)', () => { + const c = comboboxData({ allowFree: true }); + c.setOptions([]); // async source not loaded yet + assert.equal(c.filtered.length, 0); + c.setOptions(['BMW', 'Tesla']); // makes arrive + assert.equal(c.filtered.length, 2); + c.query = 'tes'; + assert.deepEqual(c.filtered.map((o) => o.value), ['Tesla']); +});