diff --git a/modules/ui_patterns_field/src/Plugin/Derivative/UIPatternsSourceFieldPropertySourceDeriver.php b/modules/ui_patterns_field/src/Plugin/Derivative/UIPatternsSourceFieldPropertySourceDeriver.php new file mode 100644 index 0000000000000000000000000000000000000000..cb9d6709130f2732445bf5f698a726b6671f15fb --- /dev/null +++ b/modules/ui_patterns_field/src/Plugin/Derivative/UIPatternsSourceFieldPropertySourceDeriver.php @@ -0,0 +1,40 @@ +<?php + +namespace Drupal\ui_patterns_field\Plugin\Derivative; + +use Drupal\Component\Plugin\PluginBase; +use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\ui_patterns\Plugin\Derivative\EntityFieldSourceDeriverBase; + +/** + * Provides Plugin for every field property of type ui_patterns_source. + */ +class UIPatternsSourceFieldPropertySourceDeriver extends EntityFieldSourceDeriverBase { + + /** + * {@inheritdoc} + */ + protected function getDerivativeDefinitionsForEntityStorageField(string $entity_type_id, string $field_name, array $base_plugin_derivative): void { + $id = implode(PluginBase::DERIVATIVE_SEPARATOR, [ + $entity_type_id, + $field_name, + ]); + $field_type = $this->entityFieldsMetadata[$entity_type_id]["field_storages"][$field_name]["metadata"]["type"]; + if ($field_type === "ui_patterns_source") { + $this->derivatives[$id] = array_merge( + $base_plugin_derivative, + [ + "id" => $id, + "tags" => array_merge($base_plugin_derivative["tags"], ["ui_patterns_source"]), + ]); + $field_storage_data = $this->entityFieldsMetadata[$entity_type_id]["field_storages"][$field_name]; + $bundle_context_for_properties = (new ContextDefinition('string')) + ->setRequired() + ->setLabel("Bundle") + ->addConstraint('AllowedValues', array_merge($field_storage_data["bundles"] ?? [], [""])); + $this->derivatives[$id]["context_definitions"]["bundle"] = $bundle_context_for_properties; + } + + } + +} diff --git a/modules/ui_patterns_field/src/Plugin/Field/FieldType/SourceValueItem.php b/modules/ui_patterns_field/src/Plugin/Field/FieldType/SourceValueItem.php new file mode 100644 index 0000000000000000000000000000000000000000..315b3cfafb397cfb9ef695adf71bea3db2788934 --- /dev/null +++ b/modules/ui_patterns_field/src/Plugin/Field/FieldType/SourceValueItem.php @@ -0,0 +1,54 @@ +<?php + +namespace Drupal\ui_patterns_field\Plugin\Field\FieldType; + +use Drupal\Core\Field\Attribute\FieldType; +use Drupal\Core\Field\FieldStorageDefinitionInterface; +use Drupal\Core\Field\MapFieldItemList; +use Drupal\Core\Field\Plugin\Field\FieldType\MapItem; +use Drupal\Core\StringTranslation\TranslatableMarkup; + +/** + * Field Type to store UI Patterns source configuration. + * + * @property string $source_id + * @property string $source + */ +#[FieldType( + id: "ui_patterns_source", + label: new TranslatableMarkup("Source (UI Patterns)"), + description: new TranslatableMarkup("Store an UI Patterns source configuration"), + default_widget: "ui_patterns_source", + default_formatter: "ui_patterns_source", + list_class: MapFieldItemList::class, +)] +class SourceValueItem extends MapItem { + + /** + * {@inheritdoc} + */ + public static function mainPropertyName() { + return 'source_id'; + } + + /** + * {@inheritdoc} + */ + public static function schema(FieldStorageDefinitionInterface $field_definition) { + return [ + 'columns' => [ + 'source_id' => [ + 'type' => 'varchar_ascii', + 'length' => 255, + + ], + 'source' => [ + 'type' => 'blob', + 'size' => 'big', + 'serialize' => TRUE, + ], + ], + ]; + } + +} diff --git a/modules/ui_patterns_field/src/Plugin/Field/FieldWidget/SourceComponentWidget.php b/modules/ui_patterns_field/src/Plugin/Field/FieldWidget/SourceComponentWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..04989affeee3e0a2f2ee9262491ea24557edaf08 --- /dev/null +++ b/modules/ui_patterns_field/src/Plugin/Field/FieldWidget/SourceComponentWidget.php @@ -0,0 +1,331 @@ +<?php + +namespace Drupal\ui_patterns_field\Plugin\Field\FieldWidget; + +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Field\Attribute\FieldWidget; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\WidgetBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\Plugin\Context\EntityContext; +use Drupal\Core\Render\Component\Exception\ComponentNotFoundException; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\Theme\ComponentPluginManager; +use Drupal\ui_patterns\Form\ComponentFormBuilderTrait; +use Drupal\ui_patterns\Plugin\Context\RequirementsContext; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\ui_patterns\ComponentPluginManager as UIPatternsComponentPluginManager; + +/** + * A widget to display the UI Patterns configuration form. + * + * @internal + * Plugin classes are internal. + */ +#[FieldWidget( + id: 'ui_patterns_source_component', + label: new TranslatableMarkup('Components only (UI Patterns)'), + description: new TranslatableMarkup('Widget to edit an UI Patterns source field, but configure only the Component source for a slot.'), + field_types: ['ui_patterns_source'], +)] +class SourceComponentWidget extends WidgetBase { + + use ComponentFormBuilderTrait; + + /** + * The component plugin manager. + * + * @var \Drupal\Core\Theme\ComponentPluginManager + */ + protected ComponentPluginManager $componentPluginManager; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->componentPluginManager = $container->get('plugin.manager.sdc'); + return $instance; + } + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + return [ + 'component_id' => NULL, + 'hide_slots' => TRUE, + 'prop_sources' => NULL, + 'prop_filter_enable' => FALSE, + 'selection' => [], + ] + parent::defaultSettings(); + } + + /** + * Get the widget settings. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * Form state. + * + * @return array + * Widget settings. + */ + protected function getWidgetSettings(FormStateInterface $form_state) : array { + $field_name = $this->fieldDefinition->getName(); + $array_parents = ["fields", $field_name, "settings_edit_form", "settings"]; + $full_form_state_values = $form_state->getValues(); + $current_settings = &NestedArray::getValue($full_form_state_values, $array_parents); + return array_merge($this->getSettings(), $current_settings ?? []); + } + + /** + * Get the component options. + * + * @return array + * Component options. + */ + protected function getComponentOptions() : array { + $definitions = []; + if ($this->componentPluginManager instanceof UIPatternsComponentPluginManager) { + $definitions = $this->componentPluginManager->getGroupedDefinitions(); + } + $options = []; + foreach ($definitions as $group_id => $group) { + foreach ($group as $component_id => $definition) { + $options[$group_id][$component_id] = $definition['annotated_name']; + } + } + return $options; + } + + /** + * {@inheritdoc} + */ + public function settingsForm(array $form, FormStateInterface $form_state) { + $options = $this->getComponentOptions(); + $field_name = $this->fieldDefinition->getName(); + $settings = $this->getWidgetSettings($form_state); + + $wrapper_id = 'component-props-selection'; + $element = []; + $element['component_id'] = [ + '#type' => 'select', + '#title' => $this->t('Component ID'), + '#default_value' => $this->getSetting('component_id'), + '#required' => FALSE, + '#options' => $options, + '#ajax' => [ + 'callback' => [static::class, 'changeSelectorFormChangeAjax'], + 'wrapper' => $wrapper_id, + 'effect' => 'fade', + ], + '#executes_submit_callback' => FALSE, + '#empty_value' => '', + '#empty_option' => t('- None -'), + ]; + $element['hide_slots'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Hide slots'), + '#default_value' => $settings['hide_slots'] ?? TRUE, + '#required' => FALSE, + ]; + $prop_sources = $settings['prop_sources'] ?? NULL; + if ($prop_sources === NULL) { + $prop_sources = ''; + } + $element['prop_sources'] = [ + '#type' => 'select', + '#title' => $this->t('Prop sources'), + '#options' => [ + '' => $this->t('Display all'), + 'widgets' => $this->t('Only widgets'), + 'default' => $this->t('Only default'), + ], + '#default_value' => $prop_sources, + '#required' => FALSE, + ]; + $element['prop_filter_enable'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Show only selected props'), + '#default_value' => $settings['prop_filter_enable'] ?? FALSE, + '#required' => FALSE, + '#states' => [ + 'visible' => [ + [ + ":input[name='fields[{$field_name}][settings_edit_form][settings][component_id]']" => ['!value' => ''], + ], + ], + ], + ]; + + $element["selection"] = [ + "#type" => "container", + "#attributes" => [ + "id" => $wrapper_id, + ], + "#tree" => TRUE, + + ]; + $component_id_selected = $settings["component_id"] ?? ''; + if (!empty($component_id_selected)) { + $selection = $settings["selection"] ?? []; + try { + $component_selected = $this->componentPluginManager->find($component_id_selected); + $props = $component_selected->metadata->schema['properties']; + $options = ["variant" => t("Variant")]; + foreach ($props as $prop_id => $prop) { + if ($prop_id === 'variant') { + continue; + } + $propTitle = $prop['title'] ?? ''; + $options[$prop_id] = empty($propTitle) ? $prop_id : $propTitle; + } + $element["selection"]['prop_filter'] = [ + "#type" => "select", + '#limit_validation_errors' => [], + "#multiple" => TRUE, + "#options" => $options, + "#default_value" => $selection['prop_filter'] ?? [], + '#states' => [ + 'visible' => [ + [ + ":input[name='fields[{$field_name}][settings_edit_form][settings][prop_filter_enable]']" => ['checked' => TRUE], + ":input[name='fields[{$field_name}][settings_edit_form][settings][component_id]']" => ['!value' => ''], + ], + ], + ], + ]; + } + catch (ComponentNotFoundException $e) { + + } + } + return $element; + } + + /** + * Ajax callback for component selector change. + */ + public static function changeSelectorFormChangeAjax( + array $form, + FormStateInterface $form_state, + ) : array { + $parents = $form_state->getTriggeringElement()['#array_parents']; + $sub_form_parents = array_merge(array_slice($parents, 0, -1), ["selection"]); + $sub_form = NestedArray::getValue($form, $sub_form_parents); + $form_state->setRebuild(); + return $sub_form; + } + + /** + * {@inheritdoc} + */ + public function settingsSummary() { + $summary = []; + if ($this->getSetting('component_id') !== NULL) { + $summary[] = $this->t('Component Id: @component', ['@component' => $this->getSetting('component_id')]); + } + if (!($this->getSetting('hide_slots') ?? TRUE)) { + $summary[] = $this->t('Hide slots'); + } + $selection = $this->getSetting('prop_filter_enable') ?? []; + $props_selection = $selection['props'] ?? NULL; + if (is_array($props_selection)) { + $summary[] = $this->t('Only selected props: @props', ['@props' => implode(",", $props_selection)]); + } + + return $summary; + } + + /** + * {@inheritdoc} + */ + public function form(FieldItemListInterface $items, array &$form, FormStateInterface $form_state, $get_delta = NULL) { + $field_name = $this->fieldDefinition->getName(); + $parents = $form['#parents']; + if (!static::getWidgetState($parents, $field_name, $form_state)) { + $field_state = [ + 'items_count' => count($items) - 1, + 'array_parents' => [], + ]; + static::setWidgetState($parents, $field_name, $form_state, $field_state); + } + return parent::form($items, $form, $form_state, $get_delta); + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $item_delta_value = $items[$delta]->getValue() ?? []; + $source_id = $item_delta_value['source_id'] ?? 'component'; + $field_name = $this->fieldDefinition->getName(); + $element['#parents'] = array_merge($element['#field_parents'] ?? [], [$field_name, $delta]); + $settings = $this->getSettings() ?? []; + $component_id = $settings['component_id'] ?? NULL; + if (empty($component_id)) { + $component_id = NULL; + } + if ($source_id !== 'component') { + // The widget can only deal with component sources. + // To make sure no data will be overwritten we disable the widget. + $element['#access'] = FALSE; + return $element; + } + $source_data = $item_delta_value["source"] ?? []; + $component_default_value = $source_data['component'] ?? []; + $component_in_data = $component_default_value['component_id'] ?? NULL; + if (!isset($component_default_value['component_id'])) { + $component_default_value['component_id'] = $component_id; + } + elseif ($component_id && $component_in_data && $component_in_data !== $component_id) { + $element['#access'] = FALSE; + return $element; + } + $contexts = $this->getComponentSourceContexts($items); + $element['source'] = [ + '#type' => 'container', + '#tree' => TRUE, + ]; + $selection = $settings["selection"] ?? []; + $prop_sources = $settings['prop_sources'] ?? ''; + $wrap = ($prop_sources !== 'default'); + $hide_slots = $settings['hide_slots'] ?? TRUE; + $element['source']["component"] = array_merge( + $this->buildComponentsForm($form_state, $contexts, $component_id), + [ + '#render_slots' => !$hide_slots, + '#tag_filter' => ((bool) ($settings['only_widgets'] ?? TRUE)) ? ["widget" => TRUE] : [], + '#default_value' => $component_default_value, + '#wrap' => $wrap, + '#render_headings' => !$hide_slots, + '#render_sources' => $wrap, + '#prop_filter' => ($this->getSetting('prop_filter_enable') ?? FALSE) ? $selection['prop_filter'] ?? NULL : NULL, + ] + ); + $element['source_id'] = ['#type' => 'hidden', '#value' => $source_id]; + return $element; + } + + /** + * Set the context. + * + * @param \Drupal\Core\Field\FieldItemListInterface|null $items + * Field items. + * + * @return array + * Source contexts. + */ + protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array { + $contexts = []; + if ($entity = $items?->getEntity()) { + $contexts['entity'] = EntityContext::fromEntity($entity); + $contexts['bundle'] = new Context(ContextDefinition::create('string'), $contexts["entity"]->getContextValue()->bundle() ?? ""); + } + $contexts = RequirementsContext::addToContext(["field_granularity:item"], $contexts); + return $contexts; + } + +} diff --git a/modules/ui_patterns_field/src/Plugin/Field/FieldWidget/SourceWidget.php b/modules/ui_patterns_field/src/Plugin/Field/FieldWidget/SourceWidget.php new file mode 100644 index 0000000000000000000000000000000000000000..d1bad2856514f020e4e13190e3e47095371eaf86 --- /dev/null +++ b/modules/ui_patterns_field/src/Plugin/Field/FieldWidget/SourceWidget.php @@ -0,0 +1,113 @@ +<?php + +namespace Drupal\ui_patterns_field\Plugin\Field\FieldWidget; + +use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Field\Attribute\FieldWidget; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\WidgetBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\Plugin\Context\EntityContext; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\ui_patterns\Element\ComponentSlotForm; +use Drupal\ui_patterns\Plugin\Context\RequirementsContext; +use Drupal\ui_patterns\SourcePluginManager; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * A widget to display the UI Patterns configuration form. + * + * @internal + * Plugin classes are internal. + */ +#[FieldWidget( + id: 'ui_patterns_source', + label: new TranslatableMarkup('All slot sources (UI Patterns)'), + description: new TranslatableMarkup('Widget to edit an UI Patterns source field and configure any source for a slot.'), + field_types: ['ui_patterns_source'], +)] +class SourceWidget extends WidgetBase { + + /** + * The source plugin manager. + * + * @var \Drupal\ui_patterns\SourcePluginManager + */ + protected SourcePluginManager $sourcePluginManager; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->sourcePluginManager = $container->get('plugin.manager.ui_patterns_source'); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) { + $field_name = $this->fieldDefinition->getName(); + $element['#slot_id'] = $delta; + $element['#parents'] = array_merge($element['#field_parents'] ?? [], [$field_name, $delta]); + $default_value = $this->getDefaultValue($items, $delta, $element, $form, $form_state); + $element['#source_contexts'] = $this->getComponentSourceContexts($items); + $element['#tag_filter'] = $this->getSetting('tag_filter') ?? []; + $source_form = ComponentSlotForm::buildSourceForm($element, $form_state, [], $default_value); + $source_form['source_id']['#empty_option'] = t("- Select a source to add -"); + return $element + $source_form; + } + + /** + * Returns the default value for the source field. + * + * @param \Drupal\Core\Field\FieldItemListInterface $items + * The field items. + * @param int $delta + * The delta. + * @param array $element + * The element. + * @param array $form + * The form. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return mixed + * The default value. + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + protected function getDefaultValue(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) : mixed { + $full_form_state_values = $form_state->getValues() ?? []; + $full_input = $form_state->getUserInput(); + $form_state_values = &NestedArray::getValue($full_form_state_values, $element["#parents"] ?? []); + if (!empty($full_input) || $form_state->isProcessingInput() || $form_state->isRebuilding()) { + $form_state_values = &NestedArray::getValue($full_input, $element["#parents"] ?? []); + } + $default_value = array_merge($items[$delta]?->getValue() ?? [], $form_state_values ?? []); + return $default_value; + } + + /** + * Set the context. + * + * @param \Drupal\Core\Field\FieldItemListInterface|null $items + * Field items. + * + * @return array + * Source contexts. + */ + protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array { + $contexts = []; + if ($entity = $items?->getEntity()) { + $contexts['entity'] = EntityContext::fromEntity($entity); + $contexts['bundle'] = new Context(ContextDefinition::create('string'), $contexts["entity"]->getContextValue()->bundle() ?? ""); + } + $contexts = RequirementsContext::addToContext(["field_granularity:item"], $contexts); + return $contexts; + } + +} diff --git a/modules/ui_patterns_field/src/Plugin/UiPatterns/Source/UIPatternsSourceFieldPropertySource.php b/modules/ui_patterns_field/src/Plugin/UiPatterns/Source/UIPatternsSourceFieldPropertySource.php new file mode 100644 index 0000000000000000000000000000000000000000..d11f95d8b3f024c65e77fd2ab84b9272231da505 --- /dev/null +++ b/modules/ui_patterns_field/src/Plugin/UiPatterns/Source/UIPatternsSourceFieldPropertySource.php @@ -0,0 +1,121 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\ui_patterns_field\Plugin\UiPatterns\Source; + +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\ui_patterns\Attribute\Source; +use Drupal\ui_patterns\Plugin\UiPatterns\Source\FieldPropertySource; +use Drupal\ui_patterns_field\Plugin\Derivative\UIPatternsSourceFieldPropertySourceDeriver; +use Drupal\ui_patterns_field\Plugin\Field\FieldType\SourceValueItem; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Plugin implementation of the prop source. + */ +#[Source( + id: 'ui_patterns_source', + label: new TranslatableMarkup('Value from the component in field.'), + description: new TranslatableMarkup('Map the prop/slot value to the one configured in the component stored the "Source" field.'), + deriver: UIPatternsSourceFieldPropertySourceDeriver::class +)] +class UIPatternsSourceFieldPropertySource extends FieldPropertySource { + + /** + * The component element builder. + * + * @var \Drupal\ui_patterns\Element\ComponentElementBuilder + */ + protected $componentElementBuilder; + + /** + * {@inheritdoc} + */ + public static function create( + ContainerInterface $container, + array $configuration, + $plugin_id, + $plugin_definition, + ) { + // We keep the same constructor as SourcePluginBase. + $instance = parent::create( + $container, + $configuration, + $plugin_id, + $plugin_definition + ); + // Defined in parent class FieldSourceBase. + $instance->componentElementBuilder = $container->get('ui_patterns.component_element_builder'); + return $instance; + } + + /** + * {@inheritdoc} + */ + public function getPropValue(): mixed { + $items = $this->getEntityFieldItemList(); + $delta = (isset($this->context['ui_patterns:field:index'])) ? $this->getContextValue('ui_patterns:field:index') : 0; + if (empty($items)) { + return NULL; + } + /** @var \Drupal\Core\Field\FieldItemInterface $field_item_at_delta */ + $field_item_at_delta = $items->get($delta); + if (!$field_item_at_delta || !($field_item_at_delta instanceof SourceValueItem)) { + return NULL; + } + $source_id = $field_item_at_delta->source_id ?? 'component'; + if ($source_id !== 'component') { + return NULL; + } + $source_configuration = $field_item_at_delta->source ?? []; + if (!is_array($source_configuration)) { + $source_configuration = []; + } + return $this->extractComponentPropValue($source_configuration); + } + + /** + * Extract the prop value from the source configuration. + * + * @param array $source_configuration + * The source configuration. + * + * @return mixed + * The prop value. + */ + protected function extractComponentPropValue(array $source_configuration) : mixed { + $component_configuration = $source_configuration['component'] ?? []; + // $component_id = $component_configuration['component_id'] ?? NULL; + $propDefinition = $this->getPropDefinition(); + $propId = $this->getPropId(); + /** @var \Drupal\ui_patterns\PropTypeInterface $propType */ + $propType = $propDefinition["ui_patterns"]["type_definition"]; + $contexts = $this->getContexts(); + $build = []; + if ($propType->getPluginId() === "slot") { + $sources = $component_configuration['slots'][$propId]["sources"] ?? []; + foreach ($sources as $source) { + $build = $this->componentElementBuilder->buildSource($build, $propId, $propDefinition, $source, $contexts); + } + return $build['#slots'][$propId] ?? []; + } + $build = []; + $prop_source_config = (($propId === "variant") && !empty($component_configuration["variant_id"])) ? $component_configuration["variant_id"] : ($component_configuration['props'][$propId] ?? []); + if (empty($prop_source_config)) { + return NULL; + } + $build = $this->componentElementBuilder->buildSource($build, $propId, $propDefinition, $prop_source_config, $contexts); + $property_value = $build['#props'][$propId] ?? NULL; + if (empty($property_value)) { + return NULL; + } + $prop_typ_types = []; + if (isset($this->propDefinition['type'])) { + // Type can be an array of types or a single type. + $prop_typ_types = is_array($this->propDefinition['type']) ? $this->propDefinition['type'] : [$this->propDefinition['type']]; + } + return $this->transTypeProp($property_value, $prop_typ_types); + } + +} diff --git a/modules/ui_patterns_field/tests/fixtures/tests.ui_patterns_source.yml b/modules/ui_patterns_field/tests/fixtures/tests.ui_patterns_source.yml new file mode 100644 index 0000000000000000000000000000000000000000..80f348d160b73d4817737024289a23510f963efb --- /dev/null +++ b/modules/ui_patterns_field/tests/fixtures/tests.ui_patterns_source.yml @@ -0,0 +1,33 @@ +--- +# this test demonstrates a simple mapping of a prop +# from a field to a component +# this fails if the field is not working (not saving) +# or if the mapping is not working +ui_patterns_source_mapping_simple_1: + component: + component_id: ui_patterns_test:test-component + props: + string: + source_id: entity_field + source: + derivable_context: 'field:node:page:field_ui_patterns_source_1' + 'field:node:page:field_ui_patterns_source_1': + value: + source_id: 'ui_patterns_source:node:field_ui_patterns_source_1' + entity: + field_ui_patterns_source_1: + source_id: 'component' + source: + component: + component_id: 'ui_patterns_test:test-component' + props: + string: + source_id: 'textfield' + source: + value: 'value_text_1' + contexts: + field_name: 'field_ui_patterns_source_1' + output: + props: + string: + value: 'value_text_1' diff --git a/modules/ui_patterns_field/tests/src/Kernel/Source/UIPatternsSourceFieldPropertySourceTest.php b/modules/ui_patterns_field/tests/src/Kernel/Source/UIPatternsSourceFieldPropertySourceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c05f1998900fe94375586962ac23b4b4af386df4 --- /dev/null +++ b/modules/ui_patterns_field/tests/src/Kernel/Source/UIPatternsSourceFieldPropertySourceTest.php @@ -0,0 +1,36 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\ui_patterns_field\Kernel\Source; + +use Drupal\Tests\ui_patterns\Kernel\SourcePluginsTestBase; + +/** + * Test UIPatternsSourceFieldPropertySource. + * + * @coversDefaultClass \Drupal\ui_patterns_field\Plugin\UiPatterns\Source\UIPatternsSourceFieldPropertySource + * @group ui_patterns_field + */ +class UIPatternsSourceFieldPropertySourceTest extends SourcePluginsTestBase { + + /** + * {@inheritdoc} + */ + protected static $modules = ['ui_patterns_field']; + + /** + * Test Field Property Plugin. + */ + public function testPlugin(): void { + $testData = self::loadTestDataFixture(__DIR__ . "/../../../fixtures/tests.ui_patterns_source.yml"); + $testSets = $testData->getTestSets(); + foreach ($testSets as $test_set_name => $test_set) { + if (!str_starts_with($test_set_name, 'ui_patterns_source_')) { + continue; + } + $this->runSourcePluginTest($test_set); + } + } + +} diff --git a/modules/ui_patterns_field/ui_patterns_field.info.yml b/modules/ui_patterns_field/ui_patterns_field.info.yml new file mode 100644 index 0000000000000000000000000000000000000000..86ca2f6fd95f995327e1a9bfba5ca3aa7c6c1a97 --- /dev/null +++ b/modules/ui_patterns_field/ui_patterns_field.info.yml @@ -0,0 +1,8 @@ +name: UI Patterns Field +type: module +description: Use UI components and UI Patterns sources in fields. +core_version_requirement: ^10.3 || ^11 +lifecycle: experimental +package: "User interface (experimental)" +dependencies: + - ui_patterns:ui_patterns diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatterBase.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatterBase.php index e2d187c5b5d755149ee05def1446758a44bf2e09..bad4a2fb3efdfb1de7d3bb14cb3faf586c84fe7c 100644 --- a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatterBase.php +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/ComponentFormatterBase.php @@ -4,14 +4,8 @@ declare(strict_types=1); namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter; -use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; -use Drupal\Core\Field\FormatterBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Plugin\Context\Context; -use Drupal\Core\Plugin\Context\ContextDefinition; -use Drupal\Core\Plugin\Context\EntityContext; use Drupal\Core\Theme\ComponentPluginManager; use Drupal\ui_patterns\Form\ComponentSettingsFormBuilderTrait; use Drupal\ui_patterns\Plugin\Context\RequirementsContext; @@ -32,69 +26,15 @@ abstract class ComponentFormatterBase extends FormatterBase { */ protected ComponentPluginManager $componentPluginManager; - /** - * The entity type manager. - * - * @var \Drupal\Core\Entity\EntityTypeManagerInterface - */ - protected $entityTypeManager; - - /** - * The entity field manager. - * - * @var \Drupal\Core\Entity\EntityFieldManagerInterface - */ - protected $entityFieldManager; - - /** - * The sample entity generator. - * - * @var \Drupal\ui_patterns\Entity\SampleEntityGenerator - */ - protected $sampleEntityGenerator; - - /** - * The chain context entity resolver. - * - * @var \Drupal\ui_patterns\Resolver\ContextEntityResolverInterface - */ - protected $chainContextEntityResolver; - - /** - * The provided plugin contexts. - * - * @var array|null - */ - protected $context = NULL; - /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); $instance->componentPluginManager = $container->get('plugin.manager.sdc'); - $instance->entityTypeManager = $container->get('entity_type.manager'); - $instance->entityFieldManager = $container->get('entity_field.manager'); - - $instance->sampleEntityGenerator = $container->get('ui_patterns.sample_entity_generator'); - $instance->chainContextEntityResolver = $container->get('ui_patterns.chain_context_entity_resolver'); - return $instance; } - /** - * Set the context. - * - * @param array $context - * Context. - * - * @return void - * Nothing. - */ - public function setContext(array $context): void { - $this->context = $context; - } - /** * {@inheritdoc} */ @@ -152,46 +92,6 @@ abstract class ComponentFormatterBase extends FormatterBase { ]; } - /** - * {@inheritdoc} - */ - protected function checkEntityHasField(EntityInterface $entity, string $entity_type_id, string $field_name) : bool { - $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()); - return ($entity->getEntityTypeId() === $entity_type_id && - array_key_exists($field_name, $field_definitions)); - } - - /** - * Find an entity bundle which has a field. - * - * @param string $entity_type_id - * The entity type id. - * @param string $field_name - * The field name to be found in searched bundle. - * - * @return string - * The bundle. - */ - protected function findEntityBundleWithField(string $entity_type_id, string $field_name) : string { - // @todo better implementation with service 'entity_type.bundle.info' - $bundle = $entity_type_id; - $bundle_entity_type = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType(); - if (NULL !== $bundle_entity_type) { - $bundle_list = $this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple(); - if (count($bundle_list) > 0) { - foreach ($bundle_list as $bundle_entity) { - $bundle_to_test = (string) $bundle_entity->id(); - $definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_to_test); - if (array_key_exists($field_name, $definitions)) { - $bundle = $bundle_to_test; - break; - } - } - } - } - return $bundle; - } - /** * Set the context of field and entity (override the method trait). * @@ -202,40 +102,8 @@ abstract class ComponentFormatterBase extends FormatterBase { * Source contexts. */ protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array { - $contexts = array_merge($this->context ?? [], $this->getThirdPartySetting('ui_patterns', 'context') ?? []); - $field_definition = $this->fieldDefinition; - $field_name = $field_definition->getName() ?? ""; - $contexts['field_name'] = new Context(ContextDefinition::create('string'), $field_name); - $contexts = RequirementsContext::addToContext(["field_formatter"], $contexts); - $bundle = $field_definition->getTargetBundle(); - $contexts['bundle'] = new Context(ContextDefinition::create('string'), $bundle ?? ""); - // Get the entity. - $entity_type_id = $field_definition->getTargetEntityTypeId(); - // When field items are available, we can get the entity directly. - $entity = ($items) ? $items->getEntity() : NULL; - if (!$entity) { - $entity = $this->chainContextEntityResolver->guessEntity($contexts); - } - if (!$entity_type_id) { - return $contexts; - } - if (!$entity || !$this->checkEntityHasField($entity, $entity_type_id, $field_name)) { - // Generate a default bundle when it is missing, - // this covers contexts like the display of a field in a view. - // the bundle selected should have the field in definition... - $entity = !empty($bundle) ? $this->sampleEntityGenerator->get($entity_type_id, $bundle) : - $this->sampleEntityGenerator->get($entity_type_id, $this->findEntityBundleWithField($entity_type_id, $field_name)); - - } - $contexts['entity'] = EntityContext::fromEntity($entity); - return $contexts; - } - - /** - * {@inheritdoc} - */ - public static function isApplicable(FieldDefinitionInterface $field_definition) { - return ($field_definition->getTargetEntityTypeId() !== NULL) && parent::isApplicable($field_definition); + $contexts = parent::getComponentSourceContexts($items); + return RequirementsContext::addToContext(["field_formatter"], $contexts); } /** diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/FormatterBase.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/FormatterBase.php new file mode 100644 index 0000000000000000000000000000000000000000..119a421a115395ec591f0a02de5eccd98c4e7ad4 --- /dev/null +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/FormatterBase.php @@ -0,0 +1,174 @@ +<?php + +namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter; + +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Field\FieldDefinitionInterface; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Field\FormatterBase as FieldFormatterBase; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\Plugin\Context\EntityContext; +use Drupal\ui_patterns\SourcePluginBase; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Base class for UI Patterns field formatters. + */ +abstract class FormatterBase extends FieldFormatterBase { + + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The entity field manager. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ + protected $entityFieldManager; + + /** + * The sample entity generator. + * + * @var \Drupal\ui_patterns\Entity\SampleEntityGenerator + */ + protected $sampleEntityGenerator; + + /** + * The chain context entity resolver. + * + * @var \Drupal\ui_patterns\Resolver\ContextEntityResolverInterface + */ + protected $chainContextEntityResolver; + + /** + * The provided plugin contexts. + * + * @var array|null + */ + protected $context = NULL; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->entityTypeManager = $container->get('entity_type.manager'); + $instance->entityFieldManager = $container->get('entity_field.manager'); + $instance->sampleEntityGenerator = $container->get('ui_patterns.sample_entity_generator'); + $instance->chainContextEntityResolver = $container->get('ui_patterns.chain_context_entity_resolver'); + return $instance; + } + + /** + * Set the context. + * + * @param array $context + * Context. + * + * @return void + * Nothing. + */ + public function setContext(array $context): void { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + protected function checkEntityHasField(EntityInterface $entity, string $entity_type_id, string $field_name) : bool { + $field_definitions = $this->entityFieldManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()); + return ($entity->getEntityTypeId() === $entity_type_id && + array_key_exists($field_name, $field_definitions)); + } + + /** + * Find an entity bundle which has a field. + * + * @param string $entity_type_id + * The entity type id. + * @param string $field_name + * The field name to be found in searched bundle. + * + * @return string + * The bundle. + */ + protected function findEntityBundleWithField(string $entity_type_id, string $field_name) : string { + // @todo better implementation with service 'entity_type.bundle.info' + $bundle = $entity_type_id; + $bundle_entity_type = $this->entityTypeManager->getDefinition($entity_type_id)->getBundleEntityType(); + if (NULL !== $bundle_entity_type) { + $bundle_list = $this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple(); + if (count($bundle_list) > 0) { + foreach ($bundle_list as $bundle_entity) { + $bundle_to_test = (string) $bundle_entity->id(); + $definitions = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_to_test); + if (array_key_exists($field_name, $definitions)) { + $bundle = $bundle_to_test; + break; + } + } + } + } + return $bundle; + } + + /** + * Set the context of field and entity (override the method trait). + * + * @param ?FieldItemListInterface $items + * Field items when available. + * + * @return array + * Source contexts. + */ + protected function getComponentSourceContexts(?FieldItemListInterface $items = NULL): array { + $contexts = array_merge($this->context ?? [], $this->getThirdPartySetting('ui_patterns', 'context') ?? []); + $field_definition = $this->fieldDefinition; + $field_name = $field_definition->getName() ?? ""; + $contexts['field_name'] = new Context(ContextDefinition::create('string'), $field_name); + $bundle = $field_definition->getTargetBundle(); + $contexts['bundle'] = new Context(ContextDefinition::create('string'), $bundle ?? ""); + // Get the entity. + $entity_type_id = $field_definition->getTargetEntityTypeId(); + // When field items are available, we can get the entity directly. + $entity = ($items) ? $items->getEntity() : NULL; + if (!$entity) { + $entity = $this->chainContextEntityResolver->guessEntity($contexts); + } + if (!$entity_type_id) { + return $contexts; + } + if (!$entity || !$this->checkEntityHasField($entity, $entity_type_id, $field_name)) { + // Generate a default bundle when it is missing, + // this covers contexts like the display of a field in a view. + // the bundle selected should have the field in definition... + $entity = !empty($bundle) ? $this->sampleEntityGenerator->get($entity_type_id, $bundle) : + $this->sampleEntityGenerator->get($entity_type_id, $this->findEntityBundleWithField($entity_type_id, $field_name)); + + } + $contexts['entity'] = EntityContext::fromEntity($entity); + return $contexts; + } + + /** + * {@inheritdoc} + */ + public static function isApplicable(FieldDefinitionInterface $field_definition) { + return ($field_definition->getTargetEntityTypeId() !== NULL) && parent::isApplicable($field_definition); + } + + /** + * {@inheritdoc} + */ + public function calculateDependencies() { + $dependencies = parent::calculateDependencies(); + SourcePluginBase::mergeConfigDependencies($dependencies, ["module" => ["ui_patterns_field_formatters"]]); + return $dependencies; + } + +} diff --git a/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/SourceFormatter.php b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/SourceFormatter.php new file mode 100644 index 0000000000000000000000000000000000000000..1dcd8746ced67f996a4e3ecca370533b4f82e0b6 --- /dev/null +++ b/modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/SourceFormatter.php @@ -0,0 +1,64 @@ +<?php + +namespace Drupal\ui_patterns_field_formatters\Plugin\Field\FieldFormatter; + +use Drupal\Core\Field\Attribute\FieldFormatter; +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Formatter to render the file URI to its download path. + */ +#[FieldFormatter( + id: 'ui_patterns_source', + label: new TranslatableMarkup('Render source (UI Patterns)'), + field_types: ['ui_patterns_source'], +)] +class SourceFormatter extends FormatterBase { + + /** + * The component element builder. + * + * @var \Drupal\ui_patterns\Element\ComponentElementBuilder + */ + protected $componentElementBuilder; + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); + $instance->componentElementBuilder = $container->get('ui_patterns.component_element_builder'); + return $instance; + } + + /** + * {@inheritdoc} + */ + public static function defaultSettings() { + $settings = parent::defaultSettings(); + return $settings; + } + + /** + * {@inheritdoc} + */ + public function viewElements(FieldItemListInterface $items, $langcode) { + $fake_build = []; + $contexts = $this->getComponentSourceContexts($items); + $contexts['ui_patterns:lang_code'] = new Context(new ContextDefinition('any'), $langcode); + $contexts['ui_patterns:field:items'] = new Context(new ContextDefinition('any'), $items); + for ($field_item_index = 0; $field_item_index < $items->count(); $field_item_index++) { + $contexts['ui_patterns:field:index'] = new Context(new ContextDefinition('integer'), $field_item_index); + $source_with_configuration = $items->get($field_item_index)->getValue(); + $fake_build = $this->componentElementBuilder->buildSource($fake_build, 'content', [], $source_with_configuration, $contexts); + } + $build = $fake_build['#slots']['content'] ?? []; + $build['#cache'] = $fake_build['#cache'] ?? []; + return $build; + } + +} diff --git a/src/Element/ComponentForm.php b/src/Element/ComponentForm.php index 87724d47c262a9528ee1ea8114f021bac97ef8cc..903d54c383b1f69d46117a4238eb626e50bb96c3 100644 --- a/src/Element/ComponentForm.php +++ b/src/Element/ComponentForm.php @@ -63,6 +63,10 @@ class ComponentForm extends ComponentFormBase { '#input' => TRUE, '#multiple' => FALSE, '#component_required' => TRUE, + '#prop_filter' => NULL, + '#wrap' => TRUE, + '#render_headings' => TRUE, + '#render_sources' => TRUE, '#default_value' => NULL, '#source_contexts' => [], '#tag_filter' => [], @@ -74,7 +78,7 @@ class ComponentForm extends ComponentFormBase { ], '#theme_wrappers' => ['form_element'], '#after_build' => [ - [$class, 'afterBuild'], + [$class, 'afterBuild'], ], ]; } @@ -201,6 +205,10 @@ class ComponentForm extends ComponentFormBase { $component_id, $element['#default_value']['variant_id'] ?? NULL, ); + $prop_filter = $element['#prop_filter'] ?? NULL; + if (is_array($prop_filter) && !in_array("variant", $prop_filter)) { + $element['variant_id']['#access'] = FALSE; + } } $element['slots'] = self::buildSlotsForm($element, $component_id); $element['props'] = self::buildPropsForm($element, $component_id); @@ -261,9 +269,10 @@ class ComponentForm extends ComponentFormBase { "#prop_id" => 'variant', '#default_value' => $default_variant_id, '#source_contexts' => $element['#source_contexts'], + '#render_sources' => $element['#render_sources'] ?? TRUE, '#tag_filter' => $element['#tag_filter'], '#ajax_url' => $element['#ajax_url'] ?? NULL, - '#wrap' => TRUE, + '#wrap' => $element['#wrap'] ?? TRUE, ]; } @@ -280,6 +289,9 @@ class ComponentForm extends ComponentFormBase { '#ajax_url' => $element['#ajax_url'] ?? NULL, '#access' => $element['#render_slots'] ?? TRUE, '#default_value' => $element['#default_value']['slots'] ?? NULL, + '#wrap' => $element['#wrap'] ?? TRUE, + '#render_headings' => $element['#render_headings'] ?? TRUE, + '#render_sources' => $element['#render_sources'] ?? TRUE, ]; } @@ -294,6 +306,10 @@ class ComponentForm extends ComponentFormBase { '#source_contexts' => $element['#source_contexts'], '#tag_filter' => $element['#tag_filter'], '#ajax_url' => $element['#ajax_url'] ?? NULL, + '#prop_filter' => $element['#prop_filter'] ?? NULL, + '#render_headings' => $element['#render_headings'] ?? TRUE, + '#render_sources' => $element['#render_sources'] ?? TRUE, + '#wrap' => $element['#wrap'] ?? TRUE, '#access' => $element['#render_props'] ?? TRUE, '#default_value' => [ 'props' => $element['#default_value']['props'] ?? [], diff --git a/src/Element/ComponentPropForm.php b/src/Element/ComponentPropForm.php index 07eff59d07a29189665a9769f9796c33c601ba60..ac48327d8cc5254b8f1f14afb5af683b102eb576 100644 --- a/src/Element/ComponentPropForm.php +++ b/src/Element/ComponentPropForm.php @@ -53,6 +53,7 @@ class ComponentPropForm extends ComponentFormBase { '#slot_id' => NULL, // Wrapped (into details/summary) or not. '#wrap' => FALSE, + '#render_sources' => TRUE, '#process' => [ [$class, 'buildForm'], [$class, 'processPropOrSlot'], @@ -97,9 +98,21 @@ class ComponentPropForm extends ComponentFormBase { 'source_id' => $source_selector, 'source' => array_merge($source_form, ['#prop_id' => $prop_id]), ]; + if (!($element['#render_sources'] ?? TRUE)) { + $element['source_id'] = [ + '#type' => 'hidden', + '#value' => $selected_source->getPluginId(), + ]; + } $element = static::addRequired($element, $prop_id); - if ($prop_id === "variant") { - $element["source"]["value"]["#title"] = $element["#title"]; + // This allows "widgets" to have a title when #wrap is unset. + if (!($element['#wrap'] ?? TRUE) && isset($element["source"]["value"])) { + if (empty($element["source"]["value"]["#title"])) { + $element["source"]["value"]["#title"] = $element["#title"]; + } + if (empty($element["source"]["value"]["#description"])) { + $element["source"]["value"]["#description"] = $element['#description'] ?? NULL; + } } return $element; } diff --git a/src/Element/ComponentPropsForm.php b/src/Element/ComponentPropsForm.php index 1e54e93673febd30974f7b409c3abcdd34fb3de0..9f284d4a7cb399e0f296ae907c4e54b7224d7498 100644 --- a/src/Element/ComponentPropsForm.php +++ b/src/Element/ComponentPropsForm.php @@ -55,6 +55,10 @@ class ComponentPropsForm extends ComponentFormBase { '#component_id' => NULL, '#source_contexts' => [], '#tag_filter' => [], + '#prop_filter' => NULL, + '#render_headings' => TRUE, + '#render_sources' => TRUE, + '#wrap' => TRUE, '#process' => [ [$class, 'buildForm'], ], @@ -74,10 +78,13 @@ class ComponentPropsForm extends ComponentFormBase { return $element; } $configuration = $element['#default_value']['props'] ?? []; - $prop_heading = new FormattableMarkup("<p><strong>@title</strong></p>", ["@title" => t("Props")]); - $element[] = [ - '#markup' => $prop_heading, - ]; + if ($element['#render_headings']) { + $prop_heading = new FormattableMarkup("<p><strong>@title</strong></p>", ["@title" => t("Props")]); + $element[] = [ + '#markup' => $prop_heading, + ]; + } + $prop_filter = $element['#prop_filter'] ?? NULL; foreach ($props as $prop_id => $prop) { if ($prop_id === 'variant') { continue; @@ -92,11 +99,15 @@ class ComponentPropsForm extends ComponentFormBase { '#tag_filter' => $element['#tag_filter'], '#component_id' => $component->getPluginId(), '#prop_id' => $prop_id, - '#wrap' => TRUE, + '#wrap' => $element['#wrap'] ?? TRUE, + '#render_sources' => $element['#render_sources'] ?? TRUE, ]; + if (is_array($prop_filter) && !in_array($prop_id, $prop_filter)) { + $element[$prop_id]['#access'] = FALSE; + } } if (count(Element::children($element)) === 0) { - hide($element); + $element['#access'] = FALSE; } return $element; } diff --git a/src/Element/ComponentSlotsForm.php b/src/Element/ComponentSlotsForm.php index 063617c1206a362cc60e57f7d09cfdbcba16b278..c2749ecdddcabcfac6e1d24d75a67d932cf7abd1 100644 --- a/src/Element/ComponentSlotsForm.php +++ b/src/Element/ComponentSlotsForm.php @@ -50,6 +50,8 @@ class ComponentSlotsForm extends ComponentFormBase { '#component_id' => NULL, '#source_contexts' => [], '#tag_filter' => [], + '#wrap' => TRUE, + '#render_headings' => TRUE, '#process' => [ [$class, 'buildForm'], ], @@ -99,15 +101,17 @@ class ComponentSlotsForm extends ComponentFormBase { if (!isset($component->metadata->slots) || count( $component->metadata->slots ) === 0) { - hide($element); + $element['#access'] = FALSE; return $element; } $contexts = $element['#source_contexts'] ?? []; $configuration = $element['#default_value'] ?? []; - $slot_heading = new FormattableMarkup("<p><strong>@title</strong></p>", ["@title" => t("Slots")]); - $element[] = [ - '#markup' => $slot_heading, - ]; + if ($element['#render_headings']) { + $slot_heading = new FormattableMarkup("<p><strong>@title</strong></p>", ["@title" => t("Slots")]); + $element[] = [ + '#markup' => $slot_heading, + ]; + } foreach ($component->metadata->slots as $slot_id => $slot) { $element[$slot_id] = [ '#title' => $slot['title'] ?? '', @@ -117,6 +121,7 @@ class ComponentSlotsForm extends ComponentFormBase { '#component_id' => $component->getPluginId(), '#slot_id' => $slot_id, '#source_contexts' => $contexts, + '#wrap' => $element['#wrap'] ?? TRUE, '#tag_filter' => $element['#tag_filter'], '#prefix' => "<div class='component-form-slot'>", '#suffix' => "</div>", diff --git a/src/Plugin/UiPatterns/Source/AttributesWidget.php b/src/Plugin/UiPatterns/Source/AttributesWidget.php index cae736aad6f08d9ab3bcbea942faa1638d23fdc9..04e9fdcd09396984ca11c3f677bdffbce7ebd3e5 100644 --- a/src/Plugin/UiPatterns/Source/AttributesWidget.php +++ b/src/Plugin/UiPatterns/Source/AttributesWidget.php @@ -21,6 +21,7 @@ use Drupal\ui_patterns\UnicodePatternValidatorTrait; label: new TranslatableMarkup('Attributes'), description: new TranslatableMarkup('Textfield with double-quoted values or a space-separated list of HTML classes.'), prop_types: ['attributes'], + tags: ['widget', 'widget:dismissible'], )] class AttributesWidget extends SourcePluginPropValueWidget implements TrustedCallbackInterface { diff --git a/src/Plugin/UiPatterns/Source/FieldPropertySource.php b/src/Plugin/UiPatterns/Source/FieldPropertySource.php index ecd9e5fdab72bbce02d3289d82bcc898180b32c8..7bd72e27e27b9a062d29663b1eb91f9bbcb7b546 100644 --- a/src/Plugin/UiPatterns/Source/FieldPropertySource.php +++ b/src/Plugin/UiPatterns/Source/FieldPropertySource.php @@ -54,7 +54,7 @@ class FieldPropertySource extends FieldValueSourceBase { * @return bool|float|int * The value converted. */ - private function transTypeProp(mixed $value, array $prop_types): mixed { + protected function transTypeProp(mixed $value, array $prop_types): mixed { foreach ($prop_types as $prop_type) { $converted = match ($prop_type) { 'integer' => is_int($value) ? $value : (int) $value, diff --git a/src/Plugin/UiPatterns/Source/ListTextareaWidget.php b/src/Plugin/UiPatterns/Source/ListTextareaWidget.php index 313eeadbc793a54f4dde0f04f3b74d8f6a80b67d..e5b15bc6e8d87f80223b772411fdce3f0b145987 100644 --- a/src/Plugin/UiPatterns/Source/ListTextareaWidget.php +++ b/src/Plugin/UiPatterns/Source/ListTextareaWidget.php @@ -16,7 +16,8 @@ use Drupal\ui_patterns\SourcePluginBase; id: 'list_textarea', label: new TranslatableMarkup('Textarea for list'), description: new TranslatableMarkup('One item by line.'), - prop_types: ['list'] + prop_types: ['list'], + tags: ['widget', 'widget:dismissible'] )] class ListTextareaWidget extends SourcePluginBase {