From 5e3f489ae92619b98ad6547c6eb6db62ed306b64 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois?= <f.mably@gmail.com>
Date: Sat, 14 Dec 2024 00:52:52 +0100
Subject: [PATCH] Issue #3493944 by mably: Make ICS compatible with the
 responsive_image module

---
 .../image_compare_media.info.yml              |   2 +-
 .../ImageCompareSliderMediaFormatter.php      |  33 +-
 .../ImageCompareSliderMediaFormatterTrait.php |  43 ++
 .../image_compare_media_responsive.schema.yml |   3 +
 .../image_compare_media_responsive.info.yml   |   8 +
 ...eCompareSliderResponsiveMediaFormatter.php |  95 ++++
 .../image_compare_responsive.schema.yml       |   3 +
 .../image_compare_responsive.info.yml         |   8 +
 .../ImageCompareSliderResponsiveFormatter.php | 154 ++++++
 .../ImageCompareSliderFormatter.php           | 397 +---------------
 .../ImageCompareSliderFormatterTrait.php      | 447 ++++++++++++++++++
 templates/image-compare-slider.html.twig      |  50 +-
 12 files changed, 798 insertions(+), 445 deletions(-)
 create mode 100644 modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatterTrait.php
 create mode 100644 modules/image_compare_media_responsive/config/schema/image_compare_media_responsive.schema.yml
 create mode 100644 modules/image_compare_media_responsive/image_compare_media_responsive.info.yml
 create mode 100644 modules/image_compare_media_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveMediaFormatter.php
 create mode 100644 modules/image_compare_responsive/config/schema/image_compare_responsive.schema.yml
 create mode 100644 modules/image_compare_responsive/image_compare_responsive.info.yml
 create mode 100644 modules/image_compare_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveFormatter.php
 create mode 100644 src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatterTrait.php

diff --git a/modules/image_compare_media/image_compare_media.info.yml b/modules/image_compare_media/image_compare_media.info.yml
index 5a9163d..b0effaa 100644
--- a/modules/image_compare_media/image_compare_media.info.yml
+++ b/modules/image_compare_media/image_compare_media.info.yml
@@ -4,5 +4,5 @@ description: Add an Image Compare Accessible Slider formatter to media fields.
 package: Media
 core_version_requirement: ^10 || ^11
 dependencies:
-  - image_compare:image_compare
   - drupal:media
+  - image_compare:image_compare
diff --git a/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatter.php b/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatter.php
index 025fd53..a7936ce 100644
--- a/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatter.php
+++ b/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatter.php
@@ -3,8 +3,6 @@
 namespace Drupal\image_compare_media\Plugin\Field\FieldFormatter;
 
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
 use Drupal\image\Plugin\Field\FieldType\ImageItem;
