From 25a726729bcab1ee3e5fd172638c5e881fa3a79b Mon Sep 17 00:00:00 2001
From: Andreas Fernandez <a.fernandez@scripting-base.de>
Date: Tue, 11 Apr 2023 11:26:10 +0200
Subject: [PATCH] [BUGFIX] Render only one toggle per node in trees

In non-SVG trees, there are currently two icons representing the node's
expansion state rendered that get toggled via CSS rules based on
hard-coded aria attributes and icon identifiers, which is a bad
practice.

To solve this, a new web component `typo3-backend-tree-node-toggle` is
added whose only responsibility is to render the correct icon based on
its `aria-expanded` attribute

Resolves: #100549
Releases: main, 12.4
Change-Id: I1a955731fc21da01a914b1ef721d7eb2d1e18ed5
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/79826
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
---
 Build/Sources/Sass/component/_treelist.scss   |  8 -----
 .../backend/tree/tree-node-toggle.ts          | 31 +++++++++++++++++++
 .../Templates/PageTsConfig/Active.html        | 11 +++----
 .../Templates/PageTsConfig/Includes.html      | 11 +++----
 .../backend/Resources/Public/Css/backend.css  |  2 --
 .../JavaScript/tree/tree-node-toggle.js       | 13 ++++++++
 .../Application/Template/TemplateCest.php     |  4 +--
 .../Controller/ConfigurationController.php    | 11 ++++---
 .../Private/Templates/Configuration.html      |  2 +-
 .../Private/Partials/ActiveTree.html          |  9 ++----
 .../Private/Partials/AnalyzerTree.html        | 11 +++----
 .../Private/Templates/ActiveMain.html         |  2 +-
 12 files changed, 70 insertions(+), 45 deletions(-)
 create mode 100644 Build/Sources/TypeScript/backend/tree/tree-node-toggle.ts
 create mode 100644 typo3/sysext/backend/Resources/Public/JavaScript/tree/tree-node-toggle.js

diff --git a/Build/Sources/Sass/component/_treelist.scss b/Build/Sources/Sass/component/_treelist.scss
index d986bcb2bdd5..941f2b8b9373 100644
--- a/Build/Sources/Sass/component/_treelist.scss
+++ b/Build/Sources/Sass/component/_treelist.scss
@@ -205,14 +205,6 @@
     typo3-backend-icon {
         --icon-color-primary: var(--treelist-color);
     }
-
-    &[aria-expanded="true"] typo3-backend-icon[identifier="actions-chevron-right"] {
-        display: none;
-    }
-
-    &[aria-expanded="false"] typo3-backend-icon[identifier="actions-chevron-down"] {
-        display: none;
-    }
 }
 
 .treelist-root {
diff --git a/Build/Sources/TypeScript/backend/tree/tree-node-toggle.ts b/Build/Sources/TypeScript/backend/tree/tree-node-toggle.ts
new file mode 100644
index 000000000000..45a4cebca6d3
--- /dev/null
+++ b/Build/Sources/TypeScript/backend/tree/tree-node-toggle.ts
@@ -0,0 +1,31 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+import { customElement, property } from 'lit/decorators';
+import { html, LitElement, TemplateResult } from 'lit';
+import '@typo3/backend/element/icon-element';
+
+@customElement('typo3-backend-tree-node-toggle')
+export default class TreeNodeToggle extends LitElement {
+  @property({ type: String, reflect: true, attribute: 'aria-expanded' }) expanded: string = 'false';
+
+  public render(): TemplateResult | symbol {
+    return html`<typo3-backend-icon size="small" identifier="${this.expanded === 'true' ? 'actions-chevron-down' : 'actions-chevron-right'}"></typo3-backend-icon>`;
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'typo3-backend-tree-node-toggle': TreeNodeToggle;
+  }
+}
diff --git a/typo3/sysext/backend/Resources/Private/Templates/PageTsConfig/Active.html b/typo3/sysext/backend/Resources/Private/Templates/PageTsConfig/Active.html
index 1d6f1ecb5a45..944911236ff7 100644
--- a/typo3/sysext/backend/Resources/Private/Templates/PageTsConfig/Active.html
+++ b/typo3/sysext/backend/Resources/Private/Templates/PageTsConfig/Active.html
@@ -12,7 +12,7 @@
         includeJavaScriptModules="{
             0: '@typo3/backend/context-menu.js',
             1: '@typo3/backend/element/immediate-action-element.js',
-            2: '@typo3/backend/element/icon-element.js',
+            2: '@typo3/backend/tree/tree-node-toggle.js',
             3: '@typo3/backend/utility/collapse-state-persister.js',
             4: '@typo3/backend/utility/collapse-state-search.js'
         }"
