From cd14de1fd5043762c2f030d1a2b1cb4123a2f1c7 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Thu, 12 Aug 2021 13:54:04 +0200
Subject: [PATCH] [BUGFIX] Optimize JavaScript implementation of new
 functionality

This change
* removes an unneeded CSS class from all tree items, making the DOM
  smaller
* adds passive listeners for touch events in Resizables. This
  way browsers do not complain anymore

Resolves: #94835
Releases: master
Change-Id: Ia240751c9f623c753be80b9fc8063bb536c54481
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/70482
Tested-by: core-ci <typo3@b13.com>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
---
 .../Resources/Public/TypeScript/SvgTree.ts     |  6 +-----
 .../TypeScript/Viewport/ResizableNavigation.ts | 18 +++++++++++++-----
 .../Resources/Public/JavaScript/SvgTree.js     |  2 +-
 .../JavaScript/Viewport/ResizableNavigation.js |  8 ++++----
 .../FormEngine/CategoryTreeCest.php            |  4 ++--
 .../Application/Impexp/ExportCest.php          |  2 +-
 .../Application/Impexp/UsersCest.php           |  2 +-
 .../Page/AddPageInPageModuleCest.php           |  2 +-
 8 files changed, 24 insertions(+), 20 deletions(-)

diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/SvgTree.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/SvgTree.ts
index bd21b3776f6a..fc138fea3fd5 100644
--- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/SvgTree.ts
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/SvgTree.ts
@@ -799,7 +799,7 @@ export class SvgTree extends LitElement {
     nodes = nodes
       .enter()
       .append('g')
-      .attr('class', this.getNodeClass)
+      .attr('class', 'node')
       .attr('id', (node: TreeNode) => {
         return 'identifier-' + node.stateIdentifier;
       })
@@ -868,10 +868,6 @@ export class SvgTree extends LitElement {
     return label;
   }
 
-  protected getNodeClass(node: TreeNode): string {
-    return 'node identifier-' + node.stateIdentifier;
-  }
-
   /**
    * Finds node by its stateIdentifier (e.g. "0_360")
    */
diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Viewport/ResizableNavigation.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Viewport/ResizableNavigation.ts
index 6b8359c99c04..0b09b57c3a77 100644
--- a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Viewport/ResizableNavigation.ts
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/Viewport/ResizableNavigation.ts
@@ -12,7 +12,7 @@
  */
 
 import {html, LitElement, TemplateResult} from 'lit';
-import {customElement, /*eventOptions,*/ property, state} from 'lit/decorators';
+import {customElement, property, state} from 'lit/decorators';
 import {lll} from 'TYPO3/CMS/Core/lit-helper';
 import Persistent = require('../Storage/Persistent');
 import 'TYPO3/CMS/Backend/Element/IconElement';
@@ -51,17 +51,26 @@ class ResizableNavigation extends LitElement {
     return this;
   }
 
+  protected async firstUpdated() {
+    // Give the browser a chance to paint
+    await new Promise((r) => setTimeout(r, 0));
+    // needed to avoid any issues related to browsers, as lit-decorators (eventOptions) do not work yet
+    // properly https://lit-element.polymer-project.org/guide/events - @touchstart would throw warnings in browser console without passive=true
+    this.querySelector('.scaffold-content-navigation-switcher-btn').addEventListener('touchstart', this.toggleNavigation, {passive: true});
+    this.querySelector('.scaffold-content-navigation-drag').addEventListener('touchstart', this.startResizeNavigation, {passive: true});
+  }
+
   protected render(): TemplateResult {
     return html`
       <div class="scaffold-content-navigation-switcher">
-        <button @mouseup="${this.toggleNavigation}" @touchstart="${this.toggleNavigation}" class="btn btn-default btn-borderless scaffold-content-navigation-switcher-btn scaffold-content-navigation-switcher-open" role="button" title="${lll('viewport_navigation_show')}">
+        <button @mouseup="${this.toggleNavigation}" class="btn btn-default btn-borderless scaffold-content-navigation-switcher-btn scaffold-content-navigation-switcher-open" role="button" title="${lll('viewport_navigation_show')}">
           <typo3-backend-icon identifier="actions-chevron-right" size="small"></typo3-backend-icon>
         </button>
-        <button @mouseup="${this.toggleNavigation}" @touchstart="${this.toggleNavigation}" class="btn btn-default btn-borderless scaffold-content-navigation-switcher-btn scaffold-content-navigation-switcher-close" role="button" title="${lll('viewport_navigation_hide')}">
+        <button @mouseup="${this.toggleNavigation}" class="btn btn-default btn-borderless scaffold-content-navigation-switcher-btn scaffold-content-navigation-switcher-close" role="button" title="${lll('viewport_navigation_hide')}">
           <typo3-backend-icon identifier="actions-chevron-left" size="small"></typo3-backend-icon>
         </button>
       </div>
-      <div @mousedown="${this.startResizeNavigation}" @touchstart="${this.startResizeNavigation}" class="scaffold-content-navigation-drag ${this.resizing ? 'resizing' : ''}"></div>
+      <div @mousedown="${this.startResizeNavigation}" class="scaffold-content-navigation-drag ${this.resizing ? 'resizing' : ''}"></div>
     `;
   }
 
@@ -96,7 +105,6 @@ class ResizableNavigation extends LitElement {
     this.setNavigationWidth(width);
   }
 
-  //@eventOptions({passive: true})
   private startResizeNavigation = (event: MouseEvent | TouchEvent) => {
     if (event instanceof MouseEvent && event.button === 2) {
       return;
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/SvgTree.js b/typo3/sysext/backend/Resources/Public/JavaScript/SvgTree.js
index e2de295fee9d..c5169ed66d5e 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/SvgTree.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/SvgTree.js
@@ -27,7 +27,7 @@ var __createBinding=this&&this.__createBinding||(Object.create?function(e,t,s,i)
         </g>
         <defs></defs>
       </svg>
-    `}firstUpdated(){this.svg=n.select(this.querySelector("svg")),this.container=n.select(this.querySelector(".nodes-wrapper")).attr("transform","translate("+this.settings.indentWidth/2+","+this.settings.nodeHeight/2+")"),this.nodesBgContainer=n.select(this.querySelector(".nodes-bg")),this.nodesActionsContainer=n.select(this.querySelector(".nodes-actions")),this.linksContainer=n.select(this.querySelector(".links")),this.nodesContainer=n.select(this.querySelector(".nodes")),this.doSetup(this.setup||{}),this.updateView()}updateView(){this.updateScrollPosition(),this.updateVisibleNodes(),this.settings.actions&&this.settings.actions.length&&this.nodesActionsContainer.attr("transform","translate("+(this.querySelector("svg").clientWidth-16-16*this.settings.actions.length)+",0)")}disableSelectedNodes(){this.getSelectedNodes().forEach(e=>{!0===e.checked&&(e.checked=!1)})}updateNodeActions(e){return this.settings.actions&&this.settings.actions.length?(this.nodesActionsContainer.selectAll(".node-action").selectChildren().remove(),e.enter().append("g").merge(e).attr("class","node-action").on("mouseover",(e,t)=>this.onMouseOverNode(t)).on("mouseout",(e,t)=>this.onMouseOutOfNode(t)).attr("data-state-id",this.getNodeStateIdentifier).attr("transform",this.getNodeActionTransform)):e.enter()}createIconAreaForAction(e,t){const s=e.append("svg").attr("class","node-icon-container").attr("height","20").attr("width","20").attr("x","0").attr("y","0");s.append("rect").attr("width","20").attr("height","20").attr("y","0").attr("x","0").attr("class","node-icon-click");s.append("svg").attr("height","16").attr("width","16").attr("y","2").attr("x","2").attr("class","node-icon-inner").append("use").attr("class","node-icon").attr("xlink:href","#icon-"+t)}isNodeSelectable(e){return!0}appendTextElement(e){return e.append("text").attr("dx",e=>this.textPosition+(e.locked?15:0)).attr("dy",5).attr("class","node-name").on("click",(e,t)=>this.selectNode(t,!0))}nodesUpdate(e){return(e=e.enter().append("g").attr("class",this.getNodeClass).attr("id",e=>"identifier-"+e.stateIdentifier).attr("role","treeitem").attr("aria-owns",e=>e.hasChildren?"group-identifier-"+e.stateIdentifier:null).attr("aria-level",this.getNodeDepth).attr("aria-setsize",this.getNodeSetsize).attr("aria-posinset",this.getNodePositionInSet).attr("aria-expanded",e=>e.hasChildren?e.expanded:null).attr("transform",this.getNodeTransform).attr("data-state-id",this.getNodeStateIdentifier).attr("title",this.getNodeTitle).on("mouseover",(e,t)=>this.onMouseOverNode(t)).on("mouseout",(e,t)=>this.onMouseOutOfNode(t)).on("contextmenu",(e,t)=>{e.preventDefault(),this.dispatchEvent(new CustomEvent("typo3:svg-tree:node-context",{detail:{node:t}}))})).append("text").text(e=>e.readableRootline).attr("class","node-rootline").attr("dx",0).attr("dy",-15).attr("visibility",e=>e.readableRootline?"visible":"hidden"),e}getNodeIdentifier(e){return e.identifier}getNodeDepth(e){return e.depth}getNodeSetsize(e){return e.siblingsCount}getNodePositionInSet(e){return e.siblingsPosition}getNodeStateIdentifier(e){return e.stateIdentifier}getNodeLabel(e){let t=(e.prefix||"")+e.name+(e.suffix||"");const s=document.createElement("div");if(s.textContent=t,t=s.innerHTML,this.searchTerm){const e=new RegExp(this.searchTerm,"gi");t=t.replace(e,'<tspan class="node-highlight-text">$&</tspan>')}return t}getNodeClass(e){return"node identifier-"+e.stateIdentifier}getNodeByIdentifier(e){return this.nodes.find(t=>t.stateIdentifier===e)}getNodeBgClass(e,t,s){let i="node-bg",n=null,o=null;return"object"==typeof s&&(n=s.data()[t-1],o=s.data()[t+1]),e.checked&&(i+=" node-selected"),(n&&e.depth>n.depth||!n)&&(e.firstChild=!0,i+=" node-first-child"),(o&&e.depth>o.depth||!o)&&(e.lastChild=!0,i+=" node-last-child"),e.class&&(i+=" "+e.class),i}getNodeTitle(e){return e.tip?e.tip:"uid="+e.identifier}getChevronTransform(e){return e.expanded?"translate(16,0) rotate(90)":" rotate(0)"}getChevronColor(e){return e.expanded?"#000":"#8e8e8e"}getToggleVisibility(e){return e.hasChildren?"visible":"hidden"}getChevronClass(e){return"chevron "+(e.expanded?"expanded":"collapsed")}getLinkPath(e){const t=e.target.x,s=e.target.y,i=[];return i.push("M"+e.source.x+" "+e.source.y),i.push("V"+s),e.target.hasChildren?i.push("H"+(t-2)):i.push("H"+(t+this.settings.indentWidth/4-2)),i.join(" ")}getNodeTransform(e){return"translate("+(e.x||0)+","+(e.y||0)+")"}getNodeBgTransform(e){return"translate(-8, "+((e.y||0)-10)+")"}getNodeActionTransform(e){return"translate(-10, "+((e.y||0)-10)+")"}clickOnIcon(e){this.dispatchEvent(new CustomEvent("typo3:svg-tree:node-context",{detail:{node:e}}))}chevronClick(e){e.expanded?this.hideChildren(e):this.showChildren(e),this.prepareDataForVisibleNodes(),this.updateVisibleNodes()}enterSvgElements(e){if(this.settings.showIcons){const e=Object.values(this.icons).filter(e=>""!==e.icon&&null!==e.icon),t=this.iconsContainer.selectAll(".icon-def").data(e,e=>e.identifier);t.exit().remove(),t.enter().append("g").attr("class","icon-def").attr("id",e=>"icon-"+e.identifier).append(e=>{if(e.icon instanceof SVGElement)return e.icon;const t="<svg>"+e.icon+"</svg>";return(new DOMParser).parseFromString(t,"image/svg+xml").documentElement.firstChild})}const t=this.nodesUpdate(e);let s=t.append("g").attr("class","toggle").attr("visibility",this.getToggleVisibility).attr("transform","translate(-8, -8)").on("click",(e,t)=>this.chevronClick(t));if(s.append("path").style("opacity",0).attr("d","M 0 0 L 16 0 L 16 16 L 0 16 Z"),s.append("path").attr("class","chevron").attr("d","M 4 3 L 13 8 L 4 13 Z"),this.settings.showIcons){const e=t.append("svg").attr("class","node-icon-container").attr("title",this.getNodeTitle).attr("height","20").attr("width","20").attr("x","6").attr("y","-10").attr("data-bs-toggle","tooltip").on("click",(e,t)=>{e.preventDefault(),this.clickOnIcon(t)});e.append("rect").style("opacity",0).attr("width","20").attr("height","20").attr("y","0").attr("x","0").attr("class","node-icon-click");const s=e.append("svg").attr("height","16").attr("width","16").attr("y","2").attr("x","2").attr("class","node-icon-inner");s.append("use").attr("class","node-icon").attr("data-uid",this.getNodeIdentifier);s.append("svg").attr("height","11").attr("width","11").attr("y","5").attr("x","5").append("use").attr("class","node-icon-overlay");s.append("svg").attr("height","11").attr("width","11").attr("y","5").attr("x","5").append("use").attr("class","node-icon-locked")}return l.initialize('[data-bs-toggle="tooltip"]',this.tooltipOptions),this.appendTextElement(t),e.merge(t)}updateScrollPosition(){this.viewportHeight=this.getBoundingClientRect().height,this.scrollBottom=this.scrollTop+this.viewportHeight+this.viewportHeight/2,setTimeout(()=>{l.hide(document.querySelector(this.tooltipOptions.container).querySelectorAll(".bs-tooltip-end"))},this.tooltipOptions.delay)}onMouseOverNode(e){e.isOver=!0,this.hoveredNode=e;let t=this.svg.select('.nodes-bg .node-bg[data-state-id="'+e.stateIdentifier+'"]');t.size()&&t.classed("node-over",!0).attr("rx","3").attr("ry","3");let s=this.nodesActionsContainer.select('.node-action[data-state-id="'+e.stateIdentifier+'"]');s.size()&&(s.classed("node-action-over",!0),s.attr("fill",t.style("fill")))}onMouseOutOfNode(e){e.isOver=!1,this.hoveredNode=null;let t=this.svg.select('.nodes-bg .node-bg[data-state-id="'+e.stateIdentifier+'"]');t.size()&&t.classed("node-over node-alert",!1).attr("rx","0").attr("ry","0");let s=this.nodesActionsContainer.select('.node-action[data-state-id="'+e.stateIdentifier+'"]');s.size()&&s.classed("node-action-over",!1)}handleKeyboardInteraction(e){const t=e.target;let s=n.select(t).datum();if(-1===[a.KeyTypesEnum.ENTER,a.KeyTypesEnum.SPACE,a.KeyTypesEnum.END,a.KeyTypesEnum.HOME,a.KeyTypesEnum.LEFT,a.KeyTypesEnum.UP,a.KeyTypesEnum.RIGHT,a.KeyTypesEnum.DOWN].indexOf(e.keyCode))return;e.preventDefault();const i=t.parentNode;switch(e.keyCode){case a.KeyTypesEnum.END:this.scrollTop=this.lastElementChild.getBoundingClientRect().height+this.settings.nodeHeight-this.viewportHeight,i.scrollIntoView({behavior:"smooth",block:"end"}),this.updateVisibleNodes(),this.switchFocus(i.lastElementChild);break;case a.KeyTypesEnum.HOME:this.scrollTo({top:this.nodes[0].y,behavior:"smooth"}),this.prepareDataForVisibleNodes(),this.updateVisibleNodes(),this.switchFocus(i.firstElementChild);break;case a.KeyTypesEnum.LEFT:if(s.expanded)s.hasChildren&&(this.hideChildren(s),this.prepareDataForVisibleNodes(),this.updateVisibleNodes());else if(s.parents.length>0){let e=this.nodes[s.parents[0]];this.scrollNodeIntoVisibleArea(e,"up"),this.switchFocusNode(e)}break;case a.KeyTypesEnum.UP:this.scrollNodeIntoVisibleArea(s,"up"),this.switchFocus(t.previousSibling);break;case a.KeyTypesEnum.RIGHT:s.expanded?(this.scrollNodeIntoVisibleArea(s,"down"),this.switchFocus(t.nextSibling)):s.hasChildren&&(this.showChildren(s),this.prepareDataForVisibleNodes(),this.updateVisibleNodes(),this.switchFocus(t));break;case a.KeyTypesEnum.DOWN:this.scrollNodeIntoVisibleArea(s,"down"),this.switchFocus(t.nextSibling);break;case a.KeyTypesEnum.ENTER:case a.KeyTypesEnum.SPACE:this.selectNode(s,!0)}}scrollNodeIntoVisibleArea(e,t="up"){let s=this.scrollTop;if("up"===t&&s>e.y-this.settings.nodeHeight)s=e.y-this.settings.nodeHeight;else{if(!("down"===t&&s+this.viewportHeight<=e.y+3*this.settings.nodeHeight))return;s+=this.settings.nodeHeight}this.scrollTo({top:s,behavior:"smooth"}),this.updateVisibleNodes()}updateLinks(){const e=this.data.links.filter(e=>e.source.y<=this.scrollBottom&&e.target.y>=this.scrollTop-this.settings.nodeHeight).map(e=>(e.source.owns=e.source.owns||[],e.source.owns.push("identifier-"+e.target.stateIdentifier),e)),t=this.linksContainer.selectAll(".link").data(e);t.exit().remove(),t.enter().append("path").attr("class","link").attr("id",this.getGroupIdentifier).attr("role",e=>1===e.target.siblingsPosition&&e.source.owns.length>0?"group":null).attr("aria-owns",e=>1===e.target.siblingsPosition&&e.source.owns.length>0?e.source.owns.join(" "):null).merge(t).attr("d",e=>this.getLinkPath(e))}getGroupIdentifier(e){return 1===e.target.siblingsPosition?"group-identifier-"+e.source.stateIdentifier:null}}__decorate([i.property({type:Object})],u.prototype,"setup",void 0),__decorate([i.state()],u.prototype,"settings",void 0),t.SvgTree=u;let g=class extends s.LitElement{constructor(){super(...arguments),this.tree=null,this.settings={searchInput:".search-input",filterTimeout:450}}createRenderRoot(){return this}firstUpdated(){const e=this.querySelector(this.settings.searchInput);e&&(new p.default("input",e=>{const t=e.target;this.tree.filter(t.value.trim())},this.settings.filterTimeout).bindTo(e),e.focus(),e.clearable({onClear:()=>{this.tree.resetFilter()}}))}render(){return s.html`
+    `}firstUpdated(){this.svg=n.select(this.querySelector("svg")),this.container=n.select(this.querySelector(".nodes-wrapper")).attr("transform","translate("+this.settings.indentWidth/2+","+this.settings.nodeHeight/2+")"),this.nodesBgContainer=n.select(this.querySelector(".nodes-bg")),this.nodesActionsContainer=n.select(this.querySelector(".nodes-actions")),this.linksContainer=n.select(this.querySelector(".links")),this.nodesContainer=n.select(this.querySelector(".nodes")),this.doSetup(this.setup||{}),this.updateView()}updateView(){this.updateScrollPosition(),this.updateVisibleNodes(),this.settings.actions&&this.settings.actions.length&&this.nodesActionsContainer.attr("transform","translate("+(this.querySelector("svg").clientWidth-16-16*this.settings.actions.length)+",0)")}disableSelectedNodes(){this.getSelectedNodes().forEach(e=>{!0===e.checked&&(e.checked=!1)})}updateNodeActions(e){return this.settings.actions&&this.settings.actions.length?(this.nodesActionsContainer.selectAll(".node-action").selectChildren().remove(),e.enter().append("g").merge(e).attr("class","node-action").on("mouseover",(e,t)=>this.onMouseOverNode(t)).on("mouseout",(e,t)=>this.onMouseOutOfNode(t)).attr("data-state-id",this.getNodeStateIdentifier).attr("transform",this.getNodeActionTransform)):e.enter()}createIconAreaForAction(e,t){const s=e.append("svg").attr("class","node-icon-container").attr("height","20").attr("width","20").attr("x","0").attr("y","0");s.append("rect").attr("width","20").attr("height","20").attr("y","0").attr("x","0").attr("class","node-icon-click");s.append("svg").attr("height","16").attr("width","16").attr("y","2").attr("x","2").attr("class","node-icon-inner").append("use").attr("class","node-icon").attr("xlink:href","#icon-"+t)}isNodeSelectable(e){return!0}appendTextElement(e){return e.append("text").attr("dx",e=>this.textPosition+(e.locked?15:0)).attr("dy",5).attr("class","node-name").on("click",(e,t)=>this.selectNode(t,!0))}nodesUpdate(e){return(e=e.enter().append("g").attr("class","node").attr("id",e=>"identifier-"+e.stateIdentifier).attr("role","treeitem").attr("aria-owns",e=>e.hasChildren?"group-identifier-"+e.stateIdentifier:null).attr("aria-level",this.getNodeDepth).attr("aria-setsize",this.getNodeSetsize).attr("aria-posinset",this.getNodePositionInSet).attr("aria-expanded",e=>e.hasChildren?e.expanded:null).attr("transform",this.getNodeTransform).attr("data-state-id",this.getNodeStateIdentifier).attr("title",this.getNodeTitle).on("mouseover",(e,t)=>this.onMouseOverNode(t)).on("mouseout",(e,t)=>this.onMouseOutOfNode(t)).on("contextmenu",(e,t)=>{e.preventDefault(),this.dispatchEvent(new CustomEvent("typo3:svg-tree:node-context",{detail:{node:t}}))})).append("text").text(e=>e.readableRootline).attr("class","node-rootline").attr("dx",0).attr("dy",-15).attr("visibility",e=>e.readableRootline?"visible":"hidden"),e}getNodeIdentifier(e){return e.identifier}getNodeDepth(e){return e.depth}getNodeSetsize(e){return e.siblingsCount}getNodePositionInSet(e){return e.siblingsPosition}getNodeStateIdentifier(e){return e.stateIdentifier}getNodeLabel(e){let t=(e.prefix||"")+e.name+(e.suffix||"");const s=document.createElement("div");if(s.textContent=t,t=s.innerHTML,this.searchTerm){const e=new RegExp(this.searchTerm,"gi");t=t.replace(e,'<tspan class="node-highlight-text">$&</tspan>')}return t}getNodeByIdentifier(e){return this.nodes.find(t=>t.stateIdentifier===e)}getNodeBgClass(e,t,s){let i="node-bg",n=null,o=null;return"object"==typeof s&&(n=s.data()[t-1],o=s.data()[t+1]),e.checked&&(i+=" node-selected"),(n&&e.depth>n.depth||!n)&&(e.firstChild=!0,i+=" node-first-child"),(o&&e.depth>o.depth||!o)&&(e.lastChild=!0,i+=" node-last-child"),e.class&&(i+=" "+e.class),i}getNodeTitle(e){return e.tip?e.tip:"uid="+e.identifier}getChevronTransform(e){return e.expanded?"translate(16,0) rotate(90)":" rotate(0)"}getChevronColor(e){return e.expanded?"#000":"#8e8e8e"}getToggleVisibility(e){return e.hasChildren?"visible":"hidden"}getChevronClass(e){return"chevron "+(e.expanded?"expanded":"collapsed")}getLinkPath(e){const t=e.target.x,s=e.target.y,i=[];return i.push("M"+e.source.x+" "+e.source.y),i.push("V"+s),e.target.hasChildren?i.push("H"+(t-2)):i.push("H"+(t+this.settings.indentWidth/4-2)),i.join(" ")}getNodeTransform(e){return"translate("+(e.x||0)+","+(e.y||0)+")"}getNodeBgTransform(e){return"translate(-8, "+((e.y||0)-10)+")"}getNodeActionTransform(e){return"translate(-10, "+((e.y||0)-10)+")"}clickOnIcon(e){this.dispatchEvent(new CustomEvent("typo3:svg-tree:node-context",{detail:{node:e}}))}chevronClick(e){e.expanded?this.hideChildren(e):this.showChildren(e),this.prepareDataForVisibleNodes(),this.updateVisibleNodes()}enterSvgElements(e){if(this.settings.showIcons){const e=Object.values(this.icons).filter(e=>""!==e.icon&&null!==e.icon),t=this.iconsContainer.selectAll(".icon-def").data(e,e=>e.identifier);t.exit().remove(),t.enter().append("g").attr("class","icon-def").attr("id",e=>"icon-"+e.identifier).append(e=>{if(e.icon instanceof SVGElement)return e.icon;const t="<svg>"+e.icon+"</svg>";return(new DOMParser).parseFromString(t,"image/svg+xml").documentElement.firstChild})}const t=this.nodesUpdate(e);let s=t.append("g").attr("class","toggle").attr("visibility",this.getToggleVisibility).attr("transform","translate(-8, -8)").on("click",(e,t)=>this.chevronClick(t));if(s.append("path").style("opacity",0).attr("d","M 0 0 L 16 0 L 16 16 L 0 16 Z"),s.append("path").attr("class","chevron").attr("d","M 4 3 L 13 8 L 4 13 Z"),this.settings.showIcons){const e=t.append("svg").attr("class","node-icon-container").attr("title",this.getNodeTitle).attr("height","20").attr("width","20").attr("x","6").attr("y","-10").attr("data-bs-toggle","tooltip").on("click",(e,t)=>{e.preventDefault(),this.clickOnIcon(t)});e.append("rect").style("opacity",0).attr("width","20").attr("height","20").attr("y","0").attr("x","0").attr("class","node-icon-click");const s=e.append("svg").attr("height","16").attr("width","16").attr("y","2").attr("x","2").attr("class","node-icon-inner");s.append("use").attr("class","node-icon").attr("data-uid",this.getNodeIdentifier);s.append("svg").attr("height","11").attr("width","11").attr("y","5").attr("x","5").append("use").attr("class","node-icon-overlay");s.append("svg").attr("height","11").attr("width","11").attr("y","5").attr("x","5").append("use").attr("class","node-icon-locked")}return l.initialize('[data-bs-toggle="tooltip"]',this.tooltipOptions),this.appendTextElement(t),e.merge(t)}updateScrollPosition(){this.viewportHeight=this.getBoundingClientRect().height,this.scrollBottom=this.scrollTop+this.viewportHeight+this.viewportHeight/2,setTimeout(()=>{l.hide(document.querySelector(this.tooltipOptions.container).querySelectorAll(".bs-tooltip-end"))},this.tooltipOptions.delay)}onMouseOverNode(e){e.isOver=!0,this.hoveredNode=e;let t=this.svg.select('.nodes-bg .node-bg[data-state-id="'+e.stateIdentifier+'"]');t.size()&&t.classed("node-over",!0).attr("rx","3").attr("ry","3");let s=this.nodesActionsContainer.select('.node-action[data-state-id="'+e.stateIdentifier+'"]');s.size()&&(s.classed("node-action-over",!0),s.attr("fill",t.style("fill")))}onMouseOutOfNode(e){e.isOver=!1,this.hoveredNode=null;let t=this.svg.select('.nodes-bg .node-bg[data-state-id="'+e.stateIdentifier+'"]');t.size()&&t.classed("node-over node-alert",!1).attr("rx","0").attr("ry","0");let s=this.nodesActionsContainer.select('.node-action[data-state-id="'+e.stateIdentifier+'"]');s.size()&&s.classed("node-action-over",!1)}handleKeyboardInteraction(e){const t=e.target;let s=n.select(t).datum();if(-1===[a.KeyTypesEnum.ENTER,a.KeyTypesEnum.SPACE,a.KeyTypesEnum.END,a.KeyTypesEnum.HOME,a.KeyTypesEnum.LEFT,a.KeyTypesEnum.UP,a.KeyTypesEnum.RIGHT,a.KeyTypesEnum.DOWN].indexOf(e.keyCode))return;e.preventDefault();const i=t.parentNode;switch(e.keyCode){case a.KeyTypesEnum.END:this.scrollTop=this.lastElementChild.getBoundingClientRect().height+this.settings.nodeHeight-this.viewportHeight,i.scrollIntoView({behavior:"smooth",block:"end"}),this.updateVisibleNodes(),this.switchFocus(i.lastElementChild);break;case a.KeyTypesEnum.HOME:this.scrollTo({top:this.nodes[0].y,behavior:"smooth"}),this.prepareDataForVisibleNodes(),this.updateVisibleNodes(),this.switchFocus(i.firstElementChild);break;case a.KeyTypesEnum.LEFT:if(s.expanded)s.hasChildren&&(this.hideChildren(s),this.prepareDataForVisibleNodes(),this.updateVisibleNodes());else if(s.parents.length>0){let e=this.nodes[s.parents[0]];this.scrollNodeIntoVisibleArea(e,"up"),this.switchFocusNode(e)}break;case a.KeyTypesEnum.UP:this.scrollNodeIntoVisibleArea(s,"up"),this.switchFocus(t.previousSibling);break;case a.KeyTypesEnum.RIGHT:s.expanded?(this.scrollNodeIntoVisibleArea(s,"down"),this.switchFocus(t.nextSibling)):s.hasChildren&&(this.showChildren(s),this.prepareDataForVisibleNodes(),this.updateVisibleNodes(),this.switchFocus(t));break;case a.KeyTypesEnum.DOWN:this.scrollNodeIntoVisibleArea(s,"down"),this.switchFocus(t.nextSibling);break;case a.KeyTypesEnum.ENTER:case a.KeyTypesEnum.SPACE:this.selectNode(s,!0)}}scrollNodeIntoVisibleArea(e,t="up"){let s=this.scrollTop;if("up"===t&&s>e.y-this.settings.nodeHeight)s=e.y-this.settings.nodeHeight;else{if(!("down"===t&&s+this.viewportHeight<=e.y+3*this.settings.nodeHeight))return;s+=this.settings.nodeHeight}this.scrollTo({top:s,behavior:"smooth"}),this.updateVisibleNodes()}updateLinks(){const e=this.data.links.filter(e=>e.source.y<=this.scrollBottom&&e.target.y>=this.scrollTop-this.settings.nodeHeight).map(e=>(e.source.owns=e.source.owns||[],e.source.owns.push("identifier-"+e.target.stateIdentifier),e)),t=this.linksContainer.selectAll(".link").data(e);t.exit().remove(),t.enter().append("path").attr("class","link").attr("id",this.getGroupIdentifier).attr("role",e=>1===e.target.siblingsPosition&&e.source.owns.length>0?"group":null).attr("aria-owns",e=>1===e.target.siblingsPosition&&e.source.owns.length>0?e.source.owns.join(" "):null).merge(t).attr("d",e=>this.getLinkPath(e))}getGroupIdentifier(e){return 1===e.target.siblingsPosition?"group-identifier-"+e.source.stateIdentifier:null}}__decorate([i.property({type:Object})],u.prototype,"setup",void 0),__decorate([i.state()],u.prototype,"settings",void 0),t.SvgTree=u;let g=class extends s.LitElement{constructor(){super(...arguments),this.tree=null,this.settings={searchInput:".search-input",filterTimeout:450}}createRenderRoot(){return this}firstUpdated(){const e=this.querySelector(this.settings.searchInput);e&&(new p.default("input",e=>{const t=e.target;this.tree.filter(t.value.trim())},this.settings.filterTimeout).bindTo(e),e.focus(),e.clearable({onClear:()=>{this.tree.resetFilter()}}))}render(){return s.html`
       <div class="tree-toolbar">
         <div class="svg-toolbar__menu">
           <div class="svg-toolbar__search">
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/Viewport/ResizableNavigation.js b/typo3/sysext/backend/Resources/Public/JavaScript/Viewport/ResizableNavigation.js
index 01aa7bd808ee..c2386a2a05af 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/Viewport/ResizableNavigation.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/Viewport/ResizableNavigation.js
@@ -10,14 +10,14 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-var __decorate=this&&this.__decorate||function(t,e,i,n){var o,a=arguments.length,s=a<3?e:null===n?n=Object.getOwnPropertyDescriptor(e,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(t,e,i,n);else for(var r=t.length-1;r>=0;r--)(o=t[r])&&(s=(a<3?o(s):a>3?o(e,i,s):o(e,i))||s);return a>3&&s&&Object.defineProperty(e,i,s),s};define(["require","exports","lit","lit/decorators","TYPO3/CMS/Core/lit-helper","../Storage/Persistent","TYPO3/CMS/Backend/Element/IconElement"],(function(t,e,i,n,o,a){"use strict";Object.defineProperty(e,"__esModule",{value:!0});const s={fromAttribute:t=>document.querySelector(t)};let r=class extends i.LitElement{constructor(){super(...arguments),this.minimumWidth=250,this.resizing=!1,this.toggleNavigation=t=>{t instanceof MouseEvent&&2===t.button||(t.stopPropagation(),this.parentContainer.classList.toggle("scaffold-content-navigation-expanded"))},this.fallbackNavigationSizeIfNeeded=t=>{let e=t.currentTarget;0!==this.getNavigationWidth()&&e.outerWidth<this.getNavigationWidth()+this.getNavigationPosition().left+this.minimumWidth&&this.autoNavigationWidth()},this.handleMouseMove=t=>{this.resizeNavigation(t.clientX)},this.handleTouchMove=t=>{this.resizeNavigation(t.changedTouches[0].clientX)},this.resizeNavigation=t=>{let e=Math.round(t)-Math.round(this.getNavigationPosition().left);this.setNavigationWidth(e)},this.startResizeNavigation=t=>{t instanceof MouseEvent&&2===t.button||(t.stopPropagation(),this.resizing=!0,document.addEventListener("mousemove",this.handleMouseMove,!1),document.addEventListener("mouseup",this.stopResizeNavigation,!1),document.addEventListener("touchmove",this.handleTouchMove,!1),document.addEventListener("touchend",this.stopResizeNavigation,!1))},this.stopResizeNavigation=()=>{this.resizing=!1,document.removeEventListener("mousemove",this.handleMouseMove,!1),document.removeEventListener("mouseup",this.stopResizeNavigation,!1),document.removeEventListener("touchmove",this.handleTouchMove,!1),document.removeEventListener("touchend",this.stopResizeNavigation,!1),a.set(this.persistenceIdentifier,this.getNavigationWidth()),document.dispatchEvent(new CustomEvent("typo3:navigation:resized"))}}connectedCallback(){super.connectedCallback();const t=this.initialWidth||parseInt(a.get(this.persistenceIdentifier),10);this.setNavigationWidth(t),window.addEventListener("resize",this.fallbackNavigationSizeIfNeeded,{passive:!0})}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("resize",this.fallbackNavigationSizeIfNeeded)}createRenderRoot(){return this}render(){return i.html`
+var __decorate=this&&this.__decorate||function(t,e,i,n){var o,a=arguments.length,s=a<3?e:null===n?n=Object.getOwnPropertyDescriptor(e,i):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(t,e,i,n);else for(var r=t.length-1;r>=0;r--)(o=t[r])&&(s=(a<3?o(s):a>3?o(e,i,s):o(e,i))||s);return a>3&&s&&Object.defineProperty(e,i,s),s};define(["require","exports","lit","lit/decorators","TYPO3/CMS/Core/lit-helper","../Storage/Persistent","TYPO3/CMS/Backend/Element/IconElement"],(function(t,e,i,n,o,a){"use strict";Object.defineProperty(e,"__esModule",{value:!0});const s={fromAttribute:t=>document.querySelector(t)};let r=class extends i.LitElement{constructor(){super(...arguments),this.minimumWidth=250,this.resizing=!1,this.toggleNavigation=t=>{t instanceof MouseEvent&&2===t.button||(t.stopPropagation(),this.parentContainer.classList.toggle("scaffold-content-navigation-expanded"))},this.fallbackNavigationSizeIfNeeded=t=>{let e=t.currentTarget;0!==this.getNavigationWidth()&&e.outerWidth<this.getNavigationWidth()+this.getNavigationPosition().left+this.minimumWidth&&this.autoNavigationWidth()},this.handleMouseMove=t=>{this.resizeNavigation(t.clientX)},this.handleTouchMove=t=>{this.resizeNavigation(t.changedTouches[0].clientX)},this.resizeNavigation=t=>{let e=Math.round(t)-Math.round(this.getNavigationPosition().left);this.setNavigationWidth(e)},this.startResizeNavigation=t=>{t instanceof MouseEvent&&2===t.button||(t.stopPropagation(),this.resizing=!0,document.addEventListener("mousemove",this.handleMouseMove,!1),document.addEventListener("mouseup",this.stopResizeNavigation,!1),document.addEventListener("touchmove",this.handleTouchMove,!1),document.addEventListener("touchend",this.stopResizeNavigation,!1))},this.stopResizeNavigation=()=>{this.resizing=!1,document.removeEventListener("mousemove",this.handleMouseMove,!1),document.removeEventListener("mouseup",this.stopResizeNavigation,!1),document.removeEventListener("touchmove",this.handleTouchMove,!1),document.removeEventListener("touchend",this.stopResizeNavigation,!1),a.set(this.persistenceIdentifier,this.getNavigationWidth()),document.dispatchEvent(new CustomEvent("typo3:navigation:resized"))}}connectedCallback(){super.connectedCallback();const t=this.initialWidth||parseInt(a.get(this.persistenceIdentifier),10);this.setNavigationWidth(t),window.addEventListener("resize",this.fallbackNavigationSizeIfNeeded,{passive:!0})}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("resize",this.fallbackNavigationSizeIfNeeded)}createRenderRoot(){return this}async firstUpdated(){await new Promise(t=>setTimeout(t,0)),this.querySelector(".scaffold-content-navigation-switcher-btn").addEventListener("touchstart",this.toggleNavigation,{passive:!0}),this.querySelector(".scaffold-content-navigation-drag").addEventListener("touchstart",this.startResizeNavigation,{passive:!0})}render(){return i.html`
       <div class="scaffold-content-navigation-switcher">
-        <button @mouseup="${this.toggleNavigation}" @touchstart="${this.toggleNavigation}" class="btn btn-default btn-borderless scaffold-content-navigation-switcher-btn scaffold-content-navigation-switcher-open" role="button" title="${o.lll("viewport_navigation_show")}">
+        <button @mouseup="${this.toggleNavigation}" class="btn btn-default btn-borderless scaffold-content-navigation-switcher-btn scaffold-content-navigation-switcher-open" role="button" title="${o.lll("viewport_navigation_show")}">
           <typo3-backend-icon identifier="actions-chevron-right" size="small"></typo3-backend-icon>
         </button>
-        <button @mouseup="${this.toggleNavigation}" @touchstart="${this.toggleNavigation}" class="btn btn-default btn-borderless scaffold-content-navigation-switcher-btn scaffold-content-navigation-switcher-close" role="button" title="${o.lll("viewport_navigation_hide")}">
+        <button @mouseup="${this.toggleNavigation}" class="btn btn-default btn-borderless scaffold-content-navigation-switcher-btn scaffold-content-navigation-switcher-close" role="button" title="${o.lll("viewport_navigation_hide")}">
           <typo3-backend-icon identifier="actions-chevron-left" size="small"></typo3-backend-icon>
         </button>
       </div>
-      <div @mousedown="${this.startResizeNavigation}" @touchstart="${this.startResizeNavigation}" class="scaffold-content-navigation-drag ${this.resizing?"resizing":""}"></div>
+      <div @mousedown="${this.startResizeNavigation}" class="scaffold-content-navigation-drag ${this.resizing?"resizing":""}"></div>
     `}getNavigationPosition(){return this.navigationContainer.getBoundingClientRect()}getNavigationWidth(){return this.navigationContainer.offsetWidth}autoNavigationWidth(){this.navigationContainer.style.width="auto"}setNavigationWidth(t){const e=Math.round(this.parentContainer.getBoundingClientRect().width/2);t>e&&(t=e),t=t>this.minimumWidth?t:this.minimumWidth,this.navigationContainer.style.width=t+"px"}};__decorate([n.property({type:Number,attribute:"minimum-width"})],r.prototype,"minimumWidth",void 0),__decorate([n.property({type:Number,attribute:"initial-width"})],r.prototype,"initialWidth",void 0),__decorate([n.property({type:String,attribute:"persistence-identifier"})],r.prototype,"persistenceIdentifier",void 0),__decorate([n.property({attribute:"parent",converter:s})],r.prototype,"parentContainer",void 0),__decorate([n.property({attribute:"navigation",converter:s})],r.prototype,"navigationContainer",void 0),__decorate([n.state()],r.prototype,"resizing",void 0),r=__decorate([n.customElement("typo3-backend-navigation-switcher")],r)}));
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Acceptance/Application/FormEngine/CategoryTreeCest.php b/typo3/sysext/core/Tests/Acceptance/Application/FormEngine/CategoryTreeCest.php
index c1669b77fb4a..ff6b89e7fcca 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application/FormEngine/CategoryTreeCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Application/FormEngine/CategoryTreeCest.php
@@ -64,8 +64,8 @@ class CategoryTreeCest
         $I->waitForText('Category', 20);
         // Change title and level to root
         $I->fillField('input[data-formengine-input-name="data[sys_category][7][title]"]', 'level-1-4');
-        $I->click('.identifier-0_7 text.node-name');
-        $I->click('.identifier-0_3 text.node-name');
+        $I->click('#identifier-0_7 text.node-name');
+        $I->click('#identifier-0_3 text.node-name');
         $I->click('button[name="_savedok"]');
         // Wait for tree and check if isset level-1-4
         $I->waitForElement('.svg-tree-wrapper svg');
diff --git a/typo3/sysext/core/Tests/Acceptance/Application/Impexp/ExportCest.php b/typo3/sysext/core/Tests/Acceptance/Application/Impexp/ExportCest.php
index 1b9bee7ae0dc..67a25cac3b67 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application/Impexp/ExportCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Application/Impexp/ExportCest.php
@@ -234,7 +234,7 @@ class ExportCest extends AbstractCest
     {
         $I->wantToTest('exporting a table of records.');
 
-        $rootPage = '.node.identifier-0_0 .node-name';
+        $rootPage = '#identifier-0_0 .node-name';
         $rootPageTitle = 'New TYPO3 site';
         $sysLanguageTableTitle = 'Website Language';
         $listModuleHeader = '.module-docheader';
diff --git a/typo3/sysext/core/Tests/Acceptance/Application/Impexp/UsersCest.php b/typo3/sysext/core/Tests/Acceptance/Application/Impexp/UsersCest.php
index bc0cf9e382da..2c96c718bc99 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application/Impexp/UsersCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Application/Impexp/UsersCest.php
@@ -156,7 +156,7 @@ class UsersCest extends AbstractCest
         $selectedPageIcon = '//*[text()=\'' . $selectedPageTitle . '\']/../*[contains(@class, \'node-icon-container\')]';
         $importPageSectionTitle = 'Select file to import';
 
-        $I->click($this->inPageTree . ' .node.identifier-0_0 .node-icon-container');
+        $I->click($this->inPageTree . ' #identifier-0_0 .node-icon-container');
         $this->selectInContextMenu($I, [$this->contextMenuMore, $this->contextMenuImport]);
         $I->switchToContentFrame();
         $I->waitForText($importPageSectionTitle);
diff --git a/typo3/sysext/core/Tests/Acceptance/Application/Page/AddPageInPageModuleCest.php b/typo3/sysext/core/Tests/Acceptance/Application/Page/AddPageInPageModuleCest.php
index 1c8677a482ea..f7606943a135 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application/Page/AddPageInPageModuleCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Application/Page/AddPageInPageModuleCest.php
@@ -47,7 +47,7 @@ class AddPageInPageModuleCest
         $typo3NavigationContainer = '.scaffold-content-navigation-component';
         $I->waitForElement($typo3NavigationContainer);
         $rootNode = 'a.x-tree-node-anchor > span';
-        $rootNodeIcon = '.node.identifier-0_0 .node-icon';
+        $rootNodeIcon = '#identifier-0_0 .node-icon';
         $rootNodeContextMenuMore = '#contentMenu0 a.list-group-item-submenu';
         //create new wizard
         $contextMenuNew = '#contentMenu1 .list-group-item[data-callback-action=newPageWizard]';
-- 
GitLab