Repository: symfony/ux
Pull request #2547 (2025-02-02 15:03:20)

[Map] markers, polygons and polylines removal
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| Issues        | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT


Add new methods to remove markers, polygons or polylines (map elements)
Map elements will be store in SplObjectStorage instead of arrays.

When create a map element, you can now add an optional identfier (string).
New methods `getMarker(string identifier)`, `getPolygon(string identifier)` and `getPolyline(string $identifier)` car retreive a map element from its identfiier.

example
    $departureMarker = new Marker (
        position: new Point(45.7640, 4.8357),
        title: 'Lyon',
        identifier: 'departure'
    )

    $map->addMarker($departureMarker);
    
    // remove marker with
    $map->removeMarker($departureMarker)
    // or 
    $map->removeMarker($map->getMarker('departure'));



smnandre: Do you need some help here @sblondeau ?
sblondeau: > Do you need some help here @sblondeau ?

No time at all last week, but I will try to work on it in the next day :-) Thanks for your comments
sblondeau: >     2. `Map::get*()` methods do not really make sense to me, since there is no modifiers on `Marker`/`Polygon`/`Polyline`. Once you construct them, you can't alter them, so not point for `Map::get*()`.

Thanks for your comments @Kocal. One question, that's true that a Marker is readonly... but can we imagine that we getMarker() to modify its position, title or infoWindow ? so we should remove and recreate it ?
Kocal: > > ```
> > 2. `Map::get*()` methods do not really make sense to me, since there is no modifiers on `Marker`/`Polygon`/`Polyline`. Once you construct them, you can't alter them, so not point for `Map::get*()`.
> > ```
> 
> Thanks for your comments @Kocal. One question, that's true that a Marker is readonly... but can we imagine that we getMarker() to modify its position, title or infoWindow ? so we should remove and recreate it ?

For the moment, yes, we can implement `Marker`, `Polygon` and `Polyline` removal without the need of `Map::get*()`. 

If really necessary, let's open a separate PR to make them non-readonly and implement `Map::get*()`. 
Kocal: I've tested your PR and it works well: 

https://github.com/user-attachments/assets/9059e877-4a9d-449c-8d7d-f183f7695ce4

I did some minor adjustments to mark those classes as internal. 

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/Map/CHANGELOG.md b/src/Map/CHANGELOG.md
index 75f70821..493b1c01 100644
--- a/src/Map/CHANGELOG.md
+++ b/src/Map/CHANGELOG.md
@@ -7,6 +7,10 @@
 -  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
 
diff --git a/src/Map/doc/index.rst b/src/Map/doc/index.rst
index 855b6fa7..b97955f2 100644
--- a/src/Map/doc/index.rst
+++ b/src/Map/doc/index.rst
@@ -136,6 +136,34 @@ 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/Element.php b/src/Map/src/Element.php
new file mode 100644
index 00000000..c7f752ea
--- /dev/null
+++ b/src/Map/src/Element.php
@@ -0,0 +1,21 @@
+<?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
new file mode 100644
index 00000000..6dee1939
--- /dev/null
+++ b/src/Map/src/Elements.php
@@ -0,0 +1,79 @@
+<?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 529f8af8..f0d49c60 100644
--- a/src/Map/src/Map.php
+++ b/src/Map/src/Map.php
@@ -20,27 +20,28 @@ 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,
-        /**
-         * @var array<Marker>
-         */
-        private array $markers = [],
-
-        /**
-         * @var array<Polygon>
-         */
-        private array $polygons = [],
-
-        /**
-         * @var array<Polyline>
-         */
-        private array $polylines = [],
+        array $markers = [],
+        array $polygons = [],
+        array $polylines = [],
     ) {
+        $this->markers = new Markers($markers);
+        $this->polygons = new Polygons($polygons);
+        $this->polylines = new Polylines($polylines);
     }
 
     public function getRendererName(): ?string
@@ -88,21 +89,42 @@ final class Map
 
     public function addMarker(Marker $marker): self
     {
-        $this->markers[] = $marker;
+        $this->markers->add($marker);
+
+        return $this;
+    }
+
+    public function removeMarker(Marker|string $markerOrId): self
+    {
+        $this->markers->remove($markerOrId);
 
         return $this;
     }
 
     public function addPolygon(Polygon $polygon): self
     {
-        $this->polygons[] = $polygon;
+        $this->polygons->add($polygon);
+
+        return $this;
+    }
+
+    public function removePolygon(Polygon|string $polygonOrId): self
+    {
+        $this->polygons->remove($polygonOrId);
 
         return $this;
     }
 
     public function addPolyline(Polyline $polyline): self
     {
-        $this->polylines[] = $polyline;
+        $this->polylines->add($polyline);
+
+        return $this;
+    }
+
+    public function removePolyline(Polyline|string $polylineOrId): self
+    {
+        $this->polylines->remove($polylineOrId);
 
         return $this;
     }
