Repository: symfony/ux
Pull request #2372 (2024-11-14 07:01:50)

[React] Fix broken Link in docs
Fix broken Link

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| Issues        | Fix
| License       | MIT



Patch:
diff --git a/src/Map/doc/index.rst b/src/Map/doc/index.rst
index 2876d8e6..6c22bc8a 100644
--- a/src/Map/doc/index.rst
+++ b/src/Map/doc/index.rst
@@ -30,11 +30,11 @@ Configuration
 Configuration is done in your ``config/packages/ux_map.yaml`` file:
 
 .. code-block:: yaml
-
+    
     # config/packages/ux_map.yaml
     ux_map:
         renderer: '%env(resolve:default::UX_MAP_DSN)%'
-
+        
         # Google Maps specific configuration
         google_maps:
             # Configure the default Map Id (https://developers.google.com/maps/documentation/get-map-id),
@@ -47,8 +47,8 @@ Map renderers
 ~~~~~~~~~~~~~
 
 The Symfony UX Map bundle supports multiple renderers. A map renderer is a
-service that provides the code and graphic assets required to render and
-interact with a map in the browser.
+service that provides the code and graphic assets required to render and 
+interact with a map in the browser. 
 
 Available renderers
 ~~~~~~~~~~~~~~~~~~~
@@ -75,7 +75,7 @@ Create a map
 
 A map is created by calling ``new Map()``. You can configure the center, zoom, and add markers.
 Start by creating a new map instance::
-
+    
     use Symfony\UX\Map\Map;
 
     // Create a new map instance
@@ -88,12 +88,12 @@ You can set the center and zoom of the map using the ``center()`` and ``zoom()``
 
     use Symfony\UX\Map\Map;
     use Symfony\UX\Map\Point;
-
+    
     $myMap
         // Explicitly set the center and zoom
         ->center(new Point(46.903354, 1.888334))
         ->zoom(6)
-
+        
         // Or automatically fit the bounds to the markers
         ->fitBoundsToMarkers()
     ;
@@ -105,13 +105,13 @@ You can add markers to a map using the ``addMarker()`` method::
 
     $myMap
         ->addMarker(new Marker(
-            position: new Point(48.8566, 2.3522),
+            position: new Point(48.8566, 2.3522), 
             title: 'Paris'
         ))
 
         // With an info window associated to the marker:
         ->addMarker(new Marker(
-            position: new Point(45.7640, 4.8357),
+            position: new Point(45.7640, 4.8357), 
             title: 'Lyon',
             infoWindow: new InfoWindow(
                 headerContent: '<b>Lyon</b>',
@@ -122,7 +122,7 @@ You can add markers to a map using the ``addMarker()`` method::
         // You can also pass arbitrary data via the `extra` option in both the marker
         // and the infoWindow; you can later use this data in your custom Stimulus controllers
         ->addMarker(new Marker(
-            position: new Point(45.7740, 4.8351), 
+            // ...
             extra: [
                 'icon_mask_url' => 'https://maps.gstatic.com/mapfiles/place_api/icons/v2/tree_pinlet.svg',
             ],
@@ -133,7 +133,7 @@ You can add markers to a map using the ``addMarker()`` method::
                     'includes_link' => true,
                 ],
             ),
-        ))
+        )
     ;
 
 Add Polygons
@@ -174,7 +174,7 @@ You can add custom HTML attributes too:
 Twig Function ``ux_map()``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The ``ux_map()`` Twig function allows you to create and render a map in your Twig
+The ``ux_map()`` Twig function allows you to create and render a map in your Twig 
 templates. The function accepts the same arguments as the ``Map`` class:
 
 .. code-block:: html+twig
@@ -216,8 +216,10 @@ Alternatively, you can use the ``<twig:ux:map />`` component.
                 "infoWindow": {"content": "Welcome to <b>New York</b>"}
             }
         ]'
-        class="foo"
-        style="height: 800px; width: 100%; border: 4px solid red; margin-block: 10vh;"
+        attributes='{
+            "class": "foo",
+            "style": "height: 800px; width: 100%; border: 4px solid red; margin-block: 10vh;"
+        }'
     />
 
 The ``<twig:ux:map />`` component requires the `Twig Component`_ package.
@@ -234,9 +236,9 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
 .. code-block:: javascript
 
     // assets/controllers/mymap_controller.js