@@ -24,6 +22,8 @@ use Drupal\media\MediaInterface;
  */
 class ImageCompareSliderMediaFormatter extends ImageCompareSliderFormatter {
 
+  use ImageCompareSliderMediaFormatterTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -80,33 +80,4 @@ class ImageCompareSliderMediaFormatter extends ImageCompareSliderFormatter {
     return $this->buildRenderArray($images, $options, $cache_tags);
   }
 
-  /**
-   * {@inheritdoc}
-   *
-   * This has to be overridden because FileFormatterBase expects $item to be
-   * of type \Drupal\file\Plugin\Field\FieldType\FileItem and calls
-   * isDisplayed() which is not in FieldItemInterface.
-   */
-  protected function needsEntityLoad(EntityReferenceItem $item) {
-    return !$item->hasNewEntity();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function isApplicable(FieldDefinitionInterface $field_definition) {
-    // This formatter is only available for entity types that reference
-    // media items.
-    return ($field_definition->getFieldStorageDefinition()
-      ->getSetting('target_type') == 'media');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function checkAccess(EntityInterface $entity) {
-    return $entity->access('view', NULL, TRUE)
-      ->andIf(parent::checkAccess($entity));
-  }
-
 }
diff --git a/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatterTrait.php b/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatterTrait.php
new file mode 100644
index 0000000..c46e46e
--- /dev/null
+++ b/modules/image_compare_media/src/Plugin/Field/FieldFormatter/ImageCompareSliderMediaFormatterTrait.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Drupal\image_compare_media\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
+
+/**
+ * Provides a trait for the Image Compare Slider Media field formatter.
+ */
+trait ImageCompareSliderMediaFormatterTrait {
+
+  /**
+   * {@inheritdoc}
+   *
+   * This has to be overridden because FileFormatterBase expects $item to be
+   * of type \Drupal\file\Plugin\Field\FieldType\FileItem and calls
+   * isDisplayed() which is not in FieldItemInterface.
+   */
+  protected function needsEntityLoad(EntityReferenceItem $item) {
+    return !$item->hasNewEntity();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function isApplicable(FieldDefinitionInterface $field_definition) {
+    // This formatter is only available for entity types that reference
+    // media items.
+    return ($field_definition->getFieldStorageDefinition()
+      ->getSetting('target_type') == 'media');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkAccess(EntityInterface $entity) {
+    return $entity->access('view', NULL, TRUE)
+      ->andIf(parent::checkAccess($entity));
+  }
+
+}
diff --git a/modules/image_compare_media_responsive/config/schema/image_compare_media_responsive.schema.yml b/modules/image_compare_media_responsive/config/schema/image_compare_media_responsive.schema.yml
new file mode 100644
index 0000000..27e4781
--- /dev/null
+++ b/modules/image_compare_media_responsive/config/schema/image_compare_media_responsive.schema.yml
@@ -0,0 +1,3 @@
+field.formatter.settings.image_compare_slider_media_responsive:
+  type: field.formatter.settings.image_compare_slider
+  label: Image Compare Accessible Slider formatter settings for responsive media
diff --git a/modules/image_compare_media_responsive/image_compare_media_responsive.info.yml b/modules/image_compare_media_responsive/image_compare_media_responsive.info.yml
new file mode 100644
index 0000000..abe600f
--- /dev/null
+++ b/modules/image_compare_media_responsive/image_compare_media_responsive.info.yml
@@ -0,0 +1,8 @@
+name: Image Compare Accessible Slider for Responsive Media
+type: module
+description: Add an Image Compare Accessible Slider formatter to responsive media fields.
+package: Media
+core_version_requirement: ^10 || ^11
+dependencies:
+  - drupal:media
+  - image_compare_responsive:image_compare_responsive
diff --git a/modules/image_compare_media_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveMediaFormatter.php b/modules/image_compare_media_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveMediaFormatter.php
new file mode 100644
index 0000000..b92efba
--- /dev/null
+++ b/modules/image_compare_media_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveMediaFormatter.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Drupal\image_compare_media_responsive\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
+use Drupal\image\Plugin\Field\FieldType\ImageItem;
+use Drupal\image_compare_responsive\Plugin\Field\FieldFormatter\ImageCompareSliderResponsiveFormatter;
+use Drupal\image_compare_media\Plugin\Field\FieldFormatter\ImageCompareSliderMediaFormatterTrait;
+use Drupal\media\MediaInterface;
+
+/**
+ * Implementation of the 'Image Compare Slider' formatter for responsive media.
+ *
+ * @FieldFormatter(
+ *   id = "image_compare_slider_responsive_media",
+ *   label = @Translation("Image Compare Accessible Responsive Media Slider"),
+ *   field_types = {
+ *     "entity_reference"
+ *   }
+ * )
+ */
+class ImageCompareSliderResponsiveMediaFormatter extends ImageCompareSliderResponsiveFormatter {
+
+  use ImageCompareSliderMediaFormatterTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items, $langcode) {
+
+    $entities = parent::getEntitiesToView($items, $langcode);
+
+    // Early opt-out if the field is empty.
+    if (empty($entities)) {
+      return [];
+    }
+
+    $settings = $this->getSettings();
+
+    $cache_tags = [];
+
+    /** @var \Drupal\responsive_image\Entity\ResponsiveImageStyle $style */
+    $style = $this->getResponsiveImageStyle($settings, $cache_tags);
+    $fallback_style = $this->loadImageStyle($style->getFallbackImageStyle());
+    $breakpoints = array_reverse($this->breakpointManager->getBreakpointsByGroup($style->getBreakpointGroup()));
+
+    $options = $this->buildTemplateOptions($items, $cache_tags);
+
+    $images = [];
+
+    foreach ($entities as $media) {
+      $source_field_name = $media->getSource()
+        ->getConfiguration()['source_field'];
+      $image_item = $media->get($source_field_name)[0];
+      // Experimental handling of media entities referencing other entities.
+      // See: https://www.drupal.org/project/media_entity_reference.
+      if (($image_item instanceof EntityReferenceItem)
+        && !($image_item instanceof ImageItem)) {
+        $target_entity = $image_item->entity;
+        // We only handle referenced entities of type Media.
+        if ($target_entity instanceof MediaInterface) {
+          $target_source_field_name = $target_entity->getSource()
+            ->getConfiguration()['source_field'];
+          $image_item = $target_entity->get($target_source_field_name)[0];
+        }
+      }
+      // We only handle medias of type Image.
+      if ($image_item instanceof ImageItem) {
+        $image = $this->buildImageData($image_item, $media, $fallback_style);
+        if (!is_null($image)) {
+          $variables['uri'] = $image_item->entity->getFileUri();
+          $variables['width'] = $image_item->width;
+          $variables['height'] = $image_item->height;
+          foreach ($style->getKeyedImageStyleMappings() as $breakpoint_id => $multipliers) {
+            if (isset($breakpoints[$breakpoint_id])) {
+              $image['sources'][] = _responsive_image_build_source_attributes($variables, $breakpoints[$breakpoint_id], $multipliers);
+            }
+          }
+          $images[] = $image;
+          $cache_tags = Cache::mergeTags(
+            $cache_tags, $image_item->entity->getCacheTags());
+        }
+
+        if (count($images) == 2) {
+          break;
+        }
+      }
+    }
+
+    return $this->buildRenderArray($images, $options, $cache_tags);
+  }
+
+}
diff --git a/modules/image_compare_responsive/config/schema/image_compare_responsive.schema.yml b/modules/image_compare_responsive/config/schema/image_compare_responsive.schema.yml
new file mode 100644
index 0000000..17f424d
--- /dev/null
+++ b/modules/image_compare_responsive/config/schema/image_compare_responsive.schema.yml
@@ -0,0 +1,3 @@
+field.formatter.settings.image_compare_slider_responsive:
+  type: field.formatter.settings.image_compare_slider
+  label: Image Compare Accessible Slider formatter settings for responsive images
diff --git a/modules/image_compare_responsive/image_compare_responsive.info.yml b/modules/image_compare_responsive/image_compare_responsive.info.yml
new file mode 100644
index 0000000..197415f
--- /dev/null
+++ b/modules/image_compare_responsive/image_compare_responsive.info.yml
@@ -0,0 +1,8 @@
+name: Image Compare Accessible Slider for responsive image
+type: module
+description: Add an Image Compare Accessible Slider formatter to responsive image fields.
+package: Media
+core_version_requirement: ^10 || ^11
+dependencies:
+  - drupal:responsive_image
+  - image_compare:image_compare
diff --git a/modules/image_compare_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveFormatter.php b/modules/image_compare_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveFormatter.php
new file mode 100644
index 0000000..059c32a
--- /dev/null
+++ b/modules/image_compare_responsive/src/Plugin/Field/FieldFormatter/ImageCompareSliderResponsiveFormatter.php
@@ -0,0 +1,154 @@
+<?php
+
+namespace Drupal\image_compare_responsive\Plugin\Field\FieldFormatter;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Utility\Error;
+use Drupal\image_compare\Plugin\Field\FieldFormatter\ImageCompareSliderFormatterTrait;
+use Drupal\responsive_image\Plugin\Field\FieldFormatter\ResponsiveImageFormatter;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Implementation of the 'Image Compare Accessible Slider' responsive formatter.
+ *
+ * @FieldFormatter(
+ *   id = "image_compare_slider_responsive",
+ *   label = @Translation("Image Compare Accessible Slider Responsive"),
+ *   field_types = {
+ *     "image"
+ *   }
+ * )
+ */
+class ImageCompareSliderResponsiveFormatter extends ResponsiveImageFormatter {
+
+  use ImageCompareSliderFormatterTrait;
+
+  /**
+   * The file URL generator.
+   *
+   * @var \Drupal\Core\File\FileUrlGeneratorInterface
+   */
+  protected $fileUrlGenerator;
+
+  /**
+   * The breakpoint manager.
+   *
+   * @var \Drupal\breakpoint\BreakpointManagerInterface
+   */
+  protected $breakpointManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
+    $instance->entityFieldManager = $container->get('entity_field.manager');
+    $instance->loggerFactory = $container->get('logger.factory');
+    $instance->fileUrlGenerator = $container->get('file_url_generator');
+    $instance->breakpointManager = $container->get('breakpoint.manager');
+    return $instance;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function addImageStyleSelect(array &$elements, array $settings) {
+    $responsive_image_options = [];
+    $responsive_image_styles = $this->responsiveImageStyleStorage->loadMultiple();
+    uasort($responsive_image_styles, '\Drupal\responsive_image\Entity\ResponsiveImageStyle::sort');
+    if (!empty($responsive_image_styles)) {
+      foreach ($responsive_image_styles as $machine_name => $responsive_image_style) {
+        if ($responsive_image_style->hasImageStyleMappings()) {
+          $responsive_image_options[$machine_name] = $responsive_image_style->label();
+        }
+      }
+    }
+
+    $elements['image_style'] = [
+      '#title' => $this->t('Image style'),
+      '#type' => 'select',
+      '#default_value' => $this->getSetting('image_style'),
+      '#required' => TRUE,
+      '#options' => $responsive_image_options,
+      '#description' => [
+        '#markup' => $this->linkGenerator->generate($this->t('Configure Responsive Image Styles'), new Url('entity.responsive_image_style.collection')),
+        '#access' => $this->currentUser->hasPermission('administer responsive image styles'),
+      ],
+    ];
+  }
+
+  /**
+   * Load ResponsiveImageStyle entity to use for display.
+   *
+   * @param array $settings
+   *   Formatter settings.
+   * @param array $cache_tags
+   *   The cache tags to update with style tags.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|null
+   *   The ImageStyle object.
+   */
+  protected function getResponsiveImageStyle(array $settings, array &$cache_tags) {
+    try {
+      $responsive_image_style = $this->responsiveImageStyleStorage->load($settings['image_style']);
+      if ($responsive_image_style) {
+        $cache_tags = Cache::mergeTags($cache_tags, $responsive_image_style->getCacheTags());
+      }
+      return $responsive_image_style;
+    }
+    catch (\Exception $e) {
+      Error::logException($this->loggerFactory->get('image_compare'), $e);
+      return NULL;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function viewElements(FieldItemListInterface $items, $langcode) {
+
+    $files = parent::getEntitiesToView($items, $langcode);
+
+    // Early opt-out if the field is empty.
+    if (empty($files)) {
+      return [];
+    }
+
+    $settings = $this->getSettings();
+
+    $cache_tags = [];
+
+    /** @var \Drupal\responsive_image\Entity\ResponsiveImageStyle $style */
+    $style = $this->getResponsiveImageStyle($settings, $cache_tags);
+    $fallback_style = $this->loadImageStyle($style->getFallbackImageStyle());
+    $breakpoints = array_reverse($this->breakpointManager->getBreakpointsByGroup($style->getBreakpointGroup()));
+
+    $options = $this->buildTemplateOptions($items, $cache_tags);
+
+    $images = [];
+    foreach ($files as $file) {
+      $item = $file->_referringItem;
+      $image = $this->buildImageData($item, NULL, $fallback_style);
+      if (!is_null($image)) {
+        $variables['uri'] = $item->entity->getFileUri();
+        $variables['width'] = $item->width;
+        $variables['height'] = $item->height;
+        foreach ($style->getKeyedImageStyleMappings() as $breakpoint_id => $multipliers) {
+          if (isset($breakpoints[$breakpoint_id])) {
+            $image['sources'][] = _responsive_image_build_source_attributes($variables, $breakpoints[$breakpoint_id], $multipliers);
+          }
+        }
+        $images[] = $image;
+        $cache_tags = Cache::mergeTags($cache_tags, $file->getCacheTags());
+      }
+      if (count($images) == 2) {
+        break;
+      }
+    }
+
+    return $this->buildRenderArray($images, $options, $cache_tags);
+  }
+
+}
diff --git a/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatter.php b/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatter.php
index 406d809..a4241ec 100644
--- a/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatter.php
+++ b/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatter.php
@@ -2,24 +2,16 @@
 
 namespace Drupal\image_compare\Plugin\Field\FieldFormatter;
 
-use Drupal\Component\Utility\Html;
 use Drupal\Core\Cache\Cache;
-use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityFieldManagerInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\File\FileUrlGeneratorInterface;
-use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Link;
-use Drupal\Core\Logger\LoggerChannelFactoryInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
-use Drupal\Core\Utility\Error;
-use Drupal\image\ImageStyleInterface;
-use Drupal\image\Plugin\Field\FieldType\ImageItem;
-use Drupal\image_compare\ImageCompareSliderUtils as ICSUtils;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -35,19 +27,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  */
 class ImageCompareSliderFormatter extends ImageFormatter implements ContainerFactoryPluginInterface {
 
-  /**
-   * The entity field manager.
-   *
-   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
-   */
-  protected EntityFieldManagerInterface $entityFieldManager;
-
-  /**
-   * The logger factory.
-   *
-   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
-   */
-  protected LoggerChannelFactoryInterface $loggerFactory;
+  use ImageCompareSliderFormatterTrait;
 
   /**
    * Constructs an ImageFormatter object.
@@ -106,22 +86,7 @@ class ImageCompareSliderFormatter extends ImageFormatter implements ContainerFac
   /**
    * {@inheritdoc}
    */
-  public static function defaultSettings() {
-    return [
-      'image_style' => '',
-      'starting_position' => '',
-      'additional_options' => '',
-      'caption_field' => '',
-      'options_field' => '',
-    ] + parent::defaultSettings();
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function settingsForm(array $form, FormStateInterface $form_state) {
-    $settings = $this->getSettings();
-
+  protected function addImageStyleSelect(array &$elements, array $settings) {
     $image_styles = image_style_options(FALSE);
     $description_link = Link::fromTextAndUrl(
       $this->t('Configure Image Styles'),
@@ -137,73 +102,6 @@ class ImageCompareSliderFormatter extends ImageFormatter implements ContainerFac
         '#access' => $this->currentUser->hasPermission('administer image styles'),
       ],
     ];
-
-    $elements['starting_position'] = [
-      '#type' => 'textfield',
-      '#title' => $this->t('Starting position'),
-      '#description' => $this->t('The starting position is expressed as a percentage, ex: 70.  Defaults to 50 if empty.'),
-      '#default_value' => $settings['starting_position'],
-      '#placeholder' => 'Ex: 30',
-    ];
-
-    $elements['additional_options'] = [
-      '#type' => 'textarea',
-      '#rows' => 5,
-      '#title' => $this->t('Additional options'),
-      '#description' => $this->t('Allows to define additional options for the Image Compare Slider formatter.'),
-      '#default_value' => $settings['additional_options'],
-      '#placeholder' => 'Ex: label_text=Use the slider to control the visibility of the two images',
-    ];
-
-    $available_fields_for_options =
-      $this->getEntityFieldsByType(['string_long']);
-    $elements['options_field'] = [
-      '#title' => $this->t('Options field'),
-      '#type' => 'select',
-      '#default_value' => $this->getSetting('options_field'),
-      '#empty_option' => $this->t('None'),
-      '#options' => $available_fields_for_options,
-      '#description' => $this->t('Specify the parent entity field that we should use to get entity specific slider configuration options.'),
-    ];
-
-    $available_fields_for_caption =
-      $this->getRelatedEntityFieldsByType(['string', 'string_long']);
-    $elements['caption_field'] = [
-      '#title' => $this->t('Caption field'),
-      '#type' => 'select',
-      '#default_value' => $this->getSetting('caption_field'),
-      '#empty_option' => $this->t('None'),
-      '#options' => $available_fields_for_caption,
-      '#description' => $this->t('Specify the related entity field that we should use as a caption for slider images.'),
-    ];
-
-    return $elements;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function settingsSummary() {
-    $settings = $this->getSettings();
-    $summary = [];
-
-    if ($settings['image_style']) {
-      $summary[] = $this->t('Image style: @image_style', ['@image_style' => $settings['image_style']]);
-    }
-
-    if ($settings['starting_position']) {
-      $summary[] = $this->t('Starting position: @starting_position', ['@starting_position' => $settings['starting_position']]);
-    }
-
-    if (!empty($settings['options_field'])) {
-      $summary[] = $this->t('Options field: @field', ['@field' => $settings['options_field']]);
-    }
-
-    if (!empty($settings['caption_field'])) {
-      $summary[] = $this->t('Caption field: @field', ['@field' => $settings['caption_field']]);
-    }
-
-    return $summary;
   }
 
   /**
@@ -242,295 +140,4 @@ class ImageCompareSliderFormatter extends ImageFormatter implements ContainerFac
     return $this->buildRenderArray($images, $options, $cache_tags);
   }
 
-  /**
-   * Build options array for rendering template.
-   *
-   * @param \Drupal\Core\Field\FieldItemListInterface $items
-   *   List of image entities to use form rendering slider.
-   * @param array $cache_tags
-   *   List of cache tags to update if needed, depending on options source.
-   *
-   * @return array
-   *   Options for template.
-   */
-  protected function buildTemplateOptions(
-    FieldItemListInterface $items,
-    array &$cache_tags,
-  ): array {
-
-    $options = [];
-
-    $settings = $this->getSettings();
-
-    $options['starting_position'] = $settings['starting_position'] ?? '50';
-
-    $additional_options = ICSUtils::parseKeyValueString($settings['additional_options']);
-
-    if (!empty($settings['options_field'])) {
-      $parent_entity = $items->getParent()->getEntity();
-      if (isset($parent_entity)
-        && $parent_entity->hasField($settings['options_field'])) {
-        $field_items = $parent_entity->get($settings['options_field']);
-        if (!$field_items->isEmpty()) {
-          foreach ($field_items as $field_item) {
-            $entity_specific_options = ICSUtils::parseKeyValueString($field_item->value);
-            $cache_tags = Cache::mergeTags($cache_tags, $parent_entity->getCacheTags());
-            $options = array_merge($options, $entity_specific_options);
-          }
-        }
-      }
-    }
-
-    return array_merge($options, $additional_options);
-  }
-
-  /**
-   * Generate image data for the Twig template from an image item.
-   *
-   * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item
-   *   The image item.
-   * @param \Drupal\Core\Entity\ContentEntityInterface|null $caption_entity
-   *   The entity to get the image caption from.
-   * @param \Drupal\image\ImageStyleInterface|null $style
-   *   The image style to use for display.
-   *
-   * @return array|null
-   *   The image data for the Twig template or NULL if file entity missing.
-   */
-  protected function buildImageData(
-    $image_item,
-    $caption_entity,
-    ?ImageStyleInterface $style,
-  ): array|null {
-    $image_entity = $image_item->entity;
-    if ($image_entity !== NULL) {
-      $url = is_null($style)
-        ? $this->fileUrlGenerator->generateAbsoluteString($image_entity->getFileUri())
-        : $style->buildUrl($image_entity->getFileUri());
-
-      $alt = $image_item->get('alt')->getValue();
-
-      $image = [];
-      $image['url'] = $url;
-      $image['alt'] = $alt;
-
-      $settings = $this->getSettings();
-      if (!empty($settings['caption_field'])) {
-        $caption_field = $settings['caption_field'];
-        if (is_null($caption_entity)) {
-          $this->setCaptionFromImageItem(
-            $image_item, $caption_field, $image);
-        }
-        else {
-          if ($caption_entity->hasField($caption_field)) {
-            $this->setCaptionFromEntity(
-              $caption_entity, $caption_field, $image);
-          }
-          // Handle image alt and title cases.
-          else {
-            $this->setCaptionFromImageItem(
-              $image_item, $caption_field, $image);
-          }
-        }
-      }
-
-      return $image;
-    }
-    else {
-      return NULL;
-    }
-  }
-
-  /**
-   * Load ImageStyle entity to use for display.
-   *
-   * @param array $settings
-   *   Formatter settings.
-   *
-   * @return \Drupal\image\ImageStyleInterface|null
-   *   The ImageStyle object.
-   */
-  protected function getImageStyle(array $settings): ?ImageStyleInterface {
-    try {
-      return $this->imageStyleStorage->load($settings['image_style']);
-    }
-    catch (\Exception $e) {
-      Error::logException($this->loggerFactory->get('image_compare'), $e);
-      return NULL;
-    }
-  }
-
-  /**
-   * Builds the CIS template render array.
-   *
-   * @param array $images
-   *   Images array to add to the Image Compare slider.
-   * @param array $options
-   *   Configuration options for the Image Compare slider.
-   * @param array $cache_tags
-   *   Array for cache tags.
-   *
-   * @return array
-   *   The Image Compare Accessible Slider render array.
-   */
-  protected function buildRenderArray(
-    array $images,
-    array $options,
-    array $cache_tags,
-  ): array {
-    $id = Html::getUniqueId('image_compare_slider');
-    return [
-      '#theme' => 'image_compare_slider',
-      '#id' => $id,
-      '#images' => $images,
-      '#options' => $options,
-      '#cache' => [
-        'tags' => $cache_tags,
-      ],
-      '#attached' => [
-        'library' => [
-          'image_compare/image_compare',
-        ],
-      ],
-    ];
-  }
-
-  /**
-   * Build a list of all text fields of the related entity.
-   *
-   * @return array
-   *   The related entity text fields.
-   */
-  protected function getEntityFieldsByType(array $types): array {
-
-    $found_fields = [];
-
-    try {
-      $entity_type = $this->fieldDefinition->getTargetEntityTypeId();
-      $bundle = $this->fieldDefinition->getTargetBundle();
-
-      $fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
-
-      foreach ($fields as $field_name => $field_definition) {
-        // Check if the field type is in list of wanted types.
-        if (in_array($field_definition->getType(), $types)) {
-          $found_fields[$field_name] = $field_definition->getLabel();
-        }
-      }
-    }
-    catch (\Exception $e) {
-      Error::logException($this->loggerFactory->get('image_compare'), $e);
-    }
-
-    return $found_fields;
-  }
-
-  /**
-   * Build a list of all text fields of the related entity.
-   *
-   * @return array
-   *   The related entity text fields.
-   */
-  protected function getRelatedEntityFieldsByType(array $types): array {
-
-    $found_fields = [];
-
-    $field_type = $this->fieldDefinition->getType();
-    $field_settings = $this->fieldDefinition->getSettings();
-
-    if ($field_type === 'image') {
-      if ($field_settings['alt_field']) {
-        $found_fields['alt'] = $this->t('Image Alt');
-      }
-      if ($field_settings['title_field']) {
-        $found_fields['title'] = $this->t('Image Title');
-      }
-    }
-    else {
-      $found_fields['alt'] = $this->t('Image Alt (if available)');
-      $found_fields['title'] = $this->t('Image Title (if available)');
-    }
-
-    if ($field_type === 'entity_reference') {
-      try {
-        $handler_settings =
-          $this->fieldDefinition->getSetting('handler_settings');
-
-        $entity_type = $field_settings['target_type'];
-        $bundles = $handler_settings['target_bundles'];
-
-        foreach ($bundles as $bundle) {
-          $fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
-          foreach ($fields as $field_name => $field_definition) {
-            // Check if the field type is in list of wanted types.
-            if (in_array($field_definition->getType(), $types)) {
-              $found_fields[$field_name] =
-                $field_definition->getLabel() . ' (' . $field_name . ')';
-            }
-          }
-        }
-      }
-      catch (\Exception $e) {
-        Error::logException($this->loggerFactory->get('image_compare'), $e);
-      }
-    }
-
-    return $found_fields;
-  }
-
-  /**
-   * Defines slider image caption from referenced entity.
-   *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
-   *   The entity to get the caption from.
-   * @param string $caption_field
-   *   The caption field name.
-   * @param array $image
-   *   The image array to set the caption to.
-   */
-  protected function setCaptionFromEntity(
-    ContentEntityInterface $entity,
-    string $caption_field,
-    array &$image,
-  ): void {
-    try {
-      $caption_field = $entity->get($caption_field);
-      if (isset($caption_field[0])) {
-        $caption = $caption_field[0]->value;
-        if (!empty($caption)) {
-          $image['caption'] = $caption;
-        }
-      }
-    }
-    catch (\Exception $e) {
-      Error::logException($this->loggerFactory->get('image_compare'), $e);
-    }
-  }
-
-  /**
-   * Defines slider image caption from image item.
-   *
-   * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item
-   *   The entity to get the caption from.
-   * @param string $caption_field
-   *   The caption field name.
-   * @param array $image
-   *   The image array to set the caption to.
-   */
-  protected function setCaptionFromImageItem(
-    ImageItem $image_item,
-    string $caption_field,
-    array &$image,
-  ): void {
-    try {
-      $caption = $image_item->get($caption_field)->getValue();
-      if (!empty($caption)) {
-        $image['caption'] = $caption;
-      }
-    }
-    catch (\Exception $e) {
-      Error::logException($this->loggerFactory->get('image_compare'), $e);
-    }
-  }
-
 }
diff --git a/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatterTrait.php b/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatterTrait.php
new file mode 100644
index 0000000..853a459
--- /dev/null
+++ b/src/Plugin/Field/FieldFormatter/ImageCompareSliderFormatterTrait.php
@@ -0,0 +1,447 @@
+<?php
+
+namespace Drupal\image_compare\Plugin\Field\FieldFormatter;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityFieldManagerInterface;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Utility\Error;
+use Drupal\image\ImageStyleInterface;
+use Drupal\image\Plugin\Field\FieldType\ImageItem;
+use Drupal\image_compare\ImageCompareSliderUtils as ICSUtils;
+
+/**
+ * Provides a trait for the Image Compare Slider field formatter.
+ */
+trait ImageCompareSliderFormatterTrait {
+
+  /**
+   * The entity field manager.
+   *
+   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
+   */
+  protected EntityFieldManagerInterface $entityFieldManager;
+
+  /**
+   * The logger factory.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
+   */
+  protected $loggerFactory;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'image_style' => '',
+      'starting_position' => '',
+      'additional_options' => '',
+      'caption_field' => '',
+      'options_field' => '',
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * Adds an image style selector to the form elements.
+   *
+   * This method populates the provided form elements array with a dropdown
+   * allowing users to select an image style. It integrates the available image
+   * styles in the system and includes a link to configure image styles if the
+   * user has the necessary permissions.
+   *
+   * @param array &$elements
+   *   The form elements array to modify. This array will have a new
+   *   'image_style' key with the configured selector.
+   * @param array $settings
+   *   The current formatter settings, used to prepopulate the default selection
+   *   for the image style.
+   */
+  abstract protected function addImageStyleSelect(array &$elements, array $settings);
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $settings = $this->getSettings();
+
+    $elements = [];
+
+    $this->addImageStyleSelect($elements, $settings);
+
+    $elements['starting_position'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Starting position'),
+      '#description' => $this->t('The starting position is expressed as a percentage, ex: 70.  Defaults to 50 if empty.'),
+      '#default_value' => $settings['starting_position'],
+      '#placeholder' => 'Ex: 30',
+    ];
+
+    $elements['additional_options'] = [
+      '#type' => 'textarea',
+      '#rows' => 5,
+      '#title' => $this->t('Additional options'),
+      '#description' => $this->t('Allows to define additional options for the Image Compare Slider formatter.'),
+      '#default_value' => $settings['additional_options'],
+      '#placeholder' => 'Ex: label_text=Use the slider to control the visibility of the two images',
+    ];
+
+    $available_fields_for_options =
+      $this->getEntityFieldsByType(['string_long']);
+    $elements['options_field'] = [
+      '#title' => $this->t('Options field'),
+      '#type' => 'select',
+      '#default_value' => $this->getSetting('options_field'),
+      '#empty_option' => $this->t('None'),
+      '#options' => $available_fields_for_options,
+      '#description' => $this->t('Specify the parent entity field that we should use to get entity specific slider configuration options.'),
+    ];
+
+    $available_fields_for_caption =
+      $this->getRelatedEntityFieldsByType(['string', 'string_long']);
+    $elements['caption_field'] = [
+      '#title' => $this->t('Caption field'),
+      '#type' => 'select',
+      '#default_value' => $this->getSetting('caption_field'),
+      '#empty_option' => $this->t('None'),
+      '#options' => $available_fields_for_caption,
+      '#description' => $this->t('Specify the related entity field that we should use as a caption for slider images.'),
+    ];
+
+    return $elements;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $settings = $this->getSettings();
+    $summary = [];
+
+    if ($settings['image_style']) {
+      $summary[] = $this->t('Image style: @image_style', ['@image_style' => $settings['image_style']]);
+    }
+
+    if ($settings['starting_position']) {
+      $summary[] = $this->t('Starting position: @starting_position', ['@starting_position' => $settings['starting_position']]);
+    }
+
+    if (!empty($settings['options_field'])) {
+      $summary[] = $this->t('Options field: @field', ['@field' => $settings['options_field']]);
+    }
+
+    if (!empty($settings['caption_field'])) {
+      $summary[] = $this->t('Caption field: @field', ['@field' => $settings['caption_field']]);
+    }
+
+    return $summary;
+  }
+
+  /**
+   * Build options array for rendering template.
+   *
+   * @param \Drupal\Core\Field\FieldItemListInterface $items
+   *   List of image entities to use form rendering slider.
+   * @param array $cache_tags
+   *   List of cache tags to update if needed, depending on options source.
+   *
+   * @return array
+   *   Options for template.
+   */
+  protected function buildTemplateOptions(
+    FieldItemListInterface $items,
+    array &$cache_tags,
+  ): array {
+
+    $options = [];
+
+    $settings = $this->getSettings();
+
+    $options['starting_position'] = $settings['starting_position'] ?? '50';
+
+    $additional_options = ICSUtils::parseKeyValueString($settings['additional_options']);
+
+    if (!empty($settings['options_field'])) {
+      $parent_entity = $items->getParent()->getEntity();
+      if (isset($parent_entity)
+        && $parent_entity->hasField($settings['options_field'])) {
+        $field_items = $parent_entity->get($settings['options_field']);
+        if (!$field_items->isEmpty()) {
+          foreach ($field_items as $field_item) {
+            $entity_specific_options = ICSUtils::parseKeyValueString($field_item->value);
+            $cache_tags = Cache::mergeTags($cache_tags, $parent_entity->getCacheTags());
+            $options = array_merge($options, $entity_specific_options);
+          }
+        }
+      }
+    }
+
+    return array_merge($options, $additional_options);
+  }
+
+  /**
+   * Generate image data for the Twig template from an image item.
+   *
+   * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item
+   *   The image item.
+   * @param \Drupal\Core\Entity\ContentEntityInterface|null $caption_entity
+   *   The entity to get the image caption from.
+   * @param \Drupal\image\ImageStyleInterface|null $style
+   *   The image style to use for display.
+   *
+   * @return array|null
+   *   The image data for the Twig template or NULL if file entity missing.
+   */
+  protected function buildImageData(
+    $image_item,
+    $caption_entity,
+    ?ImageStyleInterface $style,
+  ): array|null {
+    $image_entity = $image_item->entity;
+    if ($image_entity !== NULL) {
+      $url = is_null($style)
+        ? $this->fileUrlGenerator->generateAbsoluteString($image_entity->getFileUri())
+        : $style->buildUrl($image_entity->getFileUri());
+
+      $alt = $image_item->get('alt')->getValue();
+
+      $image = [];
+      $image['url'] = $url;
+      $image['alt'] = $alt;
+
+      $settings = $this->getSettings();
+      if (!empty($settings['caption_field'])) {
+        $caption_field = $settings['caption_field'];
+        if (is_null($caption_entity)) {
+          $this->setCaptionFromImageItem(
+            $image_item, $caption_field, $image);
+        }
+        else {
+          if ($caption_entity->hasField($caption_field)) {
+            $this->setCaptionFromEntity(
+              $caption_entity, $caption_field, $image);
+          }
+          // Handle image alt and title cases.
+          else {
+            $this->setCaptionFromImageItem(
+              $image_item, $caption_field, $image);
+          }
+        }
+      }
+
+      return $image;
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  /**
+   * Load ImageStyle entity to use for display.
+   *
+   * @param array $settings
+   *   Formatter settings.
+   *
+   * @return \Drupal\image\ImageStyleInterface|null
+   *   The ImageStyle object.
+   */
+  protected function getImageStyle(array $settings): ?ImageStyleInterface {
+    return $this->loadImageStyle($settings['image_style']);
+  }
+
+  /**
+   * Load ImageStyle entity to use for display.
+   *
+   * @param string $id
+   *   Style identifier.
+   *
+   * @return \Drupal\image\ImageStyleInterface|null
+   *   The ImageStyle object.
+   */
+  protected function loadImageStyle(string $id): ?ImageStyleInterface {
+    try {
+      return $this->imageStyleStorage->load($id);
+    }
+    catch (\Exception $e) {
+      Error::logException($this->loggerFactory->get('image_compare'), $e);
+      return NULL;
+    }
+  }
+
+  /**
+   * Builds the CIS template render array.
+   *
+   * @param array $images
+   *   Images array to add to the Image Compare slider.
+   * @param array $options
+   *   Configuration options for the Image Compare slider.
+   * @param array $cache_tags
+   *   Array for cache tags.
+   *
+   * @return array
+   *   The Image Compare Accessible Slider render array.
+   */
+  protected function buildRenderArray(
+    array $images,
+    array $options,
+    array $cache_tags,
+  ): array {
+    $id = Html::getUniqueId('image_compare_slider');
+    return [
+      '#theme' => 'image_compare_slider',
+      '#id' => $id,
+      '#images' => $images,
+      '#options' => $options,
+      '#cache' => [
+        'tags' => $cache_tags,
+      ],
+      '#attached' => [
+        'library' => [
+          'image_compare/image_compare',
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * Build a list of all text fields of the related entity.
+   *
+   * @return array
+   *   The related entity text fields.
+   */
+  protected function getEntityFieldsByType(array $types): array {
+
+    $found_fields = [];
+
+    try {
+      $entity_type = $this->fieldDefinition->getTargetEntityTypeId();
+      $bundle = $this->fieldDefinition->getTargetBundle();
+
+      $fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
+
+      foreach ($fields as $field_name => $field_definition) {
+        // Check if the field type is in list of wanted types.
+        if (in_array($field_definition->getType(), $types)) {
+          $found_fields[$field_name] = $field_definition->getLabel();
+        }
+      }
+    }
+    catch (\Exception $e) {
+      Error::logException($this->loggerFactory->get('image_compare'), $e);
+    }
+
+    return $found_fields;
+  }
+
+  /**
+   * Build a list of all text fields of the related entity.
+   *
+   * @return array
+   *   The related entity text fields.
+   */
+  protected function getRelatedEntityFieldsByType(array $types): array {
+
+    $found_fields = [];
+
+    $field_type = $this->fieldDefinition->getType();
+    $field_settings = $this->fieldDefinition->getSettings();
+
+    if ($field_type === 'image') {
+      if ($field_settings['alt_field']) {
+        $found_fields['alt'] = $this->t('Image Alt');
+      }
+      if ($field_settings['title_field']) {
+        $found_fields['title'] = $this->t('Image Title');
+      }
+    }
+    else {
+      $found_fields['alt'] = $this->t('Image Alt (if available)');
+      $found_fields['title'] = $this->t('Image Title (if available)');
+    }
+
+    if ($field_type === 'entity_reference') {
+      try {
+        $handler_settings =
+          $this->fieldDefinition->getSetting('handler_settings');
+
+        $entity_type = $field_settings['target_type'];
+        $bundles = $handler_settings['target_bundles'];
+
+        foreach ($bundles as $bundle) {
+          $fields = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
+          foreach ($fields as $field_name => $field_definition) {
+            // Check if the field type is in list of wanted types.
+            if (in_array($field_definition->getType(), $types)) {
+              $found_fields[$field_name] =
+                $field_definition->getLabel() . ' (' . $field_name . ')';
+            }
+          }
+        }
+      }
+      catch (\Exception $e) {
+        Error::logException($this->loggerFactory->get('image_compare'), $e);
+      }
+    }
+
+    return $found_fields;
+  }
+
+  /**
+   * Defines slider image caption from referenced entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to get the caption from.
+   * @param string $caption_field
+   *   The caption field name.
+   * @param array $image
+   *   The image array to set the caption to.
+   */
+  protected function setCaptionFromEntity(
+    ContentEntityInterface $entity,
+    string $caption_field,
+    array &$image,
+  ): void {
+    try {
+      $caption_field = $entity->get($caption_field);
+      if (isset($caption_field[0])) {
+        $caption = $caption_field[0]->value;
+        if (!empty($caption)) {
+          $image['caption'] = $caption;
+        }
+      }
+    }
+    catch (\Exception $e) {
+      Error::logException($this->loggerFactory->get('image_compare'), $e);
+    }
+  }
+
+  /**
+   * Defines slider image caption from image item.
+   *
+   * @param \Drupal\image\Plugin\Field\FieldType\ImageItem $image_item
+   *   The entity to get the caption from.
+   * @param string $caption_field
+   *   The caption field name.
+   * @param array $image
+   *   The image array to set the caption to.
+   */
+  protected function setCaptionFromImageItem(
+    ImageItem $image_item,
+    string $caption_field,
+    array &$image,
+  ): void {
+    try {
+      $caption = $image_item->get($caption_field)->getValue();
+      if (!empty($caption)) {
+        $image['caption'] = $caption;
+      }
+    }
+    catch (\Exception $e) {
+      Error::logException($this->loggerFactory->get('image_compare'), $e);
+    }
+  }
+
+}
diff --git a/templates/image-compare-slider.html.twig b/templates/image-compare-slider.html.twig
index 09311a3..dfb15b5 100644
--- a/templates/image-compare-slider.html.twig
+++ b/templates/image-compare-slider.html.twig
@@ -1,28 +1,42 @@
-<image-compare label-text="{{ options.label_text }}" id="{{ id }}"{%
-  if options.starting_position|length %} start="{{ options.starting_position }}"{% endif %}{%
-  if options.step|length %} step="{{ options.step }}"{% endif %}{%
-  if options.keyboard_step|length %} keyboard_step="{{ options.keyboard_step }}"{% endif %}>
+<image-compare label-text="{{ options.label_text }}"
+               id="{{ id }}"{% if options.starting_position|length %} start="{{ options.starting_position }}"{% endif %}{% if options.step|length %} step="{{ options.step }}"{% endif %}{% if options.keyboard_step|length %} keyboard_step="{{ options.keyboard_step }}"{% endif %}>
   {% if images|length >= 2 %}
     {% if options.show_captions == 'false' %}
-      <img slot="image-1" src="{{ images[0].url }}" alt="{{ images[0].alt }}">
-      <img slot="image-2" src="{{ images[1].url }}" alt="{{ images[1].alt }}">
+      <picture slot="image-1">
+        {% for source in images[0].sources %}
+          <source{{ source }}/>
+        {% endfor %}
+        <img src="{{ images[0].url }}" alt="{{ images[0].alt }}">
+      </picture>
+      <picture slot="image-2">
+        {% for source in images[1].sources %}
+          <source{{ source }}/>
+        {% endfor %}
+        <img src="{{ images[1].url }}" alt="{{ images[1].alt }}">
+      </picture>
     {% else %}
-      {% if images[0].caption|length %}
-        <figure slot="image-1">
+      <figure slot="image-1">
+        <picture>
+          {% for source in images[0].sources %}
+            <source{{ source }}/>
+          {% endfor %}
           <img src="{{ images[0].url }}" alt="{{ images[0].alt }}">
+        </picture>
+        {% if images[0].caption|length %}
           <figcaption class="image-label">{{ images[0].caption }}</figcaption>
-        </figure>
-      {% else %}
-        <img slot="image-1" src="{{ images[0].url }}" alt="{{ images[0].alt }}">
-      {% endif %}
-      {% if images[1].caption|length %}
-        <figure slot="image-2">
+        {% endif %}
+      </figure>
+      <figure slot="image-2">
+        <picture>
+          {% for source in images[1].sources %}
+            <source{{ source }}/>
+          {% endfor %}
           <img src="{{ images[1].url }}" alt="{{ images[1].alt }}">
+        </picture>
+        {% if images[1].caption|length %}
           <figcaption class="image-label">{{ images[1].caption }}</figcaption>
-        </figure>
-      {% else %}
-        <img slot="image-2" src="{{ images[1].url }}" alt="{{ images[1].alt }}">
-      {% endif %}
+        {% endif %}
+      </figure>
     {% endif %}
   {% endif %}
 </image-compare>
-- 
GitLab