@@ -124,9 +146,9 @@ final class Map
             'zoom' => $this->zoom,
             'fitBoundsToMarkers' => $this->fitBoundsToMarkers,
             'options' => $this->options ? MapOptionsNormalizer::normalize($this->options) : [],
-            '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),
+            'markers' => $this->markers->toArray(),
+            'polygons' => $this->polygons->toArray(),
+            'polylines' => $this->polylines->toArray(),
         ];
     }
 
diff --git a/src/Map/src/Marker.php b/src/Map/src/Marker.php
index 54d61ebe..0310834a 100644
--- a/src/Map/src/Marker.php
+++ b/src/Map/src/Marker.php
@@ -18,17 +18,18 @@ use Symfony\UX\Map\Exception\InvalidArgumentException;
  *
  * @author Hugo Alliaume <hugo@alliau.me>
  */
-final readonly class Marker
+final readonly class Marker implements Element
 {
     /**
      * @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(
-        private Point $position,
-        private ?string $title = null,
-        private ?InfoWindow $infoWindow = null,
-        private array $extra = [],
+        public Point $position,
+        public ?string $title = null,
+        public ?InfoWindow $infoWindow = null,
+        public array $extra = [],
+        public ?string $id = null,
     ) {
     }
 
@@ -38,6 +39,7 @@ final readonly class Marker
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
+     *     id: string|null
      * }
      */
     public function toArray(): array
@@ -47,6 +49,7 @@ final readonly class Marker
             'title' => $this->title,
             'infoWindow' => $this->infoWindow?->toArray(),
             'extra' => $this->extra,
+            'id' => $this->id,
         ];
     }
 
@@ -56,6 +59,7 @@ final readonly class Marker
      *     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
new file mode 100644
index 00000000..b82e3cf0
--- /dev/null
+++ b/src/Map/src/Markers.php
@@ -0,0 +1,33 @@
+<?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/Polygon.php b/src/Map/src/Polygon.php
index d32594ad..4faaf7e8 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
+final readonly class Polygon implements Element
 {
     /**
      * @param array<string, mixed> $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side
@@ -28,6 +28,7 @@ final readonly class Polygon
         private ?string $title = null,
         private ?InfoWindow $infoWindow = null,
         private array $extra = [],
+        public ?string $id = null,
     ) {
     }
 
@@ -39,6 +40,7 @@ final readonly class Polygon
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
+     *     id: string|null
      * }
      */
     public function toArray(): array
@@ -48,6 +50,7 @@ final readonly class Polygon
             'title' => $this->title,
             'infoWindow' => $this->infoWindow?->toArray(),
             'extra' => $this->extra,
+            'id' => $this->id,
         ];
     }
 
@@ -57,6 +60,7 @@ final readonly class Polygon
      *     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
new file mode 100644
index 00000000..5d75ba8f
--- /dev/null
+++ b/src/Map/src/Polygons.php
@@ -0,0 +1,33 @@
+<?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 46302136..15b1b778 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
+final readonly class Polyline implements Element
 {
     /**
      * @param array<string, mixed> $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side
@@ -28,6 +28,7 @@ final readonly class Polyline
         private ?string $title = null,
         private ?InfoWindow $infoWindow = null,
         private array $extra = [],
+        public ?string $id = null,
     ) {
     }
 
@@ -39,6 +40,7 @@ final readonly class Polyline
      *     title: string|null,
      *     infoWindow: array<string, mixed>|null,
      *     extra: array,
+     *     id: string|null
      * }
      */
     public function toArray(): array
@@ -48,6 +50,7 @@ final readonly class Polyline
             'title' => $this->title,
             'infoWindow' => $this->infoWindow?->toArray(),
             'extra' => $this->extra,
+            'id' => $this->id,
         ];
     }
 
@@ -57,6 +60,7 @@ final readonly class Polyline
      *     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