@@ -361,15 +361,12 @@
         </f:if>
         <li>
             <f:if condition="{child.children}">
-                <a
+                <typo3-backend-tree-node-toggle
                     class="treelist-control collapsed"
                     data-bs-toggle="collapse"
                     data-bs-target="#collapse-list-{child.identifier}"
-                    aria-expanded="false"
-                >
-                    <typo3-backend-icon size="small" identifier="actions-chevron-right"></typo3-backend-icon>
-                    <typo3-backend-icon size="small" identifier="actions-chevron-down"></typo3-backend-icon>
-                </a>
+                    aria-expanded="false">
+                </typo3-backend-tree-node-toggle>
             </f:if>
             <span class="treelist-group treelist-group-monospace">
                 <span class="treelist-label">{child.name}</span>
diff --git a/typo3/sysext/backend/Resources/Private/Templates/PageTsConfig/Includes.html b/typo3/sysext/backend/Resources/Private/Templates/PageTsConfig/Includes.html
index 70823655f610..733809b9bc5a 100644
--- a/typo3/sysext/backend/Resources/Private/Templates/PageTsConfig/Includes.html
+++ b/typo3/sysext/backend/Resources/Private/Templates/PageTsConfig/Includes.html
@@ -12,7 +12,7 @@
         includeJavaScriptModules="{
             0: '@typo3/backend/context-menu.js',
             1: '@typo3/backend/element/immediate-action-element.js',
-            2: '@typo3/backend/element/icon-element.js',
+            2: '@typo3/backend/tree/tree-node-toggle.js',
             3: '@typo3/backend/utility/collapse-state-persister.js',
             4: '@typo3/backend/pagetsconfig/pagetsconfig-includes.js'
         }"
@@ -113,15 +113,12 @@
         <f:for each="{tree.nextChild}" as="child">
             <li>
                 <f:if condition="{child.children}">
-                    <a
+                    <typo3-backend-tree-node-toggle
                         class="treelist-control treelist-control-collapsed"
                         data-bs-toggle="collapse"
                         data-bs-target="#collapse-list-{child.identifier}"
-                        aria-expanded="false"
-                    >
-                        <typo3-backend-icon size="small" identifier="actions-chevron-down"></typo3-backend-icon>
-                        <typo3-backend-icon size="small" identifier="actions-chevron-right"></typo3-backend-icon>
-                    </a>
+                        aria-expanded="false">
+                    </typo3-backend-tree-node-toggle>
                 </f:if>
                 <div class="row justify-content-between">
                     <div class="col">
diff --git a/typo3/sysext/backend/Resources/Public/Css/backend.css b/typo3/sysext/backend/Resources/Public/Css/backend.css
index 916fbf18b131..9ad4f9ae91b5 100644
--- a/typo3/sysext/backend/Resources/Public/Css/backend.css
+++ b/typo3/sysext/backend/Resources/Public/Css/backend.css
@@ -3840,8 +3840,6 @@ typo3-backend-live-search-result-item-action>* .livesearch-result-item-title .sm
 .treelist-control:before,.treelist-control:target:before{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:var(--treelist-control-size);height:var(--treelist-control-size);background-color:var(--treelist-bg)}
 .treelist-control:active,.treelist-control:focus,.treelist-control:hover{cursor:pointer;outline:0;text-decoration:none}
 .treelist-control typo3-backend-icon{--icon-color-primary:var(--treelist-color)}
