From 302a37f2fd073ddbec0958762484d4b451f175bf Mon Sep 17 00:00:00 2001
From: Andreas Fernandez <a.fernandez@scripting-base.de>
Date: Thu, 9 Feb 2023 10:06:38 +0100
Subject: [PATCH] [BUGFIX] Live Search: Rework registration of renderers and
 invoke handlers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This patch introduces a new global `LiveSearchConfigurator` module that
can be used anywhere in the TYPO3 backend to load configuration of the
Live Search.

The methods `LiveSearch.addRenderer()` and
`LiveSearch.addInvokeHandler()` were removed. Instead,
`LiveSearchConfigurator` should be used. Web components where thinned
out to not rely on getting such configuration handed in anymore.

However, there's a small API change:
`LiveSearchConfigurator.addRenderer()` now requires a new argument
`module` to be able to load modules on-demand if they were not loaded
already in the current backend scope.

Resolves: #99891
Releases: main
Change-Id: I30d9562b92671d0c6d1f8d1234dfcdd9da742a3a
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/77796
Reviewed-by: Frank Nägler <frank.naegler@typo3.com>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Frank Nägler <frank.naegler@typo3.com>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 .../element/result/item/item-container.ts     | 21 +++++---
 .../element/result/result-container.ts        | 13 ++---
 .../live-search/live-search-configurator.ts   | 48 +++++++++++++++++++
 .../result-types/default-result-type.ts       |  6 +--
 .../result-types/page-result-type.ts          | 25 +++++-----
 .../TypeScript/backend/toolbar/live-search.ts | 12 -----
 .../element/result/item/item-container.js     |  8 ++--
 .../element/result/result-container.js        |  6 +--
 .../live-search/live-search-configurator.js   | 13 +++++
 .../result-types/default-result-type.js       |  2 +-
 .../result-types/page-result-type.js          | 12 ++---
 .../Public/JavaScript/toolbar/live-search.js  |  2 +-
 12 files changed, 114 insertions(+), 54 deletions(-)
 create mode 100644 Build/Sources/TypeScript/backend/live-search/live-search-configurator.ts
 create mode 100644 typo3/sysext/backend/Resources/Public/JavaScript/live-search/live-search-configurator.js

diff --git a/Build/Sources/TypeScript/backend/live-search/element/result/item/item-container.ts b/Build/Sources/TypeScript/backend/live-search/element/result/item/item-container.ts
index b5165b0821a5..262af16deca2 100644
--- a/Build/Sources/TypeScript/backend/live-search/element/result/item/item-container.ts
+++ b/Build/Sources/TypeScript/backend/live-search/element/result/item/item-container.ts
@@ -11,10 +11,13 @@
  * The TYPO3 project - inspiring people to share!
  */
 
-import {customElement, property} from 'lit/decorators';
+import '@typo3/backend/element/spinner-element';
+import LiveSearchConfigurator from '@typo3/backend/live-search/live-search-configurator';
 import {css, html, LitElement, TemplateResult} from 'lit';
-import './item';
+import {customElement, property} from 'lit/decorators';
+import {until} from 'lit/directives/until';
 import '../../provider/default-result-item';
+import './item';
 import {Item, ResultItemActionInterface, ResultItemInterface} from './item';
 
 type GroupedResultItems = { [key: string ]: ResultItemInterface[] };