-
+    
     import { Controller } from '@hotwired/stimulus';
-
+    
     export default class extends Controller {
         connect() {
             this.element.addEventListener('ux:map:pre-connect', this._onPreConnect);
@@ -246,7 +248,7 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
             this.element.addEventListener('ux:map:info-window:before-create', this._onInfoWindowBeforeCreate);
             this.element.addEventListener('ux:map:info-window:after-create', this._onInfoWindowAfterCreate);
         }
-
+    
         disconnect() {
             // You should always remove listeners when the controller is disconnected to avoid side effects
             this.element.removeEventListener('ux:map:pre-connect', this._onPreConnect);
@@ -256,13 +258,13 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
             this.element.removeEventListener('ux:map:info-window:before-create', this._onInfoWindowBeforeCreate);
             this.element.removeEventListener('ux:map:info-window:after-create', this._onInfoWindowAfterCreate);
         }
-
+    
         _onPreConnect(event) {
             // The map is not created yet
             // You can use this event to configure the map before it is created
             console.log(event.detail.options);
         }
-
+        
         _onConnect(event) {
             // The map, markers and infoWindows are created
             // The instances depend on the renderer you are using
@@ -270,19 +272,19 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
             console.log(event.detail.markers);
             console.log(event.detail.infoWindows);
         }
-
+        
         _onMarkerBeforeCreate(event) {
             // The marker is not created yet
             // You can use this event to configure the marker before it is created
             console.log(event.detail.definition);
         }
-
+        
         _onMarkerAfterCreate(event) {
             // The marker is created
             // The instance depends on the renderer you are using
             console.log(event.detail.marker);
         }
-
+        
         _onInfoWindowBeforeCreate(event) {
             // The infoWindow is not created yet
             // You can use this event to configure the infoWindow before it is created
@@ -290,7 +292,7 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
             // The associated marker instance is also available
             console.log(event.detail.marker);
         }
-
+        
         _onInfoWindowAfterCreate(event) {
             // The infoWindow is created
             // The instance depends on the renderer you are using
@@ -304,7 +306,7 @@ Symfony UX Map allows you to extend its default behavior using a custom Stimulus
 Then, you can use this controller in your template:
 
 .. code-block:: twig
-
+    
     {{ ux_map(my_map, { 'data-controller': 'mymap', style: 'height: 300px' }) }}
 
 .. tip::
diff --git a/src/Map/src/Bridge/Google/README.md b/src/Map/src/Bridge/Google/README.md
index 707907cf..59a55a06 100644
--- a/src/Map/src/Bridge/Google/README.md
+++ b/src/Map/src/Bridge/Google/README.md
@@ -10,7 +10,7 @@ UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default
 # With options
 UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?version=weekly
 UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?language=fr&region=FR
-UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default?libraries[]=geometry&libraries[]=places
+UX_MAP_DSN=google://GOOGLE_MAPS_API_KEY@default??libraries[]=geometry&libraries[]=places
 ```
 
 Available options:
diff --git a/src/React/doc/index.rst b/src/React/doc/index.rst
index 96e16e0a..353f05e1 100644
--- a/src/React/doc/index.rst
+++ b/src/React/doc/index.rst
@@ -126,7 +126,7 @@ requires some extra steps.
 
 #. Compile your ``.jsx`` files to pure JavaScript files. This can be done by
    installing Babel and the ``@babel/preset-react`` preset. Example:
-   https://github.com/symfony/ux/blob/2.x/ux.symfony.com/package.json
+   https://github.com/symfony/ux/blob/2.x/ux.symfony.com/assets/react/build/package.json
 
 #. Point this library at the "built" controllers directory that contains the final
    JavaScript files:
diff --git a/src/Svelte/doc/index.rst b/src/Svelte/doc/index.rst
index 5daebc9a..4b259156 100644
--- a/src/Svelte/doc/index.rst
+++ b/src/Svelte/doc/index.rst
@@ -121,7 +121,7 @@ AssetMapper requires some extra steps.
 
 #. Compile your ``.svelte`` files to pure JavaScript files. This can be done by
    using the ``svelte/compiler`` library, but is a bit of a non-standard process.
-   For an example, see https://github.com/symfony/ux/blob/2.x/ux.symfony.com/assets/svelte/build/build.js.
+   For an example, see https://github.com/symfony/ux/blob/2.x/ux.symfony.com/bin/compile_svelte.js.
 
 #. Point this library at the "built" controllers directory that contains the final
    JavaScript files:
diff --git a/src/TwigComponent/src/ComponentFactory.php b/src/TwigComponent/src/ComponentFactory.php
index 9e2d618b..8699fb10 100644
--- a/src/TwigComponent/src/ComponentFactory.php
+++ b/src/TwigComponent/src/ComponentFactory.php
@@ -25,11 +25,12 @@ use Symfony\UX\TwigComponent\Event\PreMountEvent;
  */
 final class ComponentFactory implements ResetInterface
 {
-    private array $mountMethods = [];
+    private static array $mountMethods = [];
 
     /**
      * @param array<string, array>        $config
      * @param array<class-string, string> $classMap
+     * @param array<class-string, array<string, string[]> $classMounts
      */
     public function __construct(
         private ComponentTemplateFinderInterface $componentTemplateFinder,
@@ -37,7 +38,7 @@ final class ComponentFactory implements ResetInterface
         private PropertyAccessorInterface $propertyAccessor,
         private EventDispatcherInterface $eventDispatcher,
         private array $config,
-        private readonly array $classMap,
+        private array $classMap,
     ) {
     }
 
@@ -87,29 +88,29 @@ final class ComponentFactory implements ResetInterface
     public function mountFromObject(object $component, array $data, ComponentMetadata $componentMetadata): MountedComponent
     {
         $originalData = $data;
-        $event = $this->preMount($component, $data, $componentMetadata);
-        $data = $event->getData();
+        $data = $this->preMount($component, $data, $componentMetadata);
 
         $this->mount($component, $data, $componentMetadata);
 
-        if (!$componentMetadata->isAnonymous()) {
-            // set data that wasn't set in mount on the component directly
-            foreach ($data as $property => $value) {
-                if ($this->propertyAccessor->isWritable($component, $property)) {
-                    $this->propertyAccessor->setValue($component, $property, $value);
-                    unset($data[$property]);
-                }
+        // set data that wasn't set in mount on the component directly
+        foreach ($data as $property => $value) {
+            if ($this->propertyAccessor->isWritable($component, $property)) {
+                $this->propertyAccessor->setValue($component, $property, $value);
+
+                unset($data[$property]);
             }
         }
 
         $postMount = $this->postMount($component, $data, $componentMetadata);
-        $data = $postMount->getData();
+        $data = $postMount['data'];
+        $extraMetadata = $postMount['extraMetadata'];
 
         // create attributes from "attributes" key if exists
         $attributesVar = $componentMetadata->getAttributesVar();
         $attributes = $data[$attributesVar] ?? [];
         unset($data[$attributesVar]);
 
+        // ensure remaining data is scalar
         foreach ($data as $key => $value) {
             if ($value instanceof \Stringable) {
                 $data[$key] = (string) $value;
@@ -119,9 +120,9 @@ final class ComponentFactory implements ResetInterface
         return new MountedComponent(
             $componentMetadata->getName(),
             $component,
-            new ComponentAttributes([...$attributes, ...$data]),
+            new ComponentAttributes(array_merge($attributes, $data)),
             $originalData,
-            $postMount->getExtraMetadata(),
+            $extraMetadata,
         );
     }
 
@@ -153,7 +154,7 @@ final class ComponentFactory implements ResetInterface
             return;
         }
 
-        $mount = $this->mountMethods[$component::class] ??= (new \ReflectionClass($component))->getMethod('mount');
+        $mount = self::$mountMethods[$component::class] ??= (new \ReflectionClass($component))->getMethod('mount');
 
         $parameters = [];
         foreach ($mount->getParameters() as $refParameter) {
@@ -171,34 +172,40 @@ final class ComponentFactory implements ResetInterface
         $mount->invoke($component, ...$parameters);
     }
 
-    private function preMount(object $component, array $data, ComponentMetadata $componentMetadata): PreMountEvent
+    private function preMount(object $component, array $data, ComponentMetadata $componentMetadata): array
     {
         $event = new PreMountEvent($component, $data, $componentMetadata);
         $this->eventDispatcher->dispatch($event);
-
         $data = $event->getData();
+
         foreach ($componentMetadata->getPreMounts() as $preMount) {
             if (null !== $newData = $component->$preMount($data)) {
-                $event->setData($data = $newData);
+                $data = $newData;
             }
         }
 
-        return $event;
+        return $data;
     }
 
-    private function postMount(object $component, array $data, ComponentMetadata $componentMetadata): PostMountEvent
+    /**
+     * @return array{data: array<string, mixed>, extraMetadata: array<string, mixed>}
+     */
+    private function postMount(object $component, array $data, ComponentMetadata $componentMetadata): array
     {
         $event = new PostMountEvent($component, $data, $componentMetadata);
         $this->eventDispatcher->dispatch($event);
-
         $data = $event->getData();
+
         foreach ($componentMetadata->getPostMounts() as $postMount) {
             if (null !== $newData = $component->$postMount($data)) {
-                $event->setData($data = $newData);
+                $data = $newData;
             }
         }
 
-        return $event;
+        return [
+            'data' => $data,
+            'extraMetadata' => $event->getExtraMetadata(),
+        ];
     }
 
     /**
@@ -237,6 +244,6 @@ final class ComponentFactory implements ResetInterface
 
     public function reset(): void
     {
-        $this->mountMethods = [];
+        self::$mountMethods = [];
     }
 }
diff --git a/src/TwigComponent/src/ComponentRenderer.php b/src/TwigComponent/src/ComponentRenderer.php
index 2595d646..fb6a01ff 100644
--- a/src/TwigComponent/src/ComponentRenderer.php
+++ b/src/TwigComponent/src/ComponentRenderer.php
@@ -12,7 +12,6 @@
 namespace Symfony\UX\TwigComponent;
 
 use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
-use Symfony\Contracts\Service\ResetInterface;
 use Symfony\UX\TwigComponent\Event\PostRenderEvent;
 use Symfony\UX\TwigComponent\Event\PreCreateForRenderEvent;
 use Symfony\UX\TwigComponent\Event\PreRenderEvent;
@@ -23,10 +22,8 @@ use Twig\Environment;
  *
  * @internal
  */
-final class ComponentRenderer implements ComponentRendererInterface, ResetInterface
+final class ComponentRenderer implements ComponentRendererInterface
 {
-    private array $templateClasses = [];
-
     public function __construct(
         private Environment $twig,
         private EventDispatcherInterface $dispatcher,
@@ -65,15 +62,15 @@ final class ComponentRenderer implements ComponentRendererInterface, ResetInterf
         $variables = $event->getVariables();
         // see ComponentNode. When rendering an individual embedded component,
         // *not* through its parent, we need to set the parent template.
-        if ($templateIndex = $event->getTemplateIndex()) {
+        if ($event->getTemplateIndex()) {
             $variables['__parent__'] = $event->getParentTemplateForEmbedded();
         }
 
         try {
             return $this->twig->loadTemplate(
-                $this->templateClasses[$template = $event->getTemplate()] ??= $this->twig->getTemplateClass($template),
-                $template,
-                $templateIndex,
+                $this->twig->getTemplateClass($event->getTemplate()),
+                $event->getTemplate(),
+                $event->getTemplateIndex(),
             )->render($variables);
         } finally {
             $mounted = $this->componentStack->pop();
@@ -140,9 +137,4 @@ final class ComponentRenderer implements ComponentRendererInterface, ResetInterf
 
         return $event;
     }
-
-    public function reset(): void
-    {
-        $this->templateClasses = [];
-    }
 }
diff --git a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php
index ebe2bb17..19432f63 100644
--- a/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php
+++ b/src/TwigComponent/src/DependencyInjection/TwigComponentExtension.php
@@ -113,7 +113,6 @@ final class TwigComponentExtension extends Extension implements ConfigurationInt
                 new Reference('ux.twig_component.component_properties'),
                 new Reference('ux.twig_component.component_stack'),
             ])
-            ->addTag('kernel.reset', ['method' => 'reset'])
         ;
 
         $container->register('ux.twig_component.twig.component_extension', ComponentExtension::class)


Test patch:
diff --git a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
index 004a6dff..66ea8cd2 100644
--- a/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
+++ b/src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php
@@ -1875,7 +1875,6 @@ class HydrationTest
                 new ComponentMetadata([
                     'key' => '__testing',
                     'mount' => $reflectionClass->hasMethod('mount') ? ['mount'] : [],
-                    'service_id' => '__testing_id',
                 ]),
                 $metadataFactory->createPropMetadatas($reflectionClass),
             ),