From c949b4ae36c318ec6a76f331c82b536a3ea30686 Mon Sep 17 00:00:00 2001
From: Liam Morland <liam@openplus.ca>
Date: Wed, 15 Jan 2025 16:29:18 -0500
Subject: [PATCH 1/3] Issue #2789105 by liam morland: Make TOC title wrapper
 element configurable

---
 config/install/toc_api.toc_type.default.yml      |  1 +
 config/install/toc_api.toc_type.full.yml         |  1 +
 .../install/toc_api.toc_type.full_numbered.yml   |  1 +
 config/install/toc_api.toc_type.simple.yml       |  1 +
 .../install/toc_api.toc_type.simple_numbered.yml |  1 +
 config/schema/toc_api.toc_type.schema.yml        |  3 +++
 src/TocTypeForm.php                              | 16 ++++++++++++++++
 templates/toc-tree.html.twig                     |  4 +++-
 8 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/config/install/toc_api.toc_type.default.yml b/config/install/toc_api.toc_type.default.yml
index 705ec78..e28d547 100644
--- a/config/install/toc_api.toc_type.default.yml
+++ b/config/install/toc_api.toc_type.default.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
   top_max: 2
diff --git a/config/install/toc_api.toc_type.full.yml b/config/install/toc_api.toc_type.full.yml
index dd671e8..6a24b5f 100644
--- a/config/install/toc_api.toc_type.full.yml
+++ b/config/install/toc_api.toc_type.full.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
   top_max: 2
diff --git a/config/install/toc_api.toc_type.full_numbered.yml b/config/install/toc_api.toc_type.full_numbered.yml
index 61961c7..4906130 100644
--- a/config/install/toc_api.toc_type.full_numbered.yml
+++ b/config/install/toc_api.toc_type.full_numbered.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
   top_max: 2
diff --git a/config/install/toc_api.toc_type.simple.yml b/config/install/toc_api.toc_type.simple.yml
index 20bfad3..122dde5 100644
--- a/config/install/toc_api.toc_type.simple.yml
+++ b/config/install/toc_api.toc_type.simple.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
   top_max: 2
diff --git a/config/install/toc_api.toc_type.simple_numbered.yml b/config/install/toc_api.toc_type.simple_numbered.yml
index bcccb13..cc3f537 100644
--- a/config/install/toc_api.toc_type.simple_numbered.yml
+++ b/config/install/toc_api.toc_type.simple_numbered.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
   top_max: 2
diff --git a/config/schema/toc_api.toc_type.schema.yml b/config/schema/toc_api.toc_type.schema.yml
index 19bdb58..7197e2b 100644
--- a/config/schema/toc_api.toc_type.schema.yml
+++ b/config/schema/toc_api.toc_type.schema.yml
@@ -92,3 +92,6 @@ toc_api.toc_type.*:
               type: ignore
             h6:
               type: ignore
+    title_wrapper:
+      type: string
+      label: 'Title wrapper'
diff --git a/src/TocTypeForm.php b/src/TocTypeForm.php
index 9c1012e..7ba4d6c 100644
--- a/src/TocTypeForm.php
+++ b/src/TocTypeForm.php
@@ -141,6 +141,12 @@ class TocTypeForm extends EntityForm {
       '#type' => 'textfield',
       '#default_value' => $options['title'],
     ];
