Repository: symfony/ux
Pull request #2604 (2025-02-25 15:28:41)

[Notify] Update image link for native notification example
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Issues        | -
| License       | MIT

<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - For new features, provide some code snippets to help understand usage.
 - Features and deprecations must be submitted against branch main.
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

A very minor issue I noticed while looking through the repositories


Patch:
diff --git a/.github/generate-dist-files-size-diff.mjs b/.github/generate-dist-files-size-diff.mjs
index 6dba85c8..3f9022d1 100644
--- a/.github/generate-dist-files-size-diff.mjs
+++ b/.github/generate-dist-files-size-diff.mjs
@@ -6,9 +6,9 @@
 Usage:
 ```shell
 BASE_DIST_FILES='{"src/Autocomplete/assets/dist/controller.js":{"size":15382,"size_gz":3716},"src/Chartjs/assets/dist/controller.js":{"size":2281,"size_gz":771},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475}}' \
-HEAD_DIST_FILES='{"src/Chartjs/assets/dist/controller.js":{"size":1281,"size_gz":171},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475},"src/Cropperjs/assets/dist/style.min.css":{"size":32,"size_gz":66},"src/Dropzone/assets/dist/controller.js":{"size":3199,"size_gz":816},"src/Map/src/Bridge/Google/assets/dist/foo.js":{"size":3199,"size_gz":816}}' \
-HEAD_REPO_NAME='kocal/symfony-ux' \
-HEAD_REF='my-branch-name' \
+PR_DIST_FILES='{"src/Chartjs/assets/dist/controller.js":{"size":1281,"size_gz":171},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475},"src/Cropperjs/assets/dist/style.min.css":{"size":32,"size_gz":66},"src/Dropzone/assets/dist/controller.js":{"size":3199,"size_gz":816},"src/Map/src/Bridge/Google/assets/dist/foo.js":{"size":3199,"size_gz":816}}' \
+GITHUB_REPOSITORY='symfony/ux' \
+GITHUB_HEAD_REF='my-branch-name' \
   node .github/generate-dist-files-size-diff.mjs
 ```
  */
@@ -17,16 +17,16 @@ if (!process.env.BASE_DIST_FILES) {
     throw new Error('Missing or invalid "BASE_DIST_FILES" env variable.');
 }
 
