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