diff --git a/core/includes/form.inc b/core/includes/form.inc index e41efe8e52d14a0fef7ac7dffc10f187d92e15b1..d759c8862516b9e8dc221ee13fd89a51d3010d63 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -216,6 +216,10 @@ function template_preprocess_fieldset(&$variables) { if (!empty($element['#description'])) { $description_id = $element['#attributes']['id'] . '--description'; $description_attributes['id'] = $description_id; + $variables['description_display'] = $element['#description_display']; + if ($element['#description_display'] === 'invisible') { + $description_attributes['class'][] = 'visually-hidden'; + } $description_attributes['data-drupal-field-elements'] = 'description'; $variables['description']['attributes'] = new Attribute($description_attributes); $variables['description']['content'] = $element['#description']; diff --git a/core/modules/system/templates/fieldset.html.twig b/core/modules/system/templates/fieldset.html.twig index edb737111c527709e5f544ff194e3cb95ca43718..6a240e0bdc05c85f1a88cb49a8b96ec74e669837 100644 --- a/core/modules/system/templates/fieldset.html.twig +++ b/core/modules/system/templates/fieldset.html.twig @@ -14,6 +14,11 @@ * - description: The description element containing the following properties: * - content: The description content of the <fieldset>. * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * - children: The rendered child elements of the <fieldset>. * - prefix: The content to add before the <fieldset> children. * - suffix: The content to add after the <fieldset> children. @@ -44,6 +49,9 @@ <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span> </legend> <div class="fieldset-wrapper"> + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> + {% endif %} {% if errors %} <div> {{ errors }} @@ -56,7 +64,7 @@ {% if suffix %} <span class="field-suffix">{{ suffix }}</span> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> {% endif %} </div> diff --git a/core/modules/system/tests/src/Kernel/Form/ElementsFieldsetTest.php b/core/modules/system/tests/src/Kernel/Form/ElementsFieldsetTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c2aafbfbeb9622dd40f46ac91bc26a316870dc02 --- /dev/null +++ b/core/modules/system/tests/src/Kernel/Form/ElementsFieldsetTest.php @@ -0,0 +1,149 @@ +<?php + +namespace Drupal\Tests\system\Kernel\Form; + +use Drupal\Core\Form\FormInterface; +use Drupal\Core\Form\FormState; +use Drupal\Core\Form\FormStateInterface; +use Drupal\KernelTests\KernelTestBase; + +/** + * Tests fieldset element rendering and description placement. + * + * @group Form + */ +class ElementsFieldsetTest extends KernelTestBase implements FormInterface { + + /** + * {@inheritdoc} + */ + protected static $modules = ['system']; + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'form_test_fieldset_element'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form['fieldset_default'] = [ + '#type' => 'fieldset', + '#title' => 'Fieldset title for default description display', + '#description' => 'Fieldset description for default description display.', + ]; + $form['meta_default'] = [ + '#type' => 'container', + '#title' => 'Group element', + '#group' => 'fieldset_default', + ]; + $form['meta_default']['element'] = [ + '#type' => 'textfield', + '#title' => 'Nested text field inside meta_default element', + ]; + + $form['fieldset_before'] = [ + '#type' => 'fieldset', + '#title' => 'Fieldset title for description displayed before element', + '#description' => 'Fieldset description for description displayed before element.', + '#description_display' => 'before', + ]; + $form['meta_before'] = [ + '#type' => 'container', + '#title' => 'Group element', + '#group' => 'fieldset_before', + ]; + $form['meta_before']['element'] = [ + '#type' => 'textfield', + '#title' => 'Nested text field inside meta_before element', + ]; + + $form['fieldset_after'] = [ + '#type' => 'fieldset', + '#title' => 'Fieldset title for description displayed after element', + '#description' => 'Fieldset description for description displayed after element.', + '#description_display' => 'after', + ]; + $form['meta_after'] = [ + '#type' => 'container', + '#title' => 'Group element', + '#group' => 'fieldset_after', + ]; + $form['meta_after']['element'] = [ + '#type' => 'textfield', + '#title' => 'Nested text field inside meta_after element', + ]; + + $form['fieldset_invisible'] = [ + '#type' => 'fieldset', + '#title' => 'Fieldset title for description displayed as visually hidden element', + '#description' => 'Fieldset description for description displayed as visually hidden element.', + '#description_display' => 'invisible', + ]; + $form['meta_invisible'] = [ + '#type' => 'container', + '#title' => 'Group element', + '#group' => 'fieldset_invisible', + ]; + $form['meta_invisible']['element'] = [ + '#type' => 'textfield', + '#title' => 'Nested text field inside meta_invisible element', + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) {} + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, FormStateInterface $form_state) {} + + /** + * Tests different display options for fieldset element descriptions. + */ + public function testFieldsetDescriptions() { + $form_state = new FormState(); + $form = \Drupal::formBuilder()->getForm($this); + $this->render($form); + + // Check #description placement with #description_display not set. By + // default, the #description should appear after any fieldset elements. + $field_id = 'edit-fieldset-default'; + $description_id = $field_id . '--description'; + $elements = $this->xpath('//fieldset[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]//div[@id="edit-meta-default"]/following-sibling::div[@id="' . $description_id . '"]'); + $this->assertCount(1, $elements); + + // Check #description placement with #description_display set to 'before'. + $field_id = 'edit-fieldset-before'; + $description_id = $field_id . '--description'; + $elements = $this->xpath('//fieldset[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]//div[@id="edit-meta-before"]/preceding-sibling::div[@id="' . $description_id . '"]'); + $this->assertCount(1, $elements); + + // Check #description placement with #description_display set to 'after'. + $field_id = 'edit-fieldset-after'; + $description_id = $field_id . '--description'; + $elements = $this->xpath('//fieldset[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]//div[@id="edit-meta-after"]/following-sibling::div[@id="' . $description_id . '"]'); + $this->assertCount(1, $elements); + + // Check if the 'visually-hidden' class is set on the fieldset description + // with #description_display set to 'invisible'. Also check that the + // description is placed after the form element. + $field_id = 'edit-fieldset-invisible'; + $description_id = $field_id . '--description'; + $elements = $this->xpath('//fieldset[@id="' . $field_id . '" and @aria-describedby="' . $description_id . '"]//div[@id="edit-meta-invisible"]/following-sibling::div[contains(@class, "visually-hidden")]'); + $this->assertCount(1, $elements); + + \Drupal::formBuilder()->submitForm($this, $form_state); + $errors = $form_state->getErrors(); + $this->assertEmpty($errors); + } + +} diff --git a/core/profiles/demo_umami/themes/umami/templates/classy/form/fieldset.html.twig b/core/profiles/demo_umami/themes/umami/templates/classy/form/fieldset.html.twig index db63082e8a8979b3a54b84ec5b071fa424791470..93b5f54d8964c748ddce16a004bb78f2b21abc5f 100644 --- a/core/profiles/demo_umami/themes/umami/templates/classy/form/fieldset.html.twig +++ b/core/profiles/demo_umami/themes/umami/templates/classy/form/fieldset.html.twig @@ -13,6 +13,11 @@ * - description: The description element containing the following properties: * - content: The description content of the fieldset. * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * - children: The rendered child elements of the fieldset. * - prefix: The content to add before the fieldset children. * - suffix: The content to add after the fieldset children. @@ -41,6 +46,9 @@ <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span> </legend> <div class="fieldset-wrapper"> + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> + {% endif %} {% if errors %} <div class="form-item--error-message"> <strong>{{ errors }}</strong> @@ -53,7 +61,7 @@ {% if suffix %} <span class="field-suffix">{{ suffix }}</span> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> {% endif %} </div> diff --git a/core/tests/Drupal/KernelTests/Core/Theme/ConfirmClassyCopiesTest.php b/core/tests/Drupal/KernelTests/Core/Theme/ConfirmClassyCopiesTest.php index e95aad3e1e9533f5e504056b4685a98501dfc3f8..3e54a43ce4d6a0c8840644cd597b34bc9b8015c3 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/ConfirmClassyCopiesTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/ConfirmClassyCopiesTest.php @@ -752,7 +752,7 @@ protected function getClassyHash($type, $file) { 'input.html.twig' => 'b844ef5f74df3058bd6ff9ec024d907b', 'form-element-label.html.twig' => '21026361fa9ba15ccdc823d6bb00a6d9', 'datetime-wrapper.html.twig' => '765aa519f7e4ae36ee4726ae2633de6d', - 'fieldset.html.twig' => 'a9067cc8e8896e91059a50798942aca8', + 'fieldset.html.twig' => '5900e42f5ee2574a3d002d19771bd764', 'form.html.twig' => '0767ff441543553d51443b970c4b736b', 'datetime-form.html.twig' => '649c36a2900c556b8a1385c1fa755281', 'checkboxes.html.twig' => 'a5faa5fdd7de4aa42045753db65ffb0b', diff --git a/core/themes/bartik/templates/classy/form/fieldset.html.twig b/core/themes/bartik/templates/classy/form/fieldset.html.twig index db63082e8a8979b3a54b84ec5b071fa424791470..93b5f54d8964c748ddce16a004bb78f2b21abc5f 100644 --- a/core/themes/bartik/templates/classy/form/fieldset.html.twig +++ b/core/themes/bartik/templates/classy/form/fieldset.html.twig @@ -13,6 +13,11 @@ * - description: The description element containing the following properties: * - content: The description content of the fieldset. * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * - children: The rendered child elements of the fieldset. * - prefix: The content to add before the fieldset children. * - suffix: The content to add after the fieldset children. @@ -41,6 +46,9 @@ <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span> </legend> <div class="fieldset-wrapper"> + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> + {% endif %} {% if errors %} <div class="form-item--error-message"> <strong>{{ errors }}</strong> @@ -53,7 +61,7 @@ {% if suffix %} <span class="field-suffix">{{ suffix }}</span> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> {% endif %} </div> diff --git a/core/themes/claro/templates/fieldset.html.twig b/core/themes/claro/templates/fieldset.html.twig index 3609aba98fa6ace6b5cd59bca34c85f4e9f3cd45..d6c87070e9976ca6a3d25eb81ef80220e8da0bdf 100644 --- a/core/themes/claro/templates/fieldset.html.twig +++ b/core/themes/claro/templates/fieldset.html.twig @@ -3,6 +3,25 @@ * @file * Theme override for a fieldset element and its children. * + * Available variables: + * - attributes: HTML attributes for the fieldset element. + * - errors: (optional) Any errors for this fieldset element, may not be set. + * - required: Boolean indicating whether the fieldset element is required. + * - legend: The legend element containing the following properties: + * - title: Title of the fieldset, intended for use as the text of the legend. + * - attributes: HTML attributes to apply to the legend. + * - description: The description element containing the following properties: + * - content: The description content of the fieldset. + * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. + * - children: The rendered child elements of the fieldset. + * - prefix: The content to add before the fieldset children. + * - suffix: The content to add after the fieldset children. + * * @see template_preprocess_fieldset() * @see claro_preprocess_fieldset() */ @@ -54,6 +73,9 @@ {% endif %} <div{{ content_attributes.addClass(wrapper_classes) }}> + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass(description_classes) }}>{{ description.content }}</div> + {% endif %} {% if inline_items %} <div class="container-inline"> {% endif %} @@ -70,7 +92,7 @@ {{ errors }} </div> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass(description_classes) }}>{{ description.content }}</div> {% endif %} diff --git a/core/themes/classy/templates/form/fieldset.html.twig b/core/themes/classy/templates/form/fieldset.html.twig index db63082e8a8979b3a54b84ec5b071fa424791470..93b5f54d8964c748ddce16a004bb78f2b21abc5f 100644 --- a/core/themes/classy/templates/form/fieldset.html.twig +++ b/core/themes/classy/templates/form/fieldset.html.twig @@ -13,6 +13,11 @@ * - description: The description element containing the following properties: * - content: The description content of the fieldset. * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * - children: The rendered child elements of the fieldset. * - prefix: The content to add before the fieldset children. * - suffix: The content to add after the fieldset children. @@ -41,6 +46,9 @@ <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span> </legend> <div class="fieldset-wrapper"> + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> + {% endif %} {% if errors %} <div class="form-item--error-message"> <strong>{{ errors }}</strong> @@ -53,7 +61,7 @@ {% if suffix %} <span class="field-suffix">{{ suffix }}</span> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> {% endif %} </div> diff --git a/core/themes/olivero/templates/form/fieldset.html.twig b/core/themes/olivero/templates/form/fieldset.html.twig index 7994ea57f0a940c0aeb41baddd316842db675df8..9efc4e5a70fe0c17b5e1cce4928f44df502bddb5 100644 --- a/core/themes/olivero/templates/form/fieldset.html.twig +++ b/core/themes/olivero/templates/form/fieldset.html.twig @@ -14,6 +14,11 @@ * - description: The description element containing the following properties: * - content: The description content of the <fieldset>. * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * - children: The rendered child elements of the <fieldset>. * - prefix: The content to add before the <fieldset> children. * - suffix: The content to add after the <fieldset> children. @@ -75,6 +80,9 @@ <div class="container-inline"> {% endif %} + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass(description_classes) }}>{{ description.content }}</div> + {% endif %} {% if prefix %} <span class="fieldset__prefix">{{ prefix }}</span> {% endif %} @@ -87,7 +95,7 @@ {{ errors }} </div> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass(description_classes) }}>{{ description.content }}</div> {% endif %} diff --git a/core/themes/seven/templates/classy/form/fieldset.html.twig b/core/themes/seven/templates/classy/form/fieldset.html.twig index db63082e8a8979b3a54b84ec5b071fa424791470..93b5f54d8964c748ddce16a004bb78f2b21abc5f 100644 --- a/core/themes/seven/templates/classy/form/fieldset.html.twig +++ b/core/themes/seven/templates/classy/form/fieldset.html.twig @@ -13,6 +13,11 @@ * - description: The description element containing the following properties: * - content: The description content of the fieldset. * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * - children: The rendered child elements of the fieldset. * - prefix: The content to add before the fieldset children. * - suffix: The content to add after the fieldset children. @@ -41,6 +46,9 @@ <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span> </legend> <div class="fieldset-wrapper"> + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> + {% endif %} {% if errors %} <div class="form-item--error-message"> <strong>{{ errors }}</strong> @@ -53,7 +61,7 @@ {% if suffix %} <span class="field-suffix">{{ suffix }}</span> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> {% endif %} </div> diff --git a/core/themes/stable/templates/form/fieldset.html.twig b/core/themes/stable/templates/form/fieldset.html.twig index c13ddda7c427e1c4af60cb907894b10fb8e20c72..efd05e3425bcb373a0b6b9ce0ac63568748d926d 100644 --- a/core/themes/stable/templates/form/fieldset.html.twig +++ b/core/themes/stable/templates/form/fieldset.html.twig @@ -14,6 +14,11 @@ * - description: The description element containing the following properties: * - content: The description content of the <fieldset>. * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * - children: The rendered child elements of the <fieldset>. * - prefix: The content to add before the <fieldset> children. * - suffix: The content to add after the <fieldset> children. @@ -42,6 +47,9 @@ <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span> </legend> <div class="fieldset-wrapper"> + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> + {% endif %} {% if errors %} <div> {{ errors }} @@ -54,7 +62,7 @@ {% if suffix %} <span class="field-suffix">{{ suffix }}</span> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> {% endif %} </div> diff --git a/core/themes/stable9/templates/form/fieldset.html.twig b/core/themes/stable9/templates/form/fieldset.html.twig index c13ddda7c427e1c4af60cb907894b10fb8e20c72..efd05e3425bcb373a0b6b9ce0ac63568748d926d 100644 --- a/core/themes/stable9/templates/form/fieldset.html.twig +++ b/core/themes/stable9/templates/form/fieldset.html.twig @@ -14,6 +14,11 @@ * - description: The description element containing the following properties: * - content: The description content of the <fieldset>. * - attributes: HTML attributes to apply to the description container. + * - description_display: Description display setting. It can have these values: + * - before: The description is output before the element. + * - after: The description is output after the element (default). + * - invisible: The description is output after the element, hidden visually + * but available to screen readers. * - children: The rendered child elements of the <fieldset>. * - prefix: The content to add before the <fieldset> children. * - suffix: The content to add after the <fieldset> children. @@ -42,6 +47,9 @@ <span{{ legend_span.attributes.addClass(legend_span_classes) }}>{{ legend.title }}</span> </legend> <div class="fieldset-wrapper"> + {% if description_display == 'before' and description.content %} + <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> + {% endif %} {% if errors %} <div> {{ errors }} @@ -54,7 +62,7 @@ {% if suffix %} <span class="field-suffix">{{ suffix }}</span> {% endif %} - {% if description.content %} + {% if description_display in ['after', 'invisible'] and description.content %} <div{{ description.attributes.addClass('description') }}>{{ description.content }}</div> {% endif %} </div>