Repository: symfony/ux
Pull request #2423 (2024-12-04 11:04:18)

Apply natural import order for JS to synchronise the code base with Biome and its configuration
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| License       | MIT

Hello,

`Biome` sounds really good :scientist: , it seems to be a really cool tool, guys ! 

It applies a rule for ordering import "naturally" (https://biomejs.dev/analyzer/import-sorting/) so, I apply the command to check and fix

What about apply it "as is" ? Another alternative will be to disabled the rule. What do you think about that detail ?

Maybe, an import needs of a package just above, from another package import, this is what I propose also to disable the rule, WYT ?

Thanks ^^


github-actions[bot]: <h1>📊 Packages dist files size difference</h1>

Thanks for the PR! Here is the difference in size of the packages dist files between the base branch and the PR.
Please review the changes and make sure they are expected.

<table>
    <thead><tr><th>File</th><th>Before (Size / Gzip)</th><th>After (Size / Gzip)</th></tr></thead>
    <tbody><tr><td colspan="3"><a href="https://github.com/symfony/ux/tree/chore/apply-nartural-import-order/src/Map/src/Bridge/Google/assets/dist"><b>Map (Bridge Google)</b></a></td></tr><tr>
                <td><a href="https://github.com/symfony/ux/blob/chore/apply-nartural-import-order/src/Map/src/Bridge/Google/assets/dist/map_controller.js"><code>map_controller.js</code></a></td>
            <td>
                    <code>10.02 kB</code>
                    / <code>2.22 kB</code> 
                </td><td>
                    <code>6.23 kB</code><sup>-38% 📉</sup>
                    / <code>1.49 kB</code><sup>-33% 📉</sup>
                </td></tr></tbody>
</table>

<!-- Sticky Pull Request Comment -->
chadyred: Hey @Kocal,

 I didn't edit the conf (I play with it on my side :p )
 
 I exposed the fact I discovered the tool, I follow the contributing guideline, and I thinked about the rule to sort import has not been discussed / seen / treated before. So I pointed out the fact if we apply to the code base a global check then fix with actual rules of Biome config, we will apply (like in this PR) a lot of changed. If we keep Biome and its conf as is, ok, the PR is good, and the code base is synchronized with Biome conf...that's it!

Patch:
diff --git a/src/Autocomplete/assets/src/controller.ts b/src/Autocomplete/assets/src/controller.ts
index e9a55123..38b324fc 100644
--- a/src/Autocomplete/assets/src/controller.ts
+++ b/src/Autocomplete/assets/src/controller.ts
@@ -3,10 +3,10 @@ import TomSelect from 'tom-select';
 import type { TPluginHash } from 'tom-select/dist/types/contrib/microplugin';
 import type {
     RecursivePartial,
-    TomSettings,
-    TomTemplates,
     TomLoadCallback,
     TomOption,
+    TomSettings,
+    TomTemplates,
 } from 'tom-select/dist/types/types';
 import type { escape_html } from 'tom-select/dist/types/utils';
 
diff --git a/src/LiveComponent/assets/dist/Component/index.d.ts b/src/LiveComponent/assets/dist/Component/index.d.ts
index b43dae9f..84b3336c 100644
--- a/src/LiveComponent/assets/dist/Component/index.d.ts
+++ b/src/LiveComponent/assets/dist/Component/index.d.ts
@@ -1,9 +1,9 @@
 import type { BackendInterface } from '../Backend/Backend';
-import ValueStore from './ValueStore';
 import type BackendRequest from '../Backend/BackendRequest';
+import BackendResponse from '../Backend/BackendResponse';
 import type { ElementDriver } from './ElementDriver';
+import ValueStore from './ValueStore';
 import type { PluginInterface } from './plugins/PluginInterface';
-import BackendResponse from '../Backend/BackendResponse';
 type MaybePromise<T = void> = T | Promise<T>;
 export type ComponentHooks = {
     connect: (component: Component) => MaybePromise;
diff --git a/src/LiveComponent/assets/dist/Component/plugins/LazyPlugin.d.ts b/src/LiveComponent/assets/dist/Component/plugins/LazyPlugin.d.ts
index 19c7ca89..a69a080a 100644
--- a/src/LiveComponent/assets/dist/Component/plugins/LazyPlugin.d.ts
+++ b/src/LiveComponent/assets/dist/Component/plugins/LazyPlugin.d.ts
@@ -1,5 +1,5 @@
-import type { PluginInterface } from './PluginInterface';
 import type Component from '../index';
+import type { PluginInterface } from './PluginInterface';
 export default class implements PluginInterface {
     private intersectionObserver;
     attachToComponent(component: Component): void;
diff --git a/src/LiveComponent/assets/dist/Component/plugins/LoadingPlugin.d.ts b/src/LiveComponent/assets/dist/Component/plugins/LoadingPlugin.d.ts
index f19f2107..0ecb4a3b 100644
--- a/src/LiveComponent/assets/dist/Component/plugins/LoadingPlugin.d.ts
+++ b/src/LiveComponent/assets/dist/Component/plugins/LoadingPlugin.d.ts
@@ -1,6 +1,6 @@
-import { type Directive } from '../../Directive/directives_parser';
 import type BackendRequest from '../../Backend/BackendRequest';
 import type Component from '../../Component';
+import { type Directive } from '../../Directive/directives_parser';
 import type { PluginInterface } from './PluginInterface';
 interface ElementLoadingDirectives {
     element: HTMLElement | SVGElement;
diff --git a/src/LiveComponent/assets/dist/HookManager.d.ts b/src/LiveComponent/assets/dist/HookManager.d.ts
index ef92e72a..51ad961a 100644
--- a/src/LiveComponent/assets/dist/HookManager.d.ts
+++ b/src/LiveComponent/assets/dist/HookManager.d.ts
@@ -1,4 +1,4 @@
-import type { ComponentHookName, ComponentHookCallback } from './Component';
+import type { ComponentHookCallback, ComponentHookName } from './Component';
 export default class {
     private hooks;
     register<T extends string | ComponentHookName = ComponentHookName>(hookName: T, callback: ComponentHookCallback<T>): void;
diff --git a/src/LiveComponent/assets/dist/dom_utils.d.ts b/src/LiveComponent/assets/dist/dom_utils.d.ts
index 732dc6b0..72dac3db 100644
--- a/src/LiveComponent/assets/dist/dom_utils.d.ts
+++ b/src/LiveComponent/assets/dist/dom_utils.d.ts
@@ -1,6 +1,6 @@
+import type Component from './Component';
 import type ValueStore from './Component/ValueStore';
 import { type Directive } from './Directive/directives_parser';
-import type Component from './Component';
 export declare function getValueFromElement(element: HTMLElement, valueStore: ValueStore): string | string[] | null | boolean;
 export declare function setValueOnElement(element: HTMLElement, value: any): void;
 export declare function getAllModelDirectiveFromElements(element: HTMLElement): Directive[];
diff --git a/src/LiveComponent/assets/dist/live_controller.d.ts b/src/LiveComponent/assets/dist/live_controller.d.ts
index b25a5d06..7e5cff52 100644
--- a/src/LiveComponent/assets/dist/live_controller.d.ts
+++ b/src/LiveComponent/assets/dist/live_controller.d.ts
@@ -1,6 +1,6 @@
 import { Controller } from '@hotwired/stimulus';
-import Component from './Component';
 import { type BackendInterface } from './Backend/Backend';
+import Component from './Component';
 export { Component };
 export { getComponent } from './ComponentRegistry';
 export interface LiveEvent extends CustomEvent {
diff --git a/src/LiveComponent/assets/dist/live_controller.js b/src/LiveComponent/assets/dist/live_controller.js
index 8894e033..375a5b15 100644
--- a/src/LiveComponent/assets/dist/live_controller.js
+++ b/src/LiveComponent/assets/dist/live_controller.js
@@ -1,128 +1,116 @@
 import { Controller } from '@hotwired/stimulus';
 
-function parseDirectives(content) {
-    const directives = [];
-    if (!content) {
-        return directives;
-    }
-    let currentActionName = '';
-    let currentArgumentValue = '';
-    let currentArguments = [];
-    let currentModifiers = [];
-    let state = 'action';
-    const getLastActionName = () => {
-        if (currentActionName) {
-            return currentActionName;
-        }
-        if (directives.length === 0) {
-            throw new Error('Could not find any directives');
-        }
-        return directives[directives.length - 1].action;
-    };
-    const pushInstruction = () => {
-        directives.push({
-            action: currentActionName,
-            args: currentArguments,
-            modifiers: currentModifiers,
-            getString: () => {
-                return content;
-            },
+class BackendRequest {
+    constructor(promise, actions, updateModels) {
+        this.isResolved = false;
+        this.promise = promise;
+        this.promise.then((response) => {
+            this.isResolved = true;
+            return response;
         });
-        currentActionName = '';
-        currentArgumentValue = '';
-        currentArguments = [];
-        currentModifiers = [];
-        state = 'action';
-    };
-    const pushArgument = () => {
-        currentArguments.push(currentArgumentValue.trim());
-        currentArgumentValue = '';
-    };
-    const pushModifier = () => {
-        if (currentArguments.length > 1) {
-            throw new Error(`The modifier "${currentActionName}()" does not support multiple arguments.`);
+        this.actions = actions;
+        this.updatedModels = updateModels;
+    }
+    containsOneOfActions(targetedActions) {
+        return this.actions.filter((action) => targetedActions.includes(action)).length > 0;
+    }
+    areAnyModelsUpdated(targetedModels) {
+        return this.updatedModels.filter((model) => targetedModels.includes(model)).length > 0;
+    }
+}
+
+class RequestBuilder {
+    constructor(url, method = 'post') {
+        this.url = url;
+        this.method = method;
+    }
+    buildRequest(props, actions, updated, children, updatedPropsFromParent, files) {
+        const splitUrl = this.url.split('?');
+        let [url] = splitUrl;
+        const [, queryString] = splitUrl;
+        const params = new URLSearchParams(queryString || '');
+        const fetchOptions = {};
+        fetchOptions.headers = {
+            Accept: 'application/vnd.live-component+html',
+            'X-Requested-With': 'XMLHttpRequest',
+        };
+        const totalFiles = Object.entries(files).reduce((total, current) => total + current.length, 0);
+        const hasFingerprints = Object.keys(children).length > 0;
+        if (actions.length === 0 &&
+            totalFiles === 0 &&
+            this.method === 'get' &&
+            this.willDataFitInUrl(JSON.stringify(props), JSON.stringify(updated), params, JSON.stringify(children), JSON.stringify(updatedPropsFromParent))) {
+            params.set('props', JSON.stringify(props));
+            params.set('updated', JSON.stringify(updated));
+            if (Object.keys(updatedPropsFromParent).length > 0) {
+                params.set('propsFromParent', JSON.stringify(updatedPropsFromParent));
+            }
+            if (hasFingerprints) {
+                params.set('children', JSON.stringify(children));
+            }
+            fetchOptions.method = 'GET';
         }
-        currentModifiers.push({
-            name: currentActionName,
-            value: currentArguments.length > 0 ? currentArguments[0] : null,
-        });
-        currentActionName = '';
-        currentArguments = [];
-        state = 'action';
-    };
-    for (let i = 0; i < content.length; i++) {
-        const char = content[i];
-        switch (state) {
-            case 'action':
-                if (char === '(') {
-                    state = 'arguments';
-                    break;
-                }
-                if (char === ' ') {
-                    if (currentActionName) {
-                        pushInstruction();
-                    }
-                    break;
-                }
-                if (char === '|') {
-                    pushModifier();
-                    break;
-                }
-                currentActionName += char;
-                break;
-            case 'arguments':
-                if (char === ')') {
-                    pushArgument();
-                    state = 'after_arguments';
-                    break;
-                }
-                if (char === ',') {
-                    pushArgument();
-                    break;
+        else {
+            fetchOptions.method = 'POST';
+            const requestData = { props, updated };
+            if (Object.keys(updatedPropsFromParent).length > 0) {
+                requestData.propsFromParent = updatedPropsFromParent;
+            }
+            if (hasFingerprints) {
+                requestData.children = children;
+            }
+            if (actions.length > 0) {
+                if (actions.length === 1) {
+                    requestData.args = actions[0].args;
+                    url += `/${encodeURIComponent(actions[0].name)}`;
                 }
-                currentArgumentValue += char;
-                break;
-            case 'after_arguments':
-                if (char === '|') {
-                    pushModifier();
-                    break;
+                else {
+                    url += '/_batch';
+                    requestData.actions = actions;
                 }
-                if (char !== ' ') {
-                    throw new Error(`Missing space after ${getLastActionName()}()`);
+            }
+            const formData = new FormData();
+            formData.append('data', JSON.stringify(requestData));
+            for (const [key, value] of Object.entries(files)) {
+                const length = value.length;
+                for (let i = 0; i < length; ++i) {
+                    formData.append(key, value[i]);
                 }
-                pushInstruction();
-                break;
+            }
+            fetchOptions.body = formData;
         }
+        const paramsString = params.toString();
+        return {
+            url: `${url}${paramsString.length > 0 ? `?${paramsString}` : ''}`,
+            fetchOptions,
+        };
     }
-    switch (state) {
-        case 'action':
-        case 'after_arguments':
-            if (currentActionName) {
-                pushInstruction();
-            }
-            break;
-        default:
-            throw new Error(`Did you forget to add a closing ")" after "${currentActionName}"?`);
+    willDataFitInUrl(propsJson, updatedJson, params, childrenJson, propsFromParentJson) {
+        const urlEncodedJsonData = new URLSearchParams(propsJson + updatedJson + childrenJson + propsFromParentJson).toString();
+        return (urlEncodedJsonData + params.toString()).length < 1500;
     }
-    return directives;
 }
 
-function combineSpacedArray(parts) {
-    const finalParts = [];
-    parts.forEach((part) => {
-        finalParts.push(...trimAll(part).split(' '));
-    });
-    return finalParts;
-}
-function trimAll(str) {
-    return str.replace(/[\s]+/g, ' ').trim();
+class Backend {
+    constructor(url, method = 'post') {
+        this.requestBuilder = new RequestBuilder(url, method);
+    }
+    makeRequest(props, actions, updated, children, updatedPropsFromParent, files) {
+        const { url, fetchOptions } = this.requestBuilder.buildRequest(props, actions, updated, children, updatedPropsFromParent, files);
+        return new BackendRequest(fetch(url, fetchOptions), actions.map((backendAction) => backendAction.name), Object.keys(updated));
+    }
 }
-function normalizeModelName(model) {
-    return (model
-        .replace(/\[]$/, '')
-        .split('[')
-        .map((s) => s.replace(']', ''))
-        .join('.'));
+
+class BackendResponse {
+    constructor(response) {
+        this.response = response;
+    }
+    async getBody() {
+        if (!this.body) {
+            this.body = await this.response.text();
+        }
+        return this.body;
+    }
 }
 
 function getElementAsTagText(element) {
@@ -131,360 +119,810 @@ function getElementAsTagText(element) {
         : element.outerHTML;
 }
 
-function getValueFromElement(element, valueStore) {
-    if (element instanceof HTMLInputElement) {
-        if (element.type === 'checkbox') {
-            const modelNameData = getModelDirectiveFromElement(element, false);
-            if (modelNameData !== null) {
-                const modelValue = valueStore.get(modelNameData.action);
-                if (Array.isArray(modelValue)) {
-                    return getMultipleCheckboxValue(element, modelValue);
-                }
-                if (Object(modelValue) === modelValue) {
-                    return getMultipleCheckboxValue(element, Object.values(modelValue));
-                }
-            }
-            if (element.hasAttribute('value')) {
-                return element.checked ? element.getAttribute('value') : null;
-            }
-            return element.checked;
+let componentMapByElement = new WeakMap();
+let componentMapByComponent = new Map();
+const registerComponent = (component) => {
+    componentMapByElement.set(component.element, component);
+    componentMapByComponent.set(component, component.name);
+};
+const unregisterComponent = (component) => {
+    componentMapByElement.delete(component.element);
+    componentMapByComponent.delete(component);
+};
+const getComponent = (element) => new Promise((resolve, reject) => {
+    let count = 0;
+    const maxCount = 10;
+    const interval = setInterval(() => {
+        const component = componentMapByElement.get(element);
+        if (component) {
+            clearInterval(interval);
+            resolve(component);
         }
-        return inputValue(element);
-    }
-    if (element instanceof HTMLSelectElement) {
-        if (element.multiple) {
-            return Array.from(element.selectedOptions).map((el) => el.value);
+        count++;
+        if (count > maxCount) {
+            clearInterval(interval);
+            reject(new Error(`Component not found for element ${getElementAsTagText(element)}`));
         }
-        return element.value;
-    }
-    if (element.dataset.value) {
-        return element.dataset.value;
-    }
-    if ('value' in element) {
-        return element.value;
-    }
-    if (element.hasAttribute('value')) {
-        return element.getAttribute('value');
-    }
-    return null;
-}
-function setValueOnElement(element, value) {
-    if (element instanceof HTMLInputElement) {
-        if (element.type === 'file') {
+    }, 5);
+});
+const findComponents = (currentComponent, onlyParents, onlyMatchName) => {
+    const components = [];
+    componentMapByComponent.forEach((componentName, component) => {
+        if (onlyParents && (currentComponent === component || !component.element.contains(currentComponent.element))) {
             return;
         }
-        if (element.type === 'radio') {
-            element.checked = element.value == value;
+        if (onlyMatchName && componentName !== onlyMatchName) {
             return;
         }
-        if (element.type === 'checkbox') {
-            if (Array.isArray(value)) {
-                element.checked = value.some((val) => val == element.value);
+        components.push(component);
+    });
+    return components;
+};
+const findChildren = (currentComponent) => {
+    const children = [];
+    componentMapByComponent.forEach((componentName, component) => {
+        if (currentComponent === component) {
+            return;
+        }
+        if (!currentComponent.element.contains(component.element)) {
+            return;
+        }
+        let foundChildComponent = false;
+        componentMapByComponent.forEach((childComponentName, childComponent) => {
+            if (foundChildComponent) {
+                return;
             }
-            else if (element.hasAttribute('value')) {
-                element.checked = element.value == value;
+            if (childComponent === component) {
+                return;
             }
-            else {
-                element.checked = value;
+            if (childComponent.element.contains(component.element)) {
+                foundChildComponent = true;
             }
+        });
+        children.push(component);
+    });
+    return children;
+};
+const findParent = (currentComponent) => {
+    let parentElement = currentComponent.element.parentElement;
+    while (parentElement) {
+        const component = componentMapByElement.get(parentElement);
+        if (component) {
+            return component;
+        }
+        parentElement = parentElement.parentElement;
+    }
+    return null;
+};
+
+class HookManager {
+    constructor() {
+        this.hooks = new Map();
+    }
+    register(hookName, callback) {
+        const hooks = this.hooks.get(hookName) || [];
+        hooks.push(callback);
+        this.hooks.set(hookName, hooks);
+    }
+    unregister(hookName, callback) {
+        const hooks = this.hooks.get(hookName) || [];
+        const index = hooks.indexOf(callback);
+        if (index === -1) {
             return;
         }
+        hooks.splice(index, 1);
+        this.hooks.set(hookName, hooks);
     }
-    if (element instanceof HTMLSelectElement) {
-        const arrayWrappedValue = [].concat(value).map((value) => {
-            return `${value}`;
-        });
-        Array.from(element.options).forEach((option) => {
-            option.selected = arrayWrappedValue.includes(option.value);
-        });
-        return;
+    triggerHook(hookName, ...args) {
+        const hooks = this.hooks.get(hookName) || [];
+        hooks.forEach((callback) => callback(...args));
     }
-    value = value === undefined ? '' : value;
-    element.value = value;
 }
-function getAllModelDirectiveFromElements(element) {
-    if (!element.dataset.model) {
-        return [];
+
+class ChangingItemsTracker {
+    constructor() {
+        this.changedItems = new Map();
+        this.removedItems = new Map();
     }
-    const directives = parseDirectives(element.dataset.model);
-    directives.forEach((directive) => {
-        if (directive.args.length > 0) {
-            throw new Error(`The data-model="${element.dataset.model}" format is invalid: it does not support passing arguments to the model.`);
+    setItem(itemName, newValue, previousValue) {
+        if (this.removedItems.has(itemName)) {
+            const removedRecord = this.removedItems.get(itemName);
+            this.removedItems.delete(itemName);
+            if (removedRecord.original === newValue) {
+                return;
+            }
         }
-        directive.action = normalizeModelName(directive.action);
-    });
-    return directives;
-}
-function getModelDirectiveFromElement(element, throwOnMissing = true) {
-    const dataModelDirectives = getAllModelDirectiveFromElements(element);
-    if (dataModelDirectives.length > 0) {
-        return dataModelDirectives[0];
+        if (this.changedItems.has(itemName)) {
+            const originalRecord = this.changedItems.get(itemName);
+            if (originalRecord.original === newValue) {
+                this.changedItems.delete(itemName);
+                return;
+            }
+            this.changedItems.set(itemName, { original: originalRecord.original, new: newValue });
+            return;
+        }
+        this.changedItems.set(itemName, { original: previousValue, new: newValue });
     }
-    if (element.getAttribute('name')) {
-        const formElement = element.closest('form');
-        if (formElement && 'model' in formElement.dataset) {
-            const directives = parseDirectives(formElement.dataset.model || '*');
-            const directive = directives[0];
-            if (directive.args.length > 0) {
-                throw new Error(`The data-model="${formElement.dataset.model}" format is invalid: it does not support passing arguments to the model.`);
+    removeItem(itemName, currentValue) {
+        let trueOriginalValue = currentValue;
+        if (this.changedItems.has(itemName)) {
+            const originalRecord = this.changedItems.get(itemName);
+            trueOriginalValue = originalRecord.original;
+            this.changedItems.delete(itemName);
+            if (trueOriginalValue === null) {
+                return;
             }
-            directive.action = normalizeModelName(element.getAttribute('name'));
-            return directive;
+        }
+        if (!this.removedItems.has(itemName)) {
+            this.removedItems.set(itemName, { original: trueOriginalValue });
         }
     }
-    if (!throwOnMissing) {
-        return null;
+    getChangedItems() {
+        return Array.from(this.changedItems, ([name, { new: value }]) => ({ name, value }));
     }
-    throw new Error(`Cannot determine the model name for "${getElementAsTagText(element)}": the element must either have a "data-model" (or "name" attribute living inside a <form data-model="*">).`);
-}
-function elementBelongsToThisComponent(element, component) {
-    if (component.element === element) {
-        return true;
+    getRemovedItems() {
+        return Array.from(this.removedItems.keys());
     }
-    if (!component.element.contains(element)) {
-        return false;
+    isEmpty() {
+        return this.changedItems.size === 0 && this.removedItems.size === 0;
     }
-    const closestLiveComponent = element.closest('[data-controller~="live"]');
-    return closestLiveComponent === component.element;
 }
-function cloneHTMLElement(element) {
-    const newElement = element.cloneNode(true);
-    if (!(newElement instanceof HTMLElement)) {
-        throw new Error('Could not clone element');
+
+class ElementChanges {
+    constructor() {
+        this.addedClasses = new Set();
+        this.removedClasses = new Set();
+        this.styleChanges = new ChangingItemsTracker();
+        this.attributeChanges = new ChangingItemsTracker();
     }
-    return newElement;
-}
-function htmlToElement(html) {
-    const template = document.createElement('template');
-    html = html.trim();
-    template.innerHTML = html;
-    if (template.content.childElementCount > 1) {
-        throw new Error(`Component HTML contains ${template.content.childElementCount} elements, but only 1 root element is allowed.`);
+    addClass(className) {
+        if (!this.removedClasses.delete(className)) {
+            this.addedClasses.add(className);
+        }
     }
-    const child = template.content.firstElementChild;
-    if (!child) {
-        throw new Error('Child not found');
+    removeClass(className) {
+        if (!this.addedClasses.delete(className)) {
+            this.removedClasses.add(className);
+        }
     }
-    if (!(child instanceof HTMLElement)) {
-        throw new Error(`Created element is not an HTMLElement: ${html.trim()}`);
+    addStyle(styleName, newValue, originalValue) {
+        this.styleChanges.setItem(styleName, newValue, originalValue);
     }
-    return child;
-}
-const getMultipleCheckboxValue = (element, currentValues) => {
-    const finalValues = [...currentValues];
-    const value = inputValue(element);
-    const index = currentValues.indexOf(value);
-    if (element.checked) {
-        if (index === -1) {
-            finalValues.push(value);
-        }
-        return finalValues;
+    removeStyle(styleName, originalValue) {
+        this.styleChanges.removeItem(styleName, originalValue);
     }
-    if (index > -1) {
-        finalValues.splice(index, 1);
+    addAttribute(attributeName, newValue, originalValue) {
+        this.attributeChanges.setItem(attributeName, newValue, originalValue);
     }
-    return finalValues;
-};
-const inputValue = (element) => element.dataset.value ? element.dataset.value : element.value;
-
-function getDeepData(data, propertyPath) {
-    const { currentLevelData, finalKey } = parseDeepData(data, propertyPath);
-    if (currentLevelData === undefined) {
-        return undefined;
+    removeAttribute(attributeName, originalValue) {
+        this.attributeChanges.removeItem(attributeName, originalValue);
     }
-    return currentLevelData[finalKey];
-}
-const parseDeepData = (data, propertyPath) => {
-    const finalData = JSON.parse(JSON.stringify(data));
-    let currentLevelData = finalData;
-    const parts = propertyPath.split('.');
-    for (let i = 0; i < parts.length - 1; i++) {
-        currentLevelData = currentLevelData[parts[i]];
+    getAddedClasses() {
+        return [...this.addedClasses];
     }
-    const finalKey = parts[parts.length - 1];
-    return {
-        currentLevelData,
-        finalData,
-        finalKey,
-        parts,
-    };
-};
-
-class ValueStore {
-    constructor(props) {
-        this.props = {};
-        this.dirtyProps = {};
-        this.pendingProps = {};
-        this.updatedPropsFromParent = {};
-        this.props = props;
-    }
-    get(name) {
-        const normalizedName = normalizeModelName(name);
-        if (this.dirtyProps[normalizedName] !== undefined) {
-            return this.dirtyProps[normalizedName];
-        }
-        if (this.pendingProps[normalizedName] !== undefined) {
-            return this.pendingProps[normalizedName];
-        }
-        if (this.props[normalizedName] !== undefined) {
-            return this.props[normalizedName];
-        }
-        return getDeepData(this.props, normalizedName);
-    }
-    has(name) {
-        return this.get(name) !== undefined;
+    getRemovedClasses() {
+        return [...this.removedClasses];
     }
-    set(name, value) {
-        const normalizedName = normalizeModelName(name);
-        if (this.get(normalizedName) === value) {
-            return false;
-        }
-        this.dirtyProps[normalizedName] = value;
-        return true;
+    getChangedStyles() {
+        return this.styleChanges.getChangedItems();
     }
-    getOriginalProps() {
-        return { ...this.props };
+    getRemovedStyles() {
+        return this.styleChanges.getRemovedItems();
     }
-    getDirtyProps() {
-        return { ...this.dirtyProps };
+    getChangedAttributes() {
+        return this.attributeChanges.getChangedItems();
     }
-    getUpdatedPropsFromParent() {
-        return { ...this.updatedPropsFromParent };
+    getRemovedAttributes() {
+        return this.attributeChanges.getRemovedItems();
     }
-    flushDirtyPropsToPending() {
-        this.pendingProps = { ...this.dirtyProps };
-        this.dirtyProps = {};
+    applyToElement(element) {
+        element.classList.add(...this.addedClasses);
+        element.classList.remove(...this.removedClasses);
+        this.styleChanges.getChangedItems().forEach((change) => {
+            element.style.setProperty(change.name, change.value);
+            return;
+        });
+        this.styleChanges.getRemovedItems().forEach((styleName) => {
+            element.style.removeProperty(styleName);
+        });
+        this.attributeChanges.getChangedItems().forEach((change) => {
+            element.setAttribute(change.name, change.value);
+        });
+        this.attributeChanges.getRemovedItems().forEach((attributeName) => {
+            element.removeAttribute(attributeName);
+        });
     }
-    reinitializeAllProps(props) {
-        this.props = props;
-        this.updatedPropsFromParent = {};
-        this.pendingProps = {};
+    isEmpty() {
+        return (this.addedClasses.size === 0 &&
+            this.removedClasses.size === 0 &&
+            this.styleChanges.isEmpty() &&
+            this.attributeChanges.isEmpty());
     }
-    pushPendingPropsBackToDirty() {
-        this.dirtyProps = { ...this.pendingProps, ...this.dirtyProps };
-        this.pendingProps = {};
+}
+
+class ExternalMutationTracker {
+    constructor(element, shouldTrackChangeCallback) {
+        this.changedElements = new WeakMap();
+        this.changedElementsCount = 0;
+        this.addedElements = [];
+        this.removedElements = [];
+        this.isStarted = false;
+        this.element = element;
+        this.shouldTrackChangeCallback = shouldTrackChangeCallback;
+        this.mutationObserver = new MutationObserver(this.onMutations.bind(this));
     }
-    storeNewPropsFromParent(props) {
-        let changed = false;
-        for (const [key, value] of Object.entries(props)) {
-            const currentValue = this.get(key);
-            if (currentValue !== value) {
-                changed = true;
-            }
+    start() {
+        if (this.isStarted) {
+            return;
         }
-        if (changed) {
-            this.updatedPropsFromParent = props;
+        this.mutationObserver.observe(this.element, {
+            childList: true,
+            subtree: true,
+            attributes: true,
+            attributeOldValue: true,
+        });
+        this.isStarted = true;
+    }
+    stop() {
+        if (this.isStarted) {
+            this.mutationObserver.disconnect();
+            this.isStarted = false;
         }
-        return changed;
     }
-}
-
-// base IIFE to define idiomorph
-var Idiomorph = (function () {
-
-        //=============================================================================
-        // AND NOW IT BEGINS...
-        //=============================================================================
-        let EMPTY_SET = new Set();
-
-        // default configuration values, updatable by users now
-        let defaults = {
-            morphStyle: "outerHTML",
-            callbacks : {
-                beforeNodeAdded: noOp,
-                afterNodeAdded: noOp,
-                beforeNodeMorphed: noOp,
-                afterNodeMorphed: noOp,
-                beforeNodeRemoved: noOp,
-                afterNodeRemoved: noOp,
-                beforeAttributeUpdated: noOp,
-
-            },
-            head: {
-                style: 'merge',
-                shouldPreserve: function (elt) {
-                    return elt.getAttribute("im-preserve") === "true";
-                },
-                shouldReAppend: function (elt) {
-                    return elt.getAttribute("im-re-append") === "true";
-                },
-                shouldRemove: noOp,
-                afterHeadMorphed: noOp,
-            }
-        };
-
-        //=============================================================================
-        // Core Morphing Algorithm - morph, morphNormalizedContent, morphOldNodeTo, morphChildren
-        //=============================================================================
-        function morph(oldNode, newContent, config = {}) {
-
-            if (oldNode instanceof Document) {
-                oldNode = oldNode.documentElement;
+    getChangedElement(element) {
+        return this.changedElements.has(element) ? this.changedElements.get(element) : null;
+    }
+    getAddedElements() {
+        return this.addedElements;
+    }
+    wasElementAdded(element) {
+        return this.addedElements.includes(element);
+    }
+    handlePendingChanges() {
+        this.onMutations(this.mutationObserver.takeRecords());
+    }
+    onMutations(mutations) {
+        const handledAttributeMutations = new WeakMap();
+        for (const mutation of mutations) {
+            const element = mutation.target;
+            if (!this.shouldTrackChangeCallback(element)) {
+                continue;
             }
-
-            if (typeof newContent === 'string') {
-                newContent = parseContent(newContent);
+            if (this.isElementAddedByTranslation(element)) {
+                continue;
             }
-
-            let normalizedContent = normalizeContent(newContent);
-
-            let ctx = createMorphContext(oldNode, normalizedContent, config);
-
-            return morphNormalizedContent(oldNode, normalizedContent, ctx);
-        }
-
-        function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
-            if (ctx.head.block) {
-                let oldHead = oldNode.querySelector('head');
-                let newHead = normalizedNewContent.querySelector('head');
-                if (oldHead && newHead) {
-                    let promises = handleHeadElement(newHead, oldHead, ctx);
-                    // when head promises resolve, call morph again, ignoring the head tag
-                    Promise.all(promises).then(function () {
-                        morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
-                            head: {
-                                block: false,
-                                ignore: true
-                            }
-                        }));
-                    });
-                    return;
+            let isChangeInAddedElement = false;
+            for (const addedElement of this.addedElements) {
+                if (addedElement.contains(element)) {
+                    isChangeInAddedElement = true;
+                    break;
                 }
             }
-
-            if (ctx.morphStyle === "innerHTML") {
-
-                // innerHTML, so we are only updating the children
-                morphChildren(normalizedNewContent, oldNode, ctx);
-                return oldNode.children;
-
-            } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
-                // otherwise find the best element match in the new content, morph that, and merge its siblings
-                // into either side of the best match
-                let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
-
-                // stash the siblings that will need to be inserted on either side of the best match
-                let previousSibling = bestMatch?.previousSibling;
-                let nextSibling = bestMatch?.nextSibling;
-
-                // morph it
-                let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
-
-                if (bestMatch) {
-                    // if there was a best match, merge the siblings in too and return the
-                    // whole bunch
-                    return insertSiblings(previousSibling, morphedNode, nextSibling);
-                } else {
-                    // otherwise nothing was added to the DOM
-                    return []
-                }
-            } else {
-                throw "Do not understand how to morph style " + ctx.morphStyle;
+            if (isChangeInAddedElement) {
+                continue;
+            }
+            switch (mutation.type) {
+                case 'childList':
+                    this.handleChildListMutation(mutation);
+                    break;
+                case 'attributes':
+                    if (!handledAttributeMutations.has(element)) {
+                        handledAttributeMutations.set(element, []);
+                    }
+                    if (!handledAttributeMutations.get(element).includes(mutation.attributeName)) {
+                        this.handleAttributeMutation(mutation);
+                        handledAttributeMutations.set(element, [
+                            ...handledAttributeMutations.get(element),
+                            mutation.attributeName,
+                        ]);
+                    }
+                    break;
             }
         }
-
-
-        /**
+    }
+    handleChildListMutation(mutation) {
+        mutation.addedNodes.forEach((node) => {
+            if (!(node instanceof Element)) {
+                return;
+            }
+            if (this.removedElements.includes(node)) {
+                this.removedElements.splice(this.removedElements.indexOf(node), 1);
+                return;
+            }
+            if (this.isElementAddedByTranslation(node)) {
+                return;
+            }
+            this.addedElements.push(node);
+        });
+        mutation.removedNodes.forEach((node) => {
+            if (!(node instanceof Element)) {
+                return;
+            }
+            if (this.addedElements.includes(node)) {
+                this.addedElements.splice(this.addedElements.indexOf(node), 1);
+                return;
+            }
+            this.removedElements.push(node);
+        });
+    }
+    handleAttributeMutation(mutation) {
+        const element = mutation.target;
+        if (!this.changedElements.has(element)) {
+            this.changedElements.set(element, new ElementChanges());
+            this.changedElementsCount++;
+        }
+        const changedElement = this.changedElements.get(element);
+        switch (mutation.attributeName) {
+            case 'class':
+                this.handleClassAttributeMutation(mutation, changedElement);
+                break;
+            case 'style':
+                this.handleStyleAttributeMutation(mutation, changedElement);
+                break;
+            default:
+                this.handleGenericAttributeMutation(mutation, changedElement);
+        }
+        if (changedElement.isEmpty()) {
+            this.changedElements.delete(element);
+            this.changedElementsCount--;
+        }
+    }
+    handleClassAttributeMutation(mutation, elementChanges) {
+        const element = mutation.target;
+        const previousValue = mutation.oldValue || '';
+        const previousValues = previousValue.match(/(\S+)/gu) || [];
+        const newValues = [].slice.call(element.classList);
+        const addedValues = newValues.filter((value) => !previousValues.includes(value));
+        const removedValues = previousValues.filter((value) => !newValues.includes(value));
+        addedValues.forEach((value) => {
+            elementChanges.addClass(value);
+        });
+        removedValues.forEach((value) => {
+            elementChanges.removeClass(value);
+        });
+    }
+    handleStyleAttributeMutation(mutation, elementChanges) {
+        const element = mutation.target;
+        const previousValue = mutation.oldValue || '';
+        const previousStyles = this.extractStyles(previousValue);
+        const newValue = element.getAttribute('style') || '';
+        const newStyles = this.extractStyles(newValue);
+        const addedOrChangedStyles = Object.keys(newStyles).filter((key) => previousStyles[key] === undefined || previousStyles[key] !== newStyles[key]);
+        const removedStyles = Object.keys(previousStyles).filter((key) => !newStyles[key]);
+        addedOrChangedStyles.forEach((style) => {
+            elementChanges.addStyle(style, newStyles[style], previousStyles[style] === undefined ? null : previousStyles[style]);
+        });
+        removedStyles.forEach((style) => {
+            elementChanges.removeStyle(style, previousStyles[style]);
+        });
+    }
+    handleGenericAttributeMutation(mutation, elementChanges) {
+        const attributeName = mutation.attributeName;
+        const element = mutation.target;
+        let oldValue = mutation.oldValue;
+        let newValue = element.getAttribute(attributeName);
+        if (oldValue === attributeName) {
+            oldValue = '';
+        }
+        if (newValue === attributeName) {
+            newValue = '';
+        }
+        if (!element.hasAttribute(attributeName)) {
+            if (oldValue === null) {
+                return;
+            }
+            elementChanges.removeAttribute(attributeName, mutation.oldValue);
+            return;
+        }
+        if (newValue === oldValue) {
+            return;
+        }
+        elementChanges.addAttribute(attributeName, element.getAttribute(attributeName), mutation.oldValue);
+    }
+    extractStyles(styles) {
+        const styleObject = {};
+        styles.split(';').forEach((style) => {
+            const parts = style.split(':');
+            if (parts.length === 1) {
+                return;
+            }
+            const property = parts[0].trim();
+            styleObject[property] = parts.slice(1).join(':').trim();
+        });
+        return styleObject;
+    }
+    isElementAddedByTranslation(element) {
+        return element.tagName === 'FONT' && element.getAttribute('style') === 'vertical-align: inherit;';
+    }
+}
+
+function parseDirectives(content) {
+    const directives = [];
+    if (!content) {
+        return directives;
+    }
+    let currentActionName = '';
+    let currentArgumentValue = '';
+    let currentArguments = [];
+    let currentModifiers = [];
+    let state = 'action';
+    const getLastActionName = () => {
+        if (currentActionName) {
+            return currentActionName;
+        }
+        if (directives.length === 0) {
+            throw new Error('Could not find any directives');
+        }
+        return directives[directives.length - 1].action;
+    };
+    const pushInstruction = () => {
+        directives.push({
+            action: currentActionName,
+            args: currentArguments,
+            modifiers: currentModifiers,
+            getString: () => {
+                return content;
+            },
+        });
+        currentActionName = '';
+        currentArgumentValue = '';
+        currentArguments = [];
+        currentModifiers = [];
+        state = 'action';
+    };
+    const pushArgument = () => {
+        currentArguments.push(currentArgumentValue.trim());
+        currentArgumentValue = '';
+    };
+    const pushModifier = () => {
+        if (currentArguments.length > 1) {
+            throw new Error(`The modifier "${currentActionName}()" does not support multiple arguments.`);
+        }
+        currentModifiers.push({
+            name: currentActionName,
+            value: currentArguments.length > 0 ? currentArguments[0] : null,
+        });
+        currentActionName = '';
+        currentArguments = [];
+        state = 'action';
+    };
+    for (let i = 0; i < content.length; i++) {
+        const char = content[i];
+        switch (state) {
+            case 'action':
+                if (char === '(') {
+                    state = 'arguments';
+                    break;
+                }
+                if (char === ' ') {
+                    if (currentActionName) {
+                        pushInstruction();
+                    }
+                    break;
+                }
+                if (char === '|') {
+                    pushModifier();
+                    break;
+                }
+                currentActionName += char;
+                break;
+            case 'arguments':
+                if (char === ')') {
+                    pushArgument();
+                    state = 'after_arguments';
+                    break;
+                }
+                if (char === ',') {
+                    pushArgument();
+                    break;
+                }
+                currentArgumentValue += char;
+                break;
+            case 'after_arguments':
+                if (char === '|') {
+                    pushModifier();
+                    break;
+                }
+                if (char !== ' ') {
+                    throw new Error(`Missing space after ${getLastActionName()}()`);
+                }
+                pushInstruction();
+                break;
+        }
+    }
+    switch (state) {
+        case 'action':
+        case 'after_arguments':
+            if (currentActionName) {
+                pushInstruction();
+            }
+            break;
+        default:
+            throw new Error(`Did you forget to add a closing ")" after "${currentActionName}"?`);
+    }
+    return directives;
+}
+
+function combineSpacedArray(parts) {
+    const finalParts = [];
+    parts.forEach((part) => {
+        finalParts.push(...trimAll(part).split(' '));
+    });
+    return finalParts;
+}
+function trimAll(str) {
+    return str.replace(/[\s]+/g, ' ').trim();
+}
+function normalizeModelName(model) {
+    return (model
+        .replace(/\[]$/, '')
+        .split('[')
+        .map((s) => s.replace(']', ''))
+        .join('.'));
+}
+
+function getValueFromElement(element, valueStore) {
+    if (element instanceof HTMLInputElement) {
+        if (element.type === 'checkbox') {
+            const modelNameData = getModelDirectiveFromElement(element, false);
+            if (modelNameData !== null) {
+                const modelValue = valueStore.get(modelNameData.action);
+                if (Array.isArray(modelValue)) {
+                    return getMultipleCheckboxValue(element, modelValue);
+                }
+                if (Object(modelValue) === modelValue) {
+                    return getMultipleCheckboxValue(element, Object.values(modelValue));
+                }
+            }
+            if (element.hasAttribute('value')) {
+                return element.checked ? element.getAttribute('value') : null;
+            }
+            return element.checked;
+        }
+        return inputValue(element);
+    }
+    if (element instanceof HTMLSelectElement) {
+        if (element.multiple) {
+            return Array.from(element.selectedOptions).map((el) => el.value);
+        }
+        return element.value;
+    }
+    if (element.dataset.value) {
+        return element.dataset.value;
+    }
+    if ('value' in element) {
+        return element.value;
+    }
+    if (element.hasAttribute('value')) {
+        return element.getAttribute('value');
+    }
+    return null;
+}
+function setValueOnElement(element, value) {
+    if (element instanceof HTMLInputElement) {
+        if (element.type === 'file') {
+            return;
+        }
+        if (element.type === 'radio') {
+            element.checked = element.value == value;
+            return;
+        }
+        if (element.type === 'checkbox') {
+            if (Array.isArray(value)) {
+                element.checked = value.some((val) => val == element.value);
+            }
+            else if (element.hasAttribute('value')) {
+                element.checked = element.value == value;
+            }
+            else {
+                element.checked = value;
+            }
+            return;
+        }
+    }
+    if (element instanceof HTMLSelectElement) {
+        const arrayWrappedValue = [].concat(value).map((value) => {
+            return `${value}`;
+        });
+        Array.from(element.options).forEach((option) => {
+            option.selected = arrayWrappedValue.includes(option.value);
+        });
+        return;
+    }
+    value = value === undefined ? '' : value;
+    element.value = value;
+}
+function getAllModelDirectiveFromElements(element) {
+    if (!element.dataset.model) {
+        return [];
+    }
+    const directives = parseDirectives(element.dataset.model);
+    directives.forEach((directive) => {
+        if (directive.args.length > 0) {
+            throw new Error(`The data-model="${element.dataset.model}" format is invalid: it does not support passing arguments to the model.`);
+        }
+        directive.action = normalizeModelName(directive.action);
+    });
+    return directives;
+}
+function getModelDirectiveFromElement(element, throwOnMissing = true) {
+    const dataModelDirectives = getAllModelDirectiveFromElements(element);
+    if (dataModelDirectives.length > 0) {
+        return dataModelDirectives[0];
+    }
+    if (element.getAttribute('name')) {
+        const formElement = element.closest('form');
+        if (formElement && 'model' in formElement.dataset) {
+            const directives = parseDirectives(formElement.dataset.model || '*');
+            const directive = directives[0];
+            if (directive.args.length > 0) {
+                throw new Error(`The data-model="${formElement.dataset.model}" format is invalid: it does not support passing arguments to the model.`);
+            }
+            directive.action = normalizeModelName(element.getAttribute('name'));
+            return directive;
+        }
+    }
+    if (!throwOnMissing) {
+        return null;
+    }
+    throw new Error(`Cannot determine the model name for "${getElementAsTagText(element)}": the element must either have a "data-model" (or "name" attribute living inside a <form data-model="*">).`);
+}
+function elementBelongsToThisComponent(element, component) {
+    if (component.element === element) {
+        return true;
+    }
+    if (!component.element.contains(element)) {
+        return false;
+    }
+    const closestLiveComponent = element.closest('[data-controller~="live"]');
+    return closestLiveComponent === component.element;
+}
+function cloneHTMLElement(element) {
+    const newElement = element.cloneNode(true);
+    if (!(newElement instanceof HTMLElement)) {
+        throw new Error('Could not clone element');
+    }
+    return newElement;
+}
+function htmlToElement(html) {
+    const template = document.createElement('template');
+    html = html.trim();
+    template.innerHTML = html;
+    if (template.content.childElementCount > 1) {
+        throw new Error(`Component HTML contains ${template.content.childElementCount} elements, but only 1 root element is allowed.`);
+    }
+    const child = template.content.firstElementChild;
+    if (!child) {
+        throw new Error('Child not found');
+    }
+    if (!(child instanceof HTMLElement)) {
+        throw new Error(`Created element is not an HTMLElement: ${html.trim()}`);
+    }
+    return child;
+}
+const getMultipleCheckboxValue = (element, currentValues) => {
+    const finalValues = [...currentValues];
+    const value = inputValue(element);
+    const index = currentValues.indexOf(value);
+    if (element.checked) {
+        if (index === -1) {
+            finalValues.push(value);
+        }
+        return finalValues;
+    }
+    if (index > -1) {
+        finalValues.splice(index, 1);
+    }
+    return finalValues;
+};
+const inputValue = (element) => element.dataset.value ? element.dataset.value : element.value;
+
+// base IIFE to define idiomorph
+var Idiomorph = (function () {
+
+        //=============================================================================
+        // AND NOW IT BEGINS...
+        //=============================================================================
+        let EMPTY_SET = new Set();
+
+        // default configuration values, updatable by users now
+        let defaults = {
+            morphStyle: "outerHTML",
+            callbacks : {
+                beforeNodeAdded: noOp,
+                afterNodeAdded: noOp,
+                beforeNodeMorphed: noOp,
+                afterNodeMorphed: noOp,
+                beforeNodeRemoved: noOp,
+                afterNodeRemoved: noOp,
+                beforeAttributeUpdated: noOp,
+
+            },
+            head: {
+                style: 'merge',
+                shouldPreserve: function (elt) {
+                    return elt.getAttribute("im-preserve") === "true";
+                },
+                shouldReAppend: function (elt) {
+                    return elt.getAttribute("im-re-append") === "true";
+                },
+                shouldRemove: noOp,
+                afterHeadMorphed: noOp,
+            }
+        };
+
+        //=============================================================================
+        // Core Morphing Algorithm - morph, morphNormalizedContent, morphOldNodeTo, morphChildren
+        //=============================================================================
+        function morph(oldNode, newContent, config = {}) {
+
+            if (oldNode instanceof Document) {
+                oldNode = oldNode.documentElement;
+            }
+
+            if (typeof newContent === 'string') {
+                newContent = parseContent(newContent);
+            }
+
+            let normalizedContent = normalizeContent(newContent);
+
+            let ctx = createMorphContext(oldNode, normalizedContent, config);
+
+            return morphNormalizedContent(oldNode, normalizedContent, ctx);
+        }
+
+        function morphNormalizedContent(oldNode, normalizedNewContent, ctx) {
+            if (ctx.head.block) {
+                let oldHead = oldNode.querySelector('head');
+                let newHead = normalizedNewContent.querySelector('head');
+                if (oldHead && newHead) {
+                    let promises = handleHeadElement(newHead, oldHead, ctx);
+                    // when head promises resolve, call morph again, ignoring the head tag
+                    Promise.all(promises).then(function () {
+                        morphNormalizedContent(oldNode, normalizedNewContent, Object.assign(ctx, {
+                            head: {
+                                block: false,
+                                ignore: true
+                            }
+                        }));
+                    });
+                    return;
+                }
+            }
+
+            if (ctx.morphStyle === "innerHTML") {
+
+                // innerHTML, so we are only updating the children
+                morphChildren(normalizedNewContent, oldNode, ctx);
+                return oldNode.children;
+
+            } else if (ctx.morphStyle === "outerHTML" || ctx.morphStyle == null) {
+                // otherwise find the best element match in the new content, morph that, and merge its siblings
+                // into either side of the best match
+                let bestMatch = findBestNodeMatch(normalizedNewContent, oldNode, ctx);
+
+                // stash the siblings that will need to be inserted on either side of the best match
+                let previousSibling = bestMatch?.previousSibling;
+                let nextSibling = bestMatch?.nextSibling;
+
+                // morph it
+                let morphedNode = morphOldNodeTo(oldNode, bestMatch, ctx);
+
+                if (bestMatch) {
+                    // if there was a best match, merge the siblings in too and return the
+                    // whole bunch
+                    return insertSiblings(previousSibling, morphedNode, nextSibling);
+                } else {
+                    // otherwise nothing was added to the DOM
+                    return []
+                }
+            } else {
+                throw "Do not understand how to morph style " + ctx.morphStyle;
+            }
+        }
+
+
+        /**
          * @param possibleActiveElement
          * @param ctx
          * @returns {boolean}
@@ -950,922 +1388,585 @@ var Idiomorph = (function () {
                     // If we have an id match, return the current potential match
                     if (isIdSetMatch(newChild, potentialMatch, ctx)) {
                         return potentialMatch;
-                    }
-
-                    // computer the other potential matches of this new content
-                    otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
-                    if (otherMatchCount > newChildPotentialIdCount) {
-                        // if we have more potential id matches in _other_ content, we
-                        // do not have a good candidate for an id match, so return null
-                        return null;
-                    }
-
-                    // advanced to the next old content child
-                    potentialMatch = potentialMatch.nextSibling;
-                }
-            }
-            return potentialMatch;
-        }
-
-        //=============================================================================
-        // Scans forward from the insertionPoint in the old parent looking for a potential soft match
-        // for the newChild.  We stop if we find a potential soft match for the new child OR
-        // if we find a potential id match in the old parents children OR if we find two
-        // potential soft matches for the next two pieces of new content
-        //=============================================================================
-        function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
-
-            let potentialSoftMatch = insertionPoint;
-            let nextSibling = newChild.nextSibling;
-            let siblingSoftMatchCount = 0;
-
-            while (potentialSoftMatch != null) {
-
-                if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
-                    // the current potential soft match has a potential id set match with the remaining new
-                    // content so bail out of looking
-                    return null;
-                }
-
-                // if we have a soft match with the current node, return it
-                if (isSoftMatch(newChild, potentialSoftMatch)) {
-                    return potentialSoftMatch;
-                }
-
-                if (isSoftMatch(nextSibling, potentialSoftMatch)) {
-                    // the next new node has a soft match with this node, so
-                    // increment the count of future soft matches
-                    siblingSoftMatchCount++;
-                    nextSibling = nextSibling.nextSibling;
-
-                    // If there are two future soft matches, bail to allow the siblings to soft match
-                    // so that we don't consume future soft matches for the sake of the current node
-                    if (siblingSoftMatchCount >= 2) {
-                        return null;
-                    }
-                }
-
-                // advanced to the next old content child
-                potentialSoftMatch = potentialSoftMatch.nextSibling;
-            }
-
-            return potentialSoftMatch;
-        }
-
-        function parseContent(newContent) {
-            let parser = new DOMParser();
-
-            // remove svgs to avoid false-positive matches on head, etc.
-            let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
-
-            // if the newContent contains a html, head or body tag, we can simply parse it w/o wrapping
-            if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
-                let content = parser.parseFromString(newContent, "text/html");
-                // if it is a full HTML document, return the document itself as the parent container
-                if (contentWithSvgsRemoved.match(/<\/html>/)) {
-                    content.generatedByIdiomorph = true;
-                    return content;
-                } else {
-                    // otherwise return the html element as the parent container
-                    let htmlElement = content.firstChild;
-                    if (htmlElement) {
-                        htmlElement.generatedByIdiomorph = true;
-                        return htmlElement;
-                    } else {
-                        return null;
-                    }
-                }
-            } else {
-                // if it is partial HTML, wrap it in a template tag to provide a parent element and also to help
-                // deal with touchy tags like tr, tbody, etc.
-                let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
-                let content = responseDoc.body.querySelector('template').content;
-                content.generatedByIdiomorph = true;
-                return content
-            }
-        }
-
-        function normalizeContent(newContent) {
-            if (newContent == null) {
-                // noinspection UnnecessaryLocalVariableJS
-                const dummyParent = document.createElement('div');
-                return dummyParent;
-            } else if (newContent.generatedByIdiomorph) {
-                // the template tag created by idiomorph parsing can serve as a dummy parent
-                return newContent;
-            } else if (newContent instanceof Node) {
-                // a single node is added as a child to a dummy parent
-                const dummyParent = document.createElement('div');
-                dummyParent.append(newContent);
-                return dummyParent;
-            } else {
-                // all nodes in the array or HTMLElement collection are consolidated under
-                // a single dummy parent element
-                const dummyParent = document.createElement('div');
-                for (const elt of [...newContent]) {
-                    dummyParent.append(elt);
-                }
-                return dummyParent;
-            }
-        }
-
-        function insertSiblings(previousSibling, morphedNode, nextSibling) {
-            let stack = [];
-            let added = [];
-            while (previousSibling != null) {
-                stack.push(previousSibling);
-                previousSibling = previousSibling.previousSibling;
-            }
-            while (stack.length > 0) {
-                let node = stack.pop();
-                added.push(node); // push added preceding siblings on in order and insert
-                morphedNode.parentElement.insertBefore(node, morphedNode);
-            }
-            added.push(morphedNode);
-            while (nextSibling != null) {
-                stack.push(nextSibling);
-                added.push(nextSibling); // here we are going in order, so push on as we scan, rather than add
-                nextSibling = nextSibling.nextSibling;
-            }
-            while (stack.length > 0) {
-                morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
-            }
-            return added;
-        }
-
-        function findBestNodeMatch(newContent, oldNode, ctx) {
-            let currentElement;
-            currentElement = newContent.firstChild;
-            let bestElement = currentElement;
-            let score = 0;
-            while (currentElement) {
-                let newScore = scoreElement(currentElement, oldNode, ctx);
-                if (newScore > score) {
-                    bestElement = currentElement;
-                    score = newScore;
-                }
-                currentElement = currentElement.nextSibling;
-            }
-            return bestElement;
-        }
-
-        function scoreElement(node1, node2, ctx) {
-            if (isSoftMatch(node1, node2)) {
-                return .5 + getIdIntersectionCount(ctx, node1, node2);
-            }
-            return 0;
-        }
-
-        function removeNode(tempNode, ctx) {
-            removeIdsFromConsideration(ctx, tempNode);
-            if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
-
-            tempNode.remove();
-            ctx.callbacks.afterNodeRemoved(tempNode);
-        }
-
-        //=============================================================================
-        // ID Set Functions
-        //=============================================================================
-
-        function isIdInConsideration(ctx, id) {
-            return !ctx.deadIds.has(id);
-        }
-
-        function idIsWithinNode(ctx, id, targetNode) {
-            let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
-            return idSet.has(id);
-        }
-
-        function removeIdsFromConsideration(ctx, node) {
-            let idSet = ctx.idMap.get(node) || EMPTY_SET;
-            for (const id of idSet) {
-                ctx.deadIds.add(id);
-            }
-        }
-
-        function getIdIntersectionCount(ctx, node1, node2) {
-            let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
-            let matchCount = 0;
-            for (const id of sourceSet) {
-                // a potential match is an id in the source and potentialIdsSet, but
-                // that has not already been merged into the DOM
-                if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
-                    ++matchCount;
-                }
-            }
-            return matchCount;
-        }
+                    }
 
-        /**
-         * A bottom up algorithm that finds all elements with ids inside of the node
-         * argument and populates id sets for those nodes and all their parents, generating
-         * a set of ids contained within all nodes for the entire hierarchy in the DOM
-         *
-         * @param node {Element}
-         * @param {Map<Node, Set<String>>} idMap
-         */
-        function populateIdMapForNode(node, idMap) {
-            let nodeParent = node.parentElement;
-            // find all elements with an id property
-            let idElements = node.querySelectorAll('[id]');
-            for (const elt of idElements) {
-                let current = elt;
-                // walk up the parent hierarchy of that element, adding the id
-                // of element to the parent's id set
-                while (current !== nodeParent && current != null) {
-                    let idSet = idMap.get(current);
-                    // if the id set doesn't exist, create it and insert it in the  map
-                    if (idSet == null) {
-                        idSet = new Set();
-                        idMap.set(current, idSet);
+                    // computer the other potential matches of this new content
+                    otherMatchCount += getIdIntersectionCount(ctx, potentialMatch, newContent);
+                    if (otherMatchCount > newChildPotentialIdCount) {
+                        // if we have more potential id matches in _other_ content, we
+                        // do not have a good candidate for an id match, so return null
+                        return null;
                     }
-                    idSet.add(elt.id);
-                    current = current.parentElement;
+
+                    // advanced to the next old content child
+                    potentialMatch = potentialMatch.nextSibling;
                 }
             }
-        }
-
-        /**
-         * This function computes a map of nodes to all ids contained within that node (inclusive of the
-         * node).  This map can be used to ask if two nodes have intersecting sets of ids, which allows
-         * for a looser definition of "matching" than tradition id matching, and allows child nodes
-         * to contribute to a parent nodes matching.
-         *
-         * @param {Element} oldContent  the old content that will be morphed
-         * @param {Element} newContent  the new content to morph to
-         * @returns {Map<Node, Set<String>>} a map of nodes to id sets for the
-         */
-        function createIdMap(oldContent, newContent) {
-            let idMap = new Map();
-            populateIdMapForNode(oldContent, idMap);
-            populateIdMapForNode(newContent, idMap);
-            return idMap;
+            return potentialMatch;
         }
 
         //=============================================================================
-        // This is what ends up becoming the Idiomorph global object
+        // Scans forward from the insertionPoint in the old parent looking for a potential soft match
+        // for the newChild.  We stop if we find a potential soft match for the new child OR
+        // if we find a potential id match in the old parents children OR if we find two
+        // potential soft matches for the next two pieces of new content
         //=============================================================================
-        return {
-            morph,
-            defaults
-        }
-    })();
+        function findSoftMatch(newContent, oldParent, newChild, insertionPoint, ctx) {
 
-function normalizeAttributesForComparison(element) {
-    const isFileInput = element instanceof HTMLInputElement && element.type === 'file';
-    if (!isFileInput) {
-        if ('value' in element) {
-            element.setAttribute('value', element.value);
-        }
-        else if (element.hasAttribute('value')) {
-            element.setAttribute('value', '');
-        }
-    }
-    Array.from(element.children).forEach((child) => {
-        normalizeAttributesForComparison(child);
-    });
-}
+            let potentialSoftMatch = insertionPoint;
+            let nextSibling = newChild.nextSibling;
+            let siblingSoftMatchCount = 0;
 
-const syncAttributes = (fromEl, toEl) => {
-    for (let i = 0; i < fromEl.attributes.length; i++) {
-        const attr = fromEl.attributes[i];
-        toEl.setAttribute(attr.name, attr.value);
-    }
-};
-function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, getElementValue, externalMutationTracker) {
-    const originalElementIdsToSwapAfter = [];
-    const originalElementsToPreserve = new Map();
-    const markElementAsNeedingPostMorphSwap = (id, replaceWithClone) => {
-        const oldElement = originalElementsToPreserve.get(id);
-        if (!(oldElement instanceof HTMLElement)) {
-            throw new Error(`Original element with id ${id} not found`);
-        }
-        originalElementIdsToSwapAfter.push(id);
-        if (!replaceWithClone) {
-            return null;
-        }
-        const clonedOldElement = cloneHTMLElement(oldElement);
-        oldElement.replaceWith(clonedOldElement);
-        return clonedOldElement;
-    };
-    rootToElement.querySelectorAll('[data-live-preserve]').forEach((newElement) => {
-        const id = newElement.id;
-        if (!id) {
-            throw new Error('The data-live-preserve attribute requires an id attribute to be set on the element');
-        }
-        const oldElement = rootFromElement.querySelector(`#${id}`);
-        if (!(oldElement instanceof HTMLElement)) {
-            throw new Error(`The element with id "${id}" was not found in the original HTML`);
-        }
-        newElement.removeAttribute('data-live-preserve');
-        originalElementsToPreserve.set(id, oldElement);
-        syncAttributes(newElement, oldElement);
-    });
-    Idiomorph.morph(rootFromElement, rootToElement, {
-        callbacks: {
-            beforeNodeMorphed: (fromEl, toEl) => {
-                if (!(fromEl instanceof Element) || !(toEl instanceof Element)) {
-                    return true;
+            while (potentialSoftMatch != null) {
+
+                if (getIdIntersectionCount(ctx, potentialSoftMatch, newContent) > 0) {
+                    // the current potential soft match has a potential id set match with the remaining new
+                    // content so bail out of looking
+                    return null;
                 }
-                if (fromEl === rootFromElement) {
-                    return true;
+
+                // if we have a soft match with the current node, return it
+                if (isSoftMatch(newChild, potentialSoftMatch)) {
+                    return potentialSoftMatch;
                 }
-                if (fromEl.id && originalElementsToPreserve.has(fromEl.id)) {
-                    if (fromEl.id === toEl.id) {
-                        return false;
-                    }
-                    const clonedFromEl = markElementAsNeedingPostMorphSwap(fromEl.id, true);
-                    if (!clonedFromEl) {
-                        throw new Error('missing clone');
+
+                if (isSoftMatch(nextSibling, potentialSoftMatch)) {
+                    // the next new node has a soft match with this node, so
+                    // increment the count of future soft matches
+                    siblingSoftMatchCount++;
+                    nextSibling = nextSibling.nextSibling;
+
+                    // If there are two future soft matches, bail to allow the siblings to soft match
+                    // so that we don't consume future soft matches for the sake of the current node
+                    if (siblingSoftMatchCount >= 2) {
+                        return null;
                     }
-                    Idiomorph.morph(clonedFromEl, toEl);
-                    return false;
                 }
-                if (fromEl instanceof HTMLElement && toEl instanceof HTMLElement) {
-                    if (typeof fromEl.__x !== 'undefined') {
-                        if (!window.Alpine) {
-                            throw new Error('Unable to access Alpine.js though the global window.Alpine variable. Please make sure Alpine.js is loaded before Symfony UX LiveComponent.');
-                        }
-                        if (typeof window.Alpine.morph !== 'function') {
-                            throw new Error('Unable to access Alpine.js morph function. Please make sure the Alpine.js Morph plugin is installed and loaded, see https://alpinejs.dev/plugins/morph for more information.');
-                        }
-                        window.Alpine.morph(fromEl.__x, toEl);
-                    }
-                    if (externalMutationTracker.wasElementAdded(fromEl)) {
-                        fromEl.insertAdjacentElement('afterend', toEl);
-                        return false;
-                    }
-                    if (modifiedFieldElements.includes(fromEl)) {
-                        setValueOnElement(toEl, getElementValue(fromEl));
-                    }
-                    if (fromEl === document.activeElement &&
-                        fromEl !== document.body &&
-                        null !== getModelDirectiveFromElement(fromEl, false)) {
-                        setValueOnElement(toEl, getElementValue(fromEl));
-                    }
-                    const elementChanges = externalMutationTracker.getChangedElement(fromEl);
-                    if (elementChanges) {
-                        elementChanges.applyToElement(toEl);
-                    }
-                    if (fromEl.nodeName.toUpperCase() !== 'OPTION' && fromEl.isEqualNode(toEl)) {
-                        const normalizedFromEl = cloneHTMLElement(fromEl);
-                        normalizeAttributesForComparison(normalizedFromEl);
-                        const normalizedToEl = cloneHTMLElement(toEl);
-                        normalizeAttributesForComparison(normalizedToEl);
-                        if (normalizedFromEl.isEqualNode(normalizedToEl)) {
-                            return false;
-                        }
+
+                // advanced to the next old content child
+                potentialSoftMatch = potentialSoftMatch.nextSibling;
+            }
+
+            return potentialSoftMatch;
+        }
+
+        function parseContent(newContent) {
+            let parser = new DOMParser();
+
+            // remove svgs to avoid false-positive matches on head, etc.
+            let contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
+
+            // if the newContent contains a html, head or body tag, we can simply parse it w/o wrapping
+            if (contentWithSvgsRemoved.match(/<\/html>/) || contentWithSvgsRemoved.match(/<\/head>/) || contentWithSvgsRemoved.match(/<\/body>/)) {
+                let content = parser.parseFromString(newContent, "text/html");
+                // if it is a full HTML document, return the document itself as the parent container
+                if (contentWithSvgsRemoved.match(/<\/html>/)) {
+                    content.generatedByIdiomorph = true;
+                    return content;
+                } else {
+                    // otherwise return the html element as the parent container
+                    let htmlElement = content.firstChild;
+                    if (htmlElement) {
+                        htmlElement.generatedByIdiomorph = true;
+                        return htmlElement;
+                    } else {
+                        return null;
                     }
                 }
-                if (fromEl.hasAttribute('data-skip-morph') || (fromEl.id && fromEl.id !== toEl.id)) {
-                    fromEl.innerHTML = toEl.innerHTML;
-                    return true;
-                }
-                if (fromEl.parentElement?.hasAttribute('data-skip-morph')) {
-                    return false;
-                }
-                return !fromEl.hasAttribute('data-live-ignore');
-            },
-            beforeNodeRemoved(node) {
-                if (!(node instanceof HTMLElement)) {
-                    return true;
-                }
-                if (node.id && originalElementsToPreserve.has(node.id)) {
-                    markElementAsNeedingPostMorphSwap(node.id, false);
-                    return true;
-                }
-                if (externalMutationTracker.wasElementAdded(node)) {
-                    return false;
+            } else {
+                // if it is partial HTML, wrap it in a template tag to provide a parent element and also to help
+                // deal with touchy tags like tr, tbody, etc.
+                let responseDoc = parser.parseFromString("<body><template>" + newContent + "</template></body>", "text/html");
+                let content = responseDoc.body.querySelector('template').content;
+                content.generatedByIdiomorph = true;
+                return content
+            }
+        }
+
+        function normalizeContent(newContent) {
+            if (newContent == null) {
+                // noinspection UnnecessaryLocalVariableJS
+                const dummyParent = document.createElement('div');
+                return dummyParent;
+            } else if (newContent.generatedByIdiomorph) {
+                // the template tag created by idiomorph parsing can serve as a dummy parent
+                return newContent;
+            } else if (newContent instanceof Node) {
+                // a single node is added as a child to a dummy parent
+                const dummyParent = document.createElement('div');
+                dummyParent.append(newContent);
+                return dummyParent;
+            } else {
+                // all nodes in the array or HTMLElement collection are consolidated under
+                // a single dummy parent element
+                const dummyParent = document.createElement('div');
+                for (const elt of [...newContent]) {
+                    dummyParent.append(elt);
                 }
-                return !node.hasAttribute('data-live-ignore');
-            },
-        },
-    });
-    originalElementIdsToSwapAfter.forEach((id) => {
-        const newElement = rootFromElement.querySelector(`#${id}`);
-        const originalElement = originalElementsToPreserve.get(id);
-        if (!(newElement instanceof HTMLElement) || !(originalElement instanceof HTMLElement)) {
-            throw new Error('Missing elements.');
+                return dummyParent;
+            }
         }
-        newElement.replaceWith(originalElement);
-    });
-}
 
-class UnsyncedInputsTracker {
-    constructor(component, modelElementResolver) {
-        this.elementEventListeners = [
-            { event: 'input', callback: (event) => this.handleInputEvent(event) },
-        ];
-        this.component = component;
-        this.modelElementResolver = modelElementResolver;
-        this.unsyncedInputs = new UnsyncedInputContainer();
-    }
-    activate() {
-        this.elementEventListeners.forEach(({ event, callback }) => {
-            this.component.element.addEventListener(event, callback);
-        });
-    }
-    deactivate() {
-        this.elementEventListeners.forEach(({ event, callback }) => {
-            this.component.element.removeEventListener(event, callback);
-        });
-    }
-    markModelAsSynced(modelName) {
-        this.unsyncedInputs.markModelAsSynced(modelName);
-    }
-    handleInputEvent(event) {
-        const target = event.target;
-        if (!target) {
-            return;
-        }
-        this.updateModelFromElement(target);
-    }
-    updateModelFromElement(element) {
-        if (!elementBelongsToThisComponent(element, this.component)) {
-            return;
-        }
-        if (!(element instanceof HTMLElement)) {
-            throw new Error('Could not update model for non HTMLElement');
+        function insertSiblings(previousSibling, morphedNode, nextSibling) {
+            let stack = [];
+            let added = [];
+            while (previousSibling != null) {
+                stack.push(previousSibling);
+                previousSibling = previousSibling.previousSibling;
+            }
+            while (stack.length > 0) {
+                let node = stack.pop();
+                added.push(node); // push added preceding siblings on in order and insert
+                morphedNode.parentElement.insertBefore(node, morphedNode);
+            }
+            added.push(morphedNode);
+            while (nextSibling != null) {
+                stack.push(nextSibling);
+                added.push(nextSibling); // here we are going in order, so push on as we scan, rather than add
+                nextSibling = nextSibling.nextSibling;
+            }
+            while (stack.length > 0) {
+                morphedNode.parentElement.insertBefore(stack.pop(), morphedNode.nextSibling);
+            }
+            return added;
         }
-        const modelName = this.modelElementResolver.getModelName(element);
-        this.unsyncedInputs.add(element, modelName);
-    }
-    getUnsyncedInputs() {
-        return this.unsyncedInputs.allUnsyncedInputs();
-    }
-    getUnsyncedModels() {
-        return Array.from(this.unsyncedInputs.getUnsyncedModelNames());
-    }
-    resetUnsyncedFields() {
-        this.unsyncedInputs.resetUnsyncedFields();
-    }
-}
-class UnsyncedInputContainer {
-    constructor() {
-        this.unsyncedNonModelFields = [];
-        this.unsyncedModelNames = [];
-        this.unsyncedModelFields = new Map();
-    }
-    add(element, modelName = null) {
-        if (modelName) {
-            this.unsyncedModelFields.set(modelName, element);
-            if (!this.unsyncedModelNames.includes(modelName)) {
-                this.unsyncedModelNames.push(modelName);
+
+        function findBestNodeMatch(newContent, oldNode, ctx) {
+            let currentElement;
+            currentElement = newContent.firstChild;
+            let bestElement = currentElement;
+            let score = 0;
+            while (currentElement) {
+                let newScore = scoreElement(currentElement, oldNode, ctx);
+                if (newScore > score) {
+                    bestElement = currentElement;
+                    score = newScore;
+                }
+                currentElement = currentElement.nextSibling;
             }
-            return;
+            return bestElement;
         }
-        this.unsyncedNonModelFields.push(element);
-    }
-    resetUnsyncedFields() {
-        this.unsyncedModelFields.forEach((value, key) => {
-            if (!this.unsyncedModelNames.includes(key)) {
-                this.unsyncedModelFields.delete(key);
+
+        function scoreElement(node1, node2, ctx) {
+            if (isSoftMatch(node1, node2)) {
+                return .5 + getIdIntersectionCount(ctx, node1, node2);
             }
-        });
-    }
-    allUnsyncedInputs() {
-        return [...this.unsyncedNonModelFields, ...this.unsyncedModelFields.values()];
-    }
-    markModelAsSynced(modelName) {
-        const index = this.unsyncedModelNames.indexOf(modelName);
-        if (index !== -1) {
-            this.unsyncedModelNames.splice(index, 1);
+            return 0;
         }
-    }
-    getUnsyncedModelNames() {
-        return this.unsyncedModelNames;
-    }
-}
 
-class HookManager {
-    constructor() {
-        this.hooks = new Map();
-    }
-    register(hookName, callback) {
-        const hooks = this.hooks.get(hookName) || [];
-        hooks.push(callback);
-        this.hooks.set(hookName, hooks);
-    }
-    unregister(hookName, callback) {
-        const hooks = this.hooks.get(hookName) || [];
-        const index = hooks.indexOf(callback);
-        if (index === -1) {
-            return;
+        function removeNode(tempNode, ctx) {
+            removeIdsFromConsideration(ctx, tempNode);
+            if (ctx.callbacks.beforeNodeRemoved(tempNode) === false) return;
+
+            tempNode.remove();
+            ctx.callbacks.afterNodeRemoved(tempNode);
         }
-        hooks.splice(index, 1);
-        this.hooks.set(hookName, hooks);
-    }
-    triggerHook(hookName, ...args) {
-        const hooks = this.hooks.get(hookName) || [];
-        hooks.forEach((callback) => callback(...args));
-    }
-}
 
-class BackendResponse {
-    constructor(response) {
-        this.response = response;
-    }
-    async getBody() {
-        if (!this.body) {
-            this.body = await this.response.text();
+        //=============================================================================
+        // ID Set Functions
+        //=============================================================================
+
+        function isIdInConsideration(ctx, id) {
+            return !ctx.deadIds.has(id);
         }
-        return this.body;
-    }
-}
 
-class ChangingItemsTracker {
-    constructor() {
-        this.changedItems = new Map();
-        this.removedItems = new Map();
-    }
-    setItem(itemName, newValue, previousValue) {
-        if (this.removedItems.has(itemName)) {
-            const removedRecord = this.removedItems.get(itemName);
-            this.removedItems.delete(itemName);
-            if (removedRecord.original === newValue) {
-                return;
+        function idIsWithinNode(ctx, id, targetNode) {
+            let idSet = ctx.idMap.get(targetNode) || EMPTY_SET;
+            return idSet.has(id);
+        }
+
+        function removeIdsFromConsideration(ctx, node) {
+            let idSet = ctx.idMap.get(node) || EMPTY_SET;
+            for (const id of idSet) {
+                ctx.deadIds.add(id);
             }
         }
-        if (this.changedItems.has(itemName)) {
-            const originalRecord = this.changedItems.get(itemName);
-            if (originalRecord.original === newValue) {
-                this.changedItems.delete(itemName);
-                return;
+
+        function getIdIntersectionCount(ctx, node1, node2) {
+            let sourceSet = ctx.idMap.get(node1) || EMPTY_SET;
+            let matchCount = 0;
+            for (const id of sourceSet) {
+                // a potential match is an id in the source and potentialIdsSet, but
+                // that has not already been merged into the DOM
+                if (isIdInConsideration(ctx, id) && idIsWithinNode(ctx, id, node2)) {
+                    ++matchCount;
+                }
             }
-            this.changedItems.set(itemName, { original: originalRecord.original, new: newValue });
-            return;
+            return matchCount;
         }
-        this.changedItems.set(itemName, { original: previousValue, new: newValue });
-    }
-    removeItem(itemName, currentValue) {
-        let trueOriginalValue = currentValue;
-        if (this.changedItems.has(itemName)) {
-            const originalRecord = this.changedItems.get(itemName);
-            trueOriginalValue = originalRecord.original;
-            this.changedItems.delete(itemName);
-            if (trueOriginalValue === null) {
-                return;
+
+        /**
+         * A bottom up algorithm that finds all elements with ids inside of the node
+         * argument and populates id sets for those nodes and all their parents, generating
+         * a set of ids contained within all nodes for the entire hierarchy in the DOM
+         *
+         * @param node {Element}
+         * @param {Map<Node, Set<String>>} idMap
+         */
+        function populateIdMapForNode(node, idMap) {
+            let nodeParent = node.parentElement;
+            // find all elements with an id property
+            let idElements = node.querySelectorAll('[id]');
+            for (const elt of idElements) {
+                let current = elt;
+                // walk up the parent hierarchy of that element, adding the id
+                // of element to the parent's id set
+                while (current !== nodeParent && current != null) {
+                    let idSet = idMap.get(current);
+                    // if the id set doesn't exist, create it and insert it in the  map
+                    if (idSet == null) {
+                        idSet = new Set();
+                        idMap.set(current, idSet);
+                    }
+                    idSet.add(elt.id);
+                    current = current.parentElement;
+                }
             }
         }
-        if (!this.removedItems.has(itemName)) {
-            this.removedItems.set(itemName, { original: trueOriginalValue });
+
+        /**
+         * This function computes a map of nodes to all ids contained within that node (inclusive of the
+         * node).  This map can be used to ask if two nodes have intersecting sets of ids, which allows
+         * for a looser definition of "matching" than tradition id matching, and allows child nodes
+         * to contribute to a parent nodes matching.
+         *
+         * @param {Element} oldContent  the old content that will be morphed
+         * @param {Element} newContent  the new content to morph to
+         * @returns {Map<Node, Set<String>>} a map of nodes to id sets for the
+         */
+        function createIdMap(oldContent, newContent) {
+            let idMap = new Map();
+            populateIdMapForNode(oldContent, idMap);
+            populateIdMapForNode(newContent, idMap);
+            return idMap;
         }
-    }
-    getChangedItems() {
-        return Array.from(this.changedItems, ([name, { new: value }]) => ({ name, value }));
-    }
-    getRemovedItems() {
-        return Array.from(this.removedItems.keys());
-    }
-    isEmpty() {
-        return this.changedItems.size === 0 && this.removedItems.size === 0;
-    }
-}
 
-class ElementChanges {
-    constructor() {
-        this.addedClasses = new Set();
-        this.removedClasses = new Set();
-        this.styleChanges = new ChangingItemsTracker();
-        this.attributeChanges = new ChangingItemsTracker();
-    }
-    addClass(className) {
-        if (!this.removedClasses.delete(className)) {
-            this.addedClasses.add(className);
+        //=============================================================================
+        // This is what ends up becoming the Idiomorph global object
+        //=============================================================================
+        return {
+            morph,
+            defaults
         }
-    }
-    removeClass(className) {
-        if (!this.addedClasses.delete(className)) {
-            this.removedClasses.add(className);
+    })();
+
+function normalizeAttributesForComparison(element) {
+    const isFileInput = element instanceof HTMLInputElement && element.type === 'file';
+    if (!isFileInput) {
+        if ('value' in element) {
+            element.setAttribute('value', element.value);
+        }
+        else if (element.hasAttribute('value')) {
+            element.setAttribute('value', '');
         }
     }
-    addStyle(styleName, newValue, originalValue) {
-        this.styleChanges.setItem(styleName, newValue, originalValue);
-    }
-    removeStyle(styleName, originalValue) {
-        this.styleChanges.removeItem(styleName, originalValue);
-    }
-    addAttribute(attributeName, newValue, originalValue) {
-        this.attributeChanges.setItem(attributeName, newValue, originalValue);
-    }
-    removeAttribute(attributeName, originalValue) {
-        this.attributeChanges.removeItem(attributeName, originalValue);
-    }
-    getAddedClasses() {
-        return [...this.addedClasses];
-    }
-    getRemovedClasses() {
-        return [...this.removedClasses];
-    }
-    getChangedStyles() {
-        return this.styleChanges.getChangedItems();
-    }
-    getRemovedStyles() {
-        return this.styleChanges.getRemovedItems();
-    }
-    getChangedAttributes() {
-        return this.attributeChanges.getChangedItems();
-    }
-    getRemovedAttributes() {
-        return this.attributeChanges.getRemovedItems();
-    }
-    applyToElement(element) {
-        element.classList.add(...this.addedClasses);
-        element.classList.remove(...this.removedClasses);
-        this.styleChanges.getChangedItems().forEach((change) => {
-            element.style.setProperty(change.name, change.value);
-            return;
-        });
-        this.styleChanges.getRemovedItems().forEach((styleName) => {
-            element.style.removeProperty(styleName);
-        });
-        this.attributeChanges.getChangedItems().forEach((change) => {
-            element.setAttribute(change.name, change.value);
-        });
-        this.attributeChanges.getRemovedItems().forEach((attributeName) => {
-            element.removeAttribute(attributeName);
-        });
-    }
-    isEmpty() {
-        return (this.addedClasses.size === 0 &&
-            this.removedClasses.size === 0 &&
-            this.styleChanges.isEmpty() &&
-            this.attributeChanges.isEmpty());
-    }
+    Array.from(element.children).forEach((child) => {
+        normalizeAttributesForComparison(child);
+    });
 }
 
-class ExternalMutationTracker {
-    constructor(element, shouldTrackChangeCallback) {
-        this.changedElements = new WeakMap();
-        this.changedElementsCount = 0;
-        this.addedElements = [];
-        this.removedElements = [];
-        this.isStarted = false;
-        this.element = element;
-        this.shouldTrackChangeCallback = shouldTrackChangeCallback;
-        this.mutationObserver = new MutationObserver(this.onMutations.bind(this));
+const syncAttributes = (fromEl, toEl) => {
+    for (let i = 0; i < fromEl.attributes.length; i++) {
+        const attr = fromEl.attributes[i];
+        toEl.setAttribute(attr.name, attr.value);
     }
-    start() {
-        if (this.isStarted) {
-            return;
+};
+function executeMorphdom(rootFromElement, rootToElement, modifiedFieldElements, getElementValue, externalMutationTracker) {
+    const originalElementIdsToSwapAfter = [];
+    const originalElementsToPreserve = new Map();
+    const markElementAsNeedingPostMorphSwap = (id, replaceWithClone) => {
+        const oldElement = originalElementsToPreserve.get(id);
+        if (!(oldElement instanceof HTMLElement)) {
+            throw new Error(`Original element with id ${id} not found`);
         }
-        this.mutationObserver.observe(this.element, {
-            childList: true,
-            subtree: true,
-            attributes: true,
-            attributeOldValue: true,
-        });
-        this.isStarted = true;
-    }
-    stop() {
-        if (this.isStarted) {
-            this.mutationObserver.disconnect();
-            this.isStarted = false;
+        originalElementIdsToSwapAfter.push(id);
+        if (!replaceWithClone) {
+            return null;
         }
-    }
-    getChangedElement(element) {
-        return this.changedElements.has(element) ? this.changedElements.get(element) : null;
-    }
-    getAddedElements() {
-        return this.addedElements;
-    }
-    wasElementAdded(element) {
-        return this.addedElements.includes(element);
-    }
-    handlePendingChanges() {
-        this.onMutations(this.mutationObserver.takeRecords());
-    }
-    onMutations(mutations) {
-        const handledAttributeMutations = new WeakMap();
-        for (const mutation of mutations) {
-            const element = mutation.target;
-            if (!this.shouldTrackChangeCallback(element)) {
-                continue;
-            }
-            if (this.isElementAddedByTranslation(element)) {
-                continue;
-            }
-            let isChangeInAddedElement = false;
-            for (const addedElement of this.addedElements) {
-                if (addedElement.contains(element)) {
-                    isChangeInAddedElement = true;
-                    break;
+        const clonedOldElement = cloneHTMLElement(oldElement);
+        oldElement.replaceWith(clonedOldElement);
+        return clonedOldElement;
+    };
+    rootToElement.querySelectorAll('[data-live-preserve]').forEach((newElement) => {
+        const id = newElement.id;
+        if (!id) {
+            throw new Error('The data-live-preserve attribute requires an id attribute to be set on the element');
+        }
+        const oldElement = rootFromElement.querySelector(`#${id}`);
+        if (!(oldElement instanceof HTMLElement)) {
+            throw new Error(`The element with id "${id}" was not found in the original HTML`);
+        }
+        newElement.removeAttribute('data-live-preserve');
+        originalElementsToPreserve.set(id, oldElement);
+        syncAttributes(newElement, oldElement);
+    });
+    Idiomorph.morph(rootFromElement, rootToElement, {
+        callbacks: {
+            beforeNodeMorphed: (fromEl, toEl) => {
+                if (!(fromEl instanceof Element) || !(toEl instanceof Element)) {
+                    return true;
                 }
-            }
-            if (isChangeInAddedElement) {
-                continue;
-            }
-            switch (mutation.type) {
-                case 'childList':
-                    this.handleChildListMutation(mutation);
-                    break;
-                case 'attributes':
-                    if (!handledAttributeMutations.has(element)) {
-                        handledAttributeMutations.set(element, []);
+                if (fromEl === rootFromElement) {
+                    return true;
+                }
+                if (fromEl.id && originalElementsToPreserve.has(fromEl.id)) {
+                    if (fromEl.id === toEl.id) {
+                        return false;
+                    }
+                    const clonedFromEl = markElementAsNeedingPostMorphSwap(fromEl.id, true);
+                    if (!clonedFromEl) {
+                        throw new Error('missing clone');
+                    }
+                    Idiomorph.morph(clonedFromEl, toEl);
+                    return false;
+                }
+                if (fromEl instanceof HTMLElement && toEl instanceof HTMLElement) {
+                    if (typeof fromEl.__x !== 'undefined') {
+                        if (!window.Alpine) {
+                            throw new Error('Unable to access Alpine.js though the global window.Alpine variable. Please make sure Alpine.js is loaded before Symfony UX LiveComponent.');
+                        }
+                        if (typeof window.Alpine.morph !== 'function') {
+                            throw new Error('Unable to access Alpine.js morph function. Please make sure the Alpine.js Morph plugin is installed and loaded, see https://alpinejs.dev/plugins/morph for more information.');
+                        }
+                        window.Alpine.morph(fromEl.__x, toEl);
+                    }
+                    if (externalMutationTracker.wasElementAdded(fromEl)) {
+                        fromEl.insertAdjacentElement('afterend', toEl);
+                        return false;
+                    }
+                    if (modifiedFieldElements.includes(fromEl)) {
+                        setValueOnElement(toEl, getElementValue(fromEl));
+                    }
+                    if (fromEl === document.activeElement &&
+                        fromEl !== document.body &&
+                        null !== getModelDirectiveFromElement(fromEl, false)) {
+                        setValueOnElement(toEl, getElementValue(fromEl));
                     }
-                    if (!handledAttributeMutations.get(element).includes(mutation.attributeName)) {
-                        this.handleAttributeMutation(mutation);
-                        handledAttributeMutations.set(element, [
-                            ...handledAttributeMutations.get(element),
-                            mutation.attributeName,
-                        ]);
+                    const elementChanges = externalMutationTracker.getChangedElement(fromEl);
+                    if (elementChanges) {
+                        elementChanges.applyToElement(toEl);
                     }
-                    break;
-            }
+                    if (fromEl.nodeName.toUpperCase() !== 'OPTION' && fromEl.isEqualNode(toEl)) {
+                        const normalizedFromEl = cloneHTMLElement(fromEl);
+                        normalizeAttributesForComparison(normalizedFromEl);
+                        const normalizedToEl = cloneHTMLElement(toEl);
+                        normalizeAttributesForComparison(normalizedToEl);
+                        if (normalizedFromEl.isEqualNode(normalizedToEl)) {
+                            return false;
+                        }
+                    }
+                }
+                if (fromEl.hasAttribute('data-skip-morph') || (fromEl.id && fromEl.id !== toEl.id)) {
+                    fromEl.innerHTML = toEl.innerHTML;
+                    return true;
+                }
+                if (fromEl.parentElement?.hasAttribute('data-skip-morph')) {
+                    return false;
+                }
+                return !fromEl.hasAttribute('data-live-ignore');
+            },
+            beforeNodeRemoved(node) {
+                if (!(node instanceof HTMLElement)) {
+                    return true;
+                }
+                if (node.id && originalElementsToPreserve.has(node.id)) {
+                    markElementAsNeedingPostMorphSwap(node.id, false);
+                    return true;
+                }
+                if (externalMutationTracker.wasElementAdded(node)) {
+                    return false;
+                }
+                return !node.hasAttribute('data-live-ignore');
+            },
+        },
+    });
+    originalElementIdsToSwapAfter.forEach((id) => {
+        const newElement = rootFromElement.querySelector(`#${id}`);
+        const originalElement = originalElementsToPreserve.get(id);
+        if (!(newElement instanceof HTMLElement) || !(originalElement instanceof HTMLElement)) {
+            throw new Error('Missing elements.');
         }
+        newElement.replaceWith(originalElement);
+    });
+}
+
+class UnsyncedInputsTracker {
+    constructor(component, modelElementResolver) {
+        this.elementEventListeners = [
+            { event: 'input', callback: (event) => this.handleInputEvent(event) },
+        ];
+        this.component = component;
+        this.modelElementResolver = modelElementResolver;
+        this.unsyncedInputs = new UnsyncedInputContainer();
     }
-    handleChildListMutation(mutation) {
-        mutation.addedNodes.forEach((node) => {
-            if (!(node instanceof Element)) {
-                return;
-            }
-            if (this.removedElements.includes(node)) {
-                this.removedElements.splice(this.removedElements.indexOf(node), 1);
-                return;
-            }
-            if (this.isElementAddedByTranslation(node)) {
-                return;
-            }
-            this.addedElements.push(node);
+    activate() {
+        this.elementEventListeners.forEach(({ event, callback }) => {
+            this.component.element.addEventListener(event, callback);
         });
-        mutation.removedNodes.forEach((node) => {
-            if (!(node instanceof Element)) {
-                return;
-            }
-            if (this.addedElements.includes(node)) {
-                this.addedElements.splice(this.addedElements.indexOf(node), 1);
-                return;
-            }
-            this.removedElements.push(node);
+    }
+    deactivate() {
+        this.elementEventListeners.forEach(({ event, callback }) => {
+            this.component.element.removeEventListener(event, callback);
         });
     }
-    handleAttributeMutation(mutation) {
-        const element = mutation.target;
-        if (!this.changedElements.has(element)) {
-            this.changedElements.set(element, new ElementChanges());
-            this.changedElementsCount++;
+    markModelAsSynced(modelName) {
+        this.unsyncedInputs.markModelAsSynced(modelName);
+    }
+    handleInputEvent(event) {
+        const target = event.target;
+        if (!target) {
+            return;
         }
-        const changedElement = this.changedElements.get(element);
-        switch (mutation.attributeName) {
-            case 'class':
-                this.handleClassAttributeMutation(mutation, changedElement);
-                break;
-            case 'style':
-                this.handleStyleAttributeMutation(mutation, changedElement);
-                break;
-            default:
-                this.handleGenericAttributeMutation(mutation, changedElement);
+        this.updateModelFromElement(target);
+    }
+    updateModelFromElement(element) {
+        if (!elementBelongsToThisComponent(element, this.component)) {
+            return;
         }
-        if (changedElement.isEmpty()) {
-            this.changedElements.delete(element);
-            this.changedElementsCount--;
+        if (!(element instanceof HTMLElement)) {
+            throw new Error('Could not update model for non HTMLElement');
         }
+        const modelName = this.modelElementResolver.getModelName(element);
+        this.unsyncedInputs.add(element, modelName);
     }
-    handleClassAttributeMutation(mutation, elementChanges) {
-        const element = mutation.target;
-        const previousValue = mutation.oldValue || '';
-        const previousValues = previousValue.match(/(\S+)/gu) || [];
-        const newValues = [].slice.call(element.classList);
-        const addedValues = newValues.filter((value) => !previousValues.includes(value));
-        const removedValues = previousValues.filter((value) => !newValues.includes(value));
-        addedValues.forEach((value) => {
-            elementChanges.addClass(value);
-        });
-        removedValues.forEach((value) => {
-            elementChanges.removeClass(value);
-        });
+    getUnsyncedInputs() {
+        return this.unsyncedInputs.allUnsyncedInputs();
     }
-    handleStyleAttributeMutation(mutation, elementChanges) {
-        const element = mutation.target;
-        const previousValue = mutation.oldValue || '';
-        const previousStyles = this.extractStyles(previousValue);
-        const newValue = element.getAttribute('style') || '';
-        const newStyles = this.extractStyles(newValue);
-        const addedOrChangedStyles = Object.keys(newStyles).filter((key) => previousStyles[key] === undefined || previousStyles[key] !== newStyles[key]);
-        const removedStyles = Object.keys(previousStyles).filter((key) => !newStyles[key]);
-        addedOrChangedStyles.forEach((style) => {
-            elementChanges.addStyle(style, newStyles[style], previousStyles[style] === undefined ? null : previousStyles[style]);
-        });
-        removedStyles.forEach((style) => {
-            elementChanges.removeStyle(style, previousStyles[style]);
-        });
+    getUnsyncedModels() {
+        return Array.from(this.unsyncedInputs.getUnsyncedModelNames());
     }
-    handleGenericAttributeMutation(mutation, elementChanges) {
-        const attributeName = mutation.attributeName;
-        const element = mutation.target;
-        let oldValue = mutation.oldValue;
-        let newValue = element.getAttribute(attributeName);
-        if (oldValue === attributeName) {
-            oldValue = '';
-        }
-        if (newValue === attributeName) {
-            newValue = '';
-        }
-        if (!element.hasAttribute(attributeName)) {
-            if (oldValue === null) {
-                return;
+    resetUnsyncedFields() {
+        this.unsyncedInputs.resetUnsyncedFields();
+    }
+}
+class UnsyncedInputContainer {
+    constructor() {
+        this.unsyncedNonModelFields = [];
+        this.unsyncedModelNames = [];
+        this.unsyncedModelFields = new Map();
+    }
+    add(element, modelName = null) {
+        if (modelName) {
+            this.unsyncedModelFields.set(modelName, element);
+            if (!this.unsyncedModelNames.includes(modelName)) {
+                this.unsyncedModelNames.push(modelName);
             }
-            elementChanges.removeAttribute(attributeName, mutation.oldValue);
-            return;
-        }
-        if (newValue === oldValue) {
             return;
         }
-        elementChanges.addAttribute(attributeName, element.getAttribute(attributeName), mutation.oldValue);
+        this.unsyncedNonModelFields.push(element);
     }
-    extractStyles(styles) {
-        const styleObject = {};
-        styles.split(';').forEach((style) => {
-            const parts = style.split(':');
-            if (parts.length === 1) {
-                return;
+    resetUnsyncedFields() {
+        this.unsyncedModelFields.forEach((value, key) => {
+            if (!this.unsyncedModelNames.includes(key)) {
+                this.unsyncedModelFields.delete(key);
             }
-            const property = parts[0].trim();
-            styleObject[property] = parts.slice(1).join(':').trim();
         });
-        return styleObject;
     }
-    isElementAddedByTranslation(element) {
-        return element.tagName === 'FONT' && element.getAttribute('style') === 'vertical-align: inherit;';
+    allUnsyncedInputs() {
+        return [...this.unsyncedNonModelFields, ...this.unsyncedModelFields.values()];
+    }
+    markModelAsSynced(modelName) {
+        const index = this.unsyncedModelNames.indexOf(modelName);
+        if (index !== -1) {
+            this.unsyncedModelNames.splice(index, 1);
+        }
+    }
+    getUnsyncedModelNames() {
+        return this.unsyncedModelNames;
     }
 }
 
-let componentMapByElement = new WeakMap();
-let componentMapByComponent = new Map();
-const registerComponent = (component) => {
-    componentMapByElement.set(component.element, component);
-    componentMapByComponent.set(component, component.name);
-};
-const unregisterComponent = (component) => {
-    componentMapByElement.delete(component.element);
-    componentMapByComponent.delete(component);
-};
-const getComponent = (element) => new Promise((resolve, reject) => {
-    let count = 0;
-    const maxCount = 10;
-    const interval = setInterval(() => {
-        const component = componentMapByElement.get(element);
-        if (component) {
-            clearInterval(interval);
-            resolve(component);
-        }
-        count++;
-        if (count > maxCount) {
-            clearInterval(interval);
-            reject(new Error(`Component not found for element ${getElementAsTagText(element)}`));
-        }
-    }, 5);
-});
-const findComponents = (currentComponent, onlyParents, onlyMatchName) => {
-    const components = [];
-    componentMapByComponent.forEach((componentName, component) => {
-        if (onlyParents && (currentComponent === component || !component.element.contains(currentComponent.element))) {
-            return;
+function getDeepData(data, propertyPath) {
+    const { currentLevelData, finalKey } = parseDeepData(data, propertyPath);
+    if (currentLevelData === undefined) {
+        return undefined;
+    }
+    return currentLevelData[finalKey];
+}
+const parseDeepData = (data, propertyPath) => {
+    const finalData = JSON.parse(JSON.stringify(data));
+    let currentLevelData = finalData;
+    const parts = propertyPath.split('.');
+    for (let i = 0; i < parts.length - 1; i++) {
+        currentLevelData = currentLevelData[parts[i]];
+    }
+    const finalKey = parts[parts.length - 1];
+    return {
+        currentLevelData,
+        finalData,
+        finalKey,
+        parts,
+    };
+};
+
+class ValueStore {
+    constructor(props) {
+        this.props = {};
+        this.dirtyProps = {};
+        this.pendingProps = {};
+        this.updatedPropsFromParent = {};
+        this.props = props;
+    }
+    get(name) {
+        const normalizedName = normalizeModelName(name);
+        if (this.dirtyProps[normalizedName] !== undefined) {
+            return this.dirtyProps[normalizedName];
         }
-        if (onlyMatchName && componentName !== onlyMatchName) {
-            return;
+        if (this.pendingProps[normalizedName] !== undefined) {
+            return this.pendingProps[normalizedName];
         }
-        components.push(component);
-    });
-    return components;
-};
-const findChildren = (currentComponent) => {
-    const children = [];
-    componentMapByComponent.forEach((componentName, component) => {
-        if (currentComponent === component) {
-            return;
+        if (this.props[normalizedName] !== undefined) {
+            return this.props[normalizedName];
         }
-        if (!currentComponent.element.contains(component.element)) {
-            return;
+        return getDeepData(this.props, normalizedName);
+    }
+    has(name) {
+        return this.get(name) !== undefined;
+    }
+    set(name, value) {
+        const normalizedName = normalizeModelName(name);
+        if (this.get(normalizedName) === value) {
+            return false;
         }
-        let foundChildComponent = false;
-        componentMapByComponent.forEach((childComponentName, childComponent) => {
-            if (foundChildComponent) {
-                return;
-            }
-            if (childComponent === component) {
-                return;
-            }
-            if (childComponent.element.contains(component.element)) {
-                foundChildComponent = true;
+        this.dirtyProps[normalizedName] = value;
+        return true;
+    }
+    getOriginalProps() {
+        return { ...this.props };
+    }
+    getDirtyProps() {
+        return { ...this.dirtyProps };
+    }
+    getUpdatedPropsFromParent() {
+        return { ...this.updatedPropsFromParent };
+    }
+    flushDirtyPropsToPending() {
+        this.pendingProps = { ...this.dirtyProps };
+        this.dirtyProps = {};
+    }
+    reinitializeAllProps(props) {
+        this.props = props;
+        this.updatedPropsFromParent = {};
+        this.pendingProps = {};
+    }
+    pushPendingPropsBackToDirty() {
+        this.dirtyProps = { ...this.pendingProps, ...this.dirtyProps };
+        this.pendingProps = {};
+    }
+    storeNewPropsFromParent(props) {
+        let changed = false;
+        for (const [key, value] of Object.entries(props)) {
+            const currentValue = this.get(key);
+            if (currentValue !== value) {
+                changed = true;
             }
-        });
-        children.push(component);
-    });
-    return children;
-};
-const findParent = (currentComponent) => {
-    let parentElement = currentComponent.element.parentElement;
-    while (parentElement) {
-        const component = componentMapByElement.get(parentElement);
-        if (component) {
-            return component;
         }
-        parentElement = parentElement.parentElement;
+        if (changed) {
+            this.updatedPropsFromParent = props;
+        }
+        return changed;
     }
-    return null;
-};
+}
 
 class Component {
     constructor(element, name, props, listeners, id, backend, elementDriver) {
@@ -2216,126 +2317,136 @@ function proxifyComponent(component) {
     });
 }
 
-class BackendRequest {
-    constructor(promise, actions, updateModels) {
-        this.isResolved = false;
-        this.promise = promise;
-        this.promise.then((response) => {
-            this.isResolved = true;
-            return response;
-        });
-        this.actions = actions;
-        this.updatedModels = updateModels;
+class StimulusElementDriver {
+    constructor(controller) {
+        this.controller = controller;
     }
-    containsOneOfActions(targetedActions) {
-        return this.actions.filter((action) => targetedActions.includes(action)).length > 0;
+    getModelName(element) {
+        const modelDirective = getModelDirectiveFromElement(element, false);
+        if (!modelDirective) {
+            return null;
+        }
+        return modelDirective.action;
     }
-    areAnyModelsUpdated(targetedModels) {
-        return this.updatedModels.filter((model) => targetedModels.includes(model)).length > 0;
+    getComponentProps() {
+        return this.controller.propsValue;
+    }
+    getEventsToEmit() {
+        return this.controller.eventsToEmitValue;
+    }
+    getBrowserEventsToDispatch() {
+        return this.controller.eventsToDispatchValue;
     }
 }
 
-class RequestBuilder {
-    constructor(url, method = 'post') {
-        this.url = url;
-        this.method = method;
-    }
-    buildRequest(props, actions, updated, children, updatedPropsFromParent, files) {
-        const splitUrl = this.url.split('?');
-        let [url] = splitUrl;
-        const [, queryString] = splitUrl;
-        const params = new URLSearchParams(queryString || '');
-        const fetchOptions = {};
-        fetchOptions.headers = {
-            Accept: 'application/vnd.live-component+html',
-            'X-Requested-With': 'XMLHttpRequest',
-        };
-        const totalFiles = Object.entries(files).reduce((total, current) => total + current.length, 0);
-        const hasFingerprints = Object.keys(children).length > 0;
-        if (actions.length === 0 &&
-            totalFiles === 0 &&
-            this.method === 'get' &&
-            this.willDataFitInUrl(JSON.stringify(props), JSON.stringify(updated), params, JSON.stringify(children), JSON.stringify(updatedPropsFromParent))) {
-            params.set('props', JSON.stringify(props));
-            params.set('updated', JSON.stringify(updated));
-            if (Object.keys(updatedPropsFromParent).length > 0) {
-                params.set('propsFromParent', JSON.stringify(updatedPropsFromParent));
-            }
-            if (hasFingerprints) {
-                params.set('children', JSON.stringify(children));
-            }
-            fetchOptions.method = 'GET';
-        }
-        else {
-            fetchOptions.method = 'POST';
-            const requestData = { props, updated };
-            if (Object.keys(updatedPropsFromParent).length > 0) {
-                requestData.propsFromParent = updatedPropsFromParent;
-            }
-            if (hasFingerprints) {
-                requestData.children = children;
-            }
-            if (actions.length > 0) {
-                if (actions.length === 1) {
-                    requestData.args = actions[0].args;
-                    url += `/${encodeURIComponent(actions[0].name)}`;
-                }
-                else {
-                    url += '/_batch';
-                    requestData.actions = actions;
+function getModelBinding (modelDirective) {
+    let shouldRender = true;
+    let targetEventName = null;
+    let debounce = false;
+    modelDirective.modifiers.forEach((modifier) => {
+        switch (modifier.name) {
+            case 'on':
+                if (!modifier.value) {
+                    throw new Error(`The "on" modifier in ${modelDirective.getString()} requires a value - e.g. on(change).`);
                 }
-            }
-            const formData = new FormData();
-            formData.append('data', JSON.stringify(requestData));
-            for (const [key, value] of Object.entries(files)) {
-                const length = value.length;
-                for (let i = 0; i < length; ++i) {
-                    formData.append(key, value[i]);
+                if (!['input', 'change'].includes(modifier.value)) {
+                    throw new Error(`The "on" modifier in ${modelDirective.getString()} only accepts the arguments "input" or "change".`);
                 }
-            }
-            fetchOptions.body = formData;
+                targetEventName = modifier.value;
+                break;
+            case 'norender':
+                shouldRender = false;
+                break;
+            case 'debounce':
+                debounce = modifier.value ? Number.parseInt(modifier.value) : true;
+                break;
+            default:
+                throw new Error(`Unknown modifier "${modifier.name}" in data-model="${modelDirective.getString()}".`);
         }
-        const paramsString = params.toString();
-        return {
-            url: `${url}${paramsString.length > 0 ? `?${paramsString}` : ''}`,
-            fetchOptions,
-        };
+    });
+    const [modelName, innerModelName] = modelDirective.action.split(':');
+    return {
+        modelName,
+        innerModelName: innerModelName || null,
+        shouldRender,
+        debounce,
+        targetEventName,
+    };
+}
+
+class ChildComponentPlugin {
+    constructor(component) {
+        this.parentModelBindings = [];
+        this.component = component;
+        const modelDirectives = getAllModelDirectiveFromElements(this.component.element);
+        this.parentModelBindings = modelDirectives.map(getModelBinding);
     }
-    willDataFitInUrl(propsJson, updatedJson, params, childrenJson, propsFromParentJson) {
-        const urlEncodedJsonData = new URLSearchParams(propsJson + updatedJson + childrenJson + propsFromParentJson).toString();
-        return (urlEncodedJsonData + params.toString()).length < 1500;
+    attachToComponent(component) {
+        component.on('request:started', (requestData) => {
+            requestData.children = this.getChildrenFingerprints();
+        });
+        component.on('model:set', (model, value) => {
+            this.notifyParentModelChange(model, value);
+        });
+    }
+    getChildrenFingerprints() {
+        const fingerprints = {};
+        this.getChildren().forEach((child) => {
+            if (!child.id) {
+                throw new Error('missing id');
+            }
+            fingerprints[child.id] = {
+                fingerprint: child.fingerprint,
+                tag: child.element.tagName.toLowerCase(),
+            };
+        });
+        return fingerprints;
     }
-}
-
-class Backend {
-    constructor(url, method = 'post') {
-        this.requestBuilder = new RequestBuilder(url, method);
+    notifyParentModelChange(modelName, value) {
+        const parentComponent = findParent(this.component);
+        if (!parentComponent) {
+            return;
+        }
+        this.parentModelBindings.forEach((modelBinding) => {
+            const childModelName = modelBinding.innerModelName || 'value';
+            if (childModelName !== modelName) {
+                return;
+            }
+            parentComponent.set(modelBinding.modelName, value, modelBinding.shouldRender, modelBinding.debounce);
+        });
     }
-    makeRequest(props, actions, updated, children, updatedPropsFromParent, files) {
-        const { url, fetchOptions } = this.requestBuilder.buildRequest(props, actions, updated, children, updatedPropsFromParent, files);
-        return new BackendRequest(fetch(url, fetchOptions), actions.map((backendAction) => backendAction.name), Object.keys(updated));
+    getChildren() {
+        return findChildren(this.component);
     }
 }
 
-class StimulusElementDriver {
-    constructor(controller) {
-        this.controller = controller;
+class LazyPlugin {
+    constructor() {
+        this.intersectionObserver = null;
     }
-    getModelName(element) {
-        const modelDirective = getModelDirectiveFromElement(element, false);
-        if (!modelDirective) {
-            return null;
+    attachToComponent(component) {
+        if ('lazy' !== component.element.attributes.getNamedItem('loading')?.value) {
+            return;
         }
-        return modelDirective.action;
-    }
-    getComponentProps() {
-        return this.controller.propsValue;
-    }
-    getEventsToEmit() {
-        return this.controller.eventsToEmitValue;
+        component.on('connect', () => {
+            this.getObserver().observe(component.element);
+        });
+        component.on('disconnect', () => {
+            this.intersectionObserver?.unobserve(component.element);
+        });
     }
-    getBrowserEventsToDispatch() {
-        return this.controller.eventsToDispatchValue;
+    getObserver() {
+        if (!this.intersectionObserver) {
+            this.intersectionObserver = new IntersectionObserver((entries, observer) => {
+                entries.forEach((entry) => {
+                    if (entry.isIntersecting) {
+                        entry.target.dispatchEvent(new CustomEvent('live:appear'));
+                        observer.unobserve(entry.target);
+                    }
+                });
+            });
+        }
+        return this.intersectionObserver;
     }
 }
 
@@ -2514,23 +2625,6 @@ const parseLoadingAction = (action, isLoading) => {
     throw new Error(`Unknown data-loading action "${action}"`);
 };
 
-class ValidatedFieldsPlugin {
-    attachToComponent(component) {
-        component.on('model:set', (modelName) => {
-            this.handleModelSet(modelName, component.valueStore);
-        });
-    }
-    handleModelSet(modelName, valueStore) {
-        if (valueStore.has('validatedFields')) {
-            const validatedFields = [...valueStore.get('validatedFields')];
-            if (!validatedFields.includes(modelName)) {
-                validatedFields.push(modelName);
-            }
-            valueStore.set('validatedFields', validatedFields);
-        }
-    }
-}
-
 class PageUnloadingPlugin {
     constructor() {
         this.isConnected = false;
@@ -2647,77 +2741,6 @@ class PollingPlugin {
     }
 }
 
-class SetValueOntoModelFieldsPlugin {
-    attachToComponent(component) {
-        this.synchronizeValueOfModelFields(component);
-        component.on('render:finished', () => {
-            this.synchronizeValueOfModelFields(component);
-        });
-    }
-    synchronizeValueOfModelFields(component) {
-        component.element.querySelectorAll('[data-model]').forEach((element) => {
-            if (!(element instanceof HTMLElement)) {
-                throw new Error('Invalid element using data-model.');
-            }
-            if (element instanceof HTMLFormElement) {
-                return;
-            }
-            if (!elementBelongsToThisComponent(element, component)) {
-                return;
-            }
-            const modelDirective = getModelDirectiveFromElement(element);
-            if (!modelDirective) {
-                return;
-            }
-            const modelName = modelDirective.action;
-            if (component.getUnsyncedModels().includes(modelName)) {
-                return;
-            }
-            if (component.valueStore.has(modelName)) {
-                setValueOnElement(element, component.valueStore.get(modelName));
-            }
-            if (element instanceof HTMLSelectElement && !element.multiple) {
-                component.valueStore.set(modelName, getValueFromElement(element, component.valueStore));
-            }
-        });
-    }
-}
-
-function getModelBinding (modelDirective) {
-    let shouldRender = true;
-    let targetEventName = null;
-    let debounce = false;
-    modelDirective.modifiers.forEach((modifier) => {
-        switch (modifier.name) {
-            case 'on':
-                if (!modifier.value) {
-                    throw new Error(`The "on" modifier in ${modelDirective.getString()} requires a value - e.g. on(change).`);
-                }
-                if (!['input', 'change'].includes(modifier.value)) {
-                    throw new Error(`The "on" modifier in ${modelDirective.getString()} only accepts the arguments "input" or "change".`);
-                }
-                targetEventName = modifier.value;
-                break;
-            case 'norender':
-                shouldRender = false;
-                break;
-            case 'debounce':
-                debounce = modifier.value ? Number.parseInt(modifier.value) : true;
-                break;
-            default:
-                throw new Error(`Unknown modifier "${modifier.name}" in data-model="${modelDirective.getString()}".`);
-        }
-    });
-    const [modelName, innerModelName] = modelDirective.action.split(':');
-    return {
-        modelName,
-        innerModelName: innerModelName || null,
-        shouldRender,
-        debounce,
-        targetEventName,
-    };
-}
-
 function isValueEmpty(value) {
     if (null === value || value === '' || undefined === value || (Array.isArray(value) && value.length === 0)) {
         return true;
@@ -2841,79 +2864,56 @@ class QueryStringPlugin {
     }
 }
 
-class ChildComponentPlugin {
-    constructor(component) {
-        this.parentModelBindings = [];
-        this.component = component;
-        const modelDirectives = getAllModelDirectiveFromElements(this.component.element);
-        this.parentModelBindings = modelDirectives.map(getModelBinding);
-    }
+class SetValueOntoModelFieldsPlugin {
     attachToComponent(component) {
-        component.on('request:started', (requestData) => {
-            requestData.children = this.getChildrenFingerprints();
-        });
-        component.on('model:set', (model, value) => {
-            this.notifyParentModelChange(model, value);
+        this.synchronizeValueOfModelFields(component);
+        component.on('render:finished', () => {
+            this.synchronizeValueOfModelFields(component);
         });
     }
-    getChildrenFingerprints() {
-        const fingerprints = {};
-        this.getChildren().forEach((child) => {
-            if (!child.id) {
-                throw new Error('missing id');
+    synchronizeValueOfModelFields(component) {
+        component.element.querySelectorAll('[data-model]').forEach((element) => {
+            if (!(element instanceof HTMLElement)) {
+                throw new Error('Invalid element using data-model.');
             }
-            fingerprints[child.id] = {
-                fingerprint: child.fingerprint,
-                tag: child.element.tagName.toLowerCase(),
-            };
-        });
-        return fingerprints;
-    }
-    notifyParentModelChange(modelName, value) {
-        const parentComponent = findParent(this.component);
-        if (!parentComponent) {
-            return;
-        }
-        this.parentModelBindings.forEach((modelBinding) => {
-            const childModelName = modelBinding.innerModelName || 'value';
-            if (childModelName !== modelName) {
+            if (element instanceof HTMLFormElement) {
                 return;
             }
-            parentComponent.set(modelBinding.modelName, value, modelBinding.shouldRender, modelBinding.debounce);
+            if (!elementBelongsToThisComponent(element, component)) {
+                return;
+            }
+            const modelDirective = getModelDirectiveFromElement(element);
+            if (!modelDirective) {
+                return;
+            }
+            const modelName = modelDirective.action;
+            if (component.getUnsyncedModels().includes(modelName)) {
+                return;
+            }
+            if (component.valueStore.has(modelName)) {
+                setValueOnElement(element, component.valueStore.get(modelName));
+            }
+            if (element instanceof HTMLSelectElement && !element.multiple) {
+                component.valueStore.set(modelName, getValueFromElement(element, component.valueStore));
+            }
         });
     }
-    getChildren() {
-        return findChildren(this.component);
-    }
 }
 
-class LazyPlugin {
-    constructor() {
-        this.intersectionObserver = null;
-    }
+class ValidatedFieldsPlugin {
     attachToComponent(component) {
-        if ('lazy' !== component.element.attributes.getNamedItem('loading')?.value) {
-            return;
-        }
-        component.on('connect', () => {
-            this.getObserver().observe(component.element);
-        });
-        component.on('disconnect', () => {
-            this.intersectionObserver?.unobserve(component.element);
+        component.on('model:set', (modelName) => {
+            this.handleModelSet(modelName, component.valueStore);
         });
     }
-    getObserver() {
-        if (!this.intersectionObserver) {
-            this.intersectionObserver = new IntersectionObserver((entries, observer) => {
-                entries.forEach((entry) => {
-                    if (entry.isIntersecting) {
-                        entry.target.dispatchEvent(new CustomEvent('live:appear'));
-                        observer.unobserve(entry.target);
-                    }
-                });
-            });
+    handleModelSet(modelName, valueStore) {
+        if (valueStore.has('validatedFields')) {
+            const validatedFields = [...valueStore.get('validatedFields')];
+            if (!validatedFields.includes(modelName)) {
+                validatedFields.push(modelName);
+            }
+            valueStore.set('validatedFields', validatedFields);
         }
-        return this.intersectionObserver;
     }
 }
 
diff --git a/src/LiveComponent/assets/src/Component/UnsyncedInputsTracker.ts b/src/LiveComponent/assets/src/Component/UnsyncedInputsTracker.ts
index 151b785a..ed8bd1f8 100644
--- a/src/LiveComponent/assets/src/Component/UnsyncedInputsTracker.ts
+++ b/src/LiveComponent/assets/src/Component/UnsyncedInputsTracker.ts
@@ -1,5 +1,5 @@
-import type { ElementDriver } from './ElementDriver';
 import { elementBelongsToThisComponent } from '../dom_utils';
+import type { ElementDriver } from './ElementDriver';
 import type Component from './index';
 
 export default class {
diff --git a/src/LiveComponent/assets/src/Component/index.ts b/src/LiveComponent/assets/src/Component/index.ts
index 28807c57..7db1f564 100644
--- a/src/LiveComponent/assets/src/Component/index.ts
+++ b/src/LiveComponent/assets/src/Component/index.ts
@@ -1,16 +1,16 @@
 import type { BackendAction, BackendInterface } from '../Backend/Backend';
-import ValueStore from './ValueStore';
-import { normalizeModelName } from '../string_utils';
 import type BackendRequest from '../Backend/BackendRequest';
+import BackendResponse from '../Backend/BackendResponse';
+import { findComponents, registerComponent, unregisterComponent } from '../ComponentRegistry';
+import HookManager from '../HookManager';
+import ExternalMutationTracker from '../Rendering/ExternalMutationTracker';
 import { elementBelongsToThisComponent, getValueFromElement, htmlToElement } from '../dom_utils';
 import { executeMorphdom } from '../morphdom';
-import UnsyncedInputsTracker from './UnsyncedInputsTracker';
+import { normalizeModelName } from '../string_utils';
 import type { ElementDriver } from './ElementDriver';
-import HookManager from '../HookManager';
+import UnsyncedInputsTracker from './UnsyncedInputsTracker';
+import ValueStore from './ValueStore';
 import type { PluginInterface } from './plugins/PluginInterface';
-import BackendResponse from '../Backend/BackendResponse';
-import ExternalMutationTracker from '../Rendering/ExternalMutationTracker';
-import { findComponents, registerComponent, unregisterComponent } from '../ComponentRegistry';
 
 declare const Turbo: any;
 
diff --git a/src/LiveComponent/assets/src/Component/plugins/ChildComponentPlugin.ts b/src/LiveComponent/assets/src/Component/plugins/ChildComponentPlugin.ts
index be7b8ef3..36a0918f 100644
--- a/src/LiveComponent/assets/src/Component/plugins/ChildComponentPlugin.ts
+++ b/src/LiveComponent/assets/src/Component/plugins/ChildComponentPlugin.ts
@@ -1,9 +1,9 @@
-import type Component from '../../Component';
-import type { PluginInterface } from './PluginInterface';
 import type { ChildrenFingerprints } from '../../Backend/Backend';
+import type Component from '../../Component';
+import { findChildren, findParent } from '../../ComponentRegistry';
 import getModelBinding, { type ModelBinding } from '../../Directive/get_model_binding';
 import { getAllModelDirectiveFromElements } from '../../dom_utils';
-import { findChildren, findParent } from '../../ComponentRegistry';
+import type { PluginInterface } from './PluginInterface';
 
 /**
  * Handles all interactions for child components of a component.
diff --git a/src/LiveComponent/assets/src/Component/plugins/LazyPlugin.ts b/src/LiveComponent/assets/src/Component/plugins/LazyPlugin.ts
index 3cdae8ec..944c1eaa 100644
--- a/src/LiveComponent/assets/src/Component/plugins/LazyPlugin.ts
+++ b/src/LiveComponent/assets/src/Component/plugins/LazyPlugin.ts
@@ -1,5 +1,5 @@
-import type { PluginInterface } from './PluginInterface';
 import type Component from '../index';
+import type { PluginInterface } from './PluginInterface';
 
 export default class implements PluginInterface {
     private intersectionObserver: IntersectionObserver | null = null;
diff --git a/src/LiveComponent/assets/src/Component/plugins/LoadingPlugin.ts b/src/LiveComponent/assets/src/Component/plugins/LoadingPlugin.ts
index 211a7f35..de52293d 100644
--- a/src/LiveComponent/assets/src/Component/plugins/LoadingPlugin.ts
+++ b/src/LiveComponent/assets/src/Component/plugins/LoadingPlugin.ts
@@ -1,8 +1,8 @@
+import type BackendRequest from '../../Backend/BackendRequest';
+import type Component from '../../Component';
 import { type Directive, type DirectiveModifier, parseDirectives } from '../../Directive/directives_parser';
 import { elementBelongsToThisComponent } from '../../dom_utils';
 import { combineSpacedArray } from '../../string_utils';
-import type BackendRequest from '../../Backend/BackendRequest';
-import type Component from '../../Component';
 import type { PluginInterface } from './PluginInterface';
 
 interface ElementLoadingDirectives {
diff --git a/src/LiveComponent/assets/src/Component/plugins/PollingPlugin.ts b/src/LiveComponent/assets/src/Component/plugins/PollingPlugin.ts
index 505ecc1d..8a22cabe 100644
--- a/src/LiveComponent/assets/src/Component/plugins/PollingPlugin.ts
+++ b/src/LiveComponent/assets/src/Component/plugins/PollingPlugin.ts
@@ -1,6 +1,6 @@
-import type Component from '../index';
 import { parseDirectives } from '../../Directive/directives_parser';
 import PollingDirector from '../../PollingDirector';
+import type Component from '../index';
 import type { PluginInterface } from './PluginInterface';
 
 export default class implements PluginInterface {
diff --git a/src/LiveComponent/assets/src/Component/plugins/QueryStringPlugin.ts b/src/LiveComponent/assets/src/Component/plugins/QueryStringPlugin.ts
index 2d2b29ea..c0ac2f08 100644
--- a/src/LiveComponent/assets/src/Component/plugins/QueryStringPlugin.ts
+++ b/src/LiveComponent/assets/src/Component/plugins/QueryStringPlugin.ts
@@ -1,6 +1,6 @@
+import { HistoryStrategy, UrlUtils } from '../../url_utils';
 import type Component from '../index';
 import type { PluginInterface } from './PluginInterface';
-import { UrlUtils, HistoryStrategy } from '../../url_utils';
 
 interface QueryMapping {
     /**
diff --git a/src/LiveComponent/assets/src/Component/plugins/SetValueOntoModelFieldsPlugin.ts b/src/LiveComponent/assets/src/Component/plugins/SetValueOntoModelFieldsPlugin.ts
index 9606c487..3ff33c0f 100644
--- a/src/LiveComponent/assets/src/Component/plugins/SetValueOntoModelFieldsPlugin.ts
+++ b/src/LiveComponent/assets/src/Component/plugins/SetValueOntoModelFieldsPlugin.ts
@@ -1,10 +1,10 @@
-import type Component from '../index';
 import {
     elementBelongsToThisComponent,
     getModelDirectiveFromElement,
     getValueFromElement,
     setValueOnElement,
 } from '../../dom_utils';
+import type Component from '../index';
 import type { PluginInterface } from './PluginInterface';
 
 /**
diff --git a/src/LiveComponent/assets/src/Component/plugins/ValidatedFieldsPlugin.ts b/src/LiveComponent/assets/src/Component/plugins/ValidatedFieldsPlugin.ts
index dbc67681..5225b5f3 100644
--- a/src/LiveComponent/assets/src/Component/plugins/ValidatedFieldsPlugin.ts
+++ b/src/LiveComponent/assets/src/Component/plugins/ValidatedFieldsPlugin.ts
@@ -1,5 +1,5 @@
-import type Component from '../index';
 import type ValueStore from '../ValueStore';
+import type Component from '../index';
 import type { PluginInterface } from './PluginInterface';
 
 export default class implements PluginInterface {
diff --git a/src/LiveComponent/assets/src/HookManager.ts b/src/LiveComponent/assets/src/HookManager.ts
index 9db14fcc..87b4a7bf 100644
--- a/src/LiveComponent/assets/src/HookManager.ts
+++ b/src/LiveComponent/assets/src/HookManager.ts
@@ -1,4 +1,4 @@
-import type { ComponentHookName, ComponentHookCallback } from './Component';
+import type { ComponentHookCallback, ComponentHookName } from './Component';
 
 export default class {
     private hooks: Map<ComponentHookName | string, Array<(...args: any[]) => void>> = new Map();
diff --git a/src/LiveComponent/assets/src/dom_utils.ts b/src/LiveComponent/assets/src/dom_utils.ts
index 28b8d233..745e40c5 100644
--- a/src/LiveComponent/assets/src/dom_utils.ts
+++ b/src/LiveComponent/assets/src/dom_utils.ts
@@ -1,8 +1,8 @@
+import type Component from './Component';
 import type ValueStore from './Component/ValueStore';
 import { type Directive, parseDirectives } from './Directive/directives_parser';
-import { normalizeModelName } from './string_utils';
-import type Component from './Component';
 import getElementAsTagText from './Util/getElementAsTagText';
+import { normalizeModelName } from './string_utils';
 
 /**
  * Return the "value" of any given element.
diff --git a/src/LiveComponent/assets/src/live_controller.ts b/src/LiveComponent/assets/src/live_controller.ts
index 3902d56f..278915e0 100644
--- a/src/LiveComponent/assets/src/live_controller.ts
+++ b/src/LiveComponent/assets/src/live_controller.ts
@@ -1,20 +1,20 @@
 import { Controller } from '@hotwired/stimulus';
-import { parseDirectives, type DirectiveModifier } from './Directive/directives_parser';
-import { getModelDirectiveFromElement, getValueFromElement, elementBelongsToThisComponent } from './dom_utils';
-import Component, { proxifyComponent } from './Component';
 import Backend, { type BackendInterface } from './Backend/Backend';
+import Component, { proxifyComponent } from './Component';
 import { StimulusElementDriver } from './Component/ElementDriver';
+import ChildComponentPlugin from './Component/plugins/ChildComponentPlugin';
+import LazyPlugin from './Component/plugins/LazyPlugin';
 import LoadingPlugin from './Component/plugins/LoadingPlugin';
-import ValidatedFieldsPlugin from './Component/plugins/ValidatedFieldsPlugin';
 import PageUnloadingPlugin from './Component/plugins/PageUnloadingPlugin';
+import type { PluginInterface } from './Component/plugins/PluginInterface';
 import PollingPlugin from './Component/plugins/PollingPlugin';
+import QueryStringPlugin from './Component/plugins/QueryStringPlugin';
 import SetValueOntoModelFieldsPlugin from './Component/plugins/SetValueOntoModelFieldsPlugin';
-import type { PluginInterface } from './Component/plugins/PluginInterface';
+import ValidatedFieldsPlugin from './Component/plugins/ValidatedFieldsPlugin';
+import { type DirectiveModifier, parseDirectives } from './Directive/directives_parser';
 import getModelBinding from './Directive/get_model_binding';
-import QueryStringPlugin from './Component/plugins/QueryStringPlugin';
-import ChildComponentPlugin from './Component/plugins/ChildComponentPlugin';
 import getElementAsTagText from './Util/getElementAsTagText';
-import LazyPlugin from './Component/plugins/LazyPlugin';
+import { elementBelongsToThisComponent, getModelDirectiveFromElement, getValueFromElement } from './dom_utils';
 
 export { Component };
 export { getComponent } from './ComponentRegistry';
diff --git a/src/LiveComponent/assets/src/morphdom.ts b/src/LiveComponent/assets/src/morphdom.ts
index 9df94e02..af0eb630 100644
--- a/src/LiveComponent/assets/src/morphdom.ts
+++ b/src/LiveComponent/assets/src/morphdom.ts
@@ -1,8 +1,8 @@
-import { cloneHTMLElement, getModelDirectiveFromElement, setValueOnElement } from './dom_utils';
 // @ts-ignore
 import { Idiomorph } from 'idiomorph/dist/idiomorph.esm.js';
-import { normalizeAttributesForComparison } from './normalize_attributes_for_comparison';
 import type ExternalMutationTracker from './Rendering/ExternalMutationTracker';
+import { cloneHTMLElement, getModelDirectiveFromElement, setValueOnElement } from './dom_utils';
+import { normalizeAttributesForComparison } from './normalize_attributes_for_comparison';
 
 const syncAttributes = (fromEl: Element, toEl: Element): void => {
     for (let i = 0; i < fromEl.attributes.length; i++) {
diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
index 9f99a6c3..14fab318 100644
--- a/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
+++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.d.ts
@@ -1,6 +1,6 @@
-import AbstractMapController from '@symfony/ux-map';
-import type { Point, MarkerDefinition, PolygonDefinition, PolylineDefinition, InfoWindowWithoutPositionDefinition } from '@symfony/ux-map';
 import type { LoaderOptions } from '@googlemaps/js-api-loader';
+import AbstractMapController from '@symfony/ux-map';
+import type { InfoWindowWithoutPositionDefinition, MarkerDefinition, Point, PolygonDefinition, PolylineDefinition } from '@symfony/ux-map';
 type MapOptions = Pick<google.maps.MapOptions, 'mapId' | 'gestureHandling' | 'backgroundColor' | 'disableDoubleClickZoom' | 'zoomControl' | 'zoomControlOptions' | 'mapTypeControl' | 'mapTypeControlOptions' | 'streetViewControl' | 'streetViewControlOptions' | 'fullscreenControl' | 'fullscreenControlOptions'>;
 export default class extends AbstractMapController<MapOptions, google.maps.Map, google.maps.marker.AdvancedMarkerElementOptions, google.maps.marker.AdvancedMarkerElement, google.maps.InfoWindowOptions, google.maps.InfoWindow, google.maps.PolygonOptions, google.maps.Polygon, google.maps.PolylineOptions, google.maps.Polyline> {
     providerOptionsValue: Pick<LoaderOptions, 'apiKey' | 'id' | 'language' | 'region' | 'nonce' | 'retries' | 'url' | 'version' | 'libraries'>;
diff --git a/src/Map/src/Bridge/Google/assets/dist/map_controller.js b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
index 3ce719b7..412db2c1 100644
--- a/src/Map/src/Bridge/Google/assets/dist/map_controller.js
+++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
@@ -1,5 +1,5 @@
-import { Controller } from '@hotwired/stimulus';
 import { Loader } from '@googlemaps/js-api-loader';
+import { Controller } from '@hotwired/stimulus';
 
 class default_1 extends Controller {
     constructor() {
diff --git a/src/Map/src/Bridge/Google/assets/src/map_controller.ts b/src/Map/src/Bridge/Google/assets/src/map_controller.ts
index 3dcd91e6..ff2df70a 100644
--- a/src/Map/src/Bridge/Google/assets/src/map_controller.ts
+++ b/src/Map/src/Bridge/Google/assets/src/map_controller.ts
@@ -7,16 +7,16 @@
  * file that was distributed with this source code.
  */
 
+import type { LoaderOptions } from '@googlemaps/js-api-loader';
+import { Loader } from '@googlemaps/js-api-loader';
 import AbstractMapController from '@symfony/ux-map';
 import type {
-    Point,
+    InfoWindowWithoutPositionDefinition,
     MarkerDefinition,
+    Point,
     PolygonDefinition,
     PolylineDefinition,
-    InfoWindowWithoutPositionDefinition,
 } from '@symfony/ux-map';
-import type { LoaderOptions } from '@googlemaps/js-api-loader';
-import { Loader } from '@googlemaps/js-api-loader';
 
 type MapOptions = Pick<
     google.maps.MapOptions,
diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
index fbe96749..f7475812 100644
--- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
@@ -1,8 +1,8 @@
 import AbstractMapController from '@symfony/ux-map';
-import type { Point, MarkerDefinition, PolygonDefinition, PolylineDefinition, InfoWindowWithoutPositionDefinition } from '@symfony/ux-map';
+import type { InfoWindowWithoutPositionDefinition, MarkerDefinition, Point, PolygonDefinition, PolylineDefinition } from '@symfony/ux-map';
 import 'leaflet/dist/leaflet.min.css';
 import * as L from 'leaflet';
-import type { MapOptions as LeafletMapOptions, MarkerOptions, PopupOptions, PolylineOptions as PolygonOptions, PolylineOptions } from 'leaflet';
+import type { MapOptions as LeafletMapOptions, MarkerOptions, PolylineOptions as PolygonOptions, PolylineOptions, PopupOptions } from 'leaflet';
 type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom'> & {
     tileLayer: {
         url: string;
diff --git a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
index 54e10b4d..b7684446 100644
--- a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
@@ -1,20 +1,20 @@
 import AbstractMapController from '@symfony/ux-map';
 import type {
-    Point,
+    InfoWindowWithoutPositionDefinition,
     MarkerDefinition,
+    Point,
     PolygonDefinition,
     PolylineDefinition,
-    InfoWindowWithoutPositionDefinition,
 } from '@symfony/ux-map';
 import 'leaflet/dist/leaflet.min.css';
 import * as L from 'leaflet';
 import type {
+    LatLngBoundsExpression,
     MapOptions as LeafletMapOptions,
     MarkerOptions,
-    PopupOptions,
     PolylineOptions as PolygonOptions,
     PolylineOptions,
-    LatLngBoundsExpression,
+    PopupOptions,
 } from 'leaflet';
 
 type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom'> & {
diff --git a/src/React/assets/dist/loader.d.ts b/src/React/assets/dist/loader.d.ts
index 5d3cd679..109970c9 100644
--- a/src/React/assets/dist/loader.d.ts
+++ b/src/React/assets/dist/loader.d.ts
@@ -1,5 +1,5 @@
-import { type ComponentCollection } from './components.js';
 import type { ComponentClass, FunctionComponent } from 'react';
+import { type ComponentCollection } from './components.js';
 type Component = string | FunctionComponent<object> | ComponentClass<object, any>;
 declare global {
     function resolveReactComponent(name: string): Component;
diff --git a/src/React/assets/dist/render_controller.d.ts b/src/React/assets/dist/render_controller.d.ts
index 8f6dbd18..f8f47bb9 100644
--- a/src/React/assets/dist/render_controller.d.ts
+++ b/src/React/assets/dist/render_controller.d.ts
@@ -1,5 +1,5 @@
-import { type ReactElement } from 'react';
 import { Controller } from '@hotwired/stimulus';
+import { type ReactElement } from 'react';
 export default class extends Controller {
     readonly componentValue?: string;
     readonly propsValue?: object;
diff --git a/src/React/assets/dist/render_controller.js b/src/React/assets/dist/render_controller.js
index c66a3204..44dbaafc 100644
--- a/src/React/assets/dist/render_controller.js
+++ b/src/React/assets/dist/render_controller.js
@@ -1,6 +1,6 @@
+import { Controller } from '@hotwired/stimulus';
 import React from 'react';
 import require$$0 from 'react-dom';
-import { Controller } from '@hotwired/stimulus';
 
 var client = {};
 
diff --git a/src/React/assets/src/loader.ts b/src/React/assets/src/loader.ts
index ad162fea..605d592e 100644
--- a/src/React/assets/src/loader.ts
+++ b/src/React/assets/src/loader.ts
@@ -7,8 +7,8 @@
  * file that was distributed with this source code.
  */
 
-import { type ComponentCollection, components } from './components.js';
 import type { ComponentClass, FunctionComponent } from 'react';
+import { type ComponentCollection, components } from './components.js';
 
 type Component = string | FunctionComponent<object> | ComponentClass<object, any>;
 
diff --git a/src/React/assets/src/render_controller.ts b/src/React/assets/src/render_controller.ts
index b35d0499..215c6ce1 100644
--- a/src/React/assets/src/render_controller.ts
+++ b/src/React/assets/src/render_controller.ts
@@ -7,9 +7,9 @@
  * file that was distributed with this source code.
  */
 
+import { Controller } from '@hotwired/stimulus';
 import React, { type ReactElement } from 'react';
 import { createRoot } from 'react-dom/client';
-import { Controller } from '@hotwired/stimulus';
 
 export default class extends Controller {
     declare readonly componentValue?: string;
diff --git a/src/StimulusBundle/assets/src/loader.ts b/src/StimulusBundle/assets/src/loader.ts
index d937a61c..5b5009ae 100644
--- a/src/StimulusBundle/assets/src/loader.ts
+++ b/src/StimulusBundle/assets/src/loader.ts
@@ -14,11 +14,11 @@
  */
 import { Application, type ControllerConstructor } from '@hotwired/stimulus';
 import {
-    eagerControllers,
-    lazyControllers,
-    isApplicationDebug,
     type EagerControllersCollection,
     type LazyControllersCollection,
+    eagerControllers,
+    isApplicationDebug,
+    lazyControllers,
 } from './controllers.js';
 
 const controllerAttribute = 'data-controller';
diff --git a/src/Swup/assets/dist/controller.js b/src/Swup/assets/dist/controller.js
index f3a05eef..3eee5435 100644
--- a/src/Swup/assets/dist/controller.js
+++ b/src/Swup/assets/dist/controller.js
@@ -1,9 +1,9 @@
 import { Controller } from '@hotwired/stimulus';
-import Swup from 'swup';
 import SwupDebugPlugin from '@swup/debug-plugin';
-import SwupFormsPlugin from '@swup/forms-plugin';
 import SwupFadeTheme from '@swup/fade-theme';
+import SwupFormsPlugin from '@swup/forms-plugin';
 import SwupSlideTheme from '@swup/slide-theme';
+import Swup from 'swup';
 
 class default_1 extends Controller {
     connect() {
diff --git a/src/Swup/assets/src/controller.ts b/src/Swup/assets/src/controller.ts
index a9facc16..753079eb 100644
--- a/src/Swup/assets/src/controller.ts
+++ b/src/Swup/assets/src/controller.ts
@@ -8,11 +8,11 @@
  */
 
 import { Controller } from '@hotwired/stimulus';
-import Swup from 'swup';
 import SwupDebugPlugin from '@swup/debug-plugin';
-import SwupFormsPlugin from '@swup/forms-plugin';
 import SwupFadeTheme from '@swup/fade-theme';
+import SwupFormsPlugin from '@swup/forms-plugin';
 import SwupSlideTheme from '@swup/slide-theme';
+import Swup from 'swup';
 
 export default class extends Controller {
     declare readonly animateHistoryBrowsingValue: boolean;
diff --git a/src/Translator/assets/dist/translator_controller.js b/src/Translator/assets/dist/translator_controller.js
index b45f3f06..c4504b75 100644
--- a/src/Translator/assets/dist/translator_controller.js
+++ b/src/Translator/assets/dist/translator_controller.js
@@ -1,20 +1,5 @@
 import { IntlMessageFormat } from 'intl-messageformat';
 
-function formatIntl(id, parameters, locale) {
-    if (id === '') {
-        return '';
-    }
-    const intlMessage = new IntlMessageFormat(id, [locale.replace('_', '-')], undefined, { ignoreTag: true });
-    parameters = { ...parameters };
-    Object.entries(parameters).forEach(([key, value]) => {
-        if (key.includes('%') || key.includes('{')) {
-            delete parameters[key];
-            parameters[key.replace(/[%{} ]/g, '').trim()] = value;
-        }
-    });
-    return intlMessage.format(parameters);
-}
-
 function strtr(string, replacePairs) {
     const regex = Object.entries(replacePairs).map(([from]) => {
         return from.replace(/([-[\]{}()*+?.\\^$|#,])/g, '\\$1');
@@ -218,6 +203,21 @@ function getPluralizationRule(number, locale) {
     }
 }
 
+function formatIntl(id, parameters, locale) {
+    if (id === '') {
+        return '';
+    }
+    const intlMessage = new IntlMessageFormat(id, [locale.replace('_', '-')], undefined, { ignoreTag: true });
+    parameters = { ...parameters };
+    Object.entries(parameters).forEach(([key, value]) => {
+        if (key.includes('%') || key.includes('{')) {
+            delete parameters[key];
+            parameters[key.replace(/[%{} ]/g, '').trim()] = value;
+        }
+    });
+    return intlMessage.format(parameters);
+}
+
 let _locale = null;
 let _localeFallbacks = {};
 let _throwWhenNotFound = false;
diff --git a/src/Translator/assets/src/translator.ts b/src/Translator/assets/src/translator.ts
index 2659d171..6c00199e 100644
--- a/src/Translator/assets/src/translator.ts
+++ b/src/Translator/assets/src/translator.ts
@@ -33,8 +33,8 @@ export interface Message<Translations extends TranslationsType, Locale extends L
     };
 }
 
-import { formatIntl } from './formatters/intl-formatter';
 import { format } from './formatters/formatter';
+import { formatIntl } from './formatters/intl-formatter';
 
 let _locale: LocaleType | null = null;
 let _localeFallbacks: Record<LocaleType, LocaleType> = {};


Test patch:
diff --git a/src/Autocomplete/assets/test/controller.test.ts b/src/Autocomplete/assets/test/controller.test.ts
index 3f9a1d92..e17dc6ae 100644
--- a/src/Autocomplete/assets/test/controller.test.ts
+++ b/src/Autocomplete/assets/test/controller.test.ts
@@ -9,14 +9,14 @@
 
 import { Application } from '@hotwired/stimulus';
 import { getByTestId, waitFor } from '@testing-library/dom';
+import userEvent from '@testing-library/user-event';
+import type TomSelect from 'tom-select';
+import { vi } from 'vitest';
+import createFetchMock from 'vitest-fetch-mock';
 import AutocompleteController, {
     type AutocompleteConnectOptions,
     type AutocompletePreConnectOptions,
 } from '../src/controller';
-import userEvent from '@testing-library/user-event';
-import type TomSelect from 'tom-select';
-import createFetchMock from 'vitest-fetch-mock';
-import { vi } from 'vitest';
 
 const shortDelay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));
 
diff --git a/src/Cropperjs/assets/test/controller.test.ts b/src/Cropperjs/assets/test/controller.test.ts
index 4cc5983f..5f1de56a 100644
--- a/src/Cropperjs/assets/test/controller.test.ts
+++ b/src/Cropperjs/assets/test/controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import CropperjsController from '../src/controller';
 
 let cropper: Cropper | null = null;
diff --git a/src/Dropzone/assets/test/controller.test.ts b/src/Dropzone/assets/test/controller.test.ts
index 84859f06..b37dadf4 100644
--- a/src/Dropzone/assets/test/controller.test.ts
+++ b/src/Dropzone/assets/test/controller.test.ts
@@ -8,9 +8,9 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
+import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
 import { getByTestId, waitFor } from '@testing-library/dom';
 import user from '@testing-library/user-event';
-import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
 import DropzoneController from '../src/controller';
 
 // Controller used to check the actual controller was properly booted
diff --git a/src/LazyImage/assets/test/controller.test.ts b/src/LazyImage/assets/test/controller.test.ts
index 5802fe16..346a2e7b 100644
--- a/src/LazyImage/assets/test/controller.test.ts
+++ b/src/LazyImage/assets/test/controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import LazyImageController from '../src/controller';
 
 // Controller used to check the actual controller was properly booted
diff --git a/src/LiveComponent/assets/test/Component/index.test.ts b/src/LiveComponent/assets/test/Component/index.test.ts
index 1d72ee0a..79ef3655 100644
--- a/src/LiveComponent/assets/test/Component/index.test.ts
+++ b/src/LiveComponent/assets/test/Component/index.test.ts
@@ -1,9 +1,9 @@
-import Component, { proxifyComponent } from '../../src/Component';
+import { waitFor } from '@testing-library/dom';
+import { Response } from 'node-fetch';
 import type { BackendAction, BackendInterface } from '../../src/Backend/Backend';
 import BackendRequest from '../../src/Backend/BackendRequest';
-import { Response } from 'node-fetch';
-import { waitFor } from '@testing-library/dom';
 import type BackendResponse from '../../src/Backend/BackendResponse';
+import Component, { proxifyComponent } from '../../src/Component';
 import { noopElementDriver } from '../tools';
 
 interface MockBackend extends BackendInterface {
diff --git a/src/LiveComponent/assets/test/ComponentRegistry.test.ts b/src/LiveComponent/assets/test/ComponentRegistry.test.ts
index e47cc14e..3d1867de 100644
--- a/src/LiveComponent/assets/test/ComponentRegistry.test.ts
+++ b/src/LiveComponent/assets/test/ComponentRegistry.test.ts
@@ -1,8 +1,8 @@
-import Component from '../src/Component';
-import { registerComponent, resetRegistry, getComponent, findComponents } from '../src/ComponentRegistry';
-import BackendRequest from '../src/Backend/BackendRequest';
-import type { BackendInterface } from '../src/Backend/Backend';
 import { Response } from 'node-fetch';
+import type { BackendInterface } from '../src/Backend/Backend';
+import BackendRequest from '../src/Backend/BackendRequest';
+import Component from '../src/Component';
+import { findComponents, getComponent, registerComponent, resetRegistry } from '../src/ComponentRegistry';
 import { noopElementDriver } from './tools';
 
 const createComponent = (element: HTMLElement, name = 'foo-component'): Component => {
diff --git a/src/LiveComponent/assets/test/Directive/get_model_binding.test.ts b/src/LiveComponent/assets/test/Directive/get_model_binding.test.ts
index 4c4ff6c1..cbaaaf5a 100644
--- a/src/LiveComponent/assets/test/Directive/get_model_binding.test.ts
+++ b/src/LiveComponent/assets/test/Directive/get_model_binding.test.ts
@@ -1,5 +1,5 @@
-import getModelBinding from '../../src/Directive/get_model_binding';
 import { parseDirectives } from '../../src/Directive/directives_parser';
+import getModelBinding from '../../src/Directive/get_model_binding';
 
 describe('get_model_binding', () => {
     it('returns correctly with simple directive', () => {
diff --git a/src/LiveComponent/assets/test/Util/getElementAsTagText.test.ts b/src/LiveComponent/assets/test/Util/getElementAsTagText.test.ts
index 4bd12b3a..b311ea7c 100644
--- a/src/LiveComponent/assets/test/Util/getElementAsTagText.test.ts
+++ b/src/LiveComponent/assets/test/Util/getElementAsTagText.test.ts
@@ -1,5 +1,5 @@
-import { htmlToElement } from '../../src/dom_utils';
 import getElementAsTagText from '../../src/Util/getElementAsTagText';
+import { htmlToElement } from '../../src/dom_utils';
 
 describe('getElementAsTagText', () => {
     it('returns self-closing tag correctly', () => {
diff --git a/src/LiveComponent/assets/test/controller/action.test.ts b/src/LiveComponent/assets/test/controller/action.test.ts
index 283e2515..d3e10c1a 100644
--- a/src/LiveComponent/assets/test/controller/action.test.ts
+++ b/src/LiveComponent/assets/test/controller/action.test.ts
@@ -7,9 +7,9 @@
  * file that was distributed with this source code.
  */
 
-import { createTest, initComponent, shutdownTests } from '../tools';
 import { getByText, waitFor } from '@testing-library/dom';
 import userEvent from '@testing-library/user-event';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 describe('LiveController Action Tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/basic.test.ts b/src/LiveComponent/assets/test/controller/basic.test.ts
index bfe91598..3b4d149a 100644
--- a/src/LiveComponent/assets/test/controller/basic.test.ts
+++ b/src/LiveComponent/assets/test/controller/basic.test.ts
@@ -7,11 +7,11 @@
  * file that was distributed with this source code.
  */
 
-import { createTest, initComponent, shutdownTests, startStimulus } from '../tools';
-import { htmlToElement } from '../../src/dom_utils';
 import Component from '../../src/Component';
-import { getComponent } from '../../src/live_controller';
 import { findComponents } from '../../src/ComponentRegistry';
+import { htmlToElement } from '../../src/dom_utils';
+import { getComponent } from '../../src/live_controller';
+import { createTest, initComponent, shutdownTests, startStimulus } from '../tools';
 
 describe('LiveController Basic Tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/child-model.test.ts b/src/LiveComponent/assets/test/controller/child-model.test.ts
index 50b26d05..1bfa53a2 100644
--- a/src/LiveComponent/assets/test/controller/child-model.test.ts
+++ b/src/LiveComponent/assets/test/controller/child-model.test.ts
@@ -7,9 +7,9 @@
  * file that was distributed with this source code.
  */
 
-import { createTest, initComponent, shutdownTests } from '../tools';
 import { getByTestId, waitFor } from '@testing-library/dom';
 import userEvent from '@testing-library/user-event';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 describe('Component parent -> child data-model binding tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/child.test.ts b/src/LiveComponent/assets/test/controller/child.test.ts
index 8d091a5c..58f50060 100644
--- a/src/LiveComponent/assets/test/controller/child.test.ts
+++ b/src/LiveComponent/assets/test/controller/child.test.ts
@@ -7,19 +7,19 @@
  * file that was distributed with this source code.
  */
 
+import { Controller } from '@hotwired/stimulus';
+import { getByTestId, waitFor } from '@testing-library/dom';
+import userEvent from '@testing-library/user-event';
+import { findChildren } from '../../src/ComponentRegistry';
 import {
-    createTestForExistingComponent,
     createTest,
-    initComponent,
-    shutdownTests,
-    getComponent,
+    createTestForExistingComponent,
     dataToJsonAttribute,
+    getComponent,
     getStimulusApplication,
+    initComponent,
+    shutdownTests,
 } from '../tools';
-import { getByTestId, waitFor } from '@testing-library/dom';
-import userEvent from '@testing-library/user-event';
-import { findChildren } from '../../src/ComponentRegistry';
-import { Controller } from '@hotwired/stimulus';
 
 describe('Component parent -> child initialization and rendering tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/emit.test.ts b/src/LiveComponent/assets/test/controller/emit.test.ts
index f9727671..1451f7c9 100644
--- a/src/LiveComponent/assets/test/controller/emit.test.ts
+++ b/src/LiveComponent/assets/test/controller/emit.test.ts
@@ -7,8 +7,8 @@
  * file that was distributed with this source code.
  */
 
-import { createTest, initComponent, shutdownTests } from '../tools';
 import { getByText, waitFor } from '@testing-library/dom';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 describe('LiveController Emit Tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/error.test.ts b/src/LiveComponent/assets/test/controller/error.test.ts
index 883cc77e..70acd75b 100644
--- a/src/LiveComponent/assets/test/controller/error.test.ts
+++ b/src/LiveComponent/assets/test/controller/error.test.ts
@@ -7,9 +7,9 @@
  * file that was distributed with this source code.
  */
 
-import { createTest, initComponent, shutdownTests } from '../tools';
 import { getByText, waitFor } from '@testing-library/dom';
 import type BackendResponse from '../../src/Backend/BackendResponse';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 const getErrorElement = (): Element | null => {
     return document.getElementById('live-component-error');
diff --git a/src/LiveComponent/assets/test/controller/loading.test.ts b/src/LiveComponent/assets/test/controller/loading.test.ts
index 72a07854..69236671 100644
--- a/src/LiveComponent/assets/test/controller/loading.test.ts
+++ b/src/LiveComponent/assets/test/controller/loading.test.ts
@@ -7,9 +7,9 @@
  * file that was distributed with this source code.
  */
 
-import { createTest, initComponent, shutdownTests } from '../tools';
 import { getByTestId, getByText, waitFor } from '@testing-library/dom';
 import userEvent from '@testing-library/user-event';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 describe('LiveController data-loading Tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/model.test.ts b/src/LiveComponent/assets/test/controller/model.test.ts
index e0d92ce0..2aef9dcd 100644
--- a/src/LiveComponent/assets/test/controller/model.test.ts
+++ b/src/LiveComponent/assets/test/controller/model.test.ts
@@ -7,9 +7,9 @@
  * file that was distributed with this source code.
  */
 
-import { createTest, initComponent, shutdownTests } from '../tools';
 import { getByLabelText, getByTestId, getByText, waitFor } from '@testing-library/dom';
 import userEvent from '@testing-library/user-event';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 describe('LiveController data-model Tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/poll.test.ts b/src/LiveComponent/assets/test/controller/poll.test.ts
index 4825261b..1519172f 100644
--- a/src/LiveComponent/assets/test/controller/poll.test.ts
+++ b/src/LiveComponent/assets/test/controller/poll.test.ts
@@ -7,9 +7,9 @@
  * file that was distributed with this source code.
  */
 
-import { shutdownTests, createTest, initComponent } from '../tools';
 import { waitFor } from '@testing-library/dom';
 import userEvent from '@testing-library/user-event';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 describe('LiveController polling Tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/query-binding.test.ts b/src/LiveComponent/assets/test/controller/query-binding.test.ts
index a61f1c0e..f0654efe 100644
--- a/src/LiveComponent/assets/test/controller/query-binding.test.ts
+++ b/src/LiveComponent/assets/test/controller/query-binding.test.ts
@@ -7,8 +7,8 @@
  * file that was distributed with this source code.
  */
 
-import { createTest, initComponent, shutdownTests, setCurrentSearch, expectCurrentSearch } from '../tools';
 import { getByText, waitFor } from '@testing-library/dom';
+import { createTest, expectCurrentSearch, initComponent, setCurrentSearch, shutdownTests } from '../tools';
 
 describe('LiveController query string binding', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/render-with-external-changes.test.ts b/src/LiveComponent/assets/test/controller/render-with-external-changes.test.ts
index 8eb3e034..7109dcc7 100644
--- a/src/LiveComponent/assets/test/controller/render-with-external-changes.test.ts
+++ b/src/LiveComponent/assets/test/controller/render-with-external-changes.test.ts
@@ -7,9 +7,9 @@
  * file that was distributed with this source code.
  */
 
-import { shutdownTests, createTest, initComponent } from '../tools';
 import { getByTestId } from '@testing-library/dom';
 import { htmlToElement } from '../../src/dom_utils';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 describe('LiveController rendering with external changes tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/controller/render.test.ts b/src/LiveComponent/assets/test/controller/render.test.ts
index efa875c0..5dc6c64d 100644
--- a/src/LiveComponent/assets/test/controller/render.test.ts
+++ b/src/LiveComponent/assets/test/controller/render.test.ts
@@ -7,10 +7,10 @@
  * file that was distributed with this source code.
  */
 
-import { shutdownTests, createTest, initComponent } from '../tools';
 import { getByTestId, getByText, waitFor } from '@testing-library/dom';
 import userEvent from '@testing-library/user-event';
 import { htmlToElement } from '../../src/dom_utils';
+import { createTest, initComponent, shutdownTests } from '../tools';
 
 describe('LiveController rendering Tests', () => {
     afterEach(() => {
diff --git a/src/LiveComponent/assets/test/dom_utils.test.ts b/src/LiveComponent/assets/test/dom_utils.test.ts
index 40a4bfb6..a1b0a17d 100644
--- a/src/LiveComponent/assets/test/dom_utils.test.ts
+++ b/src/LiveComponent/assets/test/dom_utils.test.ts
@@ -1,14 +1,14 @@
+import Backend from '../src/Backend/Backend';
+import Component from '../src/Component';
+import ValueStore from '../src/Component/ValueStore';
 import {
-    getValueFromElement,
     cloneHTMLElement,
-    htmlToElement,
-    getModelDirectiveFromElement,
     elementBelongsToThisComponent,
+    getModelDirectiveFromElement,
+    getValueFromElement,
+    htmlToElement,
     setValueOnElement,
 } from '../src/dom_utils';
-import ValueStore from '../src/Component/ValueStore';
-import Component from '../src/Component';
-import Backend from '../src/Backend/Backend';
 import { noopElementDriver } from './tools';
 
 const createStore = (props: any = {}): ValueStore => new ValueStore(props);
diff --git a/src/LiveComponent/assets/test/normalize_attributes_for_comparison.test.ts b/src/LiveComponent/assets/test/normalize_attributes_for_comparison.test.ts
index bce5d55f..2ed1b2a5 100644
--- a/src/LiveComponent/assets/test/normalize_attributes_for_comparison.test.ts
+++ b/src/LiveComponent/assets/test/normalize_attributes_for_comparison.test.ts
@@ -1,5 +1,5 @@
-import { normalizeAttributesForComparison } from '../src/normalize_attributes_for_comparison';
 import { htmlToElement } from '../src/dom_utils';
+import { normalizeAttributesForComparison } from '../src/normalize_attributes_for_comparison';
 
 describe('normalizeAttributesForComparison', () => {
     it('makes no changes if value and attribute not set', () => {
diff --git a/src/LiveComponent/assets/test/tools.ts b/src/LiveComponent/assets/test/tools.ts
index 791d2fe2..41a6b11a 100644
--- a/src/LiveComponent/assets/test/tools.ts
+++ b/src/LiveComponent/assets/test/tools.ts
@@ -1,14 +1,14 @@
 import { Application } from '@hotwired/stimulus';
-import LiveController from '../src/live_controller';
 import { waitFor } from '@testing-library/dom';
-import { htmlToElement } from '../src/dom_utils';
-import Component from '../src/Component';
+import { Response } from 'node-fetch';
 import type { BackendAction, BackendInterface, ChildrenFingerprints } from '../src/Backend/Backend';
 import BackendRequest from '../src/Backend/BackendRequest';
-import { Response } from 'node-fetch';
+import Component from '../src/Component';
+import type { ElementDriver } from '../src/Component/ElementDriver';
 import { setDeepData } from '../src/data_manipulation_utils';
+import { htmlToElement } from '../src/dom_utils';
+import LiveController from '../src/live_controller';
 import LiveControllerDefault from '../src/live_controller';
-import type { ElementDriver } from '../src/Component/ElementDriver';
 
 let activeTests: FunctionalTest[] = [];
 
diff --git a/src/Map/assets/test/abstract_map_controller.test.ts b/src/Map/assets/test/abstract_map_controller.test.ts
index 691f2c5d..eee7def4 100644
--- a/src/Map/assets/test/abstract_map_controller.test.ts
+++ b/src/Map/assets/test/abstract_map_controller.test.ts
@@ -1,8 +1,8 @@
 import { Application } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
-import AbstractMapController from '../src/abstract_map_controller.ts';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import * as L from 'leaflet';
+import AbstractMapController from '../src/abstract_map_controller.ts';
 
 class MyMapController extends AbstractMapController {
     protected dispatchEvent(name: string, payload: Record<string, unknown> = {}): void {
diff --git a/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts b/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
index edee670c..ff5d31d0 100644
--- a/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
+++ b/src/Map/src/Bridge/Google/assets/test/map_controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import GoogleController from '../src/map_controller';
 
 // Controller used to check the actual controller was properly booted
diff --git a/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts b/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
index 379eb603..680f6d3a 100644
--- a/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/test/map_controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import LeafletController from '../src/map_controller';
 
 // Controller used to check the actual controller was properly booted
diff --git a/src/Notify/assets/test/controller.test.ts b/src/Notify/assets/test/controller.test.ts
index bfccfdfd..08a84dc2 100644
--- a/src/Notify/assets/test/controller.test.ts
+++ b/src/Notify/assets/test/controller.test.ts
@@ -8,10 +8,10 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
-import NotifyController from '../src/controller';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import { vi } from 'vitest';
+import NotifyController from '../src/controller';
 
 // Controller used to check the actual controller was properly booted
 class CheckController extends Controller {
diff --git a/src/React/assets/test/register_controller.test.tsx b/src/React/assets/test/register_controller.test.tsx
index 756de1cd..4595a7d9 100644
--- a/src/React/assets/test/register_controller.test.tsx
+++ b/src/React/assets/test/register_controller.test.tsx
@@ -8,9 +8,9 @@
  */
 
 import { registerReactControllerComponents } from '../src/register_controller';
-import MyTsxComponent from './fixtures/MyTsxComponent';
 // @ts-ignore
 import MyJsxComponent from './fixtures/MyJsxComponent';
+import MyTsxComponent from './fixtures/MyTsxComponent';
 import RequireContext = __WebpackModuleApi.RequireContext;
 
 const createFakeFixturesContext = (): RequireContext => {
diff --git a/src/React/assets/test/render_controller.test.tsx b/src/React/assets/test/render_controller.test.tsx
index c9d7d326..f8202d88 100644
--- a/src/React/assets/test/render_controller.test.tsx
+++ b/src/React/assets/test/render_controller.test.tsx
@@ -7,10 +7,10 @@
  * file that was distributed with this source code.
  */
 
-import React from 'react';
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
+import React from 'react';
 import ReactController from '../src/render_controller';
 
 // Controller used to check the actual controller was properly booted
diff --git a/src/StimulusBundle/assets/test/loader.test.ts b/src/StimulusBundle/assets/test/loader.test.ts
index cd7f884f..27fdb650 100644
--- a/src/StimulusBundle/assets/test/loader.test.ts
+++ b/src/StimulusBundle/assets/test/loader.test.ts
@@ -1,9 +1,9 @@
+import { Application, Controller } from '@hotwired/stimulus';
+import { waitFor } from '@testing-library/dom';
 // load from dist because the source TypeScript file points directly to controllers.js,
 // which does not actually exist in the source code
 import { loadControllers } from '../dist/loader';
-import { Application, Controller } from '@hotwired/stimulus';
 import type { EagerControllersCollection, LazyControllersCollection } from '../src/controllers';
-import { waitFor } from '@testing-library/dom';
 
 let isController1Initialized = false;
 let isController2Initialized = false;
diff --git a/src/Svelte/assets/test/render_controller.test.ts b/src/Svelte/assets/test/render_controller.test.ts
index b2d3fbe5..d65f9ade 100644
--- a/src/Svelte/assets/test/render_controller.test.ts
+++ b/src/Svelte/assets/test/render_controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import SvelteController from '../src/render_controller';
 import MyComponent from './fixtures/MyComponent.svelte';
 
diff --git a/src/Swup/assets/test/controller.test.ts b/src/Swup/assets/test/controller.test.ts
index 841dc5d3..c8261b10 100644
--- a/src/Swup/assets/test/controller.test.ts
+++ b/src/Swup/assets/test/controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import SwupController from '../src/controller';
 
 let actualSwupOptions: any = null;
diff --git a/src/TogglePassword/assets/test/controller.test.ts b/src/TogglePassword/assets/test/controller.test.ts
index 6a186ff6..1ce79774 100644
--- a/src/TogglePassword/assets/test/controller.test.ts
+++ b/src/TogglePassword/assets/test/controller.test.ts
@@ -8,9 +8,9 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor, getByText } from '@testing-library/dom';
-import user from '@testing-library/user-event';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, getByText, waitFor } from '@testing-library/dom';
+import user from '@testing-library/user-event';
 import TogglePasswordController from '../src/controller';
 
 // Controller used to check the actual controller was properly booted
diff --git a/src/Translator/assets/test/translator.test.ts b/src/Translator/assets/test/translator.test.ts
index 77cf2c78..2ca0ee28 100644
--- a/src/Translator/assets/test/translator.test.ts
+++ b/src/Translator/assets/test/translator.test.ts
@@ -1,7 +1,7 @@
 import {
-    getLocale,
     type Message,
     type NoParametersType,
+    getLocale,
     setLocale,
     setLocaleFallbacks,
     throwWhenNotFound,
diff --git a/src/Turbo/assets/test/turbo_controller.test.ts b/src/Turbo/assets/test/turbo_controller.test.ts
index 8b958424..996ae01d 100644
--- a/src/Turbo/assets/test/turbo_controller.test.ts
+++ b/src/Turbo/assets/test/turbo_controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application } from '@hotwired/stimulus';
-import { getByTestId } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId } from '@testing-library/dom';
 import TurboController from '../src/turbo_controller';
 
 const startStimulus = () => {
diff --git a/src/Turbo/assets/test/turbo_stream_controller.test.ts b/src/Turbo/assets/test/turbo_stream_controller.test.ts
index 66c33f9b..81042b14 100644
--- a/src/Turbo/assets/test/turbo_stream_controller.test.ts
+++ b/src/Turbo/assets/test/turbo_stream_controller.test.ts
@@ -8,10 +8,10 @@
  */
 
 import { Application } from '@hotwired/stimulus';
-import { getByTestId } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
-import TurboStreamController from '../src/turbo_stream_controller';
+import { getByTestId } from '@testing-library/dom';
 import { vi } from 'vitest';
+import TurboStreamController from '../src/turbo_stream_controller';
 
 const startStimulus = () => {
     const application = Application.start();
diff --git a/src/Typed/assets/test/controller.test.ts b/src/Typed/assets/test/controller.test.ts
index 982eb19d..82d87efa 100644
--- a/src/Typed/assets/test/controller.test.ts
+++ b/src/Typed/assets/test/controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import TypedController from '../src/controller';
 
 // Controller used to check the actual controller was properly booted
diff --git a/src/Vue/assets/test/register_controller.test.ts b/src/Vue/assets/test/register_controller.test.ts
index 9b2ed03e..290ba5b8 100644
--- a/src/Vue/assets/test/register_controller.test.ts
+++ b/src/Vue/assets/test/register_controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { registerVueControllerComponents } from '../src/register_controller';
-import Hello from './fixtures/Hello.vue';
 import Goodbye from './fixtures-lazy/Goodbye.vue';
+import Hello from './fixtures/Hello.vue';
 import RequireContext = __WebpackModuleApi.RequireContext;
 
 const createFakeFixturesContext = (lazyDir: boolean): RequireContext => {
diff --git a/src/Vue/assets/test/render_controller.test.ts b/src/Vue/assets/test/render_controller.test.ts
index 58112b23..8cc2d3a8 100644
--- a/src/Vue/assets/test/render_controller.test.ts
+++ b/src/Vue/assets/test/render_controller.test.ts
@@ -8,8 +8,8 @@
  */
 
 import { Application, Controller } from '@hotwired/stimulus';
-import { getByTestId, waitFor } from '@testing-library/dom';
 import { clearDOM, mountDOM } from '@symfony/stimulus-testing';
+import { getByTestId, waitFor } from '@testing-library/dom';
 import VueController from '../src/render_controller';
 
 // Controller used to check the actual controller was properly booted