diff --git a/src/Map/CHANGELOG.md b/src/Map/CHANGELOG.md index e68d950a111..1e1c7b381e7 100644 --- a/src/Map/CHANGELOG.md +++ b/src/Map/CHANGELOG.md @@ -39,6 +39,7 @@ this.element.addEventListener('ux:map:pre-connect', (event) => { }; }); ``` +- Add `extra` data support to `Map`, which can be accessed in `ux:map:pre-connect` and `ux:map:connect` events ## 2.26 diff --git a/src/Map/assets/dist/abstract_map_controller.d.ts b/src/Map/assets/dist/abstract_map_controller.d.ts index 93adcb1dc4f..f4b24236857 100644 --- a/src/Map/assets/dist/abstract_map_controller.d.ts +++ b/src/Map/assets/dist/abstract_map_controller.d.ts @@ -7,6 +7,7 @@ export type Identifier = string; export type WithIdentifier> = T & { '@id': Identifier; }; +type ExtraData = Record; export declare const IconTypes: { readonly Url: "url"; readonly Svg: "svg"; @@ -31,6 +32,7 @@ export type MapDefinition = { zoom: number | null; options: MapOptions; bridgeOptions?: BridgeMapOptions; + extra: ExtraData; }; export type MarkerDefinition = WithIdentifier<{ position: Point; @@ -39,7 +41,7 @@ export type MarkerDefinition = Wit icon?: Icon; rawOptions?: BridgeMarkerOptions; bridgeOptions?: BridgeMarkerOptions; - extra: Record; + extra: ExtraData; }>; export type PolygonDefinition = WithIdentifier<{ infoWindow?: Omit, 'position'>; @@ -47,7 +49,7 @@ export type PolygonDefinition = W title: string | null; rawOptions?: BridgePolygonOptions; bridgeOptions?: BridgePolygonOptions; - extra: Record; + extra: ExtraData; }>; export type PolylineDefinition = WithIdentifier<{ infoWindow?: Omit, 'position'>; @@ -55,7 +57,7 @@ export type PolylineDefinition = title: string | null; rawOptions?: BridgePolylineOptions; bridgeOptions?: BridgePolylineOptions; - extra: Record; + extra: ExtraData; }>; export type CircleDefinition = WithIdentifier<{ infoWindow?: Omit, 'position'>; @@ -64,7 +66,7 @@ export type CircleDefinition = Wit title: string | null; rawOptions?: BridgeCircleOptions; bridgeOptions?: BridgeCircleOptions; - extra: Record; + extra: ExtraData; }>; export type RectangleDefinition = WithIdentifier<{ infoWindow?: Omit, 'position'>; @@ -73,7 +75,7 @@ export type RectangleDefinition title: string | null; rawOptions?: BridgeRectangleOptions; bridgeOptions?: BridgeRectangleOptions; - extra: Record; + extra: ExtraData; }>; export type InfoWindowDefinition = { headerContent: string | null; @@ -83,7 +85,7 @@ export type InfoWindowDefinition = { autoClose: boolean; rawOptions?: BridgeInfoWindowOptions; bridgeOptions?: BridgeInfoWindowOptions; - extra: Record; + extra: ExtraData; }; export default abstract class extends Controller { static values: { @@ -97,6 +99,7 @@ export default abstract class>; rectanglesValue: Array>; optionsValue: MapOptions; + extraValue: Record; hasCenterValue: boolean; hasZoomValue: boolean; hasFitBoundsToMarkersValue: boolean; @@ -116,6 +120,7 @@ export default abstract class; protected polygons: Map; @@ -177,3 +182,4 @@ export default abstract class> = T & { '@id': Identifier }; +type ExtraData = Record; + export const IconTypes = { Url: 'url', Svg: 'svg', @@ -46,6 +48,13 @@ export type MapDefinition = { * These options are specific to the Map Bridge, and can be defined through `ux:map:pre-connect` event. */ bridgeOptions?: BridgeMapOptions; + /** + * Extra data defined by the developer. + * They are not directly used by the Stimulus controller, but they can be used by the developer with event listeners: + * - `ux:map:pre-connect` + * - `ux:map:connect` + */ + extra: ExtraData; }; export type MarkerDefinition = WithIdentifier<{ @@ -69,7 +78,7 @@ export type MarkerDefinition = Wit * - `ux:map:marker:before-create` * - `ux:map:marker:after-create` */ - extra: Record; + extra: ExtraData; }>; export type PolygonDefinition = WithIdentifier<{ @@ -92,7 +101,7 @@ export type PolygonDefinition = W * - `ux:map:polygon:before-create` * - `ux:map:polygon:after-create` */ - extra: Record; + extra: ExtraData; }>; export type PolylineDefinition = WithIdentifier<{ @@ -115,7 +124,7 @@ export type PolylineDefinition = * - `ux:map:polyline:before-create` * - `ux:map:polyline:after-create` */ - extra: Record; + extra: ExtraData; }>; export type CircleDefinition = WithIdentifier<{ @@ -139,7 +148,7 @@ export type CircleDefinition = Wit * - `ux:map:circle:before-create` * - `ux:map:circle:after-create` */ - extra: Record; + extra: ExtraData; }>; export type RectangleDefinition = WithIdentifier<{ @@ -163,7 +172,7 @@ export type RectangleDefinition * - `ux:map:rectangle:before-create` * - `ux:map:rectangle:after-create` */ - extra: Record; + extra: ExtraData; }>; export type InfoWindowDefinition = { @@ -188,7 +197,7 @@ export type InfoWindowDefinition = { * - `ux:map:info-window:before-create` * - `ux:map:info-window:after-create` */ - extra: Record; + extra: ExtraData; }; export default abstract class< @@ -219,6 +228,7 @@ export default abstract class< circles: Array, rectangles: Array, options: Object, + extra: Object, }; declare centerValue: Point | null; @@ -230,6 +240,7 @@ export default abstract class< declare circlesValue: Array>; declare rectanglesValue: Array>; declare optionsValue: MapOptions; + declare extraValue: Record; declare hasCenterValue: boolean; declare hasZoomValue: boolean; @@ -240,6 +251,7 @@ export default abstract class< declare hasCirclesValue: boolean; declare hasRectanglesValue: boolean; declare hasOptionsValue: boolean; + declare hasExtraValue: boolean; protected map: BridgeMap; protected markers = new Map(); @@ -259,10 +271,12 @@ export default abstract class< protected abstract dispatchEvent(name: string, payload: Record): void; connect() { + const extra = this.hasExtraValue ? this.extraValue : {}; const mapDefinition: MapDefinition = { center: this.hasCenterValue ? this.centerValue : null, zoom: this.hasZoomValue ? this.zoomValue : null, options: this.optionsValue, + extra, }; this.dispatchEvent('pre-connect', mapDefinition); @@ -291,6 +305,7 @@ export default abstract class< circles: [...this.circles.values()], rectangles: [...this.rectangles.values()], infoWindows: this.infoWindows, + extra, }); this.isConnected = true; diff --git a/src/Map/doc/index.rst b/src/Map/doc/index.rst index a7fc6795b25..e01cfbd718c 100644 --- a/src/Map/doc/index.rst +++ b/src/Map/doc/index.rst @@ -713,6 +713,39 @@ On the JavaScript side, you can access your extra data via the // ... } +.. versionadded:: 2.27 + + The ``Map`` class now has an ``extra`` property, which can be accessed in the ``ux:map:pre-connect`` and ``ux:map:connect`` events:: + + $map = new Map(/* ... */, extra: [ + 'foo' => 'bar', + ]); + // or + $map->extra([ + 'foo' => 'bar', + ]); + + .. code-block:: javascript + + // assets/controllers/mymap_controller.js + + import { Controller } from '@hotwired/stimulus'; + + export default class extends Controller { + + // ... + + _onPreConnect(event) { + console.log(event.detail.extra); + // { foo: 'bar', ... } + } + + _onConnect(event) { + console.log(event.detail.extra); + // { foo: 'bar', ... } + } + } + .. _map-live-component: Usage with Live Components 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 909154a9619..7c4813a9fd2 100644 --- a/src/Map/src/Bridge/Google/assets/dist/map_controller.js +++ b/src/Map/src/Bridge/Google/assets/dist/map_controller.js @@ -18,10 +18,12 @@ class default_1 extends Controller { this.isConnected = false; } connect() { + const extra = this.hasExtraValue ? this.extraValue : {}; const mapDefinition = { center: this.hasCenterValue ? this.centerValue : null, zoom: this.hasZoomValue ? this.zoomValue : null, options: this.optionsValue, + extra, }; this.dispatchEvent('pre-connect', mapDefinition); this.createMarker = this.createDrawingFactory('marker', this.markers, this.doCreateMarker.bind(this)); @@ -46,6 +48,7 @@ class default_1 extends Controller { circles: [...this.circles.values()], rectangles: [...this.rectangles.values()], infoWindows: this.infoWindows, + extra, }); this.isConnected = true; } @@ -131,6 +134,7 @@ default_1.values = { circles: Array, rectangles: Array, options: Object, + extra: Object, }; let _google; diff --git a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php index d40a1e71f56..0106e1bc1c8 100644 --- a/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php +++ b/src/Map/src/Bridge/Google/tests/GoogleRendererTest.php @@ -188,5 +188,16 @@ public function renderIcon(string $name, array $attributes = []): string ->addMarker(new Marker(position: new Point(45.7640, 4.8357), title: 'Lyon', icon: Icon::ux('fa:map-marker')->width(32)->height(32))) ->addMarker(new Marker(position: new Point(45.8566, 2.3522), title: 'Dijon', icon: Icon::svg('...'))), ]; + + yield 'with map extra data' => [ + 'renderer' => new GoogleRenderer(new StimulusHelper(null), new UxIconRenderer(null), apiKey: 'api_key'), + 'map' => (new Map()) + ->center(new Point(48.8566, 2.3522)) + ->zoom(12) + ->extra([ + 'foo' => 'bar', + 'baz' => 42, + ]), + ]; } } diff --git a/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with map extra data__1.txt b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with map extra data__1.txt new file mode 100644 index 00000000000..41c3440cae2 --- /dev/null +++ b/src/Map/src/Bridge/Google/tests/__snapshots__/GoogleRendererTest__testRenderMap with data set with map extra data__1.txt @@ -0,0 +1,16 @@ + +
\ No newline at end of file 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 da3cbc82f58..3e61359e80c 100644 --- a/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js +++ b/src/Map/src/Bridge/Leaflet/assets/dist/map_controller.js @@ -19,10 +19,12 @@ class default_1 extends Controller { this.isConnected = false; } connect() { + const extra = this.hasExtraValue ? this.extraValue : {}; const mapDefinition = { center: this.hasCenterValue ? this.centerValue : null, zoom: this.hasZoomValue ? this.zoomValue : null, options: this.optionsValue, + extra, }; this.dispatchEvent('pre-connect', mapDefinition); this.createMarker = this.createDrawingFactory('marker', this.markers, this.doCreateMarker.bind(this)); @@ -47,6 +49,7 @@ class default_1 extends Controller { circles: [...this.circles.values()], rectangles: [...this.rectangles.values()], infoWindows: this.infoWindows, + extra, }); this.isConnected = true; } @@ -132,6 +135,7 @@ default_1.values = { circles: Array, rectangles: Array, options: Object, + extra: Object, }; class map_controller extends default_1 { diff --git a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php index 07490459b89..5af905856af 100644 --- a/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php +++ b/src/Map/src/Bridge/Leaflet/tests/LeafletRendererTest.php @@ -142,5 +142,13 @@ public function renderIcon(string $name, array $attributes = []): string ->addMarker(new Marker(position: new Point(45.7640, 4.8357), title: 'Lyon', icon: Icon::ux('fa:map-marker')->width(32)->height(32))) ->addMarker(new Marker(position: new Point(45.8566, 2.3522), title: 'Dijon', icon: Icon::svg('...'))), ]; + + yield 'with map extra data' => [ + 'renderer' => new LeafletRenderer(new StimulusHelper(null), new UxIconRenderer(null)), + 'map' => (new Map()) + ->center(new Point(48.8566, 2.3522)) + ->zoom(12) + ->extra(['key1' => 'value1', 'key2' => 'value2']), + ]; } } diff --git a/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with map extra data__1.txt b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with map extra data__1.txt new file mode 100644 index 00000000000..ead1c0004c0 --- /dev/null +++ b/src/Map/src/Bridge/Leaflet/tests/__snapshots__/LeafletRendererTest__testRenderMap with data set with map extra data__1.txt @@ -0,0 +1,16 @@ + +
\ No newline at end of file diff --git a/src/Map/src/Circle.php b/src/Map/src/Circle.php index 74786eb07f4..711f4a24565 100644 --- a/src/Map/src/Circle.php +++ b/src/Map/src/Circle.php @@ -21,7 +21,9 @@ final class Circle implements Element { /** - * @param array $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side + * @param array $extra Extra data forwarded to the JavaScript side. It can be used in your custom + * Stimulus controller to benefit from greater flexibility and customization. + * These data must be serializable to JSON. These data are not used by UX Map. * @param float $radius The radius of the circle in meters */ public function __construct( diff --git a/src/Map/src/Map.php b/src/Map/src/Map.php index 340399e8c93..672369e2800 100644 --- a/src/Map/src/Map.php +++ b/src/Map/src/Map.php @@ -27,11 +27,14 @@ final class Map private Rectangles $rectangles; /** - * @param Marker[] $markers - * @param Polygon[] $polygons - * @param Polyline[] $polylines - * @param Circle[] $circles - * @param Rectangles[] $rectangles + * @param Marker[] $markers + * @param Polygon[] $polygons + * @param Polyline[] $polylines + * @param Circle[] $circles + * @param Rectangles[] $rectangles + * @param array $extra Extra data forwarded to the JavaScript side. It can be used in your custom + * Stimulus controller to benefit from greater flexibility and customization. + * These data must be serializable to JSON. These data are not used by UX Map. */ public function __construct( private readonly ?string $rendererName = null, @@ -44,6 +47,7 @@ public function __construct( array $polylines = [], array $circles = [], array $rectangles = [], + private array $extra = [], ) { $this->markers = new Markers($markers); $this->polygons = new Polygons($polygons); @@ -165,6 +169,18 @@ public function removeRectangle(Rectangle|string $rectangleOrId): self return $this; } + /** + * @param array $extra Extra data forwarded to the JavaScript side. It can be used in your custom + * Stimulus controller to benefit from greater flexibility and customization. + * These data must be serializable to JSON. These data are not used by UX Map. + */ + public function extra(array $extra): self + { + $this->extra = $extra; + + return $this; + } + public function toArray(): array { if (!$this->fitBoundsToMarkers) { @@ -187,6 +203,9 @@ public function toArray(): array 'polylines' => $this->polylines->toArray(), 'circles' => $this->circles->toArray(), 'rectangles' => $this->rectangles->toArray(), + // Send `null` if empty instead of `[]`, because Stimulus Controller values validation expect an Object, + // and sending `(object) $this->extra` mess with LiveComponent hydration checksum validation + 'extra' => [] === $this->extra ? null : $this->extra, ]; } @@ -201,6 +220,7 @@ public function toArray(): array * rectangles?: list, * fitBoundsToMarkers?: bool, * options?: array, + * extra?: array, * } $map * * @internal @@ -245,6 +265,8 @@ public static function fromArray(array $map): self } $map['rectangles'] = array_map(Rectangle::fromArray(...), $map['rectangles']); + $map['extra'] ??= []; + return new self(...$map); } } diff --git a/src/Map/src/Marker.php b/src/Map/src/Marker.php index ed58c4840c5..62e0395eec7 100644 --- a/src/Map/src/Marker.php +++ b/src/Map/src/Marker.php @@ -13,7 +13,6 @@ use Symfony\UX\Map\Exception\InvalidArgumentException; use Symfony\UX\Map\Icon\Icon; -use Symfony\UX\Map\Icon\IconType; /** * Represents a marker on a map. @@ -23,8 +22,9 @@ final class Marker implements Element { /** - * @param array $extra Extra data, can be used by the developer to store additional information and - * use them later JavaScript side + * @param array $extra Extra data forwarded to the JavaScript side. It can be used in your custom + * Stimulus controller to benefit from greater flexibility and customization. + * These data must be serializable to JSON. These data are not used by UX Map. */ public function __construct( public readonly Point $position, diff --git a/src/Map/src/Polygon.php b/src/Map/src/Polygon.php index 2622634cb2c..180c7e13033 100644 --- a/src/Map/src/Polygon.php +++ b/src/Map/src/Polygon.php @@ -22,7 +22,9 @@ final class Polygon implements Element { /** * @param array|array> $points a list of point representing the polygon, or a list of paths (each path is an array of points) representing a polygon with holes - * @param array $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side + * @param array $extra Extra data forwarded to the JavaScript side. It can be used in your custom + * Stimulus controller to benefit from greater flexibility and customization. + * These data must be serializable to JSON. These data are not used by UX Map. */ public function __construct( private readonly array $points, diff --git a/src/Map/src/Polyline.php b/src/Map/src/Polyline.php index 9a51e62ba9e..4279456e753 100644 --- a/src/Map/src/Polyline.php +++ b/src/Map/src/Polyline.php @@ -21,7 +21,9 @@ final class Polyline implements Element { /** - * @param array $extra Extra data, can be used by the developer to store additional information and use them later JavaScript side + * @param array $extra Extra data forwarded to the JavaScript side. It can be used in your custom + * Stimulus controller to benefit from greater flexibility and customization. + * These data must be serializable to JSON. These data are not used by UX Map. */ public function __construct( private readonly array $points, diff --git a/src/Map/src/Rectangle.php b/src/Map/src/Rectangle.php index 981460976ef..71479495e75 100644 --- a/src/Map/src/Rectangle.php +++ b/src/Map/src/Rectangle.php @@ -20,6 +20,11 @@ */ final class Rectangle implements Element { + /** + * @param array $extra Extra data forwarded to the JavaScript side. It can be used in your custom + * Stimulus controller to benefit from greater flexibility and customization. + * These data must be serializable to JSON. These data are not used by UX Map. + */ public function __construct( public readonly Point $southWest, public readonly Point $northEast, diff --git a/src/Map/tests/MapTest.php b/src/Map/tests/MapTest.php index 06d09a24d4c..a09e5866d6e 100644 --- a/src/Map/tests/MapTest.php +++ b/src/Map/tests/MapTest.php @@ -72,6 +72,7 @@ public function testZoomAndCenterCanBeOmittedIfFitBoundsToMarkers(): void 'polylines' => [], 'circles' => [], 'rectangles' => [], + 'extra' => null, ], $array); } @@ -94,6 +95,7 @@ public function testWithMinimumConfiguration(): void 'polylines' => [], 'circles' => [], 'rectangles' => [], + 'extra' => null, ], $array); } @@ -217,6 +219,11 @@ public function testWithMaximumConfiguration(): void autoClose: true, ), )) + ->extra([ + 'foo' => 'bar', + 'bar' => true, + 'baz' => ['qux' => 'quux'], + ]) ; self::assertEquals([ @@ -401,6 +408,11 @@ public function testWithMaximumConfiguration(): void 'id' => null, ], ], + 'extra' => [ + 'foo' => 'bar', + 'bar' => true, + 'baz' => ['qux' => 'quux'], + ], ], $map->toArray()); } }