-if (!process.env.HEAD_DIST_FILES) {
-    throw new Error('Missing or invalid "HEAD_DIST_FILES" env variable.');
+if (!process.env.PR_DIST_FILES) {
+    throw new Error('Missing or invalid "PR_DIST_FILES" env variable.');
 }
 
-if (!process.env.HEAD_REPO_NAME) {
-    throw new Error('Missing or invalid "HEAD_REPO_NAME" env variable.');
+if (!process.env.GITHUB_REPOSITORY) {
+    throw new Error('Missing or invalid "GITHUB_REPOSITORY" env variable.');
 }
 
-if (!process.env.HEAD_REF) {
-    throw new Error('Missing or invalid "HEAD_REF" env variable.');
+if (!process.env.GITHUB_HEAD_REF) {
+    throw new Error('Missing or invalid "GITHUB_HEAD_REF" env variable.');
 }
 
 /**
@@ -67,11 +67,11 @@ function formatDiffPercent(percent) {
 }
 
 export function main() {
-    const repoUrl = `https://github.com/${process.env.HEAD_REPO_NAME}`;
+    const repoUrl = `https://github.com/${process.env.GITHUB_REPOSITORY}`;
     /** @type {Record<string, {size: number, size_gz: number}>} */
     const base = JSON.parse(process.env.BASE_DIST_FILES);
     /** @type {Record<string, {size: number, size_gz: number}>} */
-    const pr = JSON.parse(process.env.HEAD_DIST_FILES);
+    const pr = JSON.parse(process.env.PR_DIST_FILES);
     let output = '<h1>📊 Packages dist files size difference</h1>\n\n';
 
     /**
@@ -108,7 +108,7 @@ export function main() {
                         meta: {
                             packageName,
                             bridgeName,
-                            url: isBridge ? `${repoUrl}/tree/${process.env.HEAD_REF}/src/${packageName}/src/Bridge/${bridgeName}/assets/dist` : `${repoUrl}/tree/${process.env.HEAD_REF}/src/${packageName}/assets/dist`,
+                            url: isBridge ? `${repoUrl}/tree/${process.env.GITHUB_HEAD_REF}/src/${packageName}/src/Bridge/${bridgeName}/assets/dist` : `${repoUrl}/tree/${process.env.GITHUB_HEAD_REF}/src/${packageName}/assets/dist`,
                         }, files: new Set(),
                     });
                 }
@@ -126,7 +126,7 @@ export function main() {
                     },
                     meta: {
                         fileNameShort: file.replace(isBridge ? `src/${file.split('/')[1]}/src/Bridge/${file.split('/')[4]}/assets/dist/` : `src/${file.split('/')[1]}/assets/dist/`, ''),
-                        fileNameUrl: `${repoUrl}/blob/${process.env.HEAD_REF}/${file}`,
+                        fileNameUrl: `${repoUrl}/blob/${process.env.GITHUB_HEAD_REF}/${file}`,
                     },
                 });
             }
diff --git a/.github/workflows/dist-files-size-diff.yaml b/.github/workflows/dist-files-size-diff.yaml
index e7d1aed6..ef80d89a 100644
--- a/.github/workflows/dist-files-size-diff.yaml
+++ b/.github/workflows/dist-files-size-diff.yaml
@@ -49,9 +49,7 @@ jobs:
             uses: actions/github-script@v7
             env:
                 BASE_DIST_FILES: ${{ steps.base-dist-files.outputs.files }}
-                HEAD_DIST_FILES: ${{ steps.pr-dist-files.outputs.files }}
-                HEAD_REPO_NAME: ${{ github.event.pull_request.head.repo.full_name }}
-                HEAD_REF: ${{ github.event.pull_request.head.ref }}
+                PR_DIST_FILES: ${{ steps.pr-dist-files.outputs.files }}
             with:
               result-encoding: string
               script: |
diff --git a/bin/rollup.js b/bin/rollup.js
index e002bd41..a75c3036 100644
--- a/bin/rollup.js
+++ b/bin/rollup.js
@@ -108,7 +108,6 @@ function getRollupConfiguration({ packageRoot, inputFiles }) {
             typescript({
                 filterRoot: '.',
                 tsconfig: path.join(__dirname, '..', 'tsconfig.json'),
-                noEmitOnError: true,
                 include: [
                     'src/**/*.ts',
                     // TODO: Remove for the next major release
diff --git a/package.json b/package.json
index 8c51e7df..6f0b5373 100644
--- a/package.json
+++ b/package.json
@@ -6,8 +6,8 @@
         "src/*/src/Bridge/*/assets"
     ],
     "scripts": {
-        "build": "yarn workspaces foreach -Ap --topological-dev run build",
-        "test": "yarn workspaces foreach -Ap --topological-dev run test",
+        "build": "yarn workspaces foreach -Apt run build",
+        "test": "yarn workspaces foreach -Apt run test",
         "check": "biome check",
         "ci": "biome ci"
     },
diff --git a/src/Autocomplete/CHANGELOG.md b/src/Autocomplete/CHANGELOG.md
index 8720236c..1f8cea05 100644
--- a/src/Autocomplete/CHANGELOG.md
+++ b/src/Autocomplete/CHANGELOG.md
@@ -4,7 +4,6 @@
 
 -   Deprecate `ExtraLazyChoiceLoader` in favor of `Symfony\Component\Form\ChoiceList\Loader\LazyChoiceLoader`
 -   Reset TomSelect when updating url attribute #1505
--   Add `getAttributes()` method to define additional attributes for autocomplete results #2541
 
 ## 2.22.0
 
diff --git a/src/Autocomplete/doc/index.rst b/src/Autocomplete/doc/index.rst
index 8240b432..33b635e3 100644
--- a/src/Autocomplete/doc/index.rst
+++ b/src/Autocomplete/doc/index.rst
@@ -315,9 +315,8 @@ the consequent renders, the default ``query_builder`` will be used::
             $builder
                 ->add('food', FoodAutocompleteField::class, [
                     'query_builder' => function (EntityRepository $er) {
-                        $qb = $er->createQueryBuilder('o');
-
-                        return $qb->andWhere($qb->expr()->notIn('o.id', [$currentFoodId]));
+                        return $er->createQueryBuilder('o')
+                            ->andWhere($qb->expr()->notIn('o.id', [$currentFoodId]));
                     };
                 ])
             ;
diff --git a/src/Autocomplete/src/AutocompleteResultsExecutor.php b/src/Autocomplete/src/AutocompleteResultsExecutor.php
index 03d10e03..0061ca8a 100644
--- a/src/Autocomplete/src/AutocompleteResultsExecutor.php
+++ b/src/Autocomplete/src/AutocompleteResultsExecutor.php
@@ -73,7 +73,10 @@ final class AutocompleteResultsExecutor
 
         if (!method_exists($autocompleter, 'getGroupBy') || null === $groupBy = $autocompleter->getGroupBy()) {
             foreach ($paginator as $entity) {
-                $results[] = $this->formatResult($autocompleter, $entity);
+                $results[] = [
+                    'value' => $autocompleter->getValue($entity),
+                    'text' => $autocompleter->getLabel($entity),
+                ];
             }
 
             return new AutocompleteResults($results, $hasNextPage);
@@ -101,7 +104,10 @@ final class AutocompleteResultsExecutor
         $optgroupLabels = [];
 
         foreach ($paginator as $entity) {
-            $result = $this->formatResult($autocompleter, $entity);
+            $result = [
+                'value' => $autocompleter->getValue($entity),
+                'text' => $autocompleter->getLabel($entity),
+            ];
 
             $groupLabels = $groupBy($entity, $result['value'], $result['text']);
 
@@ -118,21 +124,4 @@ final class AutocompleteResultsExecutor
 
         return new AutocompleteResults($results, $hasNextPage, $optgroups);
     }
-
-    /**
-     * @return array<string, mixed>
-     */
-    private function formatResult(EntityAutocompleterInterface $autocompleter, object $entity): array
-    {
-        $attributes = [];
-        if (method_exists($autocompleter, 'getAttributes')) {
-            $attributes = $autocompleter->getAttributes($entity);
-        }
-
-        return [
-            ...$attributes,
-            'value' => $autocompleter->getValue($entity),
-            'text' => $autocompleter->getLabel($entity),
-        ];
-    }
 }
diff --git a/src/Autocomplete/src/EntityAutocompleterInterface.php b/src/Autocomplete/src/EntityAutocompleterInterface.php
index 4ad8a855..df0874fd 100644
--- a/src/Autocomplete/src/EntityAutocompleterInterface.php
+++ b/src/Autocomplete/src/EntityAutocompleterInterface.php
@@ -18,50 +18,30 @@ use Symfony\Bundle\SecurityBundle\Security;
 /**
  * Interface for classes that will have an "autocomplete" endpoint exposed.
  *
- * @template T of object
- *
- * TODO Remove next lines for Symfony UX 3
- *
- * @method array getAttributes(object $entity) Returns extra attributes to add to the autocomplete result.
- * @method mixed getGroupBy()                  Return group_by option.
+ * @method mixed getGroupBy() Return group_by option.
  */
 interface EntityAutocompleterInterface
 {
     /**
      * The fully-qualified entity class this will be autocompleting.
-     *
-     * @return class-string<T>
      */
     public function getEntityClass(): string;
 
     /**
      * Create a query builder that filters for the given "query".
-     *
-     * @param EntityRepository<T> $repository
      */
     public function createFilteredQueryBuilder(EntityRepository $repository, string $query): QueryBuilder;
 
     /**
      * Returns the "choice_label" used to display this entity.
-     *
-     * @param T $entity
      */
     public function getLabel(object $entity): string;
 
     /**
      * Returns the "value" attribute for this entity, usually the id.
-     *
-     * @param T $entity
      */
     public function getValue(object $entity): mixed;
 
-    /**
-     * Returns extra attributes to add to the autocomplete result.
-     *
-     * TODO Uncomment for Symfony UX 3
-     */
-    /* public function getAttributes(object $entity): array; */
-
     /**
      * Return true if access should be granted to the autocomplete results for the current user.
      *
@@ -71,8 +51,6 @@ interface EntityAutocompleterInterface
 
     /*
      * Return group_by option.
-     *
-     * TODO Uncomment for Symfony UX 3
      */
     /* public function getGroupBy(): mixed; */
 }
diff --git a/src/LazyImage/doc/index.rst b/src/LazyImage/doc/index.rst
index 2454ab94..b8369e0f 100644
--- a/src/LazyImage/doc/index.rst
+++ b/src/LazyImage/doc/index.rst
@@ -1,12 +1,6 @@
 Symfony UX LazyImage
 ====================
 
-.. warning::
-
-    **This package is no longer recommended.** Instead, use the
-    `modern techniques to improve image loading performance`_ natively supported
-    by all major browsers.
-
 Symfony UX LazyImage is a Symfony bundle providing utilities to improve
 image loading performance. It is part of `the Symfony UX initiative`_.
 
@@ -251,7 +245,6 @@ This bundle aims at following the same Backward Compatibility promise as
 the Symfony framework:
 https://symfony.com/doc/current/contributing/code/bc.html
 
-.. _`modern techniques to improve image loading performance`: https://web.dev/learn/images/performance-issues
 .. _`the Symfony UX initiative`: https://ux.symfony.com/
 .. _`BlurHash implementation`: https://blurha.sh
 .. _`StimulusBundle`: https://symfony.com/bundles/StimulusBundle/current/index.html
diff --git a/src/LiveComponent/CHANGELOG.md b/src/LiveComponent/CHANGELOG.md
index 7ba03516..a43e9518 100644
--- a/src/LiveComponent/CHANGELOG.md
+++ b/src/LiveComponent/CHANGELOG.md
@@ -3,7 +3,6 @@
 ## 2.23.0
 
 -   Allow configuring the secret used to compute fingerprints and checksums.
--   Prevent `__component` property to be serialized when called `JSON.stringify()`
 
 ## 2.22.0
 
diff --git a/src/LiveComponent/assets/dist/live_controller.js b/src/LiveComponent/assets/dist/live_controller.js
index 41177512..375a5b15 100644
--- a/src/LiveComponent/assets/dist/live_controller.js
+++ b/src/LiveComponent/assets/dist/live_controller.js
@@ -3059,10 +3059,7 @@ class LiveControllerDefault extends Controller {
         const id = this.element.id || null;
         this.component = new Component(this.element, this.nameValue, this.propsValue, this.listenersValue, id, LiveControllerDefault.backendFactory(this), new StimulusElementDriver(this));
         this.proxiedComponent = proxifyComponent(this.component);
-        Object.defineProperty(this.element, '__component', {
-            value: this.proxiedComponent,
-            writable: true,
-        });
+        this.element.__component = this.proxiedComponent;
         if (this.hasDebounceValue) {
             this.component.defaultDebounce = this.debounceValue;
         }
diff --git a/src/LiveComponent/assets/src/live_controller.ts b/src/LiveComponent/assets/src/live_controller.ts
index a9ea7f11..278915e0 100644
--- a/src/LiveComponent/assets/src/live_controller.ts
+++ b/src/LiveComponent/assets/src/live_controller.ts
@@ -285,10 +285,8 @@ export default class LiveControllerDefault extends Controller<HTMLElement> imple
         );
         this.proxiedComponent = proxifyComponent(this.component);
 
-        Object.defineProperty(this.element, '__component', {
-            value: this.proxiedComponent,
-            writable: true,
-        });
+        // @ts-ignore Adding the dynamic property
+        this.element.__component = this.proxiedComponent;
 
         if (this.hasDebounceValue) {
             this.component.defaultDebounce = this.debounceValue;
diff --git a/src/LiveComponent/doc/index.rst b/src/LiveComponent/doc/index.rst
index 69bc0937..4c7b5fc2 100644
--- a/src/LiveComponent/doc/index.rst
+++ b/src/LiveComponent/doc/index.rst
@@ -1326,39 +1326,6 @@ The files will be available in a regular ``$request->files`` files bag::
     need to specify ``multiple`` attribute on HTML element and end ``name``
     with ``[]``.
 
-.. _downloads:
-
-Downloading files
------------------
-
-Currently, Live Components do not natively support returning file responses directly from a LiveAction. However, you can implement file downloads by redirecting to a route that handles the file response.
-
-Create a LiveAction that generates the URL for the file download and returns a ``RedirectResponse``::
-
-        #[LiveAction]
-        public function initiateDownload(UrlGeneratorInterface $urlGenerator): RedirectResponse
-        {
-            $url = $urlGenerator->generate('app_file_download');
-            return new RedirectResponse($url);
-        }
-
-.. code-block:: html+twig
-
-    <div {{ attributes }} data-turbo="false">
-        <button
-            data-action="live#action"
-            data-live-action-param="initiateDownload"
-        >
-            Download
-        </button>
-    </div>
-
-
-.. tip::
-
-    When Turbo is enabled, if a LiveAction response redirects to another URL, Turbo will make a request to prefetch the content. Here, adding ``data-turbo="false"`` ensures that the download URL is called only once.
-
-
 .. _forms:
 
 Forms
@@ -3722,9 +3689,6 @@ Test Helper
 
     The test helper was added in LiveComponents 2.11.
 
-Interact With Live-Components
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
 For testing, you can use the ``InteractsWithLiveComponents`` trait which
 uses Symfony's test client to render and make requests to your components::
 
@@ -3768,9 +3732,9 @@ uses Symfony's test client to render and make requests to your components::
                 ->set('count', 99)
             ;
 
-            // Submit form data ('my_form' for your MyFormType form)
+            // Submit form data
             $testComponent
-                ->submitForm(['my_form' => ['input' => 'value']], 'save');
+                ->submitForm(['form' => ['input' => 'value']], 'save');
 
             $this->assertStringContainsString('Count: 99', $testComponent->render());
 
@@ -3811,54 +3775,6 @@ uses Symfony's test client to render and make requests to your components::
     The ``InteractsWithLiveComponents`` trait can only be used in tests that extend
     ``Symfony\Bundle\FrameworkBundle\Test\KernelTestCase``.
 
-Test LiveCollectionType
-~~~~~~~~~~~~~~~~~~~~~~~
-
-To test the submission of a form within a Live Component (with the above ``submitForm`` helper) containing a ``LiveCollectionType``, you first need to programmatically add the desired number of entries to the form, replicating the action of clicking the "Add" button.
-
-So, if the following are the forms used::
-
-    use Symfony\UX\LiveComponent\Form\Type\LiveCollectionType;
-
-    // Parent FormType used in the Live Component
-    class LiveCollectionFormType extends AbstractType
-    {
-        public function buildForm(FormBuilderInterface $builder, array $options): void
-        {
-            $builder->add('children', LiveCollectionType::class, [
-                    'entry_type' => ChildFormType::class,
-                ])
-            ;
-        }
-    }
-
-    // Child Form Type used for each entry in the collection
-    class ChildFormType extends AbstractType
-    {
-        public function buildForm(FormBuilderInterface $builder, array $options): void
-        {
-            $builder
-                ->add('name', TextType::class)
-                ->add('age', IntegerType::class)
-            ;
-        }
-    }
-
-Use the addCollectionItem method from the LiveCollectionTrait to dynamically add entries to the children field of the form before submitting it::
-
-    // Call the addCollectionItem method as many times as needed, specifying the name of the collection field.
-    $component->call('addCollectionItem', ['name' => 'children']);
-    $component->call('addCollectionItem', ['name' => 'children']);
-    //... can be called as many times as you need entries in your 'children' field
-
-    // ... then submit the form by providing data for all the fields in the ChildFormType for each added entry:
-    $component->submitForm([ 'live_collection_form' => [
-        'children' => [
-            ['name' => 'childName1', 'age' => 10],
-            ['name' => 'childName2', 'age' => 15],
-        ]
-    ]]);
-
 Backward Compatibility promise
 ------------------------------
 
diff --git a/src/LiveComponent/src/ComponentWithFormTrait.php b/src/LiveComponent/src/ComponentWithFormTrait.php
index a25a2d1d..c8ab525d 100644
--- a/src/LiveComponent/src/ComponentWithFormTrait.php
+++ b/src/LiveComponent/src/ComponentWithFormTrait.php
@@ -11,7 +11,6 @@
 
 namespace Symfony\UX\LiveComponent;
 
-use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
 use Symfony\Component\Form\ClearableErrorsInterface;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\Form\FormView;
@@ -135,27 +134,19 @@ trait ComponentWithFormTrait
     /**
      * Reset the form to its initial state, so it can be used again.
      */
-    private function resetForm(bool $soft = false): void
+    private function resetForm(): void
     {
         // prevent the system from trying to submit this reset form
         $this->shouldAutoSubmitForm = false;
         $this->form = null;
         $this->formView = null;
-        if (true !== $soft) {
-            $this->formValues = $this->extractFormValues($this->getFormView());
-        }
+        $this->formValues = $this->extractFormValues($this->getFormView());
     }
 
     private function submitForm(bool $validateAll = true): void
     {
         if (null !== $this->formView) {
-            // Two scenarios can cause this:
-            // 1) Not intended: form was already submitted and validated in the same main request.
-            // 2) Expected: form was submitted during a sub-request (e.g., a batch action).
-            //
-            // Before 2.23, both cases triggered an exception.
-            // Since 2.23, we reset the form (preserving its values) to handle case 2 correctly.
-            $this->resetForm(true);
+            throw new \LogicException('The submitForm() method is being called, but the FormView has already been built. Are you calling $this->getForm() - which creates the FormView - before submitting the form?');
         }
 
         $form = $this->getForm();
@@ -264,39 +255,25 @@ trait ComponentWithFormTrait
                 continue;
             }
 
-            // <input type="checkbox">
             if (\array_key_exists('checked', $child->vars)) {
+                // special handling for check boxes
                 $values[$name] = $child->vars['checked'] ? $child->vars['value'] : null;
                 continue;
             }
 
-            // <select> - Simulate browser behavior
-            // When no option is selected, browsers send the value of the first
-            // option, when the following conditions are met:
-            if (
-                \array_key_exists('choices', $child->vars)
-                && $child->vars['choices']                  // has defined choices
-                && $child->vars['required']                 // is required
-                && !$child->vars['disabled']                // is not disabled
-                && '' === $child->vars['value']             // has no value set  ("0" can be a value)
-                && !array_diff_key(
-                    /* @see \Symfony\Component\Form\Extension\Core\Type\ChoiceType::buildView() */
-                    array_flip(['choices', 'expanded', 'multiple', 'placeholder', 'placeholder_in_choices', 'preferred_choices']),
-                    $child->vars,
-                )
-                && !$child->vars['expanded']                // is a <select>     (not a radio/checkbox)
-                && !$child->vars['multiple']                // is not multiple
-                && !\is_string($child->vars['placeholder'])  // has no placeholder (empty string is valid)
+            if (\array_key_exists('choices', $child->vars)
+                && $child->vars['required']
+                && !$child->vars['disabled']
+                && !$child->vars['value']
+                && (false === $child->vars['placeholder'] || null === $child->vars['placeholder'])
+                && !$child->vars['multiple']
+                && !$child->vars['expanded']
+                && $child->vars['choices']
             ) {
-                $choices = $child->vars['choices'];
-                do {
-                    $choice = $choices[array_key_first($choices)];
-                    if (!$choice instanceof ChoiceGroupView) {
-                        break;
-                    }
-                } while ($choices = $choice->choices);
-
-                $values[$name] = $choice?->value;
+                if (null !== $firstKey = array_key_first($child->vars['choices'])) {
+                    $values[$name] = $child->vars['choices'][$firstKey]->value ?? null;
+                }
+
                 continue;
             }
 
diff --git a/src/Map/CHANGELOG.md b/src/Map/CHANGELOG.md
index 493b1c01..cdad7ae9 100644
--- a/src/Map/CHANGELOG.md
+++ b/src/Map/CHANGELOG.md
@@ -1,17 +1,5 @@
 # CHANGELOG
  
-## 2.23
-
--  Add `DistanceUnit` to represent distance units (`m`, `km`, `miles`, `nmi`) and
-   ease conversion between units.
--  Add `DistanceCalculatorInterface` interface and three implementations:
-   `HaversineDistanceCalculator`, `SphericalCosineDistanceCalculator` and `VincentyDistanceCalculator`.
--  Add `CoordinateUtils` helper, to convert decimal coordinates (`43.2109`) in DMS (`56° 78' 90"`)
--  Add parameter `id` to `Marker`, `Polygon` and `Polyline` constructors
--  Add method `Map::removeMarker(string|Marker $markerOrId)`
--  Add method `Map::removePolygon(string|Polygon $polygonOrId)`
--  Add method `Map::removePolyline(string|Polyline $polylineOrId)`
-
 ## 2.22
 
 -   Add method `Symfony\UX\Map\Renderer\AbstractRenderer::tapOptions()`, to allow Renderer to modify options before rendering a Map.
diff --git a/src/Map/doc/index.rst b/src/Map/doc/index.rst
index b97955f2..855b6fa7 100644
--- a/src/Map/doc/index.rst
+++ b/src/Map/doc/index.rst
@@ -136,34 +136,6 @@ You can add markers to a map using the ``addMarker()`` method::
         ))
     ;
 
-Remove elements from Map
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-It is possible to remove elements like ``Marker``, ``Polygon`` and ``Polyline`` instances by using ``Map::remove*()`` methods::
-    
-    // Add elements
-    $map->addMarker($marker = new Marker(/* ... */));
-    $map->addPolygon($polygon = new Polygon(/* ... */));
-    $map->addPolyline($polyline = new Polyline(/* ... */));
-    
-    // And later, remove those elements
-    $map->removeMarker($marker);
-    $map->removePolygon($polygon);
-    $map->removePolyline($polyline);
-    
-If unfortunately you were unable to store an element instance, you can still remove them by passing the identifier string::
- 
-    $map = new Map(/* ... */);
-    // Add elements
-    $map->addMarker(new Marker(id: 'my-marker', /* ... */));
-    $map->addPolygon(new Polygon(id: 'my-polygon', /* ... */));
-    $map->addPolyline(new Polyline(id: 'my-marker', /* ... */));
-    
-    // And later, remove those elements
-    $map->removeMarker('my-marker');
-    $map->removePolygon('my-polygon');
-    $map->removePolyline('my-marker');
-
 Add Polygons
 ~~~~~~~~~~~~
 
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 d9c8f1cc..412db2c1 100644
--- a/src/Map/src/Bridge/Google/assets/dist/map_controller.js
+++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.js
@@ -113,9 +113,6 @@ class map_controller extends default_1 {
             libraries = ['core', ...libraries.filter((library) => library !== 'core')];
             const librariesImplementations = await Promise.all(libraries.map((library) => loader.importLibrary(library)));
             librariesImplementations.map((libraryImplementation, index) => {
-                if (typeof libraryImplementation !== 'object' || libraryImplementation === null) {
-                    return;
-                }
                 const library = libraries[index];
                 if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) {
                     _google.maps[library] = libraryImplementation;
diff --git a/src/Map/src/Bridge/Google/assets/package.json b/src/Map/src/Bridge/Google/assets/package.json
index 979556d0..300a21fd 100644
--- a/src/Map/src/Bridge/Google/assets/package.json
+++ b/src/Map/src/Bridge/Google/assets/package.json
@@ -40,7 +40,6 @@
     "devDependencies": {
         "@googlemaps/js-api-loader": "^1.16.6",
         "@hotwired/stimulus": "^3.0.0",
-        "@symfony/ux-map": "workspace:*",
         "@types/google.maps": "^3.55.9"
     }
 }
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 bcf5da7e..ff2df70a 100644
--- a/src/Map/src/Bridge/Google/assets/src/map_controller.ts
+++ b/src/Map/src/Bridge/Google/assets/src/map_controller.ts
@@ -71,16 +71,11 @@ export default class extends AbstractMapController<
                 libraries.map((library) => loader.importLibrary(library))
             );
             librariesImplementations.map((libraryImplementation, index) => {
-                if (typeof libraryImplementation !== 'object' || libraryImplementation === null) {
-                    return;
-                }
-
                 const library = libraries[index];
 
                 // The following libraries are in a sub-namespace
                 if (['marker', 'places', 'geometry', 'journeySharing', 'drawing', 'visualization'].includes(library)) {
-                    // @ts-ignore
-                    _google.maps[library] = libraryImplementation as any;
+                    _google.maps[library] = libraryImplementation;
                 } else {
                     _google.maps = { ..._google.maps, ...libraryImplementation };
                 }
diff --git a/src/Map/src/Bridge/Leaflet/assets/package.json b/src/Map/src/Bridge/Leaflet/assets/package.json
index d811f1c9..8145f0fb 100644
--- a/src/Map/src/Bridge/Leaflet/assets/package.json
+++ b/src/Map/src/Bridge/Leaflet/assets/package.json
@@ -39,7 +39,6 @@
     },
     "devDependencies": {
         "@hotwired/stimulus": "^3.0.0",
-        "@symfony/ux-map": "workspace:*",
         "@types/leaflet": "^1.9.12",
         "leaflet": "^1.9.4"
     }
diff --git a/src/Map/src/Distance/DistanceCalculator.php b/src/Map/src/Distance/DistanceCalculator.php
deleted file mode 100644
index 46e7193d..00000000
--- a/src/Map/src/Distance/DistanceCalculator.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Distance;
-
-use Symfony\UX\Map\Point;
-
-/**
- * @author Simon André <smn.andre@gmail.com>
- */
-final readonly class DistanceCalculator implements DistanceCalculatorInterface
-{
-    public function __construct(
-        private DistanceCalculatorInterface $calculator = new VincentyDistanceCalculator(),
-        private DistanceUnit $unit = DistanceUnit::Meter,
-    ) {
-    }
-
-    public function calculateDistance(Point $point1, Point $point2): float
-    {
-        return $this->calculator->calculateDistance($point1, $point2)
-            * $this->unit->getConversionFactor();
-    }
-}
diff --git a/src/Map/src/Distance/DistanceCalculatorInterface.php b/src/Map/src/Distance/DistanceCalculatorInterface.php
deleted file mode 100644
index 1019118d..00000000
--- a/src/Map/src/Distance/DistanceCalculatorInterface.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Distance;
-
-use Symfony\UX\Map\Point;
-
-/**
- * Interface for distance calculators.
- *
- * @author Simon André <smn.andre@gmail.com>
- */
-interface DistanceCalculatorInterface
-{
-    /**
-     * Returns the distance between two points given their coordinates.
-     *
-     * @return float the distance between the two points, in meters
-     */
-    public function calculateDistance(Point $point1, Point $point2): float;
-}
diff --git a/src/Map/src/Distance/DistanceUnit.php b/src/Map/src/Distance/DistanceUnit.php
deleted file mode 100644
index fa581e6a..00000000
--- a/src/Map/src/Distance/DistanceUnit.php
+++ /dev/null
@@ -1,80 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Distance;
-
-/**
- * Represents a distance unit used in mapping and geospatial calculations.
- *
- * This enum defines common units for measuring distances. Each unit has an associated
- * conversion factor which is used to convert a value from meters to that unit.
- *
- * @author Simon André <smn.andre@gmail.com>
- */
-enum DistanceUnit: string
-{
-    /**
-     * The "meter" unit.
-     *
-     * This is the International System of Units (SI) base unit for length.
-     */
-    case Meter = 'm';
-
-    /**
-     * The "kilometer" unit.
-     *
-     * This unit is commonly used for longer distances.
-     */
-    case Kilometer = 'km';
-
-    /**
-     * The "mile" unit.
-     *
-     * This unit is widely used in the United States.
-     */
-    case Mile = 'mi';
-
-    /**
-     * The "nautical mile" unit.
-     *
-     * This unit is typically used in navigation.
-     */
-    case NauticalMile = 'nmi';
-
-    /**
-     * Returns the conversion factor to convert this unit to meters.
-     */
-    public function getConversionFactor(): float
-    {
-        return match ($this) {
-            self::Meter => 1.0,
-            self::Kilometer => 0.001,
-            self::Mile => 0.000621371,
-            self::NauticalMile => 0.000539957,
-        };
-    }
-
-    /**
-     * Returns the conversion factor to convert this unit to another unit.
-     */
-    public function getConversionFactorTo(DistanceUnit $unit): float
-    {
-        return $this->getConversionFactor() / $unit->getConversionFactor();
-    }
-
-    /**
-     * Returns the conversion factor to convert another unit to this unit.
-     */
-    public function getConversionFactorFrom(DistanceUnit $unit): float
-    {
-        return $unit->getConversionFactor() / $this->getConversionFactor();
-    }
-}
diff --git a/src/Map/src/Distance/HaversineDistanceCalculator.php b/src/Map/src/Distance/HaversineDistanceCalculator.php
deleted file mode 100644
index 32b18689..00000000
--- a/src/Map/src/Distance/HaversineDistanceCalculator.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Distance;
-
-use Symfony\UX\Map\Point;
-
-/**
- * Haversine formula-based distance calculator.
- *
- * This calculator is accurate but slower than the spherical cosine formula.
- *
- * @author Simon André <smn.andre@gmail.com>
- */
-final readonly class HaversineDistanceCalculator implements DistanceCalculatorInterface
-{
-    /**
-     * @const float The Earth's radius in meters.
-     */
-    private const EARTH_RADIUS = 6371000.0;
-
-    public function calculateDistance(Point $point1, Point $point2): float
-    {
-        $lat1Rad = deg2rad($point1->getLatitude());
-        $lat2Rad = deg2rad($point2->getLatitude());
-        $deltaLat = deg2rad($point2->getLatitude() - $point1->getLatitude());
-        $deltaLng = deg2rad($point2->getLongitude() - $point1->getLongitude());
-
-        $a = sin($deltaLat / 2) ** 2 + cos($lat1Rad) * cos($lat2Rad) * sin($deltaLng / 2) ** 2;
-        $c = 2 * asin(min(1.0, sqrt($a)));
-
-        return self::EARTH_RADIUS * $c;
-    }
-}
diff --git a/src/Map/src/Distance/SphericalCosineDistanceCalculator.php b/src/Map/src/Distance/SphericalCosineDistanceCalculator.php
deleted file mode 100644
index 7e5c1f63..00000000
--- a/src/Map/src/Distance/SphericalCosineDistanceCalculator.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Distance;
-
-use Symfony\UX\Map\Point;
-
-/**
- * Sphere-based distance calculator using the cosine of the spherical distance.
- *
- * This calculator is faster than the Haversine formula, but less accurate.
- *
- * @author Simon André <smn.andre@gmail.com>
- */
-final readonly class SphericalCosineDistanceCalculator implements DistanceCalculatorInterface
-{
-    /**
-     * @const float The Earth's radius in meters.
-     */
-    private const EARTH_RADIUS = 6371000.0;
-
-    public function calculateDistance(Point $point1, Point $point2): float
-    {
-        $lat1Rad = deg2rad($point1->getLatitude());
-        $lat2Rad = deg2rad($point2->getLatitude());
-        $lng1Rad = deg2rad($point1->getLongitude());
-        $lng2Rad = deg2rad($point2->getLongitude());
-
-        $cosDistance = sin($lat1Rad) * sin($lat2Rad) + cos($lat1Rad) * cos($lat2Rad) * cos($lng2Rad - $lng1Rad);
-
-        // Correct for floating-point errors.
-        $cosDistance = min(1.0, max(-1.0, $cosDistance));
-        $angle = acos($cosDistance);
-
-        return self::EARTH_RADIUS * $angle;
-    }
-}
diff --git a/src/Map/src/Distance/VincentyDistanceCalculator.php b/src/Map/src/Distance/VincentyDistanceCalculator.php
deleted file mode 100644
index 7a08184a..00000000
--- a/src/Map/src/Distance/VincentyDistanceCalculator.php
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Distance;
-
-use Symfony\UX\Map\Point;
-
-/**
- * Vincenty formula-based distance calculator.
- *
- * This calculator is more accurate than the Haversine formula, but slower.
- *
- * @author Simon André <smn.andre@gmail.com>
- */
-final readonly class VincentyDistanceCalculator implements DistanceCalculatorInterface
-{
-    /**
-     * WS-84 ellipsoid parameters.
-     */
-    // Major Axis in meters
-    private const A = 6378137.0;
-    // Flattening
-    private const F = 1 / 298.257223563;
-    // Minor Axis in meters
-    private const B = 6356752.314245;
-
-    public function calculateDistance(Point $point1, Point $point2): float
-    {
-        $phi1 = deg2rad($point1->getLatitude());
-        $phi2 = deg2rad($point2->getLatitude());
-        $lambda1 = deg2rad($point1->getLongitude());
-        $lambda2 = deg2rad($point2->getLongitude());
-
-        $L = $lambda2 - $lambda1;
-        $U1 = atan((1 - self::F) * tan($phi1));
-        $U2 = atan((1 - self::F) * tan($phi2));
-        $sinU1 = sin($U1);
-        $cosU1 = cos($U1);
-        $sinU2 = sin($U2);
-        $cosU2 = cos($U2);
-
-        $lambda = $L;
-        $iterLimit = 100;
-        do {
-            $sinLambda = sin($lambda);
-            $cosLambda = cos($lambda);
-            $sinSigma = sqrt(($cosU2 * $sinLambda) ** 2
-                + ($cosU1 * $sinU2 - $sinU1 * $cosU2 * $cosLambda) ** 2);
-
-            if (0.0 === $sinSigma) {
-                return 0.0;
-            }
-
-            $cosSigma = $sinU1 * $sinU2 + $cosU1 * $cosU2 * $cosLambda;
-            $sigma = atan2($sinSigma, $cosSigma);
-            $sinAlpha = $cosU1 * $cosU2 * $sinLambda / $sinSigma;
-            $cosSqAlpha = 1 - $sinAlpha * $sinAlpha;
-            $cos2SigmaM = (0.0 === $cosSqAlpha) ? 0.0 : $cosSigma - 2 * $sinU1 * $sinU2 / $cosSqAlpha;
-            $C = self::F / 16 * $cosSqAlpha * (4 + self::F * (4 - 3 * $cosSqAlpha));
-
-            $lambdaPrev = $lambda;
-            $lambda = $L + (1 - $C) * self::F * $sinAlpha
-                * ($sigma + $C * $sinSigma * ($cos2SigmaM + $C * $cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)));
-        } while (abs($lambda - $lambdaPrev) > 1e-12 && --$iterLimit > 0);
-
-        if (0 === $iterLimit) {
-            throw new \RuntimeException('Vincenty formula failed to converge.');
-        }
-
-        $uSq = $cosSqAlpha * (self::A * self::A - self::B * self::B) / (self::B * self::B);
-        $Acoeff = 1 + $uSq / 16384 * (4096 + $uSq * (-768 + $uSq * (320 - 175 * $uSq)));
-        $Bcoeff = $uSq / 1024 * (256 + $uSq * (-128 + $uSq * (74 - 47 * $uSq)));
-        $deltaSigma = $Bcoeff * $sinSigma * ($cos2SigmaM + $Bcoeff / 4 * ($cosSigma * (-1 + 2 * $cos2SigmaM * $cos2SigmaM)
-                    - $Bcoeff / 6 * $cos2SigmaM * (-3 + 4 * $sinSigma * $sinSigma) * (-3 + 4 * $cos2SigmaM * $cos2SigmaM)));
-        $distance = self::B * $Acoeff * ($sigma - $deltaSigma);
-
-        return $distance;
-    }
-}
diff --git a/src/Map/src/Element.php b/src/Map/src/Element.php
deleted file mode 100644
index c7f752ea..00000000
--- a/src/Map/src/Element.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map;
-
-/**
- * @author Sylvain Blondeau <contact@sylvainblondeau.dev>
- *
- * @internal
- */
-interface Element
-{
-}
diff --git a/src/Map/src/Elements.php b/src/Map/src/Elements.php
deleted file mode 100644
index 6dee1939..00000000
--- a/src/Map/src/Elements.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map;
-
-/**
- * Represents a collection of map elements.
- *
- * @author Sylvain Blondeau <contact@sylvainblondeau.dev>
- *
- * @internal
- */
-abstract class Elements
-{
-    private \SplObjectStorage $elements;
-
-    public function __construct(
-        array $elements,
-    ) {
-        $this->elements = new \SplObjectStorage();
-        foreach ($elements as $element) {
-            $this->elements->attach($element);
-        }
-    }
-
-    public function add(Element $element): static
-    {
-        $this->elements->attach($element, $element->id ?? $this->elements->getHash($element));
-
-        return $this;
-    }
-
-    private function getElement(string $id): ?Element
-    {
-        foreach ($this->elements as $element) {
-            if ($element->id === $id) {
-                return $element;
-            }
-        }
-
-        return null;
-    }
-
-    public function remove(Element|string $elementOrId): static
-    {
-        if (\is_string($elementOrId)) {
-            $elementOrId = $this->getElement($elementOrId);
-        }
-
-        if (null === $elementOrId) {
-            return $this;
-        }
-
-        if ($this->elements->contains($elementOrId)) {
-            $this->elements->detach($elementOrId);
-        }
-
-        return $this;
-    }
-
-    public function toArray(): array
-    {
-        foreach ($this->elements as $element) {
-            $elements[] = $element->toArray();
-        }
-
-        return $elements ?? [];
-    }
-
-    abstract public static function fromArray(array $elements): self;
-}
diff --git a/src/Map/src/Map.php b/src/Map/src/Map.php
index f0d49c60..529f8af8 100644
--- a/src/Map/src/Map.php
+++ b/src/Map/src/Map.php
@@ -20,28 +20,27 @@ use Symfony\UX\Map\Exception\InvalidArgumentException;
  */
 final class Map
 {
-    private Markers $markers;
-    private Polygons $polygons;
-    private Polylines $polylines;
-
-    /**
-     * @param Marker[]   $markers
-     * @param Polygon[]  $polygons
-     * @param Polyline[] $polylines
-     */
     public function __construct(
         private readonly ?string $rendererName = null,
         private ?MapOptionsInterface $options = null,
         private ?Point $center = null,
         private ?float $zoom = null,
         private bool $fitBoundsToMarkers = false,
-        array $markers = [],
-        array $polygons = [],
-        array $polylines = [],
+        /**
+         * @var array<Marker>
+         */
+        private array $markers = [],
+
+        /**
+         * @var array<Polygon>
+         */
+        private array $polygons = [],
+
+        /**
+         * @var array<Polyline>
+         */
+        private array $polylines = [],
     ) {
-        $this->markers = new Markers($markers);
-        $this->polygons = new Polygons($polygons);
-        $this->polylines = new Polylines($polylines);
     }
 
     public function getRendererName(): ?string
@@ -89,42 +88,21 @@ final class Map
 
     public function addMarker(Marker $marker): self
     {
-        $this->markers->add($marker);
-
-        return $this;
-    }
-
-    public function removeMarker(Marker|string $markerOrId): self
-    {
-        $this->markers->remove($markerOrId);
+        $this->markers[] = $marker;
 
         return $this;
     }
 
     public function addPolygon(Polygon $polygon): self
     {
-        $this->polygons->add($polygon);
-
-        return $this;
-    }
-
-    public function removePolygon(Polygon|string $polygonOrId): self
-    {
-        $this->polygons->remove($polygonOrId);
+        $this->polygons[] = $polygon;
 
         return $this;
     }
 
     public function addPolyline(Polyline $polyline): self
     {
-        $this->polylines->add($polyline);
-
-        return $this;
-    }
-
-    public function removePolyline(Polyline|string $polylineOrId): self
-    {
-        $this->polylines->remove($polylineOrId);
+        $this->polylines[] = $polyline;
 
         return $this;
     }
@@ -146,9 +124,9 @@ final class Map
             'zoom' => $this->zoom,
             'fitBoundsToMarkers' => $this->fitBoundsToMarkers,
             'options' => $this->options ? MapOptionsNormalizer::normalize($this->options) : [],
-            'markers' => $this->markers->toArray(),
-            'polygons' => $this->polygons->toArray(),
-            'polylines' => $this->polylines->toArray(),
+            'markers' => array_map(static fn (Marker $marker) => $marker->toArray(), $this->markers),
+            'polygons' => array_map(static fn (Polygon $polygon) => $polygon->toArray(), $this->polygons),
+            'polylines' => array_map(static fn (Polyline $polyline) => $polyline->toArray(), $this->polylines),
         ];
     }
 
diff --git a/src/Map/src/Marker.php b/src/Map/src/Marker.php
index 0310834a..54d61ebe 100644
--- a/src/Map/src/Marker.php
+++ b/src/Map/src/Marker.php
@@ -18,18 +18,17 @@ use Symfony\UX\Map\Exception\InvalidArgumentException;
  *
  * @author Hugo Alliaume <hugo@alliau.me>
  */
-final readonly class Marker implements Element
+final readonly class Marker
 {
     /**
      * @param array<string, mixed> $extra Extra data, can be used by the developer to store additional information and
      *                                    use them later JavaScript side
      */
     public function __construct(
-        public Point $position,
-        public ?string $title = null,
-        public ?InfoWindow $infoWindow = null,
-        public array $extra = [],
-        public ?string $id = null,
+        private Point $position,
+        private ?string $title = null,
+        private ?InfoWindow $infoWindow = null,
+        private array $extra = [],
     ) {
     }
 
@@ -39,7 +38,6 @@ final readonly class Marker implements Element
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
-     *     id: string|null
      * }
      */
     public function toArray(): array
@@ -49,7 +47,6 @@ final readonly class Marker implements Element
             'title' => $this->title,
             'infoWindow' => $this->infoWindow?->toArray(),
             'extra' => $this->extra,
-            'id' => $this->id,
         ];
     }
 