+    $form['options']['general']['title_wrapper'] = [
+      '#title' => $this->t('Wrapper element for title'),
+      '#type' => 'textfield',
+      '#default_value' => $options['title_wrapper'] ?? 'h3',
+      '#required' => TRUE,
+    ];
     // Hide block option since it is up to TOC submodule to decide how to
     // support it.
     $form['options']['general']['block'] = [
@@ -335,6 +341,16 @@ class TocTypeForm extends EntityForm {
     return $form;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state): void {
+    $title_wrapper = $form_state->getValue(['general', 'title_wrapper']);
+    if (!preg_match('/^[a-zA-Z0-9-]+$/', $title_wrapper)) {
+      $form_state->setErrorByName('general][title_wrapper', $this->t('Invalid HTML tag name.'));
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/templates/toc-tree.html.twig b/templates/toc-tree.html.twig
index 692ae77..0df74a4 100644
--- a/templates/toc-tree.html.twig
+++ b/templates/toc-tree.html.twig
@@ -6,6 +6,7 @@
  * Returns HTML for a nested list representation of a Table of contents..
  *
  * Available variables:
+ * - options: An array of configuration options.
  * - tree: A nested list of header items. Each header item contains:
  *   - list_tag: HTML tag for the list.
  *   - list_attributes: HTML attributes for the list.
@@ -27,7 +28,8 @@
 <div{{ attributes.addClass(classes) }}>
 
   {% if tree.title and not options.block %}
-    <h3>{{ tree.title }}</h3>
+    {% set title_wrapper = options.title_wrapper ?? 'h3' %}
+    <{{ title_wrapper }}>{{ tree.title }}</{{ title_wrapper }}>
   {% endif %}
 
   {{ toc_api_tree.tree_links(tree) }}
-- 
GitLab


From 1b0809e4fc0f091911105359134a18d265ee6a34 Mon Sep 17 00:00:00 2001
From: Liam Morland <liam@openplus.ca>
Date: Thu, 16 Jan 2025 11:00:58 -0500
Subject: [PATCH 2/3] Issue #2789105 by liam morland: Make TOC wrapper element
 configurable

---
 config/install/toc_api.toc_type.default.yml   |  1 +
 config/install/toc_api.toc_type.full.yml      |  1 +
 .../toc_api.toc_type.full_numbered.yml        |  1 +
 config/install/toc_api.toc_type.simple.yml    |  1 +
 .../toc_api.toc_type.simple_numbered.yml      |  1 +
 config/schema/toc_api.toc_type.schema.yml     |  3 +++
 src/TocTypeForm.php                           | 19 ++++++++++++++++---
 templates/toc-tree.html.twig                  |  5 +++--
 8 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/config/install/toc_api.toc_type.default.yml b/config/install/toc_api.toc_type.default.yml
index e28d547..e4dddc2 100644
--- a/config/install/toc_api.toc_type.default.yml
+++ b/config/install/toc_api.toc_type.default.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  wrapper: div
   title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
diff --git a/config/install/toc_api.toc_type.full.yml b/config/install/toc_api.toc_type.full.yml
index 6a24b5f..9218b85 100644
--- a/config/install/toc_api.toc_type.full.yml
+++ b/config/install/toc_api.toc_type.full.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  wrapper: div
   title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
diff --git a/config/install/toc_api.toc_type.full_numbered.yml b/config/install/toc_api.toc_type.full_numbered.yml
index 4906130..5bfcace 100644
--- a/config/install/toc_api.toc_type.full_numbered.yml
+++ b/config/install/toc_api.toc_type.full_numbered.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  wrapper: div
   title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
diff --git a/config/install/toc_api.toc_type.simple.yml b/config/install/toc_api.toc_type.simple.yml
index 122dde5..c4f6a31 100644
--- a/config/install/toc_api.toc_type.simple.yml
+++ b/config/install/toc_api.toc_type.simple.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  wrapper: div
   title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
diff --git a/config/install/toc_api.toc_type.simple_numbered.yml b/config/install/toc_api.toc_type.simple_numbered.yml
index cc3f537..9ad46c2 100644
--- a/config/install/toc_api.toc_type.simple_numbered.yml
+++ b/config/install/toc_api.toc_type.simple_numbered.yml
@@ -14,6 +14,7 @@ options:
   header_id: title
   header_id_prefix: section
   header_exclude_xpath: ''
+  wrapper: div
   title_wrapper: h3
   top_label: 'Back to top'
   top_min: 2
diff --git a/config/schema/toc_api.toc_type.schema.yml b/config/schema/toc_api.toc_type.schema.yml
index 7197e2b..7500d66 100644
--- a/config/schema/toc_api.toc_type.schema.yml
+++ b/config/schema/toc_api.toc_type.schema.yml
@@ -92,6 +92,9 @@ toc_api.toc_type.*:
               type: ignore
             h6:
               type: ignore
+    wrapper:
+      type: string
+      label: 'Wrapper element'
     title_wrapper:
       type: string
       label: 'Title wrapper'
diff --git a/src/TocTypeForm.php b/src/TocTypeForm.php
index 7ba4d6c..a7ae284 100644
--- a/src/TocTypeForm.php
+++ b/src/TocTypeForm.php
@@ -147,6 +147,12 @@ class TocTypeForm extends EntityForm {
       '#default_value' => $options['title_wrapper'] ?? 'h3',
       '#required' => TRUE,
     ];
+    $form['options']['general']['wrapper'] = [
+      '#title' => $this->t('Wrapper element for table of contents'),
+      '#type' => 'textfield',
+      '#default_value' => $options['wrapper'] ?? 'div',
+      '#required' => TRUE,
+    ];
     // Hide block option since it is up to TOC submodule to decide how to
     // support it.
     $form['options']['general']['block'] = [
@@ -345,9 +351,16 @@ class TocTypeForm extends EntityForm {
    * {@inheritdoc}
    */
   public function validateForm(array &$form, FormStateInterface $form_state): void {
-    $title_wrapper = $form_state->getValue(['general', 'title_wrapper']);
-    if (!preg_match('/^[a-zA-Z0-9-]+$/', $title_wrapper)) {
-      $form_state->setErrorByName('general][title_wrapper', $this->t('Invalid HTML tag name.'));
+    // Validate HTML tag names.
+    $elements = [
+      'title_wrapper',
+      'wrapper',
+    ];
+    foreach ($elements as $element) {
+      $tag_name = $form_state->getValue(['general', $element]);
+      if (!preg_match('/^[a-zA-Z0-9-]+$/', $tag_name)) {
+        $form_state->setErrorByName('general][' . $element, $this->t('Invalid HTML tag name.'));
+      }
     }
   }
 
diff --git a/templates/toc-tree.html.twig b/templates/toc-tree.html.twig
index 0df74a4..0f3c593 100644
--- a/templates/toc-tree.html.twig
+++ b/templates/toc-tree.html.twig
@@ -25,7 +25,8 @@
 {% import _self as toc_api_tree %}
 {% set classes = ['toc', 'toc-tree'] %}
 
-<div{{ attributes.addClass(classes) }}>
+{% set wrapper = options.wrapper ?? 'div' %}
+<{{ wrapper }}{{ attributes.addClass(classes) }}>
 
   {% if tree.title and not options.block %}
     {% set title_wrapper = options.title_wrapper ?? 'h3' %}
@@ -34,7 +35,7 @@
 
   {{ toc_api_tree.tree_links(tree) }}
 
-</div>
+</{{ wrapper }}>
 
 {% macro tree_links(item) %}
   {% import _self as toc_api_tree %}
-- 
GitLab


From 937dedd4814abbce67394af425b4895b7c9f81a7 Mon Sep 17 00:00:00 2001
From: Liam Morland <liam@openplus.ca>
Date: Thu, 16 Jan 2025 13:10:14 -0500
Subject: [PATCH 3/3] Issue #2789105 by liam morland: Allow toc-tree classes to
 be customized in preprocess

---
 templates/toc-tree.html.twig | 3 +--
 toc_api.module               | 5 +++++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/templates/toc-tree.html.twig b/templates/toc-tree.html.twig
index 0f3c593..e582d62 100644
--- a/templates/toc-tree.html.twig
+++ b/templates/toc-tree.html.twig
@@ -23,10 +23,9 @@
   @see http://twig.sensiolabs.org/doc/tags/macro.html
 #}
 {% import _self as toc_api_tree %}
-{% set classes = ['toc', 'toc-tree'] %}
 
 {% set wrapper = options.wrapper ?? 'div' %}
-<{{ wrapper }}{{ attributes.addClass(classes) }}>
+<{{ wrapper }}{{ attributes }}>
 
   {% if tree.title and not options.block %}
     {% set title_wrapper = options.title_wrapper ?? 'h3' %}
diff --git a/toc_api.module b/toc_api.module
index cdd60e1..012f1ea 100755
--- a/toc_api.module
+++ b/toc_api.module
@@ -118,6 +118,11 @@ function template_preprocess_toc_tree(&$variables) {
 
   $variables['options'] = $toc->getOptions();
 
+  // Add default classes.
+  $variables['attributes']['class'] = (array) $variables['attributes']['class'];
+  $variables['attributes']['class'][] = 'toc';
+  $variables['attributes']['class'][] = 'toc-tree';
+
   $variables['attributes'] = new Attribute($variables['attributes']);
 
   $variables['#attached']['library'][] = 'toc_api/toc.tree';
-- 
GitLab