Flux version
v2.19.0, still present in v2.20.0
Livewire version
v4.4.4
Tailwind version
v4.3.3
Browser and Operating System
Chromium 153 and WebKit 26.6 (Playwright) on Linux
What is the problem?
Constructing a detached clone of <ui-selected> throws an uncaught error in both Chromium and WebKit.
UISelected.boot() runs during custom-element construction:
this.picker = this.closest("ui-select,ui-pillbox");
this.multiple = this.picker.hasAttribute("multiple");
cloneNode(true) upgrades the cloned custom element while it is still detached, so closest() returns null and hasAttribute() throws.
This is a follow-up to #2605, which was closed because the application-level reproduction could not be reproduced. The reproduction below needs no Laravel rendering, Livewire timing or application code. It runs the JavaScript shipped in the Flux package directly.
The same unguarded code is in every v2.20.0 runtime bundle: flux/dist/flux.min.js, flux/dist/flux-lite.min.js, flux-pro/dist/flux.js and flux-pro/dist/flux.module.js.
Fastest manual reproduction
Render any page containing a listbox select:
<flux:select variant="listbox">
<flux:select.option value="a">Option A</flux:select.option>
</flux:select>
Open DevTools and run:
document.querySelector('ui-selected').cloneNode(true)
Chromium:
Uncaught TypeError: Cannot read properties of null (reading 'hasAttribute')
WebKit:
TypeError: null is not an object (evaluating 'this.picker.hasAttribute')
Standalone automated reproduction
From the root of a Laravel application with Flux installed:
npm install --no-save playwright
npx playwright install chromium webkit
node flux-selected-repro.mjs
flux-selected-repro.mjs:
import { resolve } from 'node:path';
import { chromium, webkit } from 'playwright';
const fluxRuntime = resolve('vendor/livewire/flux/dist/flux.min.js');
for (const browserType of [chromium, webkit]) {
const browser = await browserType.launch({ headless: true });
const page = await browser.newPage();
await page.setContent('<ui-select><ui-selected></ui-selected></ui-select>');
await page.addScriptTag({ path: fluxRuntime });
await page.waitForFunction(() => customElements.get('ui-selected'));
const pageError = page.waitForEvent('pageerror');
const clone = await page.evaluate(() => {
const clone = document.querySelector('ui-selected').cloneNode(true);
return {
detached: clone.parentElement === null,
picker: clone.picker ?? null,
};
});
const error = await pageError;
console.log({
browser: browserType.name(),
browserVersion: browser.version(),
clone,
error: error.message,
});
await browser.close();
}
Output:
{
browser: 'chromium',
browserVersion: '153.0.8010.12',
clone: { detached: true, picker: null },
error: "Cannot read properties of null (reading 'hasAttribute')"
}
{
browser: 'webkit',
browserVersion: '26.6',
clone: { detached: true, picker: null },
error: "null is not an object (evaluating 'this.picker.hasAttribute')"
}
How it shows up in a real app
We hit this with two dependent listbox selects, where changing the first (wire:model.live) re-renders the second. Livewire's DOM update clones a subtree containing <ui-selected>, WebKit reports the error above, and the dependent select is left unusable.
Suggested fix
Tolerate detached construction in boot(), and resolve the picker again in mount() once the element is connected:
boot() {
this.picker = this.closest("ui-select,ui-pillbox");
this.multiple = this.picker?.hasAttribute("multiple") ?? false;
}
mount() {
this.picker ??= this.closest("ui-select,ui-pillbox");
if (!this.picker) return;
this.multiple = this.picker.hasAttribute("multiple");
// Existing mount logic...
}
We have been running this patch against the minified runtime. With it, the dependent select works, no error is reported, and connected single- and multiple-select clones pick up the correct owning picker and multiple state in Chromium and WebKit.
How do you expect it to work?
<ui-selected> should survive the detached construction that native cloneNode(true) performs without an uncaught exception. Once the clone is connected under <ui-select> or <ui-pillbox>, mount() should resolve that owner and initialize single- or multiple-selection state correctly.
Please confirm
Flux version
v2.19.0, still present in v2.20.0
Livewire version
v4.4.4
Tailwind version
v4.3.3
Browser and Operating System
Chromium 153 and WebKit 26.6 (Playwright) on Linux
What is the problem?
Constructing a detached clone of
<ui-selected>throws an uncaught error in both Chromium and WebKit.UISelected.boot()runs during custom-element construction:cloneNode(true)upgrades the cloned custom element while it is still detached, soclosest()returnsnullandhasAttribute()throws.This is a follow-up to #2605, which was closed because the application-level reproduction could not be reproduced. The reproduction below needs no Laravel rendering, Livewire timing or application code. It runs the JavaScript shipped in the Flux package directly.
The same unguarded code is in every v2.20.0 runtime bundle:
flux/dist/flux.min.js,flux/dist/flux-lite.min.js,flux-pro/dist/flux.jsandflux-pro/dist/flux.module.js.Fastest manual reproduction
Render any page containing a listbox select:
Open DevTools and run:
Chromium:
WebKit:
Standalone automated reproduction
From the root of a Laravel application with Flux installed:
flux-selected-repro.mjs:Output:
How it shows up in a real app
We hit this with two dependent listbox selects, where changing the first (
wire:model.live) re-renders the second. Livewire's DOM update clones a subtree containing<ui-selected>, WebKit reports the error above, and the dependent select is left unusable.Suggested fix
Tolerate detached construction in
boot(), and resolve the picker again inmount()once the element is connected:We have been running this patch against the minified runtime. With it, the dependent select works, no error is reported, and connected single- and multiple-select clones pick up the correct owning picker and
multiplestate in Chromium and WebKit.How do you expect it to work?
<ui-selected>should survive the detached construction that nativecloneNode(true)performs without an uncaught exception. Once the clone is connected under<ui-select>or<ui-pillbox>,mount()should resolve that owner and initialize single- or multiple-selection state correctly.Please confirm