Repository: symfony/ux
Pull request #2779 (2025-05-28 09:32:25)

[Map] Optional leaflet tilelayer
| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| Docs?         | no <!-- required for new features -->
| Issues        | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| 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.
 - Update/add documentation as required (we can help!)
 - 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).
-->

Comment from https://github.com/symfony/ux/issues/2445#issuecomment-2915208177


Patch:
diff --git a/src/Map/src/Bridge/Leaflet/CHANGELOG.md b/src/Map/src/Bridge/Leaflet/CHANGELOG.md
index 3d6ee0fc..62c9e93e 100644
--- a/src/Map/src/Bridge/Leaflet/CHANGELOG.md
+++ b/src/Map/src/Bridge/Leaflet/CHANGELOG.md
@@ -1,5 +1,11 @@
 # CHANGELOG
 
+## 2.26
+
+-  Using `new LeafletOptions(tileLayer: false)` will now disable the default `TileLayer`.
+   Useful when using a custom tiles layer rendering engine not configurable with `L.tileLayer().addTo(map)` method
+   (e.g.: [Esri/esri-leaflet-vector](https://github.com/Esri/esri-leaflet-vector))
+
 ## 2.25
 
 -  Downgrade PHP requirement from 8.3 to 8.1
diff --git a/src/Map/src/Bridge/Leaflet/README.md b/src/Map/src/Bridge/Leaflet/README.md
index d3cc9308..a6d0501e 100644
--- a/src/Map/src/Bridge/Leaflet/README.md
+++ b/src/Map/src/Bridge/Leaflet/README.md
@@ -87,7 +87,7 @@ export default class extends Controller
 
     _onMarkerBeforeCreate(event) {
         // You can access the marker definition and the Leaflet object
-        // Note: `definition.rawOptions` is the raw options object that will be passed to the `L.marker` constructor. 
+        // Note: `definition.rawOptions` is the raw options object that will be passed to the `L.marker` constructor.
         const { definition, L } = event.detail;
 
         // Use a custom icon for the marker
@@ -101,7 +101,7 @@ export default class extends Controller
           shadowAnchor: [4, 62],  // the same for the shadow
           popupAnchor: [-3, -76] // point from which the popup should open relative to the iconAnchor
         })
-  
+
         definition.rawOptions = {
           icon: redIcon,
         }
@@ -109,6 +109,20 @@ export default class extends Controller
 }
 ```
 
+### Disable the default tile layer
+
+If you need to use a custom tiles layer rendering engine that is not compatible with the `L.tileLayer().addTo(map)` method
+(e.g. e.g.: [Esri/esri-leaflet-vector](https://github.com/Esri/esri-leaflet-vector)), you can disable the default tile layer by passing `tileLayer: false` to the `LeafletOptions`:
+
+```php
+use Symfony\UX\Map\Bridge\Leaflet\LeafletOptions;
+
+$leafletOptions = new LeafletOptions(tileLayer: false);
+// or
+$leafletOptions = (new LeafletOptions())
+    ->tileLayer(false);
+```
+
 ## Known issues
 
 ### Unable to find `leaflet/dist/leaflet.min.css` file when using Webpack Encore
@@ -124,10 +138,10 @@ webpack compiled with 1 error
  ELIFECYCLE  Command failed with exit code 1.
 ```
 
-That's because the Leaflet's Stimulus controller references the `leaflet/dist/leaflet.min.css` file, 
+That's because the Leaflet's Stimulus controller references the `leaflet/dist/leaflet.min.css` file,
 which exists on [jsDelivr](https://www.jsdelivr.com/package/npm/leaflet) (used by the Symfony AssetMapper component),
 but does not in the [`leaflet` npm package](https://www.npmjs.com/package/leaflet).
-The correct path is `leaflet/dist/leaflet.css`, but it is not possible to fix it because it would break compatibility 
+The correct path is `leaflet/dist/leaflet.css`, but it is not possible to fix it because it would break compatibility
 with the Symfony AssetMapper component.
 
 As a workaround, you can configure Webpack Encore to add an alias for the `leaflet/dist/leaflet.min.css` file:
diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
index 8c10b168..f0d9085f 100644
--- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.d.ts
@@ -8,7 +8,7 @@ type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom'> & {
         url: string;
         attribution: string;
         options: Record<string, unknown>;
-    };
+    } | false;
 };
 export default class extends AbstractMapController<MapOptions, L.Map, MarkerOptions, L.Marker, PopupOptions, L.Popup, PolygonOptions, L.Polygon, PolylineOptions, L.Polyline> {
     map: L.Map;
diff --git a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
index 208cf321..5529e713 100644
--- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
+++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js
@@ -145,10 +145,12 @@ class map_controller extends default_1 {
             center: center === null ? undefined : center,
             zoom: zoom === null ? undefined : zoom,
         });