@@ -59,7 +56,6 @@ final readonly class Marker implements Element
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
-     *     id: string|null
      * } $marker
      *
      * @internal
diff --git a/src/Map/src/Markers.php b/src/Map/src/Markers.php
deleted file mode 100644
index b82e3cf0..00000000
--- a/src/Map/src/Markers.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map;
-
-/**
- * Represents a Marker collection.
- *
- * @author Sylvain Blondeau <contact@sylvainblondeau.dev>
- *
- * @internal
- */
-final class Markers extends Elements
-{
-    public static function fromArray(array $elements): self
-    {
-        $elementObjects = [];
-
-        foreach ($elements as $element) {
-            $elementObjects[] = Marker::fromArray($element);
-        }
-
-        return new self(elements: $elementObjects);
-    }
-}
diff --git a/src/Map/src/Point.php b/src/Map/src/Point.php
index 864041e2..f34f37a2 100644
--- a/src/Map/src/Point.php
+++ b/src/Map/src/Point.php
@@ -33,16 +33,6 @@ final readonly class Point
         }
     }
 
-    public function getLatitude(): float
-    {
-        return $this->latitude;
-    }
-
-    public function getLongitude(): float
-    {
-        return $this->longitude;
-    }
-
     /**
      * @return array{lat: float, lng: float}
      */
diff --git a/src/Map/src/Polygon.php b/src/Map/src/Polygon.php
index 4faaf7e8..d32594ad 100644
--- a/src/Map/src/Polygon.php
+++ b/src/Map/src/Polygon.php
@@ -18,7 +18,7 @@ use Symfony\UX\Map\Exception\InvalidArgumentException;
  *
  * @author [Pierre Svgnt]
  */
-final readonly class Polygon implements Element
+final readonly class Polygon
 {
     /**
      * @param array<string, mixed> $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side
@@ -28,7 +28,6 @@ final readonly class Polygon implements Element
         private ?string $title = null,
         private ?InfoWindow $infoWindow = null,
         private array $extra = [],
-        public ?string $id = null,
     ) {
     }
 
@@ -40,7 +39,6 @@ final readonly class Polygon implements Element
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
-     *     id: string|null
      * }
      */
     public function toArray(): array
@@ -50,7 +48,6 @@ final readonly class Polygon implements Element
             'title' => $this->title,
             'infoWindow' => $this->infoWindow?->toArray(),
             'extra' => $this->extra,
-            'id' => $this->id,
         ];
     }
 
@@ -60,7 +57,6 @@ final readonly class Polygon implements Element
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
-     *     id: string|null
      * } $polygon
      *
      * @internal
diff --git a/src/Map/src/Polygons.php b/src/Map/src/Polygons.php
deleted file mode 100644
index 5d75ba8f..00000000
--- a/src/Map/src/Polygons.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map;
-
-/**
- * Represents a Polygon collection.
- *
- * @author Sylvain Blondeau <contact@sylvainblondeau.dev>
- *
- * @internal
- */
-final class Polygons extends Elements
-{
-    public static function fromArray(array $elements): self
-    {
-        $elementObjects = [];
-
-        foreach ($elements as $element) {
-            $elementObjects[] = Polygon::fromArray($element);
-        }
-
-        return new self(elements: $elementObjects);
-    }
-}
diff --git a/src/Map/src/Polyline.php b/src/Map/src/Polyline.php
index 15b1b778..46302136 100644
--- a/src/Map/src/Polyline.php
+++ b/src/Map/src/Polyline.php
@@ -18,7 +18,7 @@ use Symfony\UX\Map\Exception\InvalidArgumentException;
  *
  * @author [Sylvain Blondeau]
  */
-final readonly class Polyline implements Element
+final readonly class Polyline
 {
     /**
      * @param array<string, mixed> $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side
@@ -28,7 +28,6 @@ final readonly class Polyline implements Element
         private ?string $title = null,
         private ?InfoWindow $infoWindow = null,
         private array $extra = [],
-        public ?string $id = null,
     ) {
     }
 
@@ -40,7 +39,6 @@ final readonly class Polyline implements Element
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
-     *     id: string|null
      * }
      */
     public function toArray(): array
@@ -50,7 +48,6 @@ final readonly class Polyline implements Element
             'title' => $this->title,
             'infoWindow' => $this->infoWindow?->toArray(),
             'extra' => $this->extra,
-            'id' => $this->id,
         ];
     }
 
@@ -60,7 +57,6 @@ final readonly class Polyline implements Element
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
-     *     id: string|null
      * } $polyline
      *
      * @internal
