From 76f6bbaf254652a5b25a4885e16eb965d37fe36c Mon Sep 17 00:00:00 2001
From: just_like_good_vibes <mickael@meulle.com>
Date: Wed, 12 Mar 2025 14:48:44 +0100
Subject: [PATCH 1/5] WIP

---
 src/Element/ComponentElementBuilder.php | 110 +++++++++++++++---------
 1 file changed, 68 insertions(+), 42 deletions(-)

diff --git a/src/Element/ComponentElementBuilder.php b/src/Element/ComponentElementBuilder.php
index d890b359..87f917e2 100644
--- a/src/Element/ComponentElementBuilder.php
+++ b/src/Element/ComponentElementBuilder.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\ui_patterns\Element;
 
+use Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType;
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Core\Plugin\Component;
 use Drupal\Core\Render\Element;
@@ -79,42 +80,79 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
    * Add a single prop to the renderable.
    */
   protected function buildProp(array $build, string $prop_id, array $definition, array $configuration, array $source_contexts): array {
-    if (isset($build["#props"][$prop_id])) {
+    if (isset($build['#props'][$prop_id])) {
       // Keep existing props. No known use case yet.
       return $build;
     }
-    $source = $this->getSource($prop_id, $definition, $configuration, $source_contexts);
-    if (!$source) {
-      return $build;
-    }
+    $this->buildSource($build, $prop_id, $definition, $configuration, $source_contexts);
+    return $build;
+  }
+
+  /**
+   * Alter the build array for a source.
+   *
+   * @param array $build
+   *   The build array.
+   * @param string $prop_or_slot_id
+   *   Prop ID or slot ID.
+   * @param array $definition
+   *   Definition.
+   * @param array $configuration
+   *   Configuration.
+   * @param array $contexts
+   *   Source contexts.
+   *
+   * @return mixed
+   *   The data returned by the source.
+   */
+  public function buildSource(array &$build, string $prop_or_slot_id, array $definition, array $configuration, array $contexts) : mixed {
     try {
-      $build = $source->alterComponent($build);
+      $source = $this->getSource($prop_or_slot_id, $definition, $configuration, $contexts);
+      if (!$source) {
+        return NULL;
+      }
+      /** @var \Drupal\ui_patterns\PropTypeInterface $prop_type */
+      // $prop_type = $this->propTypeManager->createInstance('slot', []);
       $prop_type = $definition['ui_patterns']['type_definition'];
+      // Alter the build array before getting the value.
+      $build = $source->alterComponent($build);
+      // Get the value from the source.
       $data = $source->getValue($prop_type);
+      // Alter the value by hook implementations.
       $this->moduleHandler->alter('ui_patterns_source_value', $data, $source, $configuration);
-      if (empty($data) && $prop_type->getPluginId() !== 'attributes') {
-        // For JSON Schema validator, empty value is not the same as missing
-        // value, and we want to prevent some of the prop types rules to be
-        // applied on empty values: string pattern, string format, enum, number
-        // min/max...
-        // However, we don't remove empty attributes to avoid an error with
-        // Drupal\Core\Template\TwigExtension::createAttribute() when themers
-        // forget to use the default({}) filter.
-        return $build;
+      if ($prop_type instanceof SlotPropType) {
+        if ($data !== NULL && Element::isRenderArray($data)) {
+          if ($this->isSingletonRenderArray($data)) {
+            $data = array_values($data)[0];
+          }
+          $build['#slots'][$prop_or_slot_id][] = $data;
+        }
+      }
+      else {
+        if (!empty($data) || $prop_type->getPluginId() === 'attributes') {
+          // For JSON Schema validator, empty value is not the same as missing
+          // value, and we want to prevent some of the prop types rules to be
+          // applied on empty values: string pattern, string format, enum, number
+          // min/max...
+          // However, we don't remove empty attributes to avoid an error with
+          // Drupal\Core\Template\TwigExtension::createAttribute() when themers
+          // forget to use the default({}) filter.
+          $build['#props'][$prop_or_slot_id] = $data;
+        }
       }
-      $build["#props"][$prop_id] = $data;
+      return $data;
     }
     catch (ContextException $e) {
       // ContextException is thrown when a required context is missing.
       // We don't want to break the render process, so we just ignore the prop.
-      $error_message = t("Context error for prop '@prop_id' in component '@component_id': @message", [
-        '@prop_id' => $prop_id,
+      $error_message = t("Context error for '@prop_id' in component '@component_id': @message", [
+        '@prop_id' => $prop_or_slot_id,
         '@component_id' => $build['#component'],
         '@message' => $e->getMessage(),
       ]);
       $this->logger->error($error_message);
     }
-    return $build;
+    return NULL;
   }
 
   /**
@@ -134,8 +172,8 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
    *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
-  protected function getSource(string $prop_or_slot_id, array $definition, array $configuration, array $source_contexts) : ?SourceInterface {
-    $source_id = $configuration["source_id"] ?? NULL;
+  public function getSource(string $prop_or_slot_id, array $definition, array $configuration, array $source_contexts) : ?SourceInterface {
+    $source_id = $configuration['source_id'] ?? NULL;
     if (!$source_id && isset($definition['ui_patterns']['type_definition'])) {
       $source_id = $this->sourcesManager->getPropTypeDefault($definition['ui_patterns']['type_definition']->getPluginId(), $source_contexts);
     }
@@ -167,34 +205,22 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
    * Add a single slot to the renderable.
    */
   protected function buildSlot(array $build, string $slot_id, array $definition, array $configuration, array $contexts): array {
-    if (isset($build["#slots"][$slot_id])) {
+    if (isset($build['#slots'][$slot_id])) {
       // Keep existing slots. Used by ComponentLayout for example.
       return $build;
     }
-    if (!isset($configuration["sources"])) {
+    if (!isset($configuration['sources'])) {
       return $build;
     }
     // Slots can have many sources while props can have only one.
-    $build["#slots"][$slot_id] = [];
-    /** @var \Drupal\ui_patterns\PropTypeInterface $slot_prop_type */
-    $slot_prop_type = $this->propTypeManager->createInstance("slot", []);
+    $build['#slots'][$slot_id] = [];
     // Add sources data to the slot.
-    foreach ($configuration["sources"] as $source_configuration) {
-      $source = $this->getSource($slot_id, $definition, $source_configuration, $contexts);
-      if (!$source) {
-        continue;
-      }
-      $build = $source->alterComponent($build);
-      $source_value = $source->getValue($slot_prop_type) ?? [];
-      $this->moduleHandler->alter('ui_patterns_source_value', $source_value, $source, $source_configuration);
-      if (Element::isRenderArray($source_value)) {
-        $build["#slots"][$slot_id][] = $this->isSingletonRenderArray($source_value) ? array_values($source_value)[0] : $source_value;
-      }
+    foreach ($configuration['sources'] as $source_configuration) {
+      $this->buildSource($build, $slot_id, $definition, $source_configuration, $contexts);
     }
-    if ($this->isSingletonRenderArray($build["#slots"][$slot_id])) {
-      $build["#slots"][$slot_id] = $build["#slots"][$slot_id][0];
+    if ($this->isSingletonRenderArray($build['#slots'][$slot_id])) {
+      $build['#slots'][$slot_id] = $build['#slots'][$slot_id][0];
     }
-
     return $build;
   }
 
@@ -294,10 +320,10 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
     $slots = $component->metadata->slots ?? [];
     foreach ($slots as $slot_id => $definition) {
       $slot_configuration = $configuration['slots'][$slot_id] ?? [];
-      if (!isset($slot_configuration["sources"]) || !is_array($slot_configuration["sources"])) {
+      if (!isset($slot_configuration['sources']) || !is_array($slot_configuration['sources'])) {
         continue;
       }
-      foreach ($slot_configuration["sources"] as $source_configuration) {
+      foreach ($slot_configuration['sources'] as $source_configuration) {
         if ($source = $this->getSource($slot_id, $definition, $source_configuration, $contexts)) {
           SourcePluginBase::mergeConfigDependencies($dependencies, $source->calculateDependencies());
         }
-- 
GitLab


From 45c8df8f59dbf9f429773654dec5efab96cd4441 Mon Sep 17 00:00:00 2001
From: just_like_good_vibes <mickael@meulle.com>
Date: Wed, 12 Mar 2025 15:25:37 +0100
Subject: [PATCH 2/5] WIP

---
 src/Element/ComponentElementBuilder.php | 69 +++++++++++++++----------
 1 file changed, 41 insertions(+), 28 deletions(-)

diff --git a/src/Element/ComponentElementBuilder.php b/src/Element/ComponentElementBuilder.php
index 87f917e2..4ef47d87 100644
--- a/src/Element/ComponentElementBuilder.php
+++ b/src/Element/ComponentElementBuilder.php
@@ -11,6 +11,7 @@ use Drupal\Core\Render\Element;
 use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Theme\ComponentPluginManager;
 use Drupal\ui_patterns\ComponentPluginManager as UiPatternsComponentPluginManager;
+use Drupal\ui_patterns\PropTypeInterface;
 use Drupal\ui_patterns\PropTypePluginManager;
 use Drupal\ui_patterns\SourceInterface;
 use Drupal\ui_patterns\SourcePluginBase;
@@ -84,8 +85,33 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
       // Keep existing props. No known use case yet.
       return $build;
     }
-    $this->buildSource($build, $prop_id, $definition, $configuration, $source_contexts);
-    return $build;
+    return $this->buildSource($build, $prop_id, $definition, $configuration, $source_contexts);
+  }
+
+  /**
+   * Add data to a prop or a slot.
+   */
+  protected function addDataToComponent(array &$build, string $prop_or_slot_id, PropTypeInterface $prop_type, mixed $data): void {
+    if ($prop_type instanceof SlotPropType) {
+      if ($data !== NULL && Element::isRenderArray($data)) {
+        if ($this->isSingletonRenderArray($data)) {
+          $data = array_values($data)[0];
+        }
+        $build['#slots'][$prop_or_slot_id][] = $data;
+      }
+    }
+    else {
+      if (!empty($data) || $prop_type->getPluginId() === 'attributes') {
+        // For JSON Schema validator, empty value is not the same as missing
+        // value, and we want to prevent some of the prop types rules to be
+        // applied on empty values: string pattern, string format,
+        // enum, number min/max...
+        // However, we don't remove empty attributes to avoid an error with
+        // Drupal\Core\Template\TwigExtension::createAttribute() when themers
+        // forget to use the default({}) filter.
+        $build['#props'][$prop_or_slot_id] = $data;
+      }
+    }
   }
 
   /**
@@ -105,42 +131,25 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
    * @return mixed
    *   The data returned by the source.
    */
-  public function buildSource(array &$build, string $prop_or_slot_id, array $definition, array $configuration, array $contexts) : mixed {
+  public function buildSource(array $build, string $prop_or_slot_id, array $definition, array $configuration, array $contexts) : mixed {
     try {
+      if (empty($configuration['source_id'])) {
+        return $build;
+      }
       $source = $this->getSource($prop_or_slot_id, $definition, $configuration, $contexts);
       if (!$source) {
-        return NULL;
+        return $build;
       }
       /** @var \Drupal\ui_patterns\PropTypeInterface $prop_type */
       // $prop_type = $this->propTypeManager->createInstance('slot', []);
-      $prop_type = $definition['ui_patterns']['type_definition'];
+      $prop_type = $source->getPropDefinition()['ui_patterns']['type_definition'];
       // Alter the build array before getting the value.
       $build = $source->alterComponent($build);
       // Get the value from the source.
       $data = $source->getValue($prop_type);
       // Alter the value by hook implementations.
       $this->moduleHandler->alter('ui_patterns_source_value', $data, $source, $configuration);
-      if ($prop_type instanceof SlotPropType) {
-        if ($data !== NULL && Element::isRenderArray($data)) {
-          if ($this->isSingletonRenderArray($data)) {
-            $data = array_values($data)[0];
-          }
-          $build['#slots'][$prop_or_slot_id][] = $data;
-        }
-      }
-      else {
-        if (!empty($data) || $prop_type->getPluginId() === 'attributes') {
-          // For JSON Schema validator, empty value is not the same as missing
-          // value, and we want to prevent some of the prop types rules to be
-          // applied on empty values: string pattern, string format, enum, number
-          // min/max...
-          // However, we don't remove empty attributes to avoid an error with
-          // Drupal\Core\Template\TwigExtension::createAttribute() when themers
-          // forget to use the default({}) filter.
-          $build['#props'][$prop_or_slot_id] = $data;
-        }
-      }
-      return $data;
+      $this->addDataToComponent($build, $prop_or_slot_id, $prop_type, $data);
     }
     catch (ContextException $e) {
       // ContextException is thrown when a required context is missing.
@@ -152,7 +161,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
       ]);
       $this->logger->error($error_message);
     }
-    return NULL;
+    return $build;
   }
 
   /**
@@ -173,6 +182,10 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
   public function getSource(string $prop_or_slot_id, array $definition, array $configuration, array $source_contexts) : ?SourceInterface {
+    if (empty($definition)) {
+      // We consider a slot if no definition is provided.
+      $definition = ['ui_patterns' => ['type_definition' => $this->propTypeManager->createInstance('slot')]];
+    }
     $source_id = $configuration['source_id'] ?? NULL;
     if (!$source_id && isset($definition['ui_patterns']['type_definition'])) {
       $source_id = $this->sourcesManager->getPropTypeDefault($definition['ui_patterns']['type_definition']->getPluginId(), $source_contexts);
@@ -216,7 +229,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
     $build['#slots'][$slot_id] = [];
     // Add sources data to the slot.
     foreach ($configuration['sources'] as $source_configuration) {
-      $this->buildSource($build, $slot_id, $definition, $source_configuration, $contexts);
+      $build = $this->buildSource($build, $slot_id, $definition, $source_configuration, $contexts);
     }
     if ($this->isSingletonRenderArray($build['#slots'][$slot_id])) {
       $build['#slots'][$slot_id] = $build['#slots'][$slot_id][0];
-- 
GitLab


From d1ba67f3b448375feccab2c8ba15cce13777575a Mon Sep 17 00:00:00 2001
From: just_like_good_vibes <mickael@meulle.com>
Date: Wed, 12 Mar 2025 15:26:52 +0100
Subject: [PATCH 3/5] WIP

---
 src/Element/ComponentElementBuilder.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Element/ComponentElementBuilder.php b/src/Element/ComponentElementBuilder.php
index 4ef47d87..8cce68ea 100644
--- a/src/Element/ComponentElementBuilder.php
+++ b/src/Element/ComponentElementBuilder.php
@@ -115,7 +115,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
   }
 
   /**
-   * Alter the build array for a source.
+   * Alter the build array for a configured source on a prop/slot.
    *
    * @param array $build
    *   The build array.
@@ -165,7 +165,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
   }
 
   /**
-   * Get Source plugin for a prop.
+   * Get Source plugin for a prop or slot.
    *
    * @param string $prop_or_slot_id
    *   Prop ID or slot ID.
-- 
GitLab


From 2c948ada9a62231138824a851ed13fb8f077a383 Mon Sep 17 00:00:00 2001
From: just_like_good_vibes <mickael@meulle.com>
Date: Wed, 12 Mar 2025 16:25:57 +0100
Subject: [PATCH 4/5] comments

---
 src/Element/ComponentElementBuilder.php | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/Element/ComponentElementBuilder.php b/src/Element/ComponentElementBuilder.php
index 8cce68ea..0d4b882f 100644
--- a/src/Element/ComponentElementBuilder.php
+++ b/src/Element/ComponentElementBuilder.php
@@ -115,7 +115,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
   }
 
   /**
-   * Alter the build array for a configured source on a prop/slot.
+   * Update the build array for a configured source on a prop/slot.
    *
    * @param array $build
    *   The build array.
@@ -129,7 +129,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
    *   Source contexts.
    *
    * @return mixed
-   *   The data returned by the source.
+   *   The updated build array.
    */
   public function buildSource(array $build, string $prop_or_slot_id, array $definition, array $configuration, array $contexts) : mixed {
     try {
@@ -170,18 +170,20 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
    * @param string $prop_or_slot_id
    *   Prop ID or slot ID.
    * @param array $definition
-   *   Definition.
+   *   Definition (if empty, slot will be automatically set).
    * @param array $configuration
-   *   Configuration.
+   *   Configuration for the source.
    * @param array $source_contexts
    *   Source contexts.
+   * @param array $form_array_parents
+   *   Form array parents.
    *
    * @return \Drupal\ui_patterns\SourceInterface|null
-   *   The source found or NULL.
+   *   The source found and instantiated or NULL.
    *
    * @throws \Drupal\Component\Plugin\Exception\PluginException
    */
-  public function getSource(string $prop_or_slot_id, array $definition, array $configuration, array $source_contexts) : ?SourceInterface {
+  public function getSource(string $prop_or_slot_id, array $definition, array $configuration, array $source_contexts, array $form_array_parents = []) : ?SourceInterface {
     if (empty($definition)) {
       // We consider a slot if no definition is provided.
       $definition = ['ui_patterns' => ['type_definition' => $this->propTypeManager->createInstance('slot')]];
@@ -196,7 +198,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
     /** @var \Drupal\ui_patterns\SourceInterface $source */
     $source = $this->sourcesManager->createInstance(
       $source_id,
-      SourcePluginBase::buildConfiguration($prop_or_slot_id, $definition, $configuration, $source_contexts)
+      SourcePluginBase::buildConfiguration($prop_or_slot_id, $definition, $configuration, $source_contexts, $form_array_parents)
     );
     return $source;
   }
-- 
GitLab


From 639eb831b91cdc368ff45031d3aa8181fbba3b86 Mon Sep 17 00:00:00 2001
From: just_like_good_vibes <mickael@meulle.com>
Date: Thu, 13 Mar 2025 09:44:50 +0100
Subject: [PATCH 5/5] moved getSource to source manager

---
 src/Element/ComponentElementBuilder.php | 49 ++----------------------
 src/SourcePluginManager.php             | 51 +++++++++++++++++++++++++
 ui_patterns.services.yml                |  1 -
 3 files changed, 54 insertions(+), 47 deletions(-)

diff --git a/src/Element/ComponentElementBuilder.php b/src/Element/ComponentElementBuilder.php
index 0d4b882f..e2948b34 100644
--- a/src/Element/ComponentElementBuilder.php
+++ b/src/Element/ComponentElementBuilder.php
@@ -12,8 +12,6 @@ use Drupal\Core\Security\TrustedCallbackInterface;
 use Drupal\Core\Theme\ComponentPluginManager;
 use Drupal\ui_patterns\ComponentPluginManager as UiPatternsComponentPluginManager;
 use Drupal\ui_patterns\PropTypeInterface;
-use Drupal\ui_patterns\PropTypePluginManager;
-use Drupal\ui_patterns\SourceInterface;
 use Drupal\ui_patterns\SourcePluginBase;
 use Drupal\ui_patterns\SourcePluginManager;
 use Psr\Log\LoggerInterface;
@@ -36,7 +34,6 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
    */
   public function __construct(
     protected SourcePluginManager $sourcesManager,
-    protected PropTypePluginManager $propTypeManager,
     protected ComponentPluginManager $componentPluginManager,
     protected ModuleHandlerInterface $moduleHandler,
     protected LoggerInterface $logger,
@@ -136,12 +133,11 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
       if (empty($configuration['source_id'])) {
         return $build;
       }
-      $source = $this->getSource($prop_or_slot_id, $definition, $configuration, $contexts);
+      $source = $this->sourcesManager->getSource($prop_or_slot_id, $definition, $configuration, $contexts);
       if (!$source) {
         return $build;
       }
       /** @var \Drupal\ui_patterns\PropTypeInterface $prop_type */
-      // $prop_type = $this->propTypeManager->createInstance('slot', []);
       $prop_type = $source->getPropDefinition()['ui_patterns']['type_definition'];
       // Alter the build array before getting the value.
       $build = $source->alterComponent($build);
@@ -164,45 +160,6 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
     return $build;
   }
 
-  /**
-   * Get Source plugin for a prop or slot.
-   *
-   * @param string $prop_or_slot_id
-   *   Prop ID or slot ID.
-   * @param array $definition
-   *   Definition (if empty, slot will be automatically set).
-   * @param array $configuration
-   *   Configuration for the source.
-   * @param array $source_contexts
-   *   Source contexts.
-   * @param array $form_array_parents
-   *   Form array parents.
-   *
-   * @return \Drupal\ui_patterns\SourceInterface|null
-   *   The source found and instantiated or NULL.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\PluginException
-   */
-  public function getSource(string $prop_or_slot_id, array $definition, array $configuration, array $source_contexts, array $form_array_parents = []) : ?SourceInterface {
-    if (empty($definition)) {
-      // We consider a slot if no definition is provided.
-      $definition = ['ui_patterns' => ['type_definition' => $this->propTypeManager->createInstance('slot')]];
-    }
-    $source_id = $configuration['source_id'] ?? NULL;
-    if (!$source_id && isset($definition['ui_patterns']['type_definition'])) {
-      $source_id = $this->sourcesManager->getPropTypeDefault($definition['ui_patterns']['type_definition']->getPluginId(), $source_contexts);
-    }
-    if (!$source_id) {
-      return NULL;
-    }
-    /** @var \Drupal\ui_patterns\SourceInterface $source */
-    $source = $this->sourcesManager->createInstance(
-      $source_id,
-      SourcePluginBase::buildConfiguration($prop_or_slot_id, $definition, $configuration, $source_contexts, $form_array_parents)
-    );
-    return $source;
-  }
-
   /**
    * Add slots to the renderable.
    */
@@ -310,7 +267,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
       if ($prop_id === 'variant') {
         continue;
       }
-      if ($source = $this->getSource($prop_id, $definition, $configuration['props'][$prop_id] ?? [], $contexts)) {
+      if ($source = $this->sourcesManager->getSource($prop_id, $definition, $configuration['props'][$prop_id] ?? [], $contexts)) {
         SourcePluginBase::mergeConfigDependencies($dependencies, $source->calculateDependencies());
       }
     }
@@ -339,7 +296,7 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
         continue;
       }
       foreach ($slot_configuration['sources'] as $source_configuration) {
-        if ($source = $this->getSource($slot_id, $definition, $source_configuration, $contexts)) {
+        if ($source = $this->sourcesManager->getSource($slot_id, $definition, $source_configuration, $contexts)) {
           SourcePluginBase::mergeConfigDependencies($dependencies, $source->calculateDependencies());
         }
       }
diff --git a/src/SourcePluginManager.php b/src/SourcePluginManager.php
index 32a42c61..de07ef0d 100644
--- a/src/SourcePluginManager.php
+++ b/src/SourcePluginManager.php
@@ -325,4 +325,55 @@ class SourcePluginManager extends DefaultPluginManager implements ContextAwarePl
     return isset($definitions[$source_id]);
   }
 
+  /**
+   * Get a source plugin Instance.
+   *
+   * A source instance is always related to a prop or a slot.
+   * That's why we pass first the prop or slot id and the associated definition.
+   * If definition is empty, the slot will be automatically assumed.
+   * The configuration passed is the source configuration.
+   * It has a key 'source_id' that is the source plugin identifier.
+   * When no source_id is provided,
+   * the default source for the prop type is used.
+   * The source contexts are the contexts currently in use,
+   * maybe needed for that source or not.
+   * The form array parents are the form array parents, needed
+   * when dealing with the source settingsForm.
+   *
+   * @param string $prop_or_slot_id
+   *   Prop ID or slot ID.
+   * @param array $definition
+   *   Definition (if empty, slot will be automatically set).
+   * @param array $configuration
+   *   Configuration for the source.
+   * @param array $source_contexts
+   *   Source contexts.
+   * @param array $form_array_parents
+   *   Form array parents.
+   *
+   * @return \Drupal\ui_patterns\SourceInterface|null
+   *   The source found and instantiated or NULL.
+   *
+   * @throws \Drupal\Component\Plugin\Exception\PluginException
+   */
+  public function getSource(string $prop_or_slot_id, array $definition, array $configuration, array $source_contexts = [], array $form_array_parents = []) : ?SourceInterface {
+    if (empty($definition)) {
+      // We consider a slot if no definition is provided.
+      $definition = ['ui_patterns' => ['type_definition' => $this->propTypeManager->createInstance('slot')]];
+    }
+    $source_id = $configuration['source_id'] ?? NULL;
+    if (!$source_id && isset($definition['ui_patterns']['type_definition'])) {
+      $source_id = $this->getPropTypeDefault($definition['ui_patterns']['type_definition']->getPluginId(), $source_contexts);
+    }
+    if (!$source_id) {
+      return NULL;
+    }
+    /** @var \Drupal\ui_patterns\SourceInterface $source */
+    $source = $this->createInstance(
+      $source_id,
+      SourcePluginBase::buildConfiguration($prop_or_slot_id, $definition, $configuration, $source_contexts, $form_array_parents)
+    );
+    return $source;
+  }
+
 }
diff --git a/ui_patterns.services.yml b/ui_patterns.services.yml
index ac131fbe..fc03ba97 100644
--- a/ui_patterns.services.yml
+++ b/ui_patterns.services.yml
@@ -44,7 +44,6 @@ services:
     class: Drupal\ui_patterns\Element\ComponentElementBuilder
     arguments:
       - "@plugin.manager.ui_patterns_source"
-      - "@plugin.manager.ui_patterns_prop_type"
       - "@plugin.manager.sdc"
       - "@module_handler"
       - "@logger.channel.ui_patterns"
-- 
GitLab