Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
DesignTokensPanel
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 10
420
0.00% covered (danger)
0.00%
0 / 1
 create
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildForm
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 validateForm
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 alterElement
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 onAttachToRoot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onAttachToSlot
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onActive
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onDelete
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isApplicable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 filterValues
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Plugin\display_builder\Island;
6
7use Drupal\Core\Extension\ModuleHandlerInterface;
8use Drupal\Core\Form\FormStateInterface;
9use Drupal\Core\StringTranslation\TranslatableMarkup;
10use Drupal\display_builder\Attribute\Island;
11use Drupal\display_builder\InstanceInterface;
12use Drupal\display_builder\Island\IslandPluginBase;
13use Drupal\display_builder\Island\IslandType;
14use Drupal\display_builder\Island\IslandWithFormInterface;
15use Drupal\display_builder\Island\IslandWithFormTrait;
16use Drupal\display_builder\Island\RenderableAltererInterface;
17use Drupal\ui_skins\CssVariable\CssVariablePluginManagerInterface;
18use Drupal\ui_skins\UiSkinsUtility;
19use Symfony\Component\DependencyInjection\ContainerInterface;
20
21/**
22 * Skins island plugin implementation.
23 *
24 * @todo must move to UI Styles module.
25 */
26#[Island(
27  id: 'tokens',
28  label: new TranslatableMarkup('Design tokens (CSS variables)'),
29  description: new TranslatableMarkup('Override CSS variables for the active component or block.'),
30  type: IslandType::Contextual,
31  modules: ['ui_skins'],
32)]
33class DesignTokensPanel extends IslandPluginBase implements IslandWithFormInterface, RenderableAltererInterface {
34
35  use IslandWithFormTrait;
36
37  /**
38   * The UI Skins CSS variables manager.
39   */
40  protected CssVariablePluginManagerInterface $cssVariablePluginManager;
41
42  /**
43   * The module handler.
44   */
45  protected ModuleHandlerInterface $moduleHandler;
46
47  /**
48   * {@inheritdoc}
49   */
50  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
51    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
52    $instance->cssVariablePluginManager = $container->get('plugin.manager.ui_skins.css_variable');
53    $instance->moduleHandler = $container->get('module_handler');
54
55    return $instance;
56  }
57
58  /**
59   * {@inheritdoc}
60   */
61  public function buildForm(array &$form, FormStateInterface $form_state): void {
62    $data = $form_state->getBuildInfo()['args'][0];
63    $instance = $data['instance'] ?? [];
64    $grouped_plugin_definitions = $this->cssVariablePluginManager->getGroupedDefinitions();
65
66    if (empty($grouped_plugin_definitions)) {
67      return;
68    }
69
70    foreach ($grouped_plugin_definitions as $group => $definitions) {
71      $variables = [];
72
73      foreach ($definitions as $definition_id => $definition) {
74        $default = $definition->getDefaultValues();
75
76        if (!isset($default[':root'])) {
77          continue;
78        }
79        $variables[$definition_id] = [
80          '#type' => $definition->getType(),
81          '#title' => $definition->getLabel(),
82          '#default_value' => $instance[$definition_id] ?? $default[':root'] ?? '',
83        ];
84      }
85
86      if (!empty($variables)) {
87        $variables['#type'] = 'details';
88        $variables['#title'] = $group;
89        $form[$group] = $variables;
90      }
91    }
92  }
93
94  /**
95   * {@inheritdoc}
96   */
97  public function validateForm(array &$form, FormStateInterface $form_state): void {
98    $variables = $form_state->getValues();
99    $variables = $this->filterValues($variables);
100    $variables = $form_state->setValues($variables);
101    // Those two lines are necessary to prevent the form from being rebuilt.
102    // if rebuilt, the form state values will have both the computed ones
103    // and the raw ones (wrapper key and values).
104    $form_state->setRebuild(FALSE);
105    $form_state->setExecuted();
106  }
107
108  /**
109   * {@inheritdoc}
110   */
111  public function alterElement(array $element, array $data = []): array {
112    $inline_css = [];
113
114    foreach ($data as $variable => $value) {
115      $variable = UiSkinsUtility::getCssVariableName($variable);
116      $inline_css[] = "{$variable}{$value};";
117    }
118    $element['#attributes']['style'] = \implode(' ', $inline_css);
119
120    return $element;
121  }
122
123  /**
124   * {@inheritdoc}
125   */
126  public function onAttachToRoot(InstanceInterface $instance, string $node_id): array {
127    return $this->reloadWithNodeData($instance, $node_id);
128  }
129
130  /**
131   * {@inheritdoc}
132   */
133  public function onAttachToSlot(InstanceInterface $instance, string $node_id, string $parent_id): array {
134    return $this->reloadWithNodeData($instance, $node_id);
135  }
136
137  /**
138   * {@inheritdoc}
139   */
140  public function onActive(InstanceInterface $instance, array $data): array {
141    return $this->reloadWithLocalData($instance, $data);
142  }
143
144  /**
145   * {@inheritdoc}
146   */
147  public function onDelete(InstanceInterface $instance, ?string $parent_id): array {
148    return $this->reloadWithLocalData($instance, []);
149  }
150
151  /**
152   * {@inheritdoc}
153   */
154  public function isApplicable(): bool {
155    return parent::isApplicable() && $this->moduleHandler->moduleExists('ui_skins');
156  }
157
158  /**
159   * Extract values to save in configuration.
160   *
161   * @param array $variables
162   *   The variables to filter.
163   *
164   * @return array
165   *   An array of filtered variables.
166   */
167  protected function filterValues(array $variables): array {
168    $cleaned_variables = [];
169
170    foreach ($variables as $variable => $value) {
171      /** @var \Drupal\ui_skins\Definition\CssVariableDefinition $plugin_definition */
172      $plugin_definition = $this->cssVariablePluginManager->getDefinition($variable, FALSE);
173
174      if (!$plugin_definition) {
175        continue;
176      }
177
178      // Remove values that do not differ from the default values of the plugin.
179      if ($plugin_definition->isDefaultScopeValue(':root', $value)) {
180        continue;
181      }
182
183      $cleaned_variables[$variable] = $value;
184    }
185
186    return $cleaned_variables;
187  }
188
189}