diff --git a/src/Map/src/Polylines.php b/src/Map/src/Polylines.php
deleted file mode 100644
index 810f180d..00000000
--- a/src/Map/src/Polylines.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map;
-
-/**
- * Represents a Polyline collection.
- *
- * @author Sylvain Blondeau <contact@sylvainblondeau.dev>
- *
- * @internal
- */
-final class Polylines extends Elements
-{
-    public static function fromArray(array $elements): self
-    {
-        $elementObjects = [];
-
-        foreach ($elements as $element) {
-            $elementObjects[] = Polyline::fromArray($element);
-        }
-
-        return new self(elements: $elementObjects);
-    }
-}
diff --git a/src/Map/src/Utils/CoordinateUtils.php b/src/Map/src/Utils/CoordinateUtils.php
deleted file mode 100644
index 6730080d..00000000
--- a/src/Map/src/Utils/CoordinateUtils.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Utils;
-
-/**
- * Utility class to convert between decimal and DMS coordinates.
- *
- * @author Simon André <smn.andre@gmail.com>
- */
-final class CoordinateUtils
-{
-    /**
-     * Converts a decimal coordinate to DMS (degrees, minutes, seconds).
-     *
-     * CoordinateUtils::decimalToDMS(48.8588443)
-     *      --> [48, 51, 31.8388]
-     *
-     * @param float $decimal the decimal coordinate to convert to DMS
-     *
-     * @return array{0: int, 1: int, 2: float}
-     */
-    public static function decimalToDMS(float $decimal): array
-    {
-        $sign = $decimal < 0 ? -1 : 1;
-        $decimal = abs($decimal);
-        $degrees = (int) $decimal * $sign;
-        $minutes = (int) (($decimal - abs($degrees)) * 60);
-        $seconds = ($decimal - abs($degrees) - $minutes / 60) * 3600;
-
-        return [$degrees, $minutes, round($seconds, 6)];
-    }
-
-    /**
-     * Converts a DMS (degrees, minutes, seconds) coordinate to decimal.
-     *
-     *  CoordinateUtils::DMSToDecimal(48, 51, 31.8388)
-     *      --> 48.8588443
-     *
-     * @param int   $degrees the degrees part of the DMS coordinate
-     * @param int   $minutes the minutes part of the DMS coordinate
-     * @param float $seconds the seconds part of the DMS coordinate
-     *
-     * @return float the decimal coordinate
-     */
-    public static function DMSToDecimal(int $degrees, int $minutes, float $seconds): float
-    {
-        $sign = $degrees < 0 ? -1 : 1;
-        $degrees = abs($degrees);
-        $decimal = $degrees + $minutes / 60 + $seconds / 3600;
-
-        return round($decimal * $sign, 6);
-    }
-}
diff --git a/src/Notify/README.md b/src/Notify/README.md
index 5b4f79ff..68029ad4 100644
--- a/src/Notify/README.md
+++ b/src/Notify/README.md
@@ -5,7 +5,7 @@ Symfony UX Notify is a Symfony bundle integrating server-sent
 in Symfony applications using [Mercure](https://mercure.rocks/).
 It is part of [the Symfony UX initiative](https://ux.symfony.com/).
 
-![Example of a native notification](https://github.com/symfony/ux/blob/2.x/doc/native-notification-example.png?raw=true)
+![Example of a native notification](doc/native-notification-example.png)
 
 **This repository is a READ-ONLY sub-tree split**. See
 https://github.com/symfony/ux to create issues or submit pull requests.
diff --git a/src/StimulusBundle/assets/dist/loader.js b/src/StimulusBundle/assets/dist/loader.js
index b2c49cfa..1d1ce80d 100644
--- a/src/StimulusBundle/assets/dist/loader.js
+++ b/src/StimulusBundle/assets/dist/loader.js
@@ -25,33 +25,18 @@ class StimulusLazyControllerHandler {
         this.lazyLoadNewControllers(document.documentElement);
     }
     lazyLoadExistingControllers(element) {
-        Array.from(element.querySelectorAll(`[${controllerAttribute}]`))
-            .flatMap(extractControllerNamesFrom)
-            .forEach((controllerName) => this.loadLazyController(controllerName));
+        this.queryControllerNamesWithin(element).forEach((controllerName) => this.loadLazyController(controllerName));
     }
-    loadLazyController(name) {
-        if (!this.lazyControllers[name]) {
-            return;
-        }
-        const controllerLoader = this.lazyControllers[name];
-        delete this.lazyControllers[name];
-        if (!canRegisterController(name, this.application)) {
-            return;
-        }
-        this.application.logDebugActivity(name, 'lazy:loading');
-        controllerLoader()
-            .then((controllerModule) => {
-            this.application.logDebugActivity(name, 'lazy:loaded');
+    async loadLazyController(name) {
+        if (canRegisterController(name, this.application)) {
+            if (this.lazyControllers[name] === undefined) {
+                return;
+            }
+            const controllerModule = await this.lazyControllers[name]();
             registerController(name, controllerModule.default, this.application);
-        })
-            .catch((error) => {
-            console.error(`Error loading controller "${name}":`, error);
-        });
+        }
     }
     lazyLoadNewControllers(element) {
-        if (Object.keys(this.lazyControllers).length === 0) {
-            return;
-        }
         new MutationObserver((mutationsList) => {
             for (const { attributeName, target, type } of mutationsList) {
                 switch (type) {
@@ -73,6 +58,9 @@ class StimulusLazyControllerHandler {
             childList: true,
         });
     }
+    queryControllerNamesWithin(element) {
+        return Array.from(element.querySelectorAll(`[${controllerAttribute}]`)).flatMap(extractControllerNamesFrom);
+    }
 }
 function registerController(name, controller, application) {
     if (canRegisterController(name, application)) {
diff --git a/src/StimulusBundle/assets/src/loader.ts b/src/StimulusBundle/assets/src/loader.ts
index 178214a6..5b5009ae 100644
--- a/src/StimulusBundle/assets/src/loader.ts
+++ b/src/StimulusBundle/assets/src/loader.ts
@@ -64,40 +64,22 @@ class StimulusLazyControllerHandler {
     }
 
     private lazyLoadExistingControllers(element: Element) {
-        Array.from(element.querySelectorAll(`[${controllerAttribute}]`))
-            .flatMap(extractControllerNamesFrom)
-            .forEach((controllerName) => this.loadLazyController(controllerName));
+        this.queryControllerNamesWithin(element).forEach((controllerName) => this.loadLazyController(controllerName));
     }
 
-    private loadLazyController(name: string) {
-        if (!this.lazyControllers[name]) {
-            return;
-        }
+    private async loadLazyController(name: string) {
+        if (canRegisterController(name, this.application)) {
+            if (this.lazyControllers[name] === undefined) {
+                return;
+            }
 
-        // Delete the loader to avoid loading it twice
-        const controllerLoader = this.lazyControllers[name];
-        delete this.lazyControllers[name];
+            const controllerModule = await this.lazyControllers[name]();
 
-        if (!canRegisterController(name, this.application)) {
-            return;
+            registerController(name, controllerModule.default, this.application);
         }
-
-        this.application.logDebugActivity(name, 'lazy:loading');
-
-        controllerLoader()
-            .then((controllerModule) => {
-                this.application.logDebugActivity(name, 'lazy:loaded');
-                registerController(name, controllerModule.default, this.application);
-            })
-            .catch((error) => {
-                console.error(`Error loading controller "${name}":`, error);
-            });
     }
 
     private lazyLoadNewControllers(element: Element) {
-        if (Object.keys(this.lazyControllers).length === 0) {
-            return;
-        }
         new MutationObserver((mutationsList) => {
             for (const { attributeName, target, type } of mutationsList) {
                 switch (type) {
@@ -125,6 +107,10 @@ class StimulusLazyControllerHandler {
             childList: true,
         });
     }
+
+    private queryControllerNamesWithin(element: Element): string[] {
+        return Array.from(element.querySelectorAll(`[${controllerAttribute}]`)).flatMap(extractControllerNamesFrom);
+    }
 }
 
 function registerController(name: string, controller: ControllerConstructor, application: Application) {
diff --git a/src/StimulusBundle/doc/index.rst b/src/StimulusBundle/doc/index.rst
index 88ede629..286f3871 100644
--- a/src/StimulusBundle/doc/index.rst
+++ b/src/StimulusBundle/doc/index.rst
@@ -8,7 +8,7 @@ StimulusBundle: Symfony integration with Stimulus
 This bundle adds integration between Symfony, `Stimulus`_ and the Symfony UX packages:
 
 * Twig ``stimulus_`` functions & filters to add Stimulus controllers,
-  actions & targets in your templates;
+   actions & targets in your templates;
 * Integration to load :ref:`UX Packages <ux-packages>` (extra Stimulus controllers)
 
 Installation
@@ -100,7 +100,33 @@ common problems. StimulusBundle activates any 3rd party Stimulus controllers
 that are mentioned in your ``assets/controllers.json`` file. This file is updated
 whenever you install a UX package.
 
-Check out the `official UX packages`_.
+The official UX packages are:
+
+* `ux-autocomplete`_: Transform ``EntityType``, ``ChoiceType`` or *any*
+  ``<select>`` element into an Ajax-powered autocomplete field
+  (`see demo <https://ux.symfony.com/autocomplete>`_)
+* `ux-chartjs`_: Easy charts with `Chart.js`_ (`see demo <https://ux.symfony.com/chartjs>`_)
+* `ux-cropperjs`_: Form Type and tools for cropping images (`see demo <https://ux.symfony.com/cropperjs>`_)
+* `ux-dropzone`_: Form Type for stylized "drop zone" for file uploads
+  (`see demo <https://ux.symfony.com/dropzone>`_)
+* `ux-lazy-image`_: Optimize Image Loading with BlurHash
+  (`see demo <https://ux.symfony.com/lazy-image>`_)
+* `ux-live-component`_: Build Dynamic Interfaces with Zero JavaScript
+  (`see demo <https://ux.symfony.com/live-component>`_)
+* `ux-notify`_: Send server-sent native notification with Mercure
+  (`see demo <https://ux.symfony.com/notify>`_)
+* `ux-react`_: Render `React`_ component from Twig (`see demo <https://ux.symfony.com/react>`_)
+* `ux-svelte`_: Render `Svelte`_ component from Twig (`see demo <https://ux.symfony.com/svelte>`_)
+* `ux-swup`_: Integration with `Swup`_ (`see demo <https://ux.symfony.com/swup>`_)
+* `ux-toggle-password`_: Toggle visibility of password inputs
+  (`see demo <https://ux.symfony.com/toggle-password>`_)
+* `ux-translator`_: Use your Symfony translations in JavaScript (`see demo <https://ux.symfony.com/translator>`_)
+* `ux-turbo`_: Integration with `Turbo Drive`_ for a single-page-app experience
+  (`see demo <https://ux.symfony.com/turbo>`_)
+* `ux-twig-component`_: Build Twig Components Backed by a PHP Class
+  (`see demo <https://ux.symfony.com/twig-component>`_)
+* `ux-typed`_: Integration with `Typed`_ (`see demo <https://ux.symfony.com/typed>`_)
+* `ux-vue`_: Render `Vue`_ component from Twig (`see demo <https://ux.symfony.com/vue>`_)
 
 Lazy Stimulus Controllers
 ~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -132,7 +158,7 @@ To make a third-party controller lazy, in ``assets/controllers.json``, set
 .. note::
 
     If you write your controllers using TypeScript and you're using
-    StimulusBundle 2.21.0 or earlier, make sure ``removeComments`` is not set
+    StimulusBundle ≤ 2.21.0, make sure ``removeComments`` is not set
     to ``true`` in your TypeScript config.
 
 Stimulus Tools around the World
@@ -214,10 +240,7 @@ And with outlets:
 
 .. code-block:: html+twig
 
-    <div {{ stimulus_controller('chart',
-            { 'name': 'Likes', 'data': [1, 2, 3, 4] },
-            { 'loading': 'spinner' },
-            { 'other': '.target' } ) }}>
+    <div {{ stimulus_controller('chart', { 'name': 'Likes', 'data': [1, 2, 3, 4] }, { 'loading': 'spinner' }, { 'other': '.target' ) }}>
         Hello
     </div>
 
@@ -527,9 +550,31 @@ it will normalize it:
 .. _`parameters`: https://stimulus.hotwired.dev/reference/actions#action-parameters
 .. _`Stimulus Targets`: https://stimulus.hotwired.dev/reference/targets
 .. _`StimulusBundle Flex recipe`: https://github.com/symfony/recipes/tree/main/symfony/stimulus-bundle
+.. _`ux-autocomplete`: https://symfony.com/bundles/ux-autocomplete/current/index.html
+.. _`ux-chartjs`: https://symfony.com/bundles/ux-chartjs/current/index.html
+.. _`ux-cropperjs`: https://symfony.com/bundles/ux-cropperjs/current/index.html
+.. _`ux-dropzone`: https://symfony.com/bundles/ux-dropzone/current/index.html
+.. _`ux-lazy-image`: https://symfony.com/bundles/ux-lazy-image/current/index.html
+.. _`ux-live-component`: https://symfony.com/bundles/ux-live-component/current/index.html
+.. _`ux-notify`: https://symfony.com/bundles/ux-notify/current/index.html
+.. _`ux-react`: https://symfony.com/bundles/ux-react/current/index.html
+.. _ux-translator: https://symfony.com/bundles/ux-translator/current/index.html
+.. _`ux-swup`: https://symfony.com/bundles/ux-swup/current/index.html
+.. _`ux-toggle-password`: https://symfony.com/bundles/ux-toggle-password/current/index.html
+.. _`ux-turbo`: https://symfony.com/bundles/ux-turbo/current/index.html
+.. _`ux-twig-component`: https://symfony.com/bundles/ux-twig-component/current/index.html
+.. _`ux-typed`: https://symfony.com/bundles/ux-typed/current/index.html
+.. _`ux-vue`: https://symfony.com/bundles/ux-vue/current/index.html
+.. _`ux-svelte`: https://symfony.com/bundles/ux-svelte/current/index.html
+.. _`Chart.js`: https://www.chartjs.org/
+.. _`Swup`: https://swup.js.org/
+.. _`React`: https://reactjs.org/
+.. _`Svelte`: https://svelte.dev/
+.. _`Turbo Drive`: https://turbo.hotwired.dev/
+.. _`Typed`: https://github.com/mattboldt/typed.js/
+.. _`Vue`: https://vuejs.org/
 .. _`stimulus-use`: https://stimulus-use.github.io/stimulus-use
 .. _`stimulus-components`: https://www.stimulus-components.com/
 .. _`TypeScript`: https://www.typescriptlang.org/
 .. _`sensiolabs/typescript-bundle`: https://github.com/sensiolabs/AssetMapperTypeScriptBundle
 .. _`Stimulus plugin`: https://plugins.jetbrains.com/plugin/24562-stimulus
-.. _`official UX packages`: https://ux.symfony.com/packages
diff --git a/src/Translator/assets/dist/formatters/formatter.d.ts b/src/Translator/assets/dist/formatters/formatter.d.ts
index e55715f0..c3403c04 100644
--- a/src/Translator/assets/dist/formatters/formatter.d.ts
+++ b/src/Translator/assets/dist/formatters/formatter.d.ts
@@ -1,1 +1,1 @@
-export declare function format(id: string, parameters: Record<string, string | number | Date>, locale: string): string;
+export declare function format(id: string, parameters: Record<string, string | number>, locale: string): string;
diff --git a/src/Translator/assets/dist/formatters/intl-formatter.d.ts b/src/Translator/assets/dist/formatters/intl-formatter.d.ts
index 9892f4bd..e22d7481 100644
--- a/src/Translator/assets/dist/formatters/intl-formatter.d.ts
+++ b/src/Translator/assets/dist/formatters/intl-formatter.d.ts
@@ -1,1 +1,1 @@
-export declare function formatIntl(id: string, parameters: Record<string, string | number | Date>, locale: string): string;
+export declare function formatIntl(id: string, parameters: Record<string, string | number>, locale: string): string;
diff --git a/src/Translator/assets/dist/utils.d.ts b/src/Translator/assets/dist/utils.d.ts
index fd3ca78f..b6621dfd 100644
--- a/src/Translator/assets/dist/utils.d.ts
+++ b/src/Translator/assets/dist/utils.d.ts
@@ -1,1 +1,1 @@
-export declare function strtr(string: string, replacePairs: Record<string, string | number | Date>): string;
+export declare function strtr(string: string, replacePairs: Record<string, string | number>): string;
diff --git a/src/Translator/assets/src/formatters/formatter.ts b/src/Translator/assets/src/formatters/formatter.ts
index 58e18ce1..80d99b82 100644
--- a/src/Translator/assets/src/formatters/formatter.ts
+++ b/src/Translator/assets/src/formatters/formatter.ts
@@ -47,7 +47,7 @@ import { strtr } from '../utils';
  * @param parameters An array of parameters for the message
  * @param locale     The locale
  */
-export function format(id: string, parameters: Record<string, string | number | Date>, locale: string): string {
+export function format(id: string, parameters: Record<string, string | number>, locale: string): string {
     if (null === id || '' === id) {
         return '';
     }
diff --git a/src/Translator/assets/src/formatters/intl-formatter.ts b/src/Translator/assets/src/formatters/intl-formatter.ts
index a092e54f..07bc5a60 100644
--- a/src/Translator/assets/src/formatters/intl-formatter.ts
+++ b/src/Translator/assets/src/formatters/intl-formatter.ts
@@ -7,7 +7,7 @@ import { IntlMessageFormat } from 'intl-messageformat';
  * @param parameters An array of parameters for the message
  * @param locale     The locale
  */
-export function formatIntl(id: string, parameters: Record<string, string | number | Date>, locale: string): string {
+export function formatIntl(id: string, parameters: Record<string, string | number>, locale: string): string {
     if (id === '') {
         return '';
     }
@@ -23,5 +23,5 @@ export function formatIntl(id: string, parameters: Record<string, string | numbe
         }
     });
 
-    return intlMessage.format(parameters) as string;
+    return intlMessage.format(parameters);
 }
diff --git a/src/Translator/assets/src/utils.ts b/src/Translator/assets/src/utils.ts
index 29d035e1..0cb448da 100644
--- a/src/Translator/assets/src/utils.ts
+++ b/src/Translator/assets/src/utils.ts
@@ -6,7 +6,7 @@
  * @param string The string to replace in
  * @param replacePairs The pairs of characters to replace
  */
-export function strtr(string: string, replacePairs: Record<string, string | number | Date>): string {
+export function strtr(string: string, replacePairs: Record<string, string | number>): string {
     const regex: Array<string> = Object.entries(replacePairs).map(([from]) => {
         return from.replace(/([-[\]{}()*+?.\\^$|#,])/g, '\\$1');
     });
diff --git a/src/Turbo/assets/package.json b/src/Turbo/assets/package.json
index 6b80173e..931be09d 100644
--- a/src/Turbo/assets/package.json
+++ b/src/Turbo/assets/package.json
@@ -38,7 +38,6 @@
     },
     "devDependencies": {
         "@hotwired/stimulus": "^3.0.0",
-        "@hotwired/turbo": "^7.1.0 || ^8.0",
-        "@types/hotwired__turbo": "^8.0.4"
+        "@hotwired/turbo": "^7.1.0 || ^8.0"
     }
 }
diff --git a/src/Turbo/config/services.php b/src/Turbo/config/services.php
index 0344e95a..0cd1e6a1 100644
--- a/src/Turbo/config/services.php
+++ b/src/Turbo/config/services.php
@@ -11,13 +11,11 @@
 
 namespace Symfony\Component\DependencyInjection\Loader\Configurator;
 
-use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface;
 use Symfony\UX\Turbo\Broadcaster\IdAccessor;
 use Symfony\UX\Turbo\Broadcaster\ImuxBroadcaster;
 use Symfony\UX\Turbo\Broadcaster\TwigBroadcaster;
 use Symfony\UX\Turbo\Doctrine\BroadcastListener;
-use Symfony\UX\Turbo\Request\RequestListener;
 use Symfony\UX\Turbo\Twig\TwigExtension;
 
 /*
@@ -57,8 +55,5 @@ return static function (ContainerConfigurator $container): void {
             ])
             ->tag('doctrine.event_listener', ['event' => 'onFlush'])
             ->tag('doctrine.event_listener', ['event' => 'postFlush'])
-
-        ->set('turbo.kernel.request_listener', RequestListener::class)
-            ->tag('kernel.event_listener', ['event' => KernelEvents::REQUEST, 'priority' => 256])
     ;
 };
diff --git a/src/Turbo/doc/index.rst b/src/Turbo/doc/index.rst
index 7d256360..4618bd70 100644
--- a/src/Turbo/doc/index.rst
+++ b/src/Turbo/doc/index.rst
@@ -342,6 +342,11 @@ clients. There are two main ways to receive the updates:
 Forms
 ^^^^^
 
+.. versionadded:: 2.1
+
+    Prior to 2.1, ``TurboStreamResponse::STREAM_FORMAT`` was used instead of ``TurboBundle::STREAM_FORMAT``.
+    Also, one had to return a new ``TurboStreamResponse()`` object as the third argument to ``$this->render()``.
+
 Let's discover how to use Turbo Streams to enhance your `Symfony forms`_::
 
     // src/Controller/TaskController.php
@@ -378,6 +383,7 @@ Let's discover how to use Turbo Streams to enhance your `Symfony forms`_::
                 return $this->redirectToRoute('task_success', [], Response::HTTP_SEE_OTHER);
             }
 
+            // Symfony 6.2+
             return $this->render('task/new.html.twig', [
                 'form' => $form,
             ]);
@@ -388,177 +394,19 @@ Let's discover how to use Turbo Streams to enhance your `Symfony forms`_::
 
     {# bottom of new.html.twig #}
     {% block success_stream %}
-        <turbo-stream action="replace" targets="#my_div_id">
-            <template>
-                The element having the id "my_div_id" will be replaced by this block, without a full page reload!
+    <turbo-stream action="replace" targets="#my_div_id">
+        <template>
+            The element having the id "my_div_id" will be replaced by this block, without a full page reload!
 
-                <div>The task "{{ task }}" has been created!</div>
-            </template>
-        </turbo-stream>
+            <div>The task "{{ task }}" has been created!</div>
+        </template>
+    </turbo-stream>
     {% endblock %}
 
 Supported actions are ``append``, ``prepend``, ``replace``, ``update``,
 ``remove``, ``before``, ``after`` and ``refresh``.
 `Read the Turbo Streams documentation for more details`_.
 
-Stream Messages and Actions
-^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-To render a ``<turbo-stream>`` element, this bundle provides a set of ``<twig:Turbo:Stream:*>`` Twig Components. These components make it easy to inject content directly into the ``<template>`` tag, pass attributes, and set the desired morphing mode with a clear and consistent syntax.
-
-Append
-""""""
-
-.. code-block:: html+twig
-
-    <twig:Turbo:Stream:Append target="#dom_id">
-        Content to append to container designated with the dom_id.
-    </twig:Turbo:Stream:Append>
-
-    {# renders as: #}
-    <turbo-stream action="append" targets="#dom_id">
-        <template>
-            Content to append to container designated with the dom_id.
-        </template>
-    </turbo-stream>
-
-Prepend
-"""""""
-
-.. code-block:: html+twig
-
-    <twig:Turbo:Stream:Prepend target="#dom_id">
-        Content to prepend to container designated with the dom_id.
-    </twig:Turbo:Stream:Prepend>
-
-    {# renders as: #}
-    <turbo-stream action="prepend" targets="#dom_id">
-        <template>
-            Content to prepend to container designated with the dom_id.
-        </template>
-    </turbo-stream>
-
-Replace
-"""""""
-
-.. code-block:: html+twig
-
-    <twig:Turbo:Stream:Replace target="#dom_id">
-        Content to replace the element designated with the dom_id.
-    </twig:Turbo:Stream:Replace>
-
-    {# renders as: #}
-    <turbo-stream action="replace" targets="#dom_id">
-        <template>
-            Content to replace the element designated with the dom_id.
-        </template>
-    </turbo-stream>
-
-.. code-block:: html+twig
-
-    {# with morphing #}
-    <twig:Turbo:Stream:Replace target="#dom_id" morph>
-        Content to replace the element.
-    </twig:Turbo:Stream:Replace>
-
-    {# renders as: #}
-    <turbo-stream action="replace" targets="#dom_id" method="morph">
-        <template>
-            Content to replace the element.
-        </template>
-    </turbo-stream>
-
-Update
-""""""
-
-.. code-block:: html+twig
-
-    <twig:Turbo:Stream:Update target="#dom_id">
-        Content to update to container designated with the dom_id.
-    </twig:Turbo:Stream:Update>
-
-    {# renders as: #}
-    <turbo-stream action="update" targets="#dom_id">
-        <template>
-            Content to update to container designated with the dom_id.
-        </template>
-    </turbo-stream>
-
-.. code-block:: html+twig
-
-    {# with morphing #}
-    <twig:Turbo:Stream:Update target="#dom_id" morph>
-        Content to replace the element.
-    </twig:Turbo:Stream:Update>
-
-    {# renders as: #}
-    <turbo-stream action="update" targets="#dom_id" method="morph">
-        <template>
-            Content to replace the element.
-        </template>
-    </turbo-stream>
-
-Remove
-""""""
-
-.. code-block:: html+twig
-
-    <twig:Turbo:Stream:Remove target="#dom_id" />
-
-    {# renders as: #}
-    <turbo-stream action="remove" targets="#dom_id"></turbo-stream>
-
-Before
-""""""
-
-.. code-block:: html+twig
-
-    <twig:Turbo:Stream:Before target="#dom_id">
-        Content to place before the element designated with the dom_id.
-    </twig:Turbo:Stream:Before>
-
-    {# renders as: #}
-    <turbo-stream action="before" targets="#dom_id">
-        <template>
-            Content to place before the element designated with the dom_id.
-        </template>
-    </turbo-stream>
-
-After
-"""""
-
-.. code-block:: html+twig
-
-    <twig:Turbo:Stream:Refresh target="#dom_id">
-        Content to place after the element designated with the dom_id.
-    </twig:Turbo:Stream:After>
-
-    {# renders as: #}
-    <turbo-stream action="after" targets="#dom_id">
-        <template>
-            Content to place after the element designated with the dom_id.
-        </template>
-    </turbo-stream>
-
-Refresh
-"""""""
-
-.. code-block:: html+twig
-
-    {# without [request-id] #}
-    <twig:Turbo:Stream:Refresh />
-
-    {# renders as: #}
-    <turbo-stream action="refresh"></turbo-stream>
-
-.. code-block:: html+twig
-
-    {# debounced with [request-id] #}
-    <twig:Turbo:Stream:Refresh requestId="abcd-1234" />
-
-    {# renders as: #}
-    <turbo-stream action="refresh" request-id="abcd-1234"></turbo-stream>
-
 Resetting the Form
 ~~~~~~~~~~~~~~~~~~
 
@@ -652,7 +500,7 @@ Then, enable the "mercure stream" controller in ``assets/controllers.json``:
         "mercure-turbo-stream": {
     +         "enabled": true,
     -         "enabled": false,
-            "fetch": "eager"
+            "fetch": "lazy"
         }
     },
 
@@ -664,19 +512,14 @@ If you use Symfony Flex, the configuration has been generated for you,
 be sure to update the ``MERCURE_URL`` in the ``.env`` file to point to a
 Mercure Hub (it's not necessary if you are using Symfony Docker).
 
-Otherwise, configure Mercure Hub(s) as explained in the documentation:
+Otherwise, configure Mercure Hub(s) to use:
 
 .. code-block:: yaml
 
-    # config/packages/mercure.yaml
-    mercure:
-        hubs:
-            default:
-                url: '%env(MERCURE_URL)%'
-                public_url: '%env(MERCURE_PUBLIC_URL)%'
-                jwt:
-                    secret: '%env(MERCURE_JWT_SECRET)%'
-                    publish: '*'
+    # config/packages/turbo.yaml
+    turbo:
+        mercure:
+            hubs: [default]
 
 Let's create our chat::
 
@@ -942,6 +785,13 @@ the following configuration:
                 url: https://hub2.example.net/.well-known/mercure
                 jwt: snip
 
+.. code-block:: yaml
+
+    # config/packages/turbo.yaml
+    turbo:
+        mercure:
+            hubs: [hub1, hub2]
+
 Use the appropriate Mercure ``HubInterface`` service to send a change
 using a specific transport::
 
diff --git a/src/Turbo/src/Request/RequestListener.php b/src/Turbo/src/Request/RequestListener.php
deleted file mode 100644
index a59172af..00000000
--- a/src/Turbo/src/Request/RequestListener.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Turbo\Request;
-
-use Symfony\Component\HttpFoundation\Request;
-use Symfony\UX\Turbo\TurboBundle;
-
-/**
- * Registers the Turbo request format for all requests.
- *
- * @author Alexander Hofbauer <a.hofbauer@fify.at>
- */
-final class RequestListener
-{
-    public function __invoke(): void
-    {
-        (new Request())->setFormat(TurboBundle::STREAM_FORMAT, TurboBundle::STREAM_MEDIA_TYPE);
-    }
-}
diff --git a/src/Turbo/src/TurboBundle.php b/src/Turbo/src/TurboBundle.php
index e1524e4b..4c97d525 100644
--- a/src/Turbo/src/TurboBundle.php
+++ b/src/Turbo/src/TurboBundle.php
@@ -14,6 +14,7 @@ namespace Symfony\UX\Turbo;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\Compiler\PassConfig;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Bundle\Bundle;
 
 /**
@@ -24,6 +25,11 @@ final class TurboBundle extends Bundle
     public const STREAM_FORMAT = 'turbo_stream';
     public const STREAM_MEDIA_TYPE = 'text/vnd.turbo-stream.html';
 
+    public function boot(): void
+    {
+        (new Request())->setFormat(self::STREAM_FORMAT, self::STREAM_MEDIA_TYPE);
+    }
+
     public function build(ContainerBuilder $container): void
     {
         parent::build($container);
diff --git a/src/TwigComponent/doc/index.rst b/src/TwigComponent/doc/index.rst
index efccbcc6..f06882e2 100644
--- a/src/TwigComponent/doc/index.rst
+++ b/src/TwigComponent/doc/index.rst
@@ -1342,7 +1342,7 @@ You can test how your component is mounted and rendered using the
                 data: ['foo' => 'bar'],
             );
 
-            $this->assertStringContainsString('bar', (string) $rendered);
+            $this->assertStringContainsString('bar', $rendered);
 
             // use the crawler
             $this->assertCount(5, $rendered->crawler()->filter('ul li'));
@@ -1360,7 +1360,7 @@ You can test how your component is mounted and rendered using the
                 ],
             );
 
-            $this->assertStringContainsString('bar', (string) $rendered);
+            $this->assertStringContainsString('bar', $rendered);
         }
     }
 
diff --git a/src/TwigComponent/src/Twig/ComponentNode.php b/src/TwigComponent/src/Twig/ComponentNode.php
index df735079..439175c5 100644
--- a/src/TwigComponent/src/Twig/ComponentNode.php
+++ b/src/TwigComponent/src/Twig/ComponentNode.php
@@ -112,7 +112,7 @@ final class ComponentNode extends Node implements NodeOutputInterface
             ->raw('), ')
             ->raw($this->getAttribute('only') ? '[]' : '$context')
             ->raw(', ')
-            ->string($this->getAttribute('embedded_template'))
+            ->string(TemplateNameParser::parse($this->getAttribute('embedded_template')))
             ->raw(', ')
             ->raw($this->getAttribute('embedded_index'))
             ->raw(");\n");
diff --git a/src/TwigComponent/src/Twig/ComponentTokenParser.php b/src/TwigComponent/src/Twig/ComponentTokenParser.php
index ff072124..3f94c09d 100644
--- a/src/TwigComponent/src/Twig/ComponentTokenParser.php
+++ b/src/TwigComponent/src/Twig/ComponentTokenParser.php
@@ -33,12 +33,7 @@ final class ComponentTokenParser extends AbstractTokenParser
     public function parse(Token $token): Node
     {
         $stream = $this->parser->getStream();
-        if (method_exists($this->parser, 'parseExpression')) {
-            // Since Twig 3.21
-            $componentName = $this->componentName($this->parser->parseExpression());
-        } else {
-            $componentName = $this->componentName($this->parser->getExpressionParser()->parseExpression());
-        }
+        $componentName = $this->componentName($this->parser->getExpressionParser()->parseExpression());
 
         [$propsExpression, $only] = $this->parseArguments();
 
@@ -68,7 +63,7 @@ final class ComponentTokenParser extends AbstractTokenParser
         $this->parser->embedTemplate($module);
 
         // override the embedded index with a deterministic value, so it can be loaded in a controlled manner
-        $module->setAttribute('index', $this->generateEmbeddedTemplateIndex($stream->getSourceContext()->getName(), $token->getLine()));
+        $module->setAttribute('index', $this->generateEmbeddedTemplateIndex(TemplateNameParser::parse($stream->getSourceContext()->getName()), $token->getLine()));
 
         $stream->expect(Token::BLOCK_END_TYPE);
 
@@ -103,12 +98,7 @@ final class ComponentTokenParser extends AbstractTokenParser
         $variables = null;
 
         if ($stream->nextIf(Token::NAME_TYPE, 'with')) {
-            if (method_exists($this->parser, 'parseExpression')) {
-                // Since Twig 3.21
-                $variables = $this->parser->parseExpression();
-            } else {
-                $variables = $this->parser->getExpressionParser()->parseExpression();
-            }
+            $variables = $this->parser->getExpressionParser()->parseExpression();
         }
 
         $only = false;
@@ -128,14 +118,6 @@ final class ComponentTokenParser extends AbstractTokenParser
             $this->lineAndFileCounts[$fileAndLine] = 0;
         }
 
-        $index = crc32($fileAndLine).++$this->lineAndFileCounts[$fileAndLine];
-
-        if (4 === \PHP_INT_SIZE) {
-            // On 32-bit PHP, the index can be negative or greater than PHP_INT_MAX
-            // we need to convert it to a positive 32-bit integer
-            $index = fmod(abs($index), \PHP_INT_MAX) + 1;
-        }
-
-        return (int) $index;
+        return crc32($fileAndLine).++$this->lineAndFileCounts[$fileAndLine];
     }
 }
diff --git a/src/TwigComponent/src/Twig/PropsTokenParser.php b/src/TwigComponent/src/Twig/PropsTokenParser.php
index 50c2fe43..fc3b6640 100644
--- a/src/TwigComponent/src/Twig/PropsTokenParser.php
+++ b/src/TwigComponent/src/Twig/PropsTokenParser.php
@@ -33,12 +33,7 @@ class PropsTokenParser extends AbstractTokenParser
             $name = $stream->expect(Token::NAME_TYPE)->getValue();
 
             if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) {
-                if (method_exists($parser, 'parseExpression')) {
-                    // Since Twig 3.21
-                    $values[$name] = $parser->parseExpression();
-                } else {
-                    $values[$name] = $parser->getExpressionParser()->parseExpression();
-                }
+                $values[$name] = $parser->getExpressionParser()->parseExpression();
             }
 
             $names[] = $name;
diff --git a/src/TwigComponent/src/Twig/TemplateNameParser.php b/src/TwigComponent/src/Twig/TemplateNameParser.php
new file mode 100644
index 00000000..2c9d5918
--- /dev/null
+++ b/src/TwigComponent/src/Twig/TemplateNameParser.php
@@ -0,0 +1,36 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\TwigComponent\Twig;
+
+/**
+ * @internal
+ */
+final class TemplateNameParser
+{
+    /**
+     * Copied from Twig\Loader\FilesystemLoader, and adjusted to needs for this class.
+     *
+     * @see \Twig\Loader\FilesystemLoader::parseName
+     */
+    public static function parse(string $name): string
+    {
+        if (str_starts_with($name, '@')) {
+            if (!str_contains($name, '/')) {
+                throw new \LogicException(\sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
+            }
+
+            return $name;
+        }
+
+        return $name;
+    }
+}
diff --git a/src/TwigComponent/src/Twig/TwigPreLexer.php b/src/TwigComponent/src/Twig/TwigPreLexer.php
index 3cdee464..5d6d41f3 100644
--- a/src/TwigComponent/src/Twig/TwigPreLexer.php
+++ b/src/TwigComponent/src/Twig/TwigPreLexer.php
@@ -233,7 +233,6 @@ class TwigPreLexer
             $isAttributeDynamic = false;
 
             // :someProp="dynamicVar"
-            $this->consumeWhitespace();
             if ($this->check(':')) {
                 $this->consume(':');
                 $isAttributeDynamic = true;
@@ -245,7 +244,6 @@ class TwigPreLexer
 
             // <twig:component someProp> -> someProp: true
             if (!$this->check('=')) {
-                $this->consumeWhitespace();
                 // don't allow "<twig:component :someProp>"
                 if ($isAttributeDynamic) {
                     throw new SyntaxError(\sprintf('Expected "=" after ":%s" when parsing the "<twig:%s" syntax.', $key, $componentName), $this->line);
@@ -334,12 +332,6 @@ class TwigPreLexer
         $whitespace = substr($this->input, $this->position, strspn($this->input, " \t\n\r\0\x0B", $this->position));
         $this->line += substr_count($whitespace, "\n");
         $this->position += \strlen($whitespace);
-
-        if ($this->check('#')) {
-            $this->consume('#');
-            $this->consumeUntil("\n");
-            $this->consumeWhitespace();
-        }
     }
 
     /**
diff --git a/ux.symfony.com/templates/icons/index.html.twig b/ux.symfony.com/templates/icons/index.html.twig
index d4580f39..eb0bbad4 100644
--- a/ux.symfony.com/templates/icons/index.html.twig
+++ b/ux.symfony.com/templates/icons/index.html.twig
@@ -48,11 +48,6 @@
         <p class="eyebrows" style="margin: 4rem 0 0;">Iconic Packages</p>
         <div class="d-md-flex justify-content-md-between text-center align-items-center text-md-start">
             <h2 class="ubuntu pt-2 component-headlines">Popular <em class="rainbow-emphasis">Icon Sets</em></h2>
-            <a style="height: 100%;" class="btn btn-md btn-outline-primary my-3 my-md-0"
-               href="https://symfony.com/bundles/ux-icons/current/index.html" rel="external" target="_blank">
-                Read Documentation
-                <twig:ux:icon name="arrow-right" style="transform: rotate(-45deg);"/>
-            </a>
         </div>
         <div class="mt-3 mt-md-5">
             <div style="display:grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 2rem;">
@@ -66,41 +61,6 @@
         </div>
     </section>
 
-    <section class="section">
-        <div class="container-fluid container-xxl px-4 px-md-5 py-3">
-
-            <div class="d-md-flex justify-content-md-between py-5 text-center align-items-center text-md-start">
-                <h2 class="ubuntu pt-2 component-headlines">Documentation</h2>
-                <a href="https://symfony.com/bundles/ux-icons/current/index.html" class="btn btn-md btn-outline-primary my-3 my-md-0"
-                   rel="external" target="_blank">
-                    Read Full Documentation
-                    <twig:ux:icon name="arrow-right" style="transform: rotate(-45deg);"/>
-                </a>
-            </div>
-
-            <div class="row">
-                <div class="col-4">
-                    <h3 class="ubuntu mb-4" style="font-weight: 400;" title="Install UX Icons">Installation</h3>
-                    <twig:Terminal bottomPadding="20">
-                        composer require {{ package.composerName }}
-                    </twig:Terminal>
-                </div>
-                <div class="col-4">
-                    <h3 class="ubuntu mb-4" style="font-weight: 400;" title="Search UX Icons">Search Icons</h3>
-                    <twig:Terminal bottomPadding="20">
-                        php bin/console ux:icons:search lucide
-                    </twig:Terminal>
-                </div>
-                <div class="col-4">
-                   <h3 class="ubuntu mb-4" style="font-weight: 400;" title="Symfony UX Bundle Configuration">Configuration</h3>
-                    <twig:Terminal bottomPadding="20">
-                        php bin/console debug:config ux_icons
-                    </twig:Terminal>
-                </div>
-            </div>
-        </div>
-    </section>
-
 {% endblock %}
 
 {% block aside %}
diff --git a/yarn.lock b/yarn.lock
index 29118f6c..5aeb6d65 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2888,7 +2888,6 @@ __metadata:
   dependencies:
     "@googlemaps/js-api-loader": "npm:^1.16.6"
     "@hotwired/stimulus": "npm:^3.0.0"
-    "@symfony/ux-map": "workspace:*"
     "@types/google.maps": "npm:^3.55.9"
   peerDependencies:
     "@googlemaps/js-api-loader": ^1.16.6
@@ -2914,7 +2913,6 @@ __metadata:
   resolution: "@symfony/ux-leaflet-map@workspace:src/Map/src/Bridge/Leaflet/assets"
   dependencies:
     "@hotwired/stimulus": "npm:^3.0.0"
-    "@symfony/ux-map": "workspace:*"
     "@types/leaflet": "npm:^1.9.12"
     leaflet: "npm:^1.9.4"
   peerDependencies:
@@ -2941,7 +2939,7 @@ __metadata:
   languageName: unknown
   linkType: soft
 
-"@symfony/ux-map@workspace:*, @symfony/ux-map@workspace:src/Map/assets":
+"@symfony/ux-map@workspace:src/Map/assets":
   version: 0.0.0-use.local
   resolution: "@symfony/ux-map@workspace:src/Map/assets"
   dependencies:
@@ -3042,7 +3040,6 @@ __metadata:
   dependencies:
     "@hotwired/stimulus": "npm:^3.0.0"
     "@hotwired/turbo": "npm:^7.1.0 || ^8.0"
-    "@types/hotwired__turbo": "npm:^8.0.4"
   peerDependencies:
     "@hotwired/stimulus": ^3.0.0
     "@hotwired/turbo": ^7.1.1 || ^8.0
@@ -3281,13 +3278,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@types/hotwired__turbo@npm:^8.0.4":
-  version: 8.0.4
-  resolution: "@types/hotwired__turbo@npm:8.0.4"
-  checksum: 10c0/87fb69ea0dab21fd2a6e3e6d18cdaf679e3d7c8ea1f6f2b59d05c24a96cf3faf80a4c0ce5f57161afea9b61f2cc542f9e33cbdc6475fc4003d79516a94cedb5c
-  languageName: node
-  linkType: hard
-
 "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1":
   version: 2.0.4
   resolution: "@types/istanbul-lib-coverage@npm:2.0.4"


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 1ececd75..24dd1d62 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -17,9 +17,16 @@ jobs:
         steps:
             - uses: actions/checkout@v4
             - run: corepack enable
-            - uses: actions/setup-node@v4
+            - name: Get yarn cache directory path
+              id: yarn-cache-dir-path
+              run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
+            - uses: actions/cache@v4
+              id: yarn-cache
               with:
-                cache: 'yarn'
+                  path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+                  key: ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }}
+                  restore-keys: |
+                      ${{ runner.os }}-yarn-
             - run: yarn --immutable
             - run: yarn ci
 
@@ -29,24 +36,32 @@ jobs:
         steps:
             - uses: actions/checkout@v4
             - run: corepack enable
-            - uses: actions/setup-node@v4
+            - name: Get yarn cache directory path
+              id: yarn-cache-dir-path
+              run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
+            - uses: actions/cache@v4
+              id: yarn-cache
               with:
-                cache: 'yarn'
-                cache-dependency-path: |
-                  yarn.lock
-                  **/package.json
+                  path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+                  key: ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }}
+                  restore-keys: |
+                      ${{ runner.os }}-yarn-
             - run: yarn --immutable && yarn build
+            - name: Check if js dist files are current
+              id: changes
+              run: |
+                echo "STATUS=$(git status --porcelain)" >> $GITHUB_OUTPUT
 
-            - name: Check if JS dist files are current
+            - name: No changes found
+              if: steps.changes.outputs.STATUS == ''
+              run: |
+                echo "git status is clean"
+            - name: Changes were found
+              if: steps.changes.outputs.STATUS != ''
               run: |
-                if [[ -n $(git status --porcelain) ]]; then
-                  echo "The Git workspace is unclean! Changes detected:"
-                  git status --porcelain
-                  git diff
-                  exit 1
-                else
-                  echo "The Git workspace is clean. No changes detected."
-                fi
+                echo "JS dist files need to be rebuilt"
+                echo "${{ steps.changes.outputs.STATUS }}"
+                exit 1
 
     tests-php-components:
         runs-on: ubuntu-latest
@@ -68,7 +83,6 @@ jobs:
             fail-fast: false
             matrix:
                 php-version: ['8.1', '8.3', '8.4']
-                minimum-stability: ['stable', 'dev']
                 include:
                   - php-version: '8.1'
                     dependency-version: 'lowest'
@@ -78,10 +92,6 @@ jobs:
                     dependency-version: 'highest'
                 component: ${{ fromJson(needs.tests-php-components.outputs.components )}}
                 exclude:
-                  - php-version: '8.1'
-                    minimum-stability: 'dev'
-                  - php-version: '8.3'
-                    minimum-stability: 'dev'
                   - component: Map # does not support PHP 8.1
                     php-version: '8.1'
                   - component: Map/src/Bridge/Google # does not support PHP 8.1
@@ -109,11 +119,6 @@ jobs:
               run: php .github/build-packages.php
               working-directory: ${{ github.workspace }}
 
-            - name: Configure ${{ matrix.minimum-stability }} stability
-              if: ${{ matrix.minimum-stability }}
-              run: composer config minimum-stability ${{ matrix.minimum-stability }}
-              working-directory: "src/${{ matrix.component }}"
-
             - name: Install ${{ matrix.component }} packages
               uses: ramsey/composer-install@v3
               with:
@@ -129,12 +134,16 @@ jobs:
         steps:
             - uses: actions/checkout@v4
             - run: corepack enable
-            - uses: actions/setup-node@v4
+            - name: Get yarn cache directory path
+              id: yarn-cache-dir-path
+              run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
+            - uses: actions/cache@v4
+              id: yarn-cache
               with:
-                cache: 'yarn'
-                cache-dependency-path: |
-                  yarn.lock
-                  **/package.json
+                  path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
+                  key: ${{ runner.os }}-yarn-${{ hashFiles('**/package.json') }}
+                  restore-keys: |
+                      ${{ runner.os }}-yarn-
             - run: yarn --immutable
             - run: yarn playwright install
             - run: yarn test
diff --git a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php
deleted file mode 100644
index 7f86431a..00000000
--- a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomAttributesProductAutocompleter.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter;
-
-class CustomAttributesProductAutocompleter extends CustomProductAutocompleter
-{
-    public function getAttributes(object $entity): array
-    {
-        return [
-            'disabled' => true,
-            'value' => 'This value should be replaced with the result of getValue()',
-            'text' => 'This value should be replaced with the result of getText()',
-        ];
-    }
-}
diff --git a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php
index 09b5b96c..912a864c 100644
--- a/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php
+++ b/src/Autocomplete/tests/Fixtures/Autocompleter/CustomProductAutocompleter.php
@@ -19,9 +19,6 @@ use Symfony\UX\Autocomplete\Doctrine\EntitySearchUtil;
 use Symfony\UX\Autocomplete\EntityAutocompleterInterface;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Entity\Product;
 
-/**
- * @implements EntityAutocompleterInterface<Product>
- */
 class CustomProductAutocompleter implements EntityAutocompleterInterface
 {
     public function __construct(
@@ -61,11 +58,6 @@ class CustomProductAutocompleter implements EntityAutocompleterInterface
         return $entity->getId();
     }
 
-    public function getAttributes(object $entity): array
-    {
-        return [];
-    }
-
     public function isGranted(Security $security): bool
     {
         if ($this->requestStack->getCurrentRequest()?->query->get('enforce_test_security')) {
diff --git a/src/Autocomplete/tests/Fixtures/Kernel.php b/src/Autocomplete/tests/Fixtures/Kernel.php
index de2877b0..f1991a22 100644
--- a/src/Autocomplete/tests/Fixtures/Kernel.php
+++ b/src/Autocomplete/tests/Fixtures/Kernel.php
@@ -34,7 +34,6 @@ use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
 use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
 use Symfony\UX\Autocomplete\AutocompleteBundle;
 use Symfony\UX\Autocomplete\DependencyInjection\AutocompleteFormTypePass;
-use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomAttributesProductAutocompleter;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomGroupByProductAutocompleter;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomProductAutocompleter;
 use Symfony\UX\Autocomplete\Tests\Fixtures\Form\ProductType;
@@ -177,13 +176,6 @@ final class Kernel extends BaseKernel
                 'alias' => 'custom_group_by_product',
             ]);
 
-        $services->set(CustomAttributesProductAutocompleter::class)
-            ->public()
-            ->arg(1, new Reference('ux.autocomplete.entity_search_util'))
-            ->tag(AutocompleteFormTypePass::ENTITY_AUTOCOMPLETER_TAG, [
-                'alias' => 'custom_attributes_product',
-            ]);
-
         $services->alias('public.results_executor', 'ux.autocomplete.results_executor')
             ->public();
 
diff --git a/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php b/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php
deleted file mode 100644
index b804afd1..00000000
--- a/src/Autocomplete/tests/Integration/AutocompleteResultsExecutorTest.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Autocomplete\Tests\Integration;
-
-use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
-use Symfony\UX\Autocomplete\AutocompleteResultsExecutor;
-use Symfony\UX\Autocomplete\Tests\Fixtures\Autocompleter\CustomAttributesProductAutocompleter;
-use Symfony\UX\Autocomplete\Tests\Fixtures\Factory\ProductFactory;
-use Symfony\UX\Autocomplete\Tests\Fixtures\Kernel;
-use Zenstruck\Foundry\Test\Factories;
-use Zenstruck\Foundry\Test\ResetDatabase;
-
-class AutocompleteResultsExecutorTest extends KernelTestCase
-{
-    use Factories;
-    use ResetDatabase;
-
-    public function testItReturnsExtraAttributes(): void
-    {
-        $kernel = new Kernel('test', true);
-        $kernel->disableForms();
-        $kernel->boot();
-
-        $product = ProductFactory::createOne(['name' => 'Foo']);
-
-        /** @var AutocompleteResultsExecutor $executor */
-        $executor = $kernel->getContainer()->get('public.results_executor');
-        $autocompleter = $kernel->getContainer()->get(CustomAttributesProductAutocompleter::class);
-        $data = $executor->fetchResults($autocompleter, '', 1);
-        $this->assertCount(1, $data->results);
-        $this->assertSame(['disabled' => true, 'value' => $product->getId(), 'text' => 'Foo'], $data->results[0]);
-    }
-}
diff --git a/src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php b/src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php
index 59537991..f2a0e322 100644
--- a/src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php
+++ b/src/LiveComponent/tests/Fixtures/Form/FormWithManyDifferentFieldsType.php
@@ -65,15 +65,6 @@ class FormWithManyDifferentFieldsType extends AbstractType
                     'foo' => 1,
                 ],
             ])
-            ->add('choice_required_without_placeholder_and_choice_group', ChoiceType::class, [
-                'choices' => [
-                    'Bar Group' => [
-                        'Bar Label' => 'ok',
-                        'Foo Label' => 'foo_value',
-                    ],
-                    'foo' => 1,
-                ],
-            ])
             ->add('choice_expanded', ChoiceType::class, [
                 'choices' => [
                     'foo' => 1,
diff --git a/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php b/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php
index bfbe477c..a3ed0f7c 100644
--- a/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php
+++ b/src/LiveComponent/tests/Functional/Form/ComponentWithFormTest.php
@@ -181,7 +181,6 @@ class ComponentWithFormTest extends KernelTestCase
             'choice_required_with_placeholder' => '',
             'choice_required_with_empty_placeholder' => '',
             'choice_required_without_placeholder' => '2',
-            'choice_required_without_placeholder_and_choice_group' => 'ok',
             'choice_expanded' => '',
             'choice_multiple' => ['2'],
             'select_multiple' => ['2'],
diff --git a/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php b/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php
index 097e4d77..6bae36f9 100644
--- a/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php
+++ b/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php
@@ -18,7 +18,6 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
 use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component2;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory;
-use Zenstruck\Foundry\Test\Factories;
 use Zenstruck\Foundry\Test\ResetDatabase;
 
 /**
@@ -26,7 +25,6 @@ use Zenstruck\Foundry\Test\ResetDatabase;
  */
 final class InteractsWithLiveComponentsTest extends KernelTestCase
 {
-    use Factories;
     use InteractsWithLiveComponents;
     use ResetDatabase;
 
diff --git a/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php b/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php
index 01941f10..931430d8 100644
--- a/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php
+++ b/src/LiveComponent/tests/Unit/Form/ComponentWithFormTest.php
@@ -14,7 +14,6 @@ namespace Symfony\UX\LiveComponent\Tests\Unit\Form;
 use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Component\FormComponentWithManyDifferentFieldsType;
 use Symfony\UX\LiveComponent\Tests\Fixtures\Factory\CategoryFixtureEntityFactory;
-use Zenstruck\Foundry\Test\Factories;
 use Zenstruck\Foundry\Test\ResetDatabase;
 
 /**
@@ -22,7 +21,6 @@ use Zenstruck\Foundry\Test\ResetDatabase;
  */
 class ComponentWithFormTest extends KernelTestCase
 {
-    use Factories;
     use ResetDatabase;
 
     public function testFormValues(): void
@@ -48,7 +46,6 @@ class ComponentWithFormTest extends KernelTestCase
                 'choice_required_with_placeholder' => '',
                 'choice_required_with_empty_placeholder' => '',
                 'choice_required_without_placeholder' => '2',
-                'choice_required_without_placeholder_and_choice_group' => 'ok',
                 'choice_expanded' => '',
                 'choice_multiple' => ['2'],
                 'select_multiple' => ['2'],
diff --git a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
index dda259fe..d8695096 100644
--- a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
+++ b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
@@ -29,9 +29,6 @@ class GoogleRendererTest extends RendererTestCase
         $map = (new Map())
             ->center(new Point(48.8566, 2.3522))
             ->zoom(12);
-        $marker1 = new Marker(position: new Point(48.8566, 2.3522), title: 'Paris', id: 'marker1');
-        $marker2 = new Marker(position: new Point(48.8566, 2.3522), title: 'Lyon', infoWindow: new InfoWindow(content: 'Lyon'), id: 'marker2');
-        $marker3 = new Marker(position: new Point(45.8566, 2.3522), title: 'Dijon', id: 'marker3');
 
         yield 'simple map, with minimum options' => [
             'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
@@ -53,55 +50,25 @@ class GoogleRendererTest extends RendererTestCase
         ];
 
         yield 'with markers and infoWindows' => [
-            'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;marker1&quot;,&quot;@id&quot;:&quot;ff8e1883540bc717&quot;},{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Lyon&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Lyon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;adcbe35d50b3c983&quot;}]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
+            'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;@id&quot;:&quot;abd45a2c703af97a&quot;},{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Lyon&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Lyon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;219637cbd8e62ea6&quot;}]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
-                ->addMarker(new Marker(position: new Point(48.8566, 2.3522), title: 'Paris', id: 'marker1'))
+            'map' => (clone $map)
+                ->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
                 ->addMarker(new Marker(new Point(48.8566, 2.3522), 'Lyon', infoWindow: new InfoWindow(content: 'Lyon'))),
         ];
 
-        yield 'with all markers removed' => [
-            'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
-            'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
-                ->addMarker($marker1)
-                ->addMarker($marker2)
-                ->removeMarker($marker1)
-                ->removeMarker($marker2),
-        ];
-
-        yield 'with marker remove and new ones added' => [
-            'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;marker1&quot;,&quot;@id&quot;:&quot;ff8e1883540bc717&quot;},{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Lyon&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Lyon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;marker2&quot;,&quot;@id&quot;:&quot;9514c2b94def6c52&quot;}]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
-            'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
-                ->addMarker($marker3)
-                ->removeMarker($marker3)
-                ->addMarker($marker1)
-                ->addMarker($marker2),
-        ];
-
         yield 'with polygons and infoWindows' => [
-            'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;7cdd432ea54d0ce9&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;9074e0a9ead08c1e&quot;}]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
+            'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;@id&quot;:&quot;2dc7aa290e8fc88a&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;d6cab193e60e5faf&quot;}]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
+            'map' => (clone $map)
                 ->addPolygon(new Polygon(points: [new Point(48.8566, 2.3522), new Point(48.8566, 2.3522), new Point(48.8566, 2.3522)]))
                 ->addPolygon(new Polygon(points: [new Point(1.1, 2.2), new Point(3.3, 4.4), new Point(5.5, 6.6)], infoWindow: new InfoWindow(content: 'Polygon'))),
         ];
 
         yield 'with polylines and infoWindows' => [
-            'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;7cdd432ea54d0ce9&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;9074e0a9ead08c1e&quot;}]"></div>',
+            'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;@id&quot;:&quot;2dc7aa290e8fc88a&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;d6cab193e60e5faf&quot;}]"></div>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
+            'map' => (clone $map)
                 ->addPolyline(new Polyline(points: [new Point(48.8566, 2.3522), new Point(48.8566, 2.3522), new Point(48.8566, 2.3522)]))
                 ->addPolyline(new Polyline(points: [new Point(1.1, 2.2), new Point(3.3, 4.4), new Point(5.5, 6.6)], infoWindow: new InfoWindow(content: 'Polygon'))),
         ];
@@ -109,9 +76,7 @@ class GoogleRendererTest extends RendererTestCase
         yield 'with controls enabled' => [
             'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
+            'map' => (clone $map)
                 ->options(new GoogleOptions(
                     zoomControl: true,
                     mapTypeControl: true,
@@ -123,9 +88,7 @@ class GoogleRendererTest extends RendererTestCase
         yield 'without controls enabled' => [
             'expected_render' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:null,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
+            'map' => (clone $map)
                 ->options(new GoogleOptions(
                     zoomControl: false,
                     mapTypeControl: false,
@@ -137,24 +100,18 @@ class GoogleRendererTest extends RendererTestCase
         yield 'with default map id' => [
             'expected_renderer' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;my_api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:&quot;DefaultMapId&quot;,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), 'my_api_key', defaultMapId: 'DefaultMapId'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12),
+            'map' => (clone $map),
         ];
         yield 'with default map id, when passing options (except the "mapId")' => [
             'expected_renderer' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;my_api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:&quot;DefaultMapId&quot;,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), 'my_api_key', defaultMapId: 'DefaultMapId'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
+            'map' => (clone $map)
                 ->options(new GoogleOptions()),
         ];
         yield 'with default map id overridden by option "mapId"' => [
             'expected_renderer' => '<div data-controller="symfony--ux-google-map--map" data-symfony--ux-google-map--map-provider-options-value="{&quot;apiKey&quot;:&quot;my_api_key&quot;}" data-symfony--ux-google-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-google-map--map-zoom-value="12" data-symfony--ux-google-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-google-map--map-options-value="{&quot;mapId&quot;:&quot;CustomMapId&quot;,&quot;gestureHandling&quot;:&quot;auto&quot;,&quot;backgroundColor&quot;:null,&quot;disableDoubleClickZoom&quot;:false,&quot;zoomControlOptions&quot;:{&quot;position&quot;:22},&quot;mapTypeControlOptions&quot;:{&quot;mapTypeIds&quot;:[],&quot;position&quot;:14,&quot;style&quot;:0},&quot;streetViewControlOptions&quot;:{&quot;position&quot;:22},&quot;fullscreenControlOptions&quot;:{&quot;position&quot;:20},&quot;@provider&quot;:&quot;google&quot;}" data-symfony--ux-google-map--map-markers-value="[]" data-symfony--ux-google-map--map-polygons-value="[]" data-symfony--ux-google-map--map-polylines-value="[]"></div>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), 'my_api_key', defaultMapId: 'DefaultMapId'),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
+            'map' => (clone $map)
                 ->options(new GoogleOptions(mapId: 'CustomMapId')),
         ];
     }
diff --git a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
index e9f8cec5..81d53160 100644
--- a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
+++ b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
@@ -29,74 +29,41 @@ class LeafletRendererTest extends RendererTestCase
             ->center(new Point(48.8566, 2.3522))
             ->zoom(12);
 
-        $marker1 = new Marker(position: new Point(48.8566, 2.3522), title: 'Paris', id: 'marker1');
-        $marker2 = new Marker(position: new Point(48.8566, 2.3522), title: 'Lyon', infoWindow: new InfoWindow(content: 'Lyon'), id: 'marker2');
-        $marker3 = new Marker(position: new Point(45.8566, 2.3522), title: 'Dijon', id: 'marker3');
-
         yield 'simple map' => [
             'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[]" data-symfony--ux-leaflet-map--map-polygons-value="[]" data-symfony--ux-leaflet-map--map-polylines-value="[]"></div>',
             'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            'map' => (clone $map),
+            'map' => $map,
         ];
 
         yield 'with custom attributes' => [
             'expected_render' => '<div data-controller="my-custom-controller symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[]" data-symfony--ux-leaflet-map--map-polygons-value="[]" data-symfony--ux-leaflet-map--map-polylines-value="[]" class="map"></div>',
             'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            'map' => (clone $map),
+            'map' => $map,
             'attributes' => ['data-controller' => 'my-custom-controller', 'class' => 'map'],
         ];
-        yield 'with markers and infoWindows' => [
-            'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;marker1&quot;,&quot;@id&quot;:&quot;ff8e1883540bc717&quot;},{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Lyon&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Lyon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:null,&quot;@id&quot;:&quot;adcbe35d50b3c983&quot;}]" data-symfony--ux-leaflet-map--map-polygons-value="[]" data-symfony--ux-leaflet-map--map-polylines-value="[]"></div>',
-            'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
-                ->addMarker($marker1)
-                ->addMarker(new Marker(position: new Point(48.8566, 2.3522), title: 'Lyon', infoWindow: new InfoWindow(content: 'Lyon'))),
-        ];
 
-        yield 'with all markers removed' => [
-            'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[]" data-symfony--ux-leaflet-map--map-polygons-value="[]" data-symfony--ux-leaflet-map--map-polylines-value="[]"></div>',
-            'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
-                ->addMarker($marker1)
-                ->addMarker($marker2)
-                ->removeMarker($marker1)
-                ->removeMarker($marker2),
-        ];
-
-        yield 'with marker remove and new ones added' => [
-            'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;marker1&quot;,&quot;@id&quot;:&quot;ff8e1883540bc717&quot;},{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Lyon&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Lyon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;marker2&quot;,&quot;@id&quot;:&quot;9514c2b94def6c52&quot;}]" data-symfony--ux-leaflet-map--map-polygons-value="[]" data-symfony--ux-leaflet-map--map-polylines-value="[]"></div>',
+        yield 'with markers and infoWindows' => [
+            'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Paris&quot;,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;@id&quot;:&quot;abd45a2c703af97a&quot;},{&quot;position&quot;:{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},&quot;title&quot;:&quot;Lyon&quot;,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Lyon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;219637cbd8e62ea6&quot;}]" data-symfony--ux-leaflet-map--map-polygons-value="[]" data-symfony--ux-leaflet-map--map-polylines-value="[]"></div>',
             'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
-                ->addMarker($marker3)
-                ->removeMarker($marker3)
-                ->addMarker($marker1)
-                ->addMarker($marker2),
+            'map' => (clone $map)
+                ->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
+                ->addMarker(new Marker(new Point(48.8566, 2.3522), 'Lyon', infoWindow: new InfoWindow(content: 'Lyon'))),
         ];
 
         yield 'with polygons and infoWindows' => [
-            'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[]" data-symfony--ux-leaflet-map--map-polygons-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;polygon1&quot;,&quot;@id&quot;:&quot;35bfa920335b849d&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;polygon2&quot;,&quot;@id&quot;:&quot;7be1fe9f10489d73&quot;}]" data-symfony--ux-leaflet-map--map-polylines-value="[]"></div>',
+            'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[]" data-symfony--ux-leaflet-map--map-polygons-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;@id&quot;:&quot;2dc7aa290e8fc88a&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;d6cab193e60e5faf&quot;}]" data-symfony--ux-leaflet-map--map-polylines-value="[]"></div>',
             'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
-                ->addPolygon(new Polygon(points: [new Point(48.8566, 2.3522), new Point(48.8566, 2.3522), new Point(48.8566, 2.3522)], id: 'polygon1'))
-                ->addPolygon(new Polygon(points: [new Point(1.1, 2.2), new Point(3.3, 4.4), new Point(5.5, 6.6)], infoWindow: new InfoWindow(content: 'Polygon'), id: 'polygon2')),
+            'map' => (clone $map)
+                ->addPolygon(new Polygon(points: [new Point(48.8566, 2.3522), new Point(48.8566, 2.3522), new Point(48.8566, 2.3522)]))
+                ->addPolygon(new Polygon(points: [new Point(1.1, 2.2), new Point(3.3, 4.4), new Point(5.5, 6.6)], infoWindow: new InfoWindow(content: 'Polygon'))),
         ];
 
         yield 'with polylines and infoWindows' => [
-            'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[]" data-symfony--ux-leaflet-map--map-polygons-value="[]" data-symfony--ux-leaflet-map--map-polylines-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;id&quot;:&quot;polyline1&quot;,&quot;@id&quot;:&quot;823f6ee5acdb5db3&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polyline&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;id&quot;:&quot;polyline2&quot;,&quot;@id&quot;:&quot;77fb0e390b5e91f1&quot;}]"></div>',
+            'expected_render' => '<div data-controller="symfony--ux-leaflet-map--map" data-symfony--ux-leaflet-map--map-provider-options-value="{}" data-symfony--ux-leaflet-map--map-center-value="{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}" data-symfony--ux-leaflet-map--map-zoom-value="12" data-symfony--ux-leaflet-map--map-fit-bounds-to-markers-value="false" data-symfony--ux-leaflet-map--map-options-value="{&quot;tileLayer&quot;:{&quot;url&quot;:&quot;https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png&quot;,&quot;attribution&quot;:&quot;\u00a9 &lt;a href=\&quot;https:\/\/www.openstreetmap.org\/copyright\&quot;&gt;OpenStreetMap&lt;\/a&gt;&quot;,&quot;options&quot;:[]},&quot;@provider&quot;:&quot;leaflet&quot;}" data-symfony--ux-leaflet-map--map-markers-value="[]" data-symfony--ux-leaflet-map--map-polygons-value="[]" data-symfony--ux-leaflet-map--map-polylines-value="[{&quot;points&quot;:[{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522},{&quot;lat&quot;:48.8566,&quot;lng&quot;:2.3522}],&quot;title&quot;:null,&quot;infoWindow&quot;:null,&quot;extra&quot;:[],&quot;@id&quot;:&quot;2dc7aa290e8fc88a&quot;},{&quot;points&quot;:[{&quot;lat&quot;:1.1,&quot;lng&quot;:2.2},{&quot;lat&quot;:3.3,&quot;lng&quot;:4.4},{&quot;lat&quot;:5.5,&quot;lng&quot;:6.6}],&quot;title&quot;:null,&quot;infoWindow&quot;:{&quot;headerContent&quot;:null,&quot;content&quot;:&quot;Polygon&quot;,&quot;position&quot;:null,&quot;opened&quot;:false,&quot;autoClose&quot;:true,&quot;extra&quot;:[]},&quot;extra&quot;:[],&quot;@id&quot;:&quot;d6cab193e60e5faf&quot;}]"></div>',
             'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            'map' => (new Map())
-                ->center(new Point(48.8566, 2.3522))
-                ->zoom(12)
-                ->addPolyline(new Polyline(points: [new Point(48.8566, 2.3522), new Point(48.8566, 2.3522), new Point(48.8566, 2.3522)], id: 'polyline1'))
-                ->addPolyline(new Polyline(points: [new Point(1.1, 2.2), new Point(3.3, 4.4), new Point(5.5, 6.6)], infoWindow: new InfoWindow(content: 'Polyline'), id: 'polyline2')),
+            'map' => (clone $map)
+                ->addPolyline(new Polyline(points: [new Point(48.8566, 2.3522), new Point(48.8566, 2.3522), new Point(48.8566, 2.3522)]))
+                ->addPolyline(new Polyline(points: [new Point(1.1, 2.2), new Point(3.3, 4.4), new Point(5.5, 6.6)], infoWindow: new InfoWindow(content: 'Polygon'))),
         ];
     }
 }
diff --git a/src/Map/tests/Distance/DistanceCalculatorTest.php b/src/Map/tests/Distance/DistanceCalculatorTest.php
deleted file mode 100644
index 3f6c982a..00000000
--- a/src/Map/tests/Distance/DistanceCalculatorTest.php
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Tests\Distance;
-
-use PHPUnit\Framework\TestCase;
-use Symfony\UX\Map\Distance\DistanceCalculator;
-use Symfony\UX\Map\Distance\DistanceCalculatorInterface;
-use Symfony\UX\Map\Distance\HaversineDistanceCalculator;
-use Symfony\UX\Map\Distance\SphericalCosineDistanceCalculator;
-use Symfony\UX\Map\Distance\VincentyDistanceCalculator;
-use Symfony\UX\Map\Point;
-
-class DistanceCalculatorTest extends TestCase
-{
-    public function testCalculateDistanceUseCalculator(): void
-    {
-        $calculator = new class implements DistanceCalculatorInterface {
-            public function calculateDistance(Point $point1, Point $point2): float
-            {
-                return $point1->getLatitude() + $point2->getLongitude();
-            }
-        };
-
-        $distanceCalculator = new DistanceCalculator($calculator);
-        $this->assertSame(0.0, $distanceCalculator->calculateDistance(new Point(0.0, 0.0), new Point(0.0, 0.0)));
-        $this->assertSame(90.0, $distanceCalculator->calculateDistance(new Point(45.0, 0.0), new Point(0.0, 45.0)));
-    }
-
-    /**
-     * Test that the non-reference calculators (Haversine and Spherical Cosine)
-     * produce results close to the reference (Vincenty) within an acceptable margin.
-     *
-     * @dataProvider distanceAccuracyProvider
-     */
-    public function testAccuracyAgainstVincenty(Point $point1, Point $point2, float $tolerance): void
-    {
-        $vincenty = new VincentyDistanceCalculator();
-        $referenceDistance = $vincenty->calculateDistance($point1, $point2);
-
-        $calculators = [
-            new HaversineDistanceCalculator(),
-            new SphericalCosineDistanceCalculator(),
-        ];
-
-        foreach ($calculators as $calculator) {
-            $distance = $calculator->calculateDistance($point1, $point2);
-            $difference = abs($referenceDistance - $distance);
-
-            $this->assertLessThanOrEqual($tolerance, $difference, \sprintf('%s difference (%.2f m) exceeds tolerance (%.2f m).', $calculator::class, $difference, $tolerance));
-        }
-    }
-
-    /**
-     * @return array<array{Point, Point, float}>
-     */
-    public static function distanceAccuracyProvider(): array
-    {
-        return [
-            'Short distance: around the equator (111.32m)' => [
-                new Point(0.0, 0.0),
-                new Point(0.0, 0.001),
-                .25,
-            ],
-            'Small distance: Highbury to Emirates (445.61m)' => [
-                new Point(51.55703, -0.10280),
-                new Point(51.55509, -0.10844),
-                2.5,
-            ],
-            'Moderate distance: Rennes to Saint-Malo (64.537 km)' => [
-                new Point(48.1173, -1.6778),
-                new Point(48.6497, -2.0258),
-                50.0,
-            ],
-            'Moderate distance: Paris to London  (343.556 km)' => [
-                new Point(48.8566, 2.3522),
-                new Point(51.5074, -0.1278),
-                500.0,
-            ],
-            'Long distance: Metropolis to Gotham City (1,144.291 km)' => [
-                new Point(40.7128, -74.0060),
-                new Point(41.8781, -87.6298),
-                5000.0,
-            ],
-            'Long distance: New York to Los Angeles (3,935.746 km)' => [
-                new Point(40.7128, -74.0060),
-                new Point(34.0522, -118.2437),
-                10000.0,
-            ],
-        ];
-    }
-}
diff --git a/src/Map/tests/Distance/DistanceUnitTest.php b/src/Map/tests/Distance/DistanceUnitTest.php
deleted file mode 100644
index f33b985d..00000000
--- a/src/Map/tests/Distance/DistanceUnitTest.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Tests\Distance;
-
-use PHPUnit\Framework\TestCase;
-use Symfony\UX\Map\Distance\DistanceUnit;
-
-class DistanceUnitTest extends TestCase
-{
-    public function testConversionFactorIsPositive()
-    {
-        foreach (DistanceUnit::cases() as $unit) {
-            $this->assertGreaterThan(0, $unit->getConversionFactor());
-        }
-    }
-
-    public function testConversionFactorToMeterIsSameAsConversionFactor()
-    {
-        foreach (DistanceUnit::cases() as $unit) {
-            $this->assertEquals($unit->getConversionFactor(), $unit->getConversionFactorTo(DistanceUnit::Meter));
-        }
-    }
-
-    /**
-     * @dataProvider provideConvertedUnits
-     */
-    public function testConversionFactorFrom(DistanceUnit $unit, DistanceUnit $otherUnit, float $expected): void
-    {
-        $this->assertEqualsWithDelta($expected, $unit->getConversionFactorFrom($otherUnit), 0.001);
-    }
-
-    public static function provideConvertedUnits(): iterable
-    {
-        yield 'Kilometer to Kilometer' => [DistanceUnit::Kilometer, DistanceUnit::Kilometer, 1.0];
-        yield 'Kilometer to Meter' => [DistanceUnit::Kilometer, DistanceUnit::Meter, 1000.0];
-        yield 'Kilometer to Mile' => [DistanceUnit::Kilometer, DistanceUnit::Mile, 0.621371];
-        yield 'Kilometer to Nautical Mile' => [DistanceUnit::Kilometer, DistanceUnit::NauticalMile, 0.539957];
-
-        yield 'Meter to Kilometer' => [DistanceUnit::Meter, DistanceUnit::Kilometer, 0.001];
-        yield 'Meter to Meter' => [DistanceUnit::Meter, DistanceUnit::Meter, 1.0];
-        yield 'Meter to Mile' => [DistanceUnit::Meter, DistanceUnit::Mile, 0.000621371];
-        yield 'Meter to Nautical Mile' => [DistanceUnit::Meter, DistanceUnit::NauticalMile, 0.000539957];
-
-        yield 'Mile to Kilometer' => [DistanceUnit::Mile, DistanceUnit::Kilometer, 1.609344];
-        yield 'Mile to Meter' => [DistanceUnit::Mile, DistanceUnit::Meter, 1609.344];
-        yield 'Mile to Mile' => [DistanceUnit::Mile, DistanceUnit::Mile, 1.0];
-        yield 'Mile to Nautical Mile' => [DistanceUnit::Mile, DistanceUnit::NauticalMile, 0.868976];
-
-        yield 'Nautical Mile to Kilometer' => [DistanceUnit::NauticalMile, DistanceUnit::Kilometer, 1.852];
-        yield 'Nautical Mile to Meter' => [DistanceUnit::NauticalMile, DistanceUnit::Meter, 1852.0];
-        yield 'Nautical Mile to Mile' => [DistanceUnit::NauticalMile, DistanceUnit::Mile, 1.15078];
-        yield 'Nautical Mile to Nautical Mile' => [DistanceUnit::NauticalMile, DistanceUnit::NauticalMile, 1.0];
-    }
-}
diff --git a/src/Map/tests/MapTest.php b/src/Map/tests/MapTest.php
index 2f91323e..b176e0d9 100644
--- a/src/Map/tests/MapTest.php
+++ b/src/Map/tests/MapTest.php
@@ -187,7 +187,6 @@ class MapTest extends TestCase
                         'extra' => ['baz' => 'qux'],
                     ],
                     'extra' => ['foo' => 'bar'],
-                    'id' => null,
                 ],
                 [
                     'position' => ['lat' => 45.764, 'lng' => 4.8357],
@@ -201,7 +200,6 @@ class MapTest extends TestCase
                         'extra' => [],
                     ],
                     'extra' => [],
-                    'id' => null,
                 ],
                 [
                     'position' => ['lat' => 43.2965, 'lng' => 5.3698],
@@ -215,7 +213,6 @@ class MapTest extends TestCase
                         'extra' => [],
                     ],
                     'extra' => [],
-                    'id' => null,
                 ],
             ],
             'polygons' => [
@@ -228,7 +225,6 @@ class MapTest extends TestCase
                     'title' => 'Polygon 1',
                     'infoWindow' => null,
                     'extra' => [],
-                    'id' => null,
                 ],
                 [
                     'points' => [
@@ -246,7 +242,6 @@ class MapTest extends TestCase
                         'extra' => [],
                     ],
                     'extra' => [],
-                    'id' => null,
                 ],
             ],
             'polylines' => [
@@ -259,7 +254,6 @@ class MapTest extends TestCase
                     'title' => 'Polyline 1',
                     'infoWindow' => null,
                     'extra' => [],
-                    'id' => null,
                 ],
                 [
                     'points' => [
@@ -277,7 +271,6 @@ class MapTest extends TestCase
                         'extra' => [],
                     ],
                     'extra' => [],
-                    'id' => null,
                 ],
             ],
         ], $map->toArray());
diff --git a/src/Map/tests/MarkerTest.php b/src/Map/tests/MarkerTest.php
index f5f9d21d..e55dca0e 100644
--- a/src/Map/tests/MarkerTest.php
+++ b/src/Map/tests/MarkerTest.php
@@ -31,7 +31,6 @@ class MarkerTest extends TestCase
             'title' => null,
             'infoWindow' => null,
             'extra' => $array['extra'],
-            'id' => null,
         ], $array);
 
         $marker = new Marker(
@@ -58,7 +57,6 @@ class MarkerTest extends TestCase
                 'extra' => $array['infoWindow']['extra'],
             ],
             'extra' => $array['extra'],
-            'id' => null,
         ], $array);
     }
 }
diff --git a/src/Map/tests/PointTest.php b/src/Map/tests/PointTest.php
index 20326806..96108040 100644
--- a/src/Map/tests/PointTest.php
+++ b/src/Map/tests/PointTest.php
@@ -36,20 +36,6 @@ class PointTest extends TestCase
         new Point($latitude, $longitude);
     }
 
-    public function testGetLatitude(): void
-    {
-        $point = new Point(48.8566, 2.3533);
-
-        self::assertSame(48.8566, $point->getLatitude());
-    }
-
-    public function testGetLongitude(): void
-    {
-        $point = new Point(48.8566, 2.3533);
-
-        self::assertSame(2.3533, $point->getLongitude());
-    }
-
     public function testToArray(): void
     {
         $point = new Point(48.8566, 2.3533);
diff --git a/src/Map/tests/Utils/CoordinateUtilsTest.php b/src/Map/tests/Utils/CoordinateUtilsTest.php
deleted file mode 100644
index e7ac82dc..00000000
--- a/src/Map/tests/Utils/CoordinateUtilsTest.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Map\Utils;
-
-use PHPUnit\Framework\TestCase;
-
-class CoordinateUtilsTest extends TestCase
-{
-    public function testDecimalToDMSConvertsCorrectly(): void
-    {
-        $result = CoordinateUtils::decimalToDMS(48.8588443);
-        $this->assertSame([48, 51, 31.83948], $result);
-    }
-
-    public function testDecimalToDMSHandlesNegativeValues(): void
-    {
-        $result = CoordinateUtils::decimalToDMS(-48.8588443);
-        $this->assertSame([-48, 51, 31.83948], $result);
-    }
-
-    public function testDMSToDecimalConvertsCorrectly(): void
-    {
-        $result = CoordinateUtils::DMSToDecimal(48, 51, 31.8388);
-        $this->assertSame(48.858844, $result);
-    }
-
-    public function testDMSToDecimalHandlesNegativeValues(): void
-    {
-        $result = CoordinateUtils::DMSToDecimal(-48, 51, 31.8388);
-        $this->assertSame(-48.858844, $result);
-    }
-
-    public function testDMSToDecimalHandlesZeroValues(): void
-    {
-        $result = CoordinateUtils::DMSToDecimal(0, 0, 0.0);
-        $this->assertSame(0.0, $result);
-    }
-}
diff --git a/src/Turbo/tests/Request/RequestListenerTest.php b/src/Turbo/tests/Request/RequestListenerTest.php
deleted file mode 100644
index 27b1c73f..00000000
--- a/src/Turbo/tests/Request/RequestListenerTest.php
+++ /dev/null
@@ -1,54 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\UX\Turbo\Tests\Request;
-
-use Symfony\Bundle\FrameworkBundle\KernelBrowser;
-use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
-use Symfony\UX\Turbo\TurboBundle;
-
-/**
- * Tests the Turbo request listener.
- *
- * @author Alexander Hofbauer <a.hofbauer@fify.at>
- */
-class RequestListenerTest extends WebTestCase
-{
-    public function testAddsTurboRequestFormat(): void
-    {
-        $client = static::createClient(server: [
-            'HTTP_ACCEPT' => 'text/vnd.turbo-stream.html, text/html, application/xhtml+xml',
-        ]);
-
-        // simulate worker mode
-        $client->disableReboot();
-
-        // request twice to test if listener is always called
-        $this->assertPreferredFormat();
-        $this->assertPreferredFormat();
-    }
-
-    private function assertPreferredFormat(): void
-    {
-        /** @var KernelBrowser $client */
-        $client = static::getClient();
-
-        $client->request('POST', '/turboRequest');
-
-        $response = $client->getResponse()->getContent();
-
-        $expectedJson = json_encode([
-            'preferred_format' => TurboBundle::STREAM_FORMAT,
-        ]);
-
-        $this->assertJsonStringEqualsJsonString($expectedJson ?: '', $response ?: '');
-    }
-}
diff --git a/src/Turbo/tests/app/Kernel.php b/src/Turbo/tests/app/Kernel.php
index 3f68994e..675a0f8b 100644
--- a/src/Turbo/tests/app/Kernel.php
+++ b/src/Turbo/tests/app/Kernel.php
@@ -29,7 +29,6 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
 use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 use Symfony\Component\Form\Extension\Core\Type\TextType;
 use Symfony\Component\Form\FormFactoryInterface;
-use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -145,7 +144,6 @@ class Kernel extends BaseKernel
         $routes->add('artists', '/artists')->controller('kernel::artists');
         $routes->add('artist', '/artists/{id}')->controller('kernel::artist');
         $routes->add('artist_from_song', '/artistFromSong')->controller('kernel::artistFromSong');
-        $routes->add('turbo_request', '/turboRequest')->controller('kernel::turboRequest');
     }
 
     public function getProjectDir(): string
@@ -314,11 +312,4 @@ class Kernel extends BaseKernel
             'song' => $song,
         ]));
     }
-
-    public function turboRequest(Request $request): Response
-    {
-        return new JsonResponse([
-            'preferred_format' => $request->getPreferredFormat(),
-        ]);
-    }
 }
diff --git a/src/TwigComponent/tests/Unit/Twig/TemplateNameParserTest.php b/src/TwigComponent/tests/Unit/Twig/TemplateNameParserTest.php
new file mode 100644
index 00000000..890a8caa
--- /dev/null
+++ b/src/TwigComponent/tests/Unit/Twig/TemplateNameParserTest.php
@@ -0,0 +1,47 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\UX\TwigComponent\Tests\Unit\Twig;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\UX\TwigComponent\Twig\TemplateNameParser;
+
+/**
+ * @author Simon André <smn.andre@gmail.com>
+ */
+class TemplateNameParserTest extends TestCase
+{
+    /**
+     * @dataProvider provideParse
+     */
+    public function testParse(string $name, string $parsedName): void
+    {
+        $this->assertSame($parsedName, TemplateNameParser::parse($name));
+    }
+
+    public function testParseThrowsExceptionWhenInvalidName(): void
+    {
+        $this->expectException(\LogicException::class);
+        $this->expectExceptionMessage('Malformed namespaced template name "@foo" (expecting "@namespace/template_name").');
+        TemplateNameParser::parse('@foo');
+    }
+
+    /**
+     * @return iterable<array{0: string, 1: string}>
+     */
+    public static function provideParse(): iterable
+    {
+        yield ['foo', 'foo'];
+        yield ['foo/bar', 'foo/bar'];
+        yield ['@Admin/foo', '@Admin/foo'];
+        yield ['Admin/foo', 'Admin/foo'];
+    }
+}
diff --git a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php
index 2878fb3b..f4489867 100644
--- a/src/TwigComponent/tests/Unit/TwigPreLexerTest.php
+++ b/src/TwigComponent/tests/Unit/TwigPreLexerTest.php
@@ -376,65 +376,5 @@ final class TwigPreLexerTest extends TestCase
             '<twig:foobar bar="baz" {{ ...attr }}>content</twig:foobar>',
             '{% component \'foobar\' with { bar: \'baz\', ...attr } %}{% block content %}content{% endblock %}{% endcomponent %}',
         ];
-        yield 'component_with_comment_line' => [
-            "<twig:foo \n   # bar  \n />",
-            '{{ component(\'foo\') }}',
-        ];
-        yield 'component_with_comment_line_between_args' => [
-            <<<TWIG
-            <twig:foo
-                # bar
-                bar="baz"
-            />
-            TWIG,
-            '{{ component(\'foo\', { bar: \'baz\' }) }}',
-        ];
-        yield 'component_with_comment_lines_between_args' => [
-            <<<TWIG
-            <twig:foo
-                # comment
-                foo="foo"
-                # comment
-                bar="bar"
-            />
-            TWIG,
-            '{{ component(\'foo\', { foo: \'foo\', bar: \'bar\' }) }}',
-        ];
-        yield 'component_with_comment_line_containing_ending_tag' => [
-            <<<TWIG
-            <twig:foo
-                # comment /></twig:foo>
-                bar="bar"
-            />
-            TWIG,
-            '{{ component(\'foo\', { bar: \'bar\' }) }}',
-        ];
-        yield 'component_with_comment_line_in_argument_value' => [
-            <<<TWIG
-            <twig:foo
-                bar="# bar"
-            />
-            TWIG,
-            '{{ component(\'foo\', { bar: \'# bar\' }) }}',
-        ];
-        yield 'component_with_comment_line_in_argument_array_value_is_kept' => [
-            <<<TWIG
-            <twig:foo
-                bar="{{ {
-                    a: 'b',
-                    # comment
-                    c: 'd'
-                } }}"
-            />
-            TWIG,
-            // Twig will remove the comment, we don't need to remove it
-            <<<TWIG
-            {{ component('foo', { bar: ({
-                    a: 'b',
-                    # comment
-                    c: 'd'
-                }) }) }}
-            TWIG,
-        ];
     }
 }