-.treelist-control[aria-expanded=true] typo3-backend-icon[identifier=actions-chevron-right]{display:none}
-.treelist-control[aria-expanded=false] typo3-backend-icon[identifier=actions-chevron-down]{display:none}
 .treelist-root{padding-left:0}
 .treelist-root:before{display:none}
 .treelist-root>li{padding-left:var(--treelist-control-size)}
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/tree/tree-node-toggle.js b/typo3/sysext/backend/Resources/Public/JavaScript/tree/tree-node-toggle.js
new file mode 100644
index 000000000000..d486a2925111
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/tree/tree-node-toggle.js
@@ -0,0 +1,13 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+var __decorate=function(e,t,o,r){var n,c=arguments.length,d=c<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,o):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)d=Reflect.decorate(e,t,o,r);else for(var l=e.length-1;l>=0;l--)(n=e[l])&&(d=(c<3?n(d):c>3?n(t,o,d):n(t,o))||d);return c>3&&d&&Object.defineProperty(t,o,d),d};import{customElement,property}from"lit/decorators.js";import{html,LitElement}from"lit";import"@typo3/backend/element/icon-element.js";let TreeNodeToggle=class extends LitElement{constructor(){super(...arguments),this.expanded="false"}render(){return html`<typo3-backend-icon size="small" identifier="${"true"===this.expanded?"actions-chevron-down":"actions-chevron-right"}"></typo3-backend-icon>`}};__decorate([property({type:String,reflect:!0,attribute:"aria-expanded"})],TreeNodeToggle.prototype,"expanded",void 0),TreeNodeToggle=__decorate([customElement("typo3-backend-tree-node-toggle")],TreeNodeToggle);export default TreeNodeToggle;
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Acceptance/Application/Template/TemplateCest.php b/typo3/sysext/core/Tests/Acceptance/Application/Template/TemplateCest.php
index ca0210bf89d9..72958ccd2fd1 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application/Template/TemplateCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Application/Template/TemplateCest.php
@@ -108,10 +108,10 @@ final class TemplateCest
         $I->waitForText('Setup');
         // find and open page in tree
         $I->waitForText('page = PAGE');
-        $I->click('//span[@class="treelist-label"]/a[text()=\'page\']/../../../a');
+        $I->click('//span[@class="treelist-label"]/a[text()=\'page\']/../../../typo3-backend-tree-node-toggle');
         // find and open page.10 in tree
         $I->waitForText('10 = TEXT');
-        $I->click('//span[@class="treelist-label"]/a[text()=\'page\']/../../../div/ul//span[@class="treelist-label"]/a[text()=\'10\']/../../../a');
+        $I->click('//span[@class="treelist-label"]/a[text()=\'page\']/../../../div/ul//span[@class="treelist-label"]/a[text()=\'10\']/../../../typo3-backend-tree-node-toggle');
         // find and edit page.10.value in tree
         $I->waitForText('value = Hello Acceptance Test!');
         $I->click('//span[@class="treelist-label"]/a[text()=\'10\']/../../../div/ul//span[@class="treelist-label"]/a[text()=\'value\']');
diff --git a/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php b/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
index 7c73156fcfec..cf95cd8c79ea 100644
--- a/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
+++ b/typo3/sysext/lowlevel/Classes/Controller/ConfigurationController.php
@@ -101,10 +101,13 @@ final class ConfigurationController
             $newIdentifier = '';
             if ($isValueIterable && !empty($value)) {
                 $newIdentifier = hash('xxh3', $incomingIdentifier . $key);
-                $html .= '<a class="treelist-control collapsed" data-bs-toggle="collapse" data-bs-target="#collapse-list-' . $newIdentifier . '" aria-expanded="false">' .
-                    '<typo3-backend-icon size="small" identifier="actions-chevron-right"></typo3-backend-icon>' .
-                    '<typo3-backend-icon size="small" identifier="actions-chevron-down"></typo3-backend-icon>' .
-                    '</a>';
+                $html .= '
+                    <typo3-backend-tree-node-toggle
+                        class="treelist-control collapsed"
+                        data-bs-toggle="collapse"
+                        data-bs-target="#collapse-list-' . $newIdentifier . '"
+                        aria-expanded="false">
+                    </typo3-backend-tree-node-toggle>';
             }
             $html .= '<span class="treelist-group treelist-group-monospace">';
             $html .= '<span class="treelist-label">' . htmlspecialchars((string)$key) . '</span>';