@@ -24,7 +27,6 @@ export const componentName = 'typo3-backend-live-search-result-item-container';
 @customElement(componentName)
 export class ItemContainer extends LitElement {
   @property({type: Object, attribute: false}) results: ResultItemInterface[]|null = null;
-  @property({type: Object, attribute: false}) renderers: { [key: string]: Function } = {};
 
   public connectedCallback() {
     super.connectedCallback();
@@ -60,17 +62,22 @@ export class ItemContainer extends LitElement {
     const items = [];
     for (let [type, results] of Object.entries(groupedResults)) {
       items.push(html`<h6 class="livesearch-result-item-group-label">${type}</h6>`);
-      items.push(...results.map((result: ResultItemInterface) => this.renderResultItem(result)));
+      items.push(...results.map((result: ResultItemInterface) => html`${until(
+        this.renderResultItem(result),
+        html`<typo3-backend-spinner></typo3-backend-spinner>`
+      )}`));
     }
 
     return html`${items}`
   }
 
-  private renderResultItem(resultItem: ResultItemInterface): TemplateResult {
+  private async renderResultItem(resultItem: ResultItemInterface): Promise<TemplateResult> {
+    const renderers = LiveSearchConfigurator.getRenderers();
     let innerResultItemComponent;
 
-    if (typeof this.renderers[resultItem.provider] === 'function') {
-      innerResultItemComponent = this.renderers[resultItem.provider](resultItem);
+    if (renderers[resultItem.provider] !== undefined) {
+      await import(renderers[resultItem.provider].module);
+      innerResultItemComponent = renderers[resultItem.provider].callback(resultItem);
     } else {
       innerResultItemComponent = html`<typo3-backend-live-search-result-item-default
         title="${resultItem.typeLabel}: ${resultItem.itemTitle}"
diff --git a/Build/Sources/TypeScript/backend/live-search/element/result/result-container.ts b/Build/Sources/TypeScript/backend/live-search/element/result/result-container.ts
index 32917de52afc..4c056bfc0962 100644
--- a/Build/Sources/TypeScript/backend/live-search/element/result/result-container.ts
+++ b/Build/Sources/TypeScript/backend/live-search/element/result/result-container.ts
@@ -11,6 +11,7 @@
  * The TYPO3 project - inspiring people to share!
  */
 
+import LiveSearchConfigurator from '@typo3/backend/live-search/live-search-configurator';
 import {customElement, property, query} from 'lit/decorators';
 import {html, LitElement, TemplateResult} from 'lit';
 import {lll} from '@typo3/core/lit-helper';
@@ -24,20 +25,20 @@ export const componentName = 'typo3-backend-live-search-result-container';
 
 @customElement(componentName)
 export class ResultContainer extends LitElement {
-  @property({type: Object, attribute: false}) results: ResultItemInterface[]|null = null;
+  @property({type: Object}) results: ResultItemInterface[]|null = null;
   @property({type: Boolean, attribute: false}) loading: boolean = false;
-  @property({type: Object, attribute: false}) renderers: { [key: string]: Function } = {};
-  @property({type: Object, attribute: false}) invokeHandlers: { [key: string]: Function } = {};
 
   @query('typo3-backend-live-search-result-item-container') itemContainer: ItemContainer;
   @query('typo3-backend-live-search-result-item-detail-container') resultDetailContainer: ResultDetailContainer;
 
   public connectedCallback() {
     super.connectedCallback();
+
     this.addEventListener('livesearch:request-actions', (e: CustomEvent): void => {
       this.resultDetailContainer.resultItem = e.detail.resultItem;
     })
     this.addEventListener('livesearch:invoke-action', (e: CustomEvent): void => {
+      const invokeHandlers = LiveSearchConfigurator.getInvokeHandlers();
       const resultItem = e.detail.resultItem;
       const action = e.detail.action;
 
@@ -45,8 +46,8 @@ export class ResultContainer extends LitElement {
         return;
       }
 
-      if (typeof this.invokeHandlers[resultItem.provider + '_' + action.identifier] === 'function') {
-        this.invokeHandlers[resultItem.provider + '_' + action.identifier](resultItem, action);
+      if (typeof invokeHandlers[resultItem.provider + '_' + action.identifier] === 'function') {
+        invokeHandlers[resultItem.provider + '_' + action.identifier](resultItem, action);
       } else {
         // Default handler to open the URL
         TYPO3.Backend.ContentContainer.setUrl(action.url);
@@ -76,7 +77,7 @@ export class ResultContainer extends LitElement {
     }
 
     return html`
-      <typo3-backend-live-search-result-item-container .results="${this.results}" .renderers="${this.renderers}"></typo3-backend-live-search-result-item-container>
+      <typo3-backend-live-search-result-item-container .results="${this.results}"></typo3-backend-live-search-result-item-container>
       <typo3-backend-live-search-result-item-detail-container></typo3-backend-live-search-result-item-detail-container>
     `;
   }
diff --git a/Build/Sources/TypeScript/backend/live-search/live-search-configurator.ts b/Build/Sources/TypeScript/backend/live-search/live-search-configurator.ts
new file mode 100644
index 000000000000..2d0528e74969
--- /dev/null
+++ b/Build/Sources/TypeScript/backend/live-search/live-search-configurator.ts
@@ -0,0 +1,48 @@
+/*
+ * 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!
+ */
+
+type RendererDeclaration = { module: string, callback: Function };
+type RendererDeclarationCollection = { [key: string]: RendererDeclaration };
+type FunctionObjects = { [key: string]: Function };
+
+class LiveSearchConfigurator {
+  private renderers: RendererDeclarationCollection = {};
+  private invokeHandlers: FunctionObjects = {};
+
+  public getRenderers(): RendererDeclarationCollection {
+    return this.renderers;
+  }
+
+  public addRenderer(type: string, module: string, callback: Function): void {
+    this.renderers[type] = { module, callback };
+  }
+
+  public getInvokeHandlers(): FunctionObjects {
+    return this.invokeHandlers;
+  }
+
+  public addInvokeHandler(type: string, action: string, callback: Function): void {
+    this.invokeHandlers[type + '_' + action] = callback;
+  }
+}
+
+let configuratorObject: LiveSearchConfigurator;
+if (!top.TYPO3.LiveSearchConfigurator) {
+  configuratorObject = new LiveSearchConfigurator();
+  top.TYPO3.LiveSearchConfigurator = configuratorObject;
+} else {
+  configuratorObject = top.TYPO3.LiveSearchConfigurator;
+}
+
+export default configuratorObject;
+
diff --git a/Build/Sources/TypeScript/backend/live-search/result-types/default-result-type.ts b/Build/Sources/TypeScript/backend/live-search/result-types/default-result-type.ts
index 1b199f1d40da..75df36fee8b5 100644
--- a/Build/Sources/TypeScript/backend/live-search/result-types/default-result-type.ts
+++ b/Build/Sources/TypeScript/backend/live-search/result-types/default-result-type.ts
@@ -1,12 +1,12 @@
-import {ResultItemActionInterface, ResultItemInterface} from '@typo3/backend/live-search/element/result/item/item';
-import LiveSearch from '@typo3/backend/toolbar/live-search';
+import LiveSearchConfigurator from '@typo3/backend/live-search/live-search-configurator';
+import {ResultItemInterface} from '@typo3/backend/live-search/element/result/item/item';
 import '@typo3/backend/live-search/element/provider/page-provider-result-item';
 import AjaxRequest from '@typo3/core/ajax/ajax-request';
 import {AjaxResponse} from '@typo3/core/ajax/ajax-response';
 import Notification from '@typo3/backend/notification';
 
 export function registerType(type: string) {
-  LiveSearch.addInvokeHandler(type, 'switch_backend_user', (resultItem: ResultItemInterface): void => {
+  LiveSearchConfigurator.addInvokeHandler(type, 'switch_backend_user', (resultItem: ResultItemInterface): void => {
     (new AjaxRequest(TYPO3.settings.ajaxUrls.switch_user)).post({
       targetUser: resultItem.extraData.uid,
     }).then(async (response: AjaxResponse): Promise<any> => {
diff --git a/Build/Sources/TypeScript/backend/live-search/result-types/page-result-type.ts b/Build/Sources/TypeScript/backend/live-search/result-types/page-result-type.ts
index e683a2944d6c..ff6f6db6416e 100644
--- a/Build/Sources/TypeScript/backend/live-search/result-types/page-result-type.ts
+++ b/Build/Sources/TypeScript/backend/live-search/result-types/page-result-type.ts
@@ -1,20 +1,23 @@
+import LiveSearchConfigurator from '@typo3/backend/live-search/live-search-configurator';
 import {html, TemplateResult} from 'lit';
 import {ResultItemActionInterface, ResultItemInterface} from '@typo3/backend/live-search/element/result/item/item';
-import LiveSearch from '@typo3/backend/toolbar/live-search';
-import '@typo3/backend/live-search/element/provider/page-provider-result-item';
 import windowManager from '@typo3/backend/window-manager';
 
 export function registerRenderer(type: string) {
-  LiveSearch.addRenderer(type, (attributes: ResultItemInterface): TemplateResult => {
-    return html`<typo3-backend-live-search-result-item-page-provider
-      .icon="${attributes.icon}"
-      .itemTitle="${attributes.itemTitle}"
-      .typeLabel="${attributes.typeLabel}"
-      .extraData="${attributes.extraData}">
-    </typo3-backend-live-search-result-item-page-provider>`;
-  });
+  LiveSearchConfigurator.addRenderer(
+    type,
+    '@typo3/backend/live-search/element/provider/page-provider-result-item.js',
+    (attributes: ResultItemInterface): TemplateResult => {
+      return html`<typo3-backend-live-search-result-item-page-provider
+        .icon="${attributes.icon}"
+        .itemTitle="${attributes.itemTitle}"
+        .typeLabel="${attributes.typeLabel}"
+        .extraData="${attributes.extraData}">
+      </typo3-backend-live-search-result-item-page-provider>`;
+    }
+  );
 
-  LiveSearch.addInvokeHandler(type, 'preview_page', (resultItem: ResultItemInterface, action: ResultItemActionInterface): void => {
+  LiveSearchConfigurator.addInvokeHandler(type, 'preview_page', (resultItem: ResultItemInterface, action: ResultItemActionInterface): void => {
     windowManager.localOpen(action.url, true);
   });
 }
diff --git a/Build/Sources/TypeScript/backend/toolbar/live-search.ts b/Build/Sources/TypeScript/backend/toolbar/live-search.ts
index 09f17727ef7a..be8d7a756094 100644
--- a/Build/Sources/TypeScript/backend/toolbar/live-search.ts
+++ b/Build/Sources/TypeScript/backend/toolbar/live-search.ts
@@ -45,8 +45,6 @@ interface SearchOption {
  * @exports @typo3/backend/toolbar/live-search
  */
 class LiveSearch {
-  private renderers: { [key: string]: Function } = {};
-  private invokeHandlers: { [key: string]: Function } = {};
   private searchTerm: string = '';
   private searchOptions: { [key: string]: string[] } = {};
 
@@ -56,14 +54,6 @@ class LiveSearch {
     });
   }
 
-  public addRenderer(type: string, callback: Function): void {
-    this.renderers[type] = callback;
-  }
-
-  public addInvokeHandler(type: string, action: string, callback: Function): void {
-    this.invokeHandlers[type + '_' + action] = callback;
-  }
-
   private registerEvents(): void {
     new RegularEvent('click', (): void => {
       this.openSearchModal();
@@ -185,8 +175,6 @@ class LiveSearch {
     searchAllButton.parentElement.hidden = searchResults === null || searchResults.length === 0;
 
     const searchResultContainer: ResultContainer = document.querySelector('typo3-backend-live-search-result-container') as ResultContainer;
-    searchResultContainer.renderers = this.renderers;
-    searchResultContainer.invokeHandlers = this.invokeHandlers;
 
     searchResultContainer.results = searchResults;
     searchResultContainer.loading = false;
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/element/result/item/item-container.js b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/element/result/item/item-container.js
index 2930aec1270a..e9604e19f757 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/element/result/item/item-container.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/element/result/item/item-container.js
@@ -10,9 +10,9 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-var __decorate=function(e,t,r,n){var l,s=arguments.length,i=s<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var o=e.length-1;o>=0;o--)(l=e[o])&&(i=(s<3?l(i):s>3?l(t,r,i):l(t,r))||i);return s>3&&i&&Object.defineProperty(t,r,i),i};import{customElement,property}from"lit/decorators.js";import{css,html,LitElement}from"lit";import"@typo3/backend/live-search/element/result/item/item.js";import"@typo3/backend/live-search/element/provider/default-result-item.js";export const componentName="typo3-backend-live-search-result-item-container";let ItemContainer=class extends LitElement{constructor(){super(...arguments),this.results=null,this.renderers={}}connectedCallback(){super.connectedCallback(),this.addEventListener("scroll",this.onScroll)}disconnectedCallback(){this.removeEventListener("scroll",this.onScroll),super.disconnectedCallback()}createRenderRoot(){return this}render(){const e={};return this.results.forEach((t=>{t.typeLabel in e?e[t.typeLabel].push(t):e[t.typeLabel]=[t]})),html`<typo3-backend-live-search-result-list>
+var __decorate=function(e,t,r,n){var l,i=arguments.length,o=i<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,r,n);else for(var s=e.length-1;s>=0;s--)(l=e[s])&&(o=(i<3?l(o):i>3?l(t,r,o):l(t,r))||o);return i>3&&o&&Object.defineProperty(t,r,o),o};import"@typo3/backend/element/spinner-element.js";import LiveSearchConfigurator from"@typo3/backend/live-search/live-search-configurator.js";import{css,html,LitElement}from"lit";import{customElement,property}from"lit/decorators.js";import{until}from"lit/directives/until.js";import"@typo3/backend/live-search/element/provider/default-result-item.js";import"@typo3/backend/live-search/element/result/item/item.js";export const componentName="typo3-backend-live-search-result-item-container";let ItemContainer=class extends LitElement{constructor(){super(...arguments),this.results=null}connectedCallback(){super.connectedCallback(),this.addEventListener("scroll",this.onScroll)}disconnectedCallback(){this.removeEventListener("scroll",this.onScroll),super.disconnectedCallback()}createRenderRoot(){return this}render(){const e={};return this.results.forEach((t=>{t.typeLabel in e?e[t.typeLabel].push(t):e[t.typeLabel]=[t]})),html`<typo3-backend-live-search-result-list>
       ${this.renderGroupedResults(e)}
-    </typo3-backend-live-search-result-list>`}renderGroupedResults(e){const t=[];for(let[r,n]of Object.entries(e))t.push(html`<h6 class="livesearch-result-item-group-label">${r}</h6>`),t.push(...n.map((e=>this.renderResultItem(e))));return html`${t}`}renderResultItem(e){let t;return t="function"==typeof this.renderers[e.provider]?this.renderers[e.provider](e):html`<typo3-backend-live-search-result-item-default
+    </typo3-backend-live-search-result-list>`}renderGroupedResults(e){const t=[];for(let[r,n]of Object.entries(e))t.push(html`<h6 class="livesearch-result-item-group-label">${r}</h6>`),t.push(...n.map((e=>html`${until(this.renderResultItem(e),html`<typo3-backend-spinner></typo3-backend-spinner>`)}`)));return html`${t}`}async renderResultItem(e){const t=LiveSearchConfigurator.getRenderers();let r;return void 0!==t[e.provider]?(await import(t[e.provider].module),r=t[e.provider].callback(e)):r=html`<typo3-backend-live-search-result-item-default
         title="${e.typeLabel}: ${e.itemTitle}"
         .icon="${e.icon}"
         .itemTitle="${e.itemTitle}"
@@ -22,8 +22,8 @@ var __decorate=function(e,t,r,n){var l,s=arguments.length,i=s<3?t:null===n?n=Obj
       .resultItem="${e}"
       @click="${()=>this.invokeAction(e,e.actions[0])}"
       @focus="${()=>this.requestActions(e)}">
-      ${t}
-    </typo3-backend-live-search-result-item>`}requestActions(e){this.parentElement.dispatchEvent(new CustomEvent("livesearch:request-actions",{detail:{resultItem:e}}))}invokeAction(e,t){this.parentElement.dispatchEvent(new CustomEvent("livesearch:invoke-action",{detail:{resultItem:e,action:t}}))}onScroll(e){this.querySelectorAll(".livesearch-result-item-group-label").forEach((t=>{t.classList.toggle("sticky",t.offsetTop<=e.target.scrollTop)}))}};__decorate([property({type:Object,attribute:!1})],ItemContainer.prototype,"results",void 0),__decorate([property({type:Object,attribute:!1})],ItemContainer.prototype,"renderers",void 0),ItemContainer=__decorate([customElement(componentName)],ItemContainer);export{ItemContainer};let ResultList=class extends LitElement{connectedCallback(){this.parentContainer=this.closest("typo3-backend-live-search-result-container"),this.resultItemDetailContainer=this.parentContainer.querySelector("typo3-backend-live-search-result-item-detail-container"),super.connectedCallback(),this.addEventListener("keydown",this.handleKeyDown),this.addEventListener("keyup",this.handleKeyUp)}disconnectedCallback(){this.removeEventListener("keydown",this.handleKeyDown),this.removeEventListener("keyup",this.handleKeyUp),super.disconnectedCallback()}render(){return html`<slot></slot>`}handleKeyDown(e){if(!["ArrowDown","ArrowUp","ArrowRight"].includes(e.key))return;const t="typo3-backend-live-search-result-item";if(document.activeElement.tagName.toLowerCase()!==t)return;let r;if(e.preventDefault(),"ArrowDown"===e.key){let e=document.activeElement.nextElementSibling;for(;null!==e&&e.tagName.toLowerCase()!==t;)e=e.nextElementSibling;r=e}else if("ArrowUp"===e.key){let e=document.activeElement.previousElementSibling;for(;null!==e&&e.tagName.toLowerCase()!==t;)e=e.previousElementSibling;r=e,null===r&&(r=document.querySelector("typo3-backend-live-search").querySelector('input[type="search"]'))}else"ArrowRight"===e.key&&(r=this.resultItemDetailContainer.querySelector("typo3-backend-live-search-result-item-action"));null!==r&&r.focus()}handleKeyUp(e){if(!["Enter"," "].includes(e.key))return;e.preventDefault();const t=e.target.resultItem;this.invokeAction(t)}invokeAction(e){this.parentContainer.dispatchEvent(new CustomEvent("livesearch:invoke-action",{detail:{resultItem:e,action:e.actions[0]}}))}};ResultList.styles=css`
+      ${r}
+    </typo3-backend-live-search-result-item>`}requestActions(e){this.parentElement.dispatchEvent(new CustomEvent("livesearch:request-actions",{detail:{resultItem:e}}))}invokeAction(e,t){this.parentElement.dispatchEvent(new CustomEvent("livesearch:invoke-action",{detail:{resultItem:e,action:t}}))}onScroll(e){this.querySelectorAll(".livesearch-result-item-group-label").forEach((t=>{t.classList.toggle("sticky",t.offsetTop<=e.target.scrollTop)}))}};__decorate([property({type:Object,attribute:!1})],ItemContainer.prototype,"results",void 0),ItemContainer=__decorate([customElement(componentName)],ItemContainer);export{ItemContainer};let ResultList=class extends LitElement{connectedCallback(){this.parentContainer=this.closest("typo3-backend-live-search-result-container"),this.resultItemDetailContainer=this.parentContainer.querySelector("typo3-backend-live-search-result-item-detail-container"),super.connectedCallback(),this.addEventListener("keydown",this.handleKeyDown),this.addEventListener("keyup",this.handleKeyUp)}disconnectedCallback(){this.removeEventListener("keydown",this.handleKeyDown),this.removeEventListener("keyup",this.handleKeyUp),super.disconnectedCallback()}render(){return html`<slot></slot>`}handleKeyDown(e){if(!["ArrowDown","ArrowUp","ArrowRight"].includes(e.key))return;const t="typo3-backend-live-search-result-item";if(document.activeElement.tagName.toLowerCase()!==t)return;let r;if(e.preventDefault(),"ArrowDown"===e.key){let e=document.activeElement.nextElementSibling;for(;null!==e&&e.tagName.toLowerCase()!==t;)e=e.nextElementSibling;r=e}else if("ArrowUp"===e.key){let e=document.activeElement.previousElementSibling;for(;null!==e&&e.tagName.toLowerCase()!==t;)e=e.previousElementSibling;r=e,null===r&&(r=document.querySelector("typo3-backend-live-search").querySelector('input[type="search"]'))}else"ArrowRight"===e.key&&(r=this.resultItemDetailContainer.querySelector("typo3-backend-live-search-result-item-action"));null!==r&&r.focus()}handleKeyUp(e){if(!["Enter"," "].includes(e.key))return;e.preventDefault();const t=e.target.resultItem;this.invokeAction(t)}invokeAction(e){this.parentContainer.dispatchEvent(new CustomEvent("livesearch:invoke-action",{detail:{resultItem:e,action:e.actions[0]}}))}};ResultList.styles=css`
     :host {
       display: block;
     }
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/element/result/result-container.js b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/element/result/result-container.js
index 4ac64709742c..1745a8bbdd0e 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/element/result/result-container.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/element/result/result-container.js
@@ -10,7 +10,7 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-var __decorate=function(e,t,r,i){var n,o=arguments.length,l=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,r,i);else for(var s=e.length-1;s>=0;s--)(n=e[s])&&(l=(o<3?n(l):o>3?n(t,r,l):n(t,r))||l);return o>3&&l&&Object.defineProperty(t,r,l),l};import{customElement,property,query}from"lit/decorators.js";import{html,LitElement}from"lit";import{lll}from"@typo3/core/lit-helper.js";import"@typo3/backend/live-search/element/result/item/item-container.js";import"@typo3/backend/live-search/element/result/result-detail-container.js";export const componentName="typo3-backend-live-search-result-container";let ResultContainer=class extends LitElement{constructor(){super(...arguments),this.results=null,this.loading=!1,this.renderers={},this.invokeHandlers={}}connectedCallback(){super.connectedCallback(),this.addEventListener("livesearch:request-actions",(e=>{this.resultDetailContainer.resultItem=e.detail.resultItem})),this.addEventListener("livesearch:invoke-action",(e=>{const t=e.detail.resultItem,r=e.detail.action;void 0!==r&&("function"==typeof this.invokeHandlers[t.provider+"_"+r.identifier]?this.invokeHandlers[t.provider+"_"+r.identifier](t,r):TYPO3.Backend.ContentContainer.setUrl(r.url),this.dispatchEvent(new CustomEvent("live-search:item-chosen",{detail:{resultItem:t}})))}))}createRenderRoot(){return this}render(){return this.loading?html`<div class="d-flex flex-fill justify-content-center mt-2"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`:null===this.results?html``:0===this.results.length?html`<div class="alert alert-info">${lll("liveSearch_listEmptyText")}</div>`:html`
-      <typo3-backend-live-search-result-item-container .results="${this.results}" .renderers="${this.renderers}"></typo3-backend-live-search-result-item-container>
+var __decorate=function(e,t,r,i){var n,o=arguments.length,l=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,r):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)l=Reflect.decorate(e,t,r,i);else for(var a=e.length-1;a>=0;a--)(n=e[a])&&(l=(o<3?n(l):o>3?n(t,r,l):n(t,r))||l);return o>3&&l&&Object.defineProperty(t,r,l),l};import LiveSearchConfigurator from"@typo3/backend/live-search/live-search-configurator.js";import{customElement,property,query}from"lit/decorators.js";import{html,LitElement}from"lit";import{lll}from"@typo3/core/lit-helper.js";import"@typo3/backend/live-search/element/result/item/item-container.js";import"@typo3/backend/live-search/element/result/result-detail-container.js";export const componentName="typo3-backend-live-search-result-container";let ResultContainer=class extends LitElement{constructor(){super(...arguments),this.results=null,this.loading=!1}connectedCallback(){super.connectedCallback(),this.addEventListener("livesearch:request-actions",(e=>{this.resultDetailContainer.resultItem=e.detail.resultItem})),this.addEventListener("livesearch:invoke-action",(e=>{const t=LiveSearchConfigurator.getInvokeHandlers(),r=e.detail.resultItem,i=e.detail.action;void 0!==i&&("function"==typeof t[r.provider+"_"+i.identifier]?t[r.provider+"_"+i.identifier](r,i):TYPO3.Backend.ContentContainer.setUrl(i.url),this.dispatchEvent(new CustomEvent("live-search:item-chosen",{detail:{resultItem:r}})))}))}createRenderRoot(){return this}render(){return this.loading?html`<div class="d-flex flex-fill justify-content-center mt-2"><typo3-backend-spinner size="large"></typo3-backend-spinner></div>`:null===this.results?html``:0===this.results.length?html`<div class="alert alert-info">${lll("liveSearch_listEmptyText")}</div>`:html`
+      <typo3-backend-live-search-result-item-container .results="${this.results}"></typo3-backend-live-search-result-item-container>
       <typo3-backend-live-search-result-item-detail-container></typo3-backend-live-search-result-item-detail-container>
-    `}};__decorate([property({type:Object,attribute:!1})],ResultContainer.prototype,"results",void 0),__decorate([property({type:Boolean,attribute:!1})],ResultContainer.prototype,"loading",void 0),__decorate([property({type:Object,attribute:!1})],ResultContainer.prototype,"renderers",void 0),__decorate([property({type:Object,attribute:!1})],ResultContainer.prototype,"invokeHandlers",void 0),__decorate([query("typo3-backend-live-search-result-item-container")],ResultContainer.prototype,"itemContainer",void 0),__decorate([query("typo3-backend-live-search-result-item-detail-container")],ResultContainer.prototype,"resultDetailContainer",void 0),ResultContainer=__decorate([customElement(componentName)],ResultContainer);export{ResultContainer};
\ No newline at end of file
+    `}};__decorate([property({type:Object})],ResultContainer.prototype,"results",void 0),__decorate([property({type:Boolean,attribute:!1})],ResultContainer.prototype,"loading",void 0),__decorate([query("typo3-backend-live-search-result-item-container")],ResultContainer.prototype,"itemContainer",void 0),__decorate([query("typo3-backend-live-search-result-item-detail-container")],ResultContainer.prototype,"resultDetailContainer",void 0),ResultContainer=__decorate([customElement(componentName)],ResultContainer);export{ResultContainer};
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/live-search-configurator.js b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/live-search-configurator.js
new file mode 100644
index 000000000000..819701a55254
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/live-search-configurator.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!
+ */
+class LiveSearchConfigurator{constructor(){this.renderers={},this.invokeHandlers={}}getRenderers(){return this.renderers}addRenderer(r,e,t){this.renderers[r]={module:e,callback:t}}getInvokeHandlers(){return this.invokeHandlers}addInvokeHandler(r,e,t){this.invokeHandlers[r+"_"+e]=t}}let configuratorObject;top.TYPO3.LiveSearchConfigurator?configuratorObject=top.TYPO3.LiveSearchConfigurator:(configuratorObject=new LiveSearchConfigurator,top.TYPO3.LiveSearchConfigurator=configuratorObject);export default configuratorObject;
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/result-types/default-result-type.js b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/result-types/default-result-type.js
index 3c87155d5f57..ce04ea026cbf 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/result-types/default-result-type.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/result-types/default-result-type.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import LiveSearch from"@typo3/backend/toolbar/live-search.js";import"@typo3/backend/live-search/element/provider/page-provider-result-item.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import Notification from"@typo3/backend/notification.js";export function registerType(e){LiveSearch.addInvokeHandler(e,"switch_backend_user",(e=>{new AjaxRequest(TYPO3.settings.ajaxUrls.switch_user).post({targetUser:e.extraData.uid}).then((async e=>{const t=await e.resolve();!0===t.success&&t.url?top.window.location.href=t.url:Notification.error("Switching to user went wrong.")}))}))}
\ No newline at end of file
+import LiveSearchConfigurator from"@typo3/backend/live-search/live-search-configurator.js";import"@typo3/backend/live-search/element/provider/page-provider-result-item.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import Notification from"@typo3/backend/notification.js";export function registerType(e){LiveSearchConfigurator.addInvokeHandler(e,"switch_backend_user",(e=>{new AjaxRequest(TYPO3.settings.ajaxUrls.switch_user).post({targetUser:e.extraData.uid}).then((async e=>{const r=await e.resolve();!0===r.success&&r.url?top.window.location.href=r.url:Notification.error("Switching to user went wrong.")}))}))}
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/result-types/page-result-type.js b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/result-types/page-result-type.js
index 96ac8f52d9d0..224c042a5e54 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/live-search/result-types/page-result-type.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/live-search/result-types/page-result-type.js
@@ -10,9 +10,9 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import{html}from"lit";import LiveSearch from"@typo3/backend/toolbar/live-search.js";import"@typo3/backend/live-search/element/provider/page-provider-result-item.js";import windowManager from"@typo3/backend/window-manager.js";export function registerRenderer(e){LiveSearch.addRenderer(e,(e=>html`<typo3-backend-live-search-result-item-page-provider
-      .icon="${e.icon}"
-      .itemTitle="${e.itemTitle}"
-      .typeLabel="${e.typeLabel}"
-      .extraData="${e.extraData}">
-    </typo3-backend-live-search-result-item-page-provider>`)),LiveSearch.addInvokeHandler(e,"preview_page",((e,r)=>{windowManager.localOpen(r.url,!0)}))}
\ No newline at end of file
+import LiveSearchConfigurator from"@typo3/backend/live-search/live-search-configurator.js";import{html}from"lit";import windowManager from"@typo3/backend/window-manager.js";export function registerRenderer(e){LiveSearchConfigurator.addRenderer(e,"@typo3/backend/live-search/element/provider/page-provider-result-item.js",(e=>html`<typo3-backend-live-search-result-item-page-provider
+        .icon="${e.icon}"
+        .itemTitle="${e.itemTitle}"
+        .typeLabel="${e.typeLabel}"
+        .extraData="${e.extraData}">
+      </typo3-backend-live-search-result-item-page-provider>`)),LiveSearchConfigurator.addInvokeHandler(e,"preview_page",((e,r)=>{windowManager.localOpen(r.url,!0)}))}
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/toolbar/live-search.js b/typo3/sysext/backend/Resources/Public/JavaScript/toolbar/live-search.js
index 7ba6305800fd..ede0cfc3e1b7 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/toolbar/live-search.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/toolbar/live-search.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import{lll}from"@typo3/core/lit-helper.js";import Modal from"@typo3/backend/modal.js";import"@typo3/backend/element/icon-element.js";import"@typo3/backend/input/clearable.js";import"@typo3/backend/live-search/element/search-option-item.js";import"@typo3/backend/live-search/element/show-all.js";import"@typo3/backend/live-search/live-search-shortcut.js";import DocumentService from"@typo3/core/document-service.js";import RegularEvent from"@typo3/core/event/regular-event.js";import DebounceEvent from"@typo3/core/event/debounce-event.js";import{SeverityEnum}from"@typo3/backend/enum/severity.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import BrowserSession from"@typo3/backend/storage/browser-session.js";import{componentName as resultContainerComponentName}from"@typo3/backend/live-search/element/result/result-container.js";var Identifiers;!function(e){e.toolbarItem=".t3js-topbar-button-search",e.searchOptionDropdown=".t3js-search-provider-dropdown",e.searchOptionDropdownToggle=".t3js-search-provider-dropdown-toggle"}(Identifiers||(Identifiers={}));class LiveSearch{constructor(){this.renderers={},this.invokeHandlers={},this.searchTerm="",this.searchOptions={},this.search=async()=>{BrowserSession.set("livesearch-term",this.searchTerm);let e=null;if(""!==this.searchTerm){document.querySelector(resultContainerComponentName).loading=!0;const r=await new AjaxRequest(TYPO3.settings.ajaxUrls.livesearch).post({q:this.searchTerm,options:this.searchOptions});e=await r.raw().json()}this.updateSearchResults(e)},DocumentService.ready().then((()=>{this.registerEvents()}))}addRenderer(e,r){this.renderers[e]=r}addInvokeHandler(e,r,t){this.invokeHandlers[e+"_"+r]=t}registerEvents(){new RegularEvent("click",(()=>{this.openSearchModal()})).delegateTo(document,Identifiers.toolbarItem),new RegularEvent("typo3:live-search:trigger-open",(()=>{Modal.currentModal||this.openSearchModal()})).bindTo(document)}openSearchModal(){const e=Modal.advanced({type:Modal.types.ajax,content:TYPO3.settings.ajaxUrls.livesearch_form+"&q="+(BrowserSession.get("livesearch-term")??""),title:lll("labels.search"),severity:SeverityEnum.notice,size:Modal.sizes.medium});e.addEventListener("typo3-modal-shown",(()=>{this.searchTerm=BrowserSession.get("livesearch-term")??"";const r=Object.entries(BrowserSession.getByPrefix("livesearch-option-")).filter((e=>"1"===e[1])).map((e=>{const r=e[0].replace("livesearch-option-",""),[t,o]=r.split("-",2);return{key:t,value:o}}));this.composeSearchOptions(r);const t=e.querySelector('input[type="search"]');t.clearable({onClear:()=>{this.searchTerm="",this.search()}}),t.focus(),t.select();const o=document.querySelector("typo3-backend-live-search-result-container");new RegularEvent("live-search:item-chosen",(()=>{Modal.dismiss()})).bindTo(o),new RegularEvent("hide.bs.dropdown",(()=>{const r=Array.from(e.querySelectorAll(Identifiers.searchOptionDropdown+" typo3-backend-live-search-option-item")).filter((e=>e.active)).map((e=>({key:e.optionName,value:e.optionId})));this.composeSearchOptions(r),this.search()})).bindTo(e.querySelector(Identifiers.searchOptionDropdownToggle)),new DebounceEvent("input",(e=>{this.searchTerm=e.target.value,this.search()})).bindTo(t),new RegularEvent("keydown",this.handleKeyDown).bindTo(t),this.search()}))}composeSearchOptions(e){this.searchOptions={},e.forEach((e=>{void 0===this.searchOptions[e.key]&&(this.searchOptions[e.key]=[]),this.searchOptions[e.key].push(e.value)}))}handleKeyDown(e){if("ArrowDown"!==e.key)return;e.preventDefault();document.querySelector("typo3-backend-live-search").querySelector("typo3-backend-live-search-result-item")?.focus()}updateSearchResults(e){document.querySelector("typo3-backend-live-search-show-all").parentElement.hidden=null===e||0===e.length;const r=document.querySelector("typo3-backend-live-search-result-container");r.renderers=this.renderers,r.invokeHandlers=this.invokeHandlers,r.results=e,r.loading=!1}}export default top.TYPO3.LiveSearch??new LiveSearch;
\ No newline at end of file
+import{lll}from"@typo3/core/lit-helper.js";import Modal from"@typo3/backend/modal.js";import"@typo3/backend/element/icon-element.js";import"@typo3/backend/input/clearable.js";import"@typo3/backend/live-search/element/search-option-item.js";import"@typo3/backend/live-search/element/show-all.js";import"@typo3/backend/live-search/live-search-shortcut.js";import DocumentService from"@typo3/core/document-service.js";import RegularEvent from"@typo3/core/event/regular-event.js";import DebounceEvent from"@typo3/core/event/debounce-event.js";import{SeverityEnum}from"@typo3/backend/enum/severity.js";import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import BrowserSession from"@typo3/backend/storage/browser-session.js";import{componentName as resultContainerComponentName}from"@typo3/backend/live-search/element/result/result-container.js";var Identifiers;!function(e){e.toolbarItem=".t3js-topbar-button-search",e.searchOptionDropdown=".t3js-search-provider-dropdown",e.searchOptionDropdownToggle=".t3js-search-provider-dropdown-toggle"}(Identifiers||(Identifiers={}));class LiveSearch{constructor(){this.searchTerm="",this.searchOptions={},this.search=async()=>{BrowserSession.set("livesearch-term",this.searchTerm);let e=null;if(""!==this.searchTerm){document.querySelector(resultContainerComponentName).loading=!0;const t=await new AjaxRequest(TYPO3.settings.ajaxUrls.livesearch).post({q:this.searchTerm,options:this.searchOptions});e=await t.raw().json()}this.updateSearchResults(e)},DocumentService.ready().then((()=>{this.registerEvents()}))}registerEvents(){new RegularEvent("click",(()=>{this.openSearchModal()})).delegateTo(document,Identifiers.toolbarItem),new RegularEvent("typo3:live-search:trigger-open",(()=>{Modal.currentModal||this.openSearchModal()})).bindTo(document)}openSearchModal(){const e=Modal.advanced({type:Modal.types.ajax,content:TYPO3.settings.ajaxUrls.livesearch_form+"&q="+(BrowserSession.get("livesearch-term")??""),title:lll("labels.search"),severity:SeverityEnum.notice,size:Modal.sizes.medium});e.addEventListener("typo3-modal-shown",(()=>{this.searchTerm=BrowserSession.get("livesearch-term")??"";const t=Object.entries(BrowserSession.getByPrefix("livesearch-option-")).filter((e=>"1"===e[1])).map((e=>{const t=e[0].replace("livesearch-option-",""),[r,o]=t.split("-",2);return{key:r,value:o}}));this.composeSearchOptions(t);const r=e.querySelector('input[type="search"]');r.clearable({onClear:()=>{this.searchTerm="",this.search()}}),r.focus(),r.select();const o=document.querySelector("typo3-backend-live-search-result-container");new RegularEvent("live-search:item-chosen",(()=>{Modal.dismiss()})).bindTo(o),new RegularEvent("hide.bs.dropdown",(()=>{const t=Array.from(e.querySelectorAll(Identifiers.searchOptionDropdown+" typo3-backend-live-search-option-item")).filter((e=>e.active)).map((e=>({key:e.optionName,value:e.optionId})));this.composeSearchOptions(t),this.search()})).bindTo(e.querySelector(Identifiers.searchOptionDropdownToggle)),new DebounceEvent("input",(e=>{this.searchTerm=e.target.value,this.search()})).bindTo(r),new RegularEvent("keydown",this.handleKeyDown).bindTo(r),this.search()}))}composeSearchOptions(e){this.searchOptions={},e.forEach((e=>{void 0===this.searchOptions[e.key]&&(this.searchOptions[e.key]=[]),this.searchOptions[e.key].push(e.value)}))}handleKeyDown(e){if("ArrowDown"!==e.key)return;e.preventDefault();document.querySelector("typo3-backend-live-search").querySelector("typo3-backend-live-search-result-item")?.focus()}updateSearchResults(e){document.querySelector("typo3-backend-live-search-show-all").parentElement.hidden=null===e||0===e.length;const t=document.querySelector("typo3-backend-live-search-result-container");t.results=e,t.loading=!1}}export default top.TYPO3.LiveSearch??new LiveSearch;
\ No newline at end of file
-- 
GitLab