-        L.tileLayer(options.tileLayer.url, {
-            attribution: options.tileLayer.attribution,
-            ...options.tileLayer.options,
-        }).addTo(map);
+        if (options.tileLayer) {
+            L.tileLayer(options.tileLayer.url, {
+                attribution: options.tileLayer.attribution,
+                ...options.tileLayer.options,
+            }).addTo(map);
+        }
         return map;
     }
     doCreateMarker({ definition }) {
diff --git a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
index 53333592..dbc733b9 100644
--- a/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
+++ b/src/Map/src/Bridge/Leaflet/assets/src/map_controller.ts
@@ -19,7 +19,7 @@ import type {
 } from 'leaflet';
 
 type MapOptions = Pick<LeafletMapOptions, 'center' | 'zoom'> & {
-    tileLayer: { url: string; attribution: string; options: Record<string, unknown> };
+    tileLayer: { url: string; attribution: string; options: Record<string, unknown> } | false;
 };
 
 export default class extends AbstractMapController<
@@ -81,10 +81,12 @@ export default class extends AbstractMapController<
             zoom: zoom === null ? undefined : zoom,
         });
 
-        L.tileLayer(options.tileLayer.url, {
-            attribution: options.tileLayer.attribution,
-            ...options.tileLayer.options,
-        }).addTo(map);
+        if (options.tileLayer) {
+            L.tileLayer(options.tileLayer.url, {
+                attribution: options.tileLayer.attribution,
+                ...options.tileLayer.options,
+            }).addTo(map);
+        }
 
         return map;
     }
diff --git a/src/Map/src/Bridge/Leaflet/src/LeafletOptions.php b/src/Map/src/Bridge/Leaflet/src/LeafletOptions.php
index b0c85a82..4edcdca2 100644
--- a/src/Map/src/Bridge/Leaflet/src/LeafletOptions.php
+++ b/src/Map/src/Bridge/Leaflet/src/LeafletOptions.php
@@ -20,14 +20,14 @@ use Symfony\UX\Map\MapOptionsInterface;
 final class LeafletOptions implements MapOptionsInterface
 {
     public function __construct(
-        private TileLayer $tileLayer = new TileLayer(
+        private TileLayer|false $tileLayer = new TileLayer(
             url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
             attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
         ),
     ) {
     }
 
-    public function tileLayer(TileLayer $tileLayer): self
+    public function tileLayer(TileLayer|false $tileLayer): self
     {
         $this->tileLayer = $tileLayer;
 
@@ -40,7 +40,7 @@ final class LeafletOptions implements MapOptionsInterface
     public static function fromArray(array $array): MapOptionsInterface
     {
         return new self(
-            tileLayer: TileLayer::fromArray($array['tileLayer']),
+            tileLayer: $array['tileLayer'] ? TileLayer::fromArray($array['tileLayer']) : false,
         );
     }
 
@@ -50,7 +50,7 @@ final class LeafletOptions implements MapOptionsInterface
     public function toArray(): array
     {
         return [
-            'tileLayer' => $this->tileLayer->toArray(),
+            'tileLayer' => $this->tileLayer ? $this->tileLayer->toArray() : false,
         ];
     }
 }


Test patch:
diff --git a/.github/workflows/app-tests.yaml b/.github/workflows/app-tests.yaml
index b139c113..c6b5da06 100644
--- a/.github/workflows/app-tests.yaml
+++ b/.github/workflows/app-tests.yaml
@@ -45,15 +45,19 @@ jobs:
         - uses: shivammathur/setup-php@v2
 
         - name: Install root dependencies
-          run: composer install
+          uses: ramsey/composer-install@v3
+          with:
+            working-directory: ${{ github.workspace }}
 
         - name: Build root packages
           run: php .github/build-packages.php
+          working-directory: ${{ github.workspace }}
 
         # We always install PHP deps because we of the UX Translator, which requires `var/translations` to exists
-        - name: Install App dependenies
-          run: composer update
-          working-directory: test_apps/encore-app
+        - uses: ramsey/composer-install@v3
+          with:
+            dependency-versions: 'highest'
+            working-directory: test_apps/encore-app
 
         - if: matrix.ux-packages-source == 'php-vendor'
           name: Refresh dependencies from vendor/
diff --git a/src/Map/src/Bridge/Leaflet/tests/LeafletOptionsTest.php b/src/Map/src/Bridge/Leaflet/tests/LeafletOptionsTest.php
index 4c22f976..55591336 100644
--- a/src/Map/src/Bridge/Leaflet/tests/LeafletOptionsTest.php
+++ b/src/Map/src/Bridge/Leaflet/tests/LeafletOptionsTest.php
@@ -62,4 +62,15 @@ class LeafletOptionsTest extends TestCase
 
         self::assertEquals($leafletOptions, LeafletOptions::fromArray($leafletOptions->toArray()));
     }
+
+    public function testWithTileLayerFalse(): void
+    {
+        $leafletOptions = new LeafletOptions(tileLayer: false);
+
+        self::assertSame([
+            'tileLayer' => false,
+        ], $leafletOptions->toArray());
+
+        self::assertEquals($leafletOptions, LeafletOptions::fromArray($leafletOptions->toArray()));
+    }
 }