diff --git a/typo3/sysext/lowlevel/Resources/Private/Templates/Configuration.html b/typo3/sysext/lowlevel/Resources/Private/Templates/Configuration.html
index dac60a5038bc..6c347010ba56 100644
--- a/typo3/sysext/lowlevel/Resources/Private/Templates/Configuration.html
+++ b/typo3/sysext/lowlevel/Resources/Private/Templates/Configuration.html
@@ -10,7 +10,7 @@
 
     <f:be.pageRenderer
         includeJavaScriptModules="{
-            0: '@typo3/backend/element/icon-element.js',
+            0: '@typo3/backend/tree/tree-node-toggle.js',
             1: '@typo3/backend/utility/collapse-state-persister.js',
             2: '@typo3/backend/utility/collapse-state-search.js'
         }"
diff --git a/typo3/sysext/tstemplate/Resources/Private/Partials/ActiveTree.html b/typo3/sysext/tstemplate/Resources/Private/Partials/ActiveTree.html
index 0f63168a1454..d9d3faa0658f 100644
--- a/typo3/sysext/tstemplate/Resources/Private/Partials/ActiveTree.html
+++ b/typo3/sysext/tstemplate/Resources/Private/Partials/ActiveTree.html
@@ -31,15 +31,12 @@
     </f:if>
     <li>
         <f:if condition="{child.children}">
-            <a
+            <typo3-backend-tree-node-toggle
                 class="treelist-control collapsed"
                 data-bs-toggle="collapse"
                 data-bs-target="#collapse-list-{child.identifier}"
-                aria-expanded="false"
-            >
-                <typo3-backend-icon size="small" identifier="actions-chevron-right"></typo3-backend-icon>
-                <typo3-backend-icon size="small"identifier="actions-chevron-down"></typo3-backend-icon>
-            </a>
+                aria-expanded="false">
+            </typo3-backend-tree-node-toggle>
         </f:if>
         <span class="treelist-group treelist-group-monospace">
 
diff --git a/typo3/sysext/tstemplate/Resources/Private/Partials/AnalyzerTree.html b/typo3/sysext/tstemplate/Resources/Private/Partials/AnalyzerTree.html
index 5618f9ab7e92..0737c03e0e06 100644
--- a/typo3/sysext/tstemplate/Resources/Private/Partials/AnalyzerTree.html
+++ b/typo3/sysext/tstemplate/Resources/Private/Partials/AnalyzerTree.html
@@ -7,7 +7,7 @@
 
 <f:be.pageRenderer
     includeJavaScriptModules="{
-        0: '@typo3/backend/element/icon-element.js',
+        0: '@typo3/backend/tree/tree-node-toggle.js',
         1: '@typo3/backend/utility/collapse-state-persister.js'
     }"
 />
@@ -18,15 +18,12 @@
     <f:for each="{tree.nextChild}" as="child">
         <li>
             <f:if condition="{child.children}">
-                <a
+                <typo3-backend-tree-node-toggle
                     class="treelist-control treelist-control-collapsed"
                     data-bs-toggle="collapse"
                     data-bs-target="#collapse-list-{child.identifier}"
-                    aria-expanded="false"
-                >
-                    <typo3-backend-icon size="small" identifier="actions-chevron-down"></typo3-backend-icon>
-                    <typo3-backend-icon size="small" identifier="actions-chevron-right"></typo3-backend-icon>
-                </a>
+                    aria-expanded="false">
+                </typo3-backend-tree-node-toggle>
             </f:if>
             <div class="row justify-content-between">
                 <div class="col">
diff --git a/typo3/sysext/tstemplate/Resources/Private/Templates/ActiveMain.html b/typo3/sysext/tstemplate/Resources/Private/Templates/ActiveMain.html
index 92ad7746e884..6110a25bcb7c 100644
--- a/typo3/sysext/tstemplate/Resources/Private/Templates/ActiveMain.html
+++ b/typo3/sysext/tstemplate/Resources/Private/Templates/ActiveMain.html
@@ -10,7 +10,7 @@
         includeJavaScriptModules="{
             0: '@typo3/backend/context-menu.js',
             1: '@typo3/backend/element/immediate-action-element.js',
-            3: '@typo3/backend/element/icon-element.js',
+            3: '@typo3/backend/tree/tree-node-toggle.js',
             4: '@typo3/backend/utility/collapse-state-persister.js',
             5: '@typo3/backend/utility/collapse-state-search.js'
         }"
-- 
GitLab