new file mode 100644
index 00000000..810f180d
--- /dev/null
+++ b/src/Map/src/Polylines.php
@@ -0,0 +1,33 @@
+<?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/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/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/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
index d8695096..dda259fe 100644
--- a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
+++ b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php
@@ -29,6 +29,9 @@ 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>',
@@ -50,25 +53,55 @@ 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;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>',
+            '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>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (clone $map)
-                ->addMarker(new Marker(new Point(48.8566, 2.3522), 'Paris'))
+            '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'))
                 ->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;:&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>',
+            '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>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (clone $map)
+            '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)]))
                 ->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;:&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>',
+            '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>',
             'renderer' => new GoogleRenderer(new StimulusHelper(null), apiKey: 'api_key'),
-            'map' => (clone $map)
+            '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)]))
                 ->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'))),
         ];
@@ -76,7 +109,9 @@ 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' => (clone $map)
+            'map' => (new Map())
+                ->center(new Point(48.8566, 2.3522))
+                ->zoom(12)
                 ->options(new GoogleOptions(
                     zoomControl: true,
                     mapTypeControl: true,
@@ -88,7 +123,9 @@ 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' => (clone $map)
+            'map' => (new Map())
+                ->center(new Point(48.8566, 2.3522))
+                ->zoom(12)
                 ->options(new GoogleOptions(
                     zoomControl: false,
                     mapTypeControl: false,
@@ -100,18 +137,24 @@ 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' => (clone $map),
+            'map' => (new Map())
+                ->center(new Point(48.8566, 2.3522))
+                ->zoom(12),
         ];
         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' => (clone $map)
+            'map' => (new Map())
+                ->center(new Point(48.8566, 2.3522))
+                ->zoom(12)
                 ->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' => (clone $map)
+            'map' => (new Map())
+                ->center(new Point(48.8566, 2.3522))
+                ->zoom(12)
                 ->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 81d53160..e9f8cec5 100644
--- a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
+++ b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php
@@ -29,41 +29,74 @@ 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' => $map,
+            'map' => (clone $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' => $map,
+            'map' => (clone $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;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>',
+            '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>',
             'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            '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'))),
+            '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-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>',
+            '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>',
             'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            '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'))),
+            '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')),
         ];
 
         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;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>',
+            '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>',
             'renderer' => new LeafletRenderer(new StimulusHelper(null)),
-            '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'))),
+            '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')),
         ];
     }
 }
diff --git a/src/Map/tests/MapTest.php b/src/Map/tests/MapTest.php
index b176e0d9..2f91323e 100644
--- a/src/Map/tests/MapTest.php
+++ b/src/Map/tests/MapTest.php
@@ -187,6 +187,7 @@ class MapTest extends TestCase
                         'extra' => ['baz' => 'qux'],
                     ],
                     'extra' => ['foo' => 'bar'],
+                    'id' => null,
                 ],
                 [
                     'position' => ['lat' => 45.764, 'lng' => 4.8357],
@@ -200,6 +201,7 @@ class MapTest extends TestCase
                         'extra' => [],
                     ],
                     'extra' => [],
+                    'id' => null,
                 ],
                 [
                     'position' => ['lat' => 43.2965, 'lng' => 5.3698],
@@ -213,6 +215,7 @@ class MapTest extends TestCase
                         'extra' => [],
                     ],
                     'extra' => [],
+                    'id' => null,
                 ],
             ],
             'polygons' => [
@@ -225,6 +228,7 @@ class MapTest extends TestCase
                     'title' => 'Polygon 1',
                     'infoWindow' => null,
                     'extra' => [],
+                    'id' => null,
                 ],
                 [
                     'points' => [
@@ -242,6 +246,7 @@ class MapTest extends TestCase
                         'extra' => [],
                     ],
                     'extra' => [],
+                    'id' => null,
                 ],
             ],
             'polylines' => [
@@ -254,6 +259,7 @@ class MapTest extends TestCase
                     'title' => 'Polyline 1',
                     'infoWindow' => null,
                     'extra' => [],
+                    'id' => null,
                 ],
                 [
                     'points' => [
@@ -271,6 +277,7 @@ 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 e55dca0e..f5f9d21d 100644
--- a/src/Map/tests/MarkerTest.php
+++ b/src/Map/tests/MarkerTest.php
@@ -31,6 +31,7 @@ class MarkerTest extends TestCase
             'title' => null,
             'infoWindow' => null,
             'extra' => $array['extra'],
+            'id' => null,
         ], $array);
 
         $marker = new Marker(
@@ -57,6 +58,7 @@ class MarkerTest extends TestCase
                 'extra' => $array['infoWindow']['extra'],
             ],
             'extra' => $array['extra'],
+            'id' => null,
         ], $array);
     }
 }