diff --git a/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/MultiRecordSelection.ts b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/MultiRecordSelection.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c85854a0ff834b9e69ebd132134207d86b6fb9cb
--- /dev/null
+++ b/Build/Sources/TypeScript/backend/Resources/Public/TypeScript/MultiRecordSelection.ts
@@ -0,0 +1,271 @@
+/*
+ * 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!
+ */
+
+import Notification = require('TYPO3/CMS/Backend/Notification');
+import DocumentService = require('TYPO3/CMS/Core/DocumentService');
+import RegularEvent = require('TYPO3/CMS/Core/Event/RegularEvent');
+
+enum Selectors {
+  actionsSelector = '.t3js-multi-record-selection-actions',
+  checkboxSelector = '.t3js-multi-record-selection-check',
+  checkboxActionsSelector = '#multi-record-selection-check-actions',
+}
+
+enum Buttons {
+  actionButton = 'button[data-multi-record-selection-action]',
+  checkboxActionButton = 'button[data-multi-record-selection-check-action]',
+  checkboxActionsToggleButton = 'button[data-bs-target="multi-record-selection-check-actions"]'
+}
+
+enum Actions {
+  edit = 'edit'
+}
+
+enum CheckboxActions {
+  checkAll = 'check-all',
+  checkNone = 'check-none',
+  toggle = 'toggle'
+}
+
+enum CheckboxState {
+  any = '',
+  checked = ':checked',
+  unchecked = ':not(:checked)'
+}
+
+interface ActionConfiguration {
+  idField: string;
+}
+
+interface EditActionConfiguration extends ActionConfiguration{
+  table: string;
+  returnUrl: string;
+}
+
+/**
+ * Module: TYPO3/CMS/Backend/MultiRecordSelection
+ */
+class MultiRecordSelection {
+  private static getCheckboxes(state: CheckboxState = CheckboxState.any): NodeListOf<HTMLInputElement> {
+    return document.querySelectorAll(Selectors.checkboxSelector + state);
+  }
+
+  private static changeCheckboxState(checkbox: HTMLInputElement, check: boolean): void {
+    if (checkbox.checked === check) {
+      // Return if state did not change
+      return;
+    }
+    checkbox.checked = check;
+    checkbox.dispatchEvent(new Event('checkbox:state:changed', {bubbles: true, cancelable: false}));
+  }
+
+  private static getReturnUrl(returnUrl: string): string {
+    if (returnUrl === '') {
+      returnUrl = top.list_frame.document.location.pathname + top.list_frame.document.location.search;
+    }
+    return encodeURIComponent(returnUrl);
+  }
+
+  /**
+   * This restores (initializes) a temporary state, which is required in case
+   * the user returns to the listing using the browsers' history back feature,
+   * which will not result in a new request.
+   */
+  private static restoreTemporaryState(): void {
+    const checked: NodeListOf<HTMLInputElement> = MultiRecordSelection.getCheckboxes(CheckboxState.checked);
+    // In case nothing is checked we don't have to do anything here
+    if (!checked.length) {
+      return;
+    }
+    checked.forEach((checkbox: HTMLInputElement) => {
+      checkbox.closest('tr').classList.add('success');
+    });
+    const actionsContainer: HTMLElement = document.querySelector(Selectors.actionsSelector);
+    if (actionsContainer !== null) {
+      actionsContainer.classList.remove('hidden');
+    }
+  }
+
+  /**
+   * Toggles the state of the actions, depending on the
+   * currently selected elements and their nature.
+   */
+  private static toggleActionsState(): void {
+    const actions: NodeListOf<HTMLButtonElement> = document.querySelectorAll([Selectors.actionsSelector, Buttons.actionButton].join(' '));
+    if (!actions.length) {
+      // Early return in case no action is defined
+      return;
+    }
+
+    actions.forEach((action: HTMLButtonElement): void => {
+      if (!action.dataset.multiRecordSelectionActionConfig) {
+        // In case the action does not define any configuration, no toggling is possible
+        return;
+      }
+      const configuration: ActionConfiguration = JSON.parse(action.dataset.multiRecordSelectionActionConfig);
+      if (!configuration.idField) {
+        // Return in case the idField (where to find the id on selected elements) is not defined
+        return;
+      }
+      // Start the evaluation by disabling the action
+      action.classList.add('disabled');
+      // Get all currently checked elements
+      const checked: NodeListOf<HTMLInputElement> = MultiRecordSelection.getCheckboxes(CheckboxState.checked);
+      for (let i=0; i < checked.length; i++) {
+        // Evaluate each checked element if it contains the specified idField
+        if (checked[i].closest('tr').dataset[configuration.idField]) {
+          // If a checked element contains the idField, remove the "disabled"
+          // state and end the search since the action can be performed.
+          action.classList.remove('disabled');
+          break;
+        }
+      }
+    });
+  }
+
+  constructor() {
+    DocumentService.ready().then((): void => {
+      MultiRecordSelection.restoreTemporaryState();
+      this.registerActions();
+      this.registerCheckboxActions();
+      this.registerToggleCheckboxActions();
+      this.registerDispatchCheckboxStateChangedEvent();
+      this.registerCheckboxStateChangedEventHandler();
+    });
+  }
+
+  private registerActions(): void {
+    new RegularEvent('click', (e: Event, target: HTMLButtonElement): void => {
+      const checked: NodeListOf<HTMLInputElement> = MultiRecordSelection.getCheckboxes(CheckboxState.checked);
+
+      if (!target.dataset.multiRecordSelectionAction || !checked.length) {
+        // Return if we don't deal with a valid action or in case there is
+        // currently no element checked to perform the action on.
+        return;
+      }
+
+      // Perform requested action
+      switch (target.dataset.multiRecordSelectionAction) {
+        case Actions.edit:
+          e.preventDefault();
+          const configuration: EditActionConfiguration = JSON.parse(target.dataset.multiRecordSelectionActionConfig || '');
+          if (!configuration || !configuration.idField || !configuration.table) {
+            break;
+          }
+          const list: Array<string> = [];
+          checked.forEach((checkbox: HTMLInputElement) => {
+            const checkboxContainer: HTMLElement = checkbox.closest('tr');
+            if (checkboxContainer !== null && checkboxContainer.dataset[configuration.idField]) {
+              list.push(checkboxContainer.dataset[configuration.idField]);
+            }
+          });
+          if (list.length) {
+            window.location.href = top.TYPO3.settings.FormEngine.moduleUrl
+              + '&edit[' + configuration.table + '][' + list.join(',') + ']=edit'
+              + '&returnUrl=' + MultiRecordSelection.getReturnUrl(configuration.returnUrl || '');
+          } else {
+            Notification.warning('The selected elements can not be edited.');
+          }
+          break;
+        default:
+          // Not all actions are handled here. Therefore we simply skip them and just
+          // dispatch an event so those components can react on the triggered action.
+          target.dispatchEvent(new Event('multiRecordSelection:action:' + target.dataset.multiRecordSelectionAction, {bubbles: true, cancelable: false}));
+          break;
+      }
+    }).delegateTo(document, [Selectors.actionsSelector, Buttons.actionButton].join(' '));
+
+    // After registering the event, toggle their state
+    MultiRecordSelection.toggleActionsState();
+  }
+
+  private registerCheckboxActions(): void {
+    new RegularEvent('click', (e: Event, target: HTMLButtonElement): void => {
+      e.preventDefault();
+
+      const checkboxes: NodeListOf<HTMLInputElement> = MultiRecordSelection.getCheckboxes();
+      if (!target.dataset.multiRecordSelectionCheckAction || !checkboxes.length) {
+        // Return if we don't deal with a valid action or in case there
+        // are no checkboxes (elements) to perform the action on.
+        return;
+      }
+
+      // Perform requested action
+      switch (target.dataset.multiRecordSelectionCheckAction) {
+        case CheckboxActions.checkAll:
+          checkboxes.forEach((checkbox: HTMLInputElement) => {
+            MultiRecordSelection.changeCheckboxState(checkbox, true);
+          });
+          break;
+        case CheckboxActions.checkNone:
+          checkboxes.forEach((checkbox: HTMLInputElement) => {
+            MultiRecordSelection.changeCheckboxState(checkbox, false);
+          });
+          break;
+        case CheckboxActions.toggle:
+          checkboxes.forEach((checkbox: HTMLInputElement) => {
+            MultiRecordSelection.changeCheckboxState(checkbox, !checkbox.checked);
+          });
+          break;
+        default:
+          // Unknown action
+          Notification.warning('Unknown checkbox action');
+      }
+    }).delegateTo(document, [Selectors.checkboxActionsSelector, Buttons.checkboxActionButton].join(' '));
+  }
+
+  private registerDispatchCheckboxStateChangedEvent(): void {
+    new RegularEvent('change', (e: Event, target: HTMLInputElement): void => {
+      target.dispatchEvent(new Event('checkbox:state:changed', {bubbles: true, cancelable: false}));
+    }).delegateTo(document, Selectors.checkboxSelector);
+  }
+
+  private registerCheckboxStateChangedEventHandler(): void {
+    new RegularEvent('checkbox:state:changed', (e: Event): void => {
+      const checkbox: HTMLInputElement = <HTMLInputElement>e.target;
+      if (checkbox.checked) {
+        checkbox.closest('tr').classList.add('success');
+      } else {
+        checkbox.closest('tr').classList.remove('success');
+      }
+
+      const actionsContainer: HTMLElement = document.querySelector(Selectors.actionsSelector);
+      if (actionsContainer !== null) {
+        if (MultiRecordSelection.getCheckboxes(CheckboxState.checked).length) {
+          actionsContainer.classList.remove('hidden');
+        } else {
+          actionsContainer.classList.add('hidden');
+        }
+      }
+
+      // Toggle actions for changed checkbox state
+      MultiRecordSelection.toggleActionsState();
+    }).bindTo(document);
+  }
+
+  private registerToggleCheckboxActions(): void {
+    new RegularEvent('click', (): void => {
+      const checkAll: HTMLButtonElement = document.querySelector('button[data-multi-record-selection-check-action="' + CheckboxActions.checkAll + '"]');
+      if (checkAll !== null) {
+        checkAll.classList.toggle('disabled', !MultiRecordSelection.getCheckboxes(CheckboxState.unchecked).length)
+      }
+
+      const checkNone: HTMLButtonElement = document.querySelector('button[data-multi-record-selection-check-action="' + CheckboxActions.checkNone + '"]');
+      if (checkNone !== null) {
+        checkNone.classList.toggle('disabled', !MultiRecordSelection.getCheckboxes(CheckboxState.checked).length);
+      }
+    }).delegateTo(document, Buttons.checkboxActionsToggleButton);
+  }
+}
+
+export = new MultiRecordSelection();
diff --git a/Build/Sources/TypeScript/recordlist/Resources/Public/TypeScript/BrowseFiles.ts b/Build/Sources/TypeScript/recordlist/Resources/Public/TypeScript/BrowseFiles.ts
index 2c6b731b934cc7dbece6cd2fc0c16838450baf5a..edbb8da2e71d709f780bd6cec8b403967dbc2c6b 100644
--- a/Build/Sources/TypeScript/recordlist/Resources/Public/TypeScript/BrowseFiles.ts
+++ b/Build/Sources/TypeScript/recordlist/Resources/Public/TypeScript/BrowseFiles.ts
@@ -17,21 +17,12 @@ import NProgress = require('nprogress');
 import RegularEvent = require('TYPO3/CMS/Core/Event/RegularEvent');
 import Icons = TYPO3.Icons;
 
-enum Selectors {
-  bulkItemSelector = '.typo3-list-check',
-  importSelectionSelector = 'button[data-action="import"]',
-  selectionToggleSelector = '.typo3-selection-toggle',
-  listContainer = '[data-list-container="files"]'
-}
-
 interface LinkElement {
   fileName: string;
   uid: string;
 }
 
 class BrowseFiles {
-  public static Selector: Selector;
-
   public static insertElement(fileName: string, fileUid: number, close?: boolean): boolean {
     return ElementBrowser.insertElement(
       'sys_file',
@@ -42,9 +33,14 @@ class BrowseFiles {
     );
   }
 
-  constructor() {
-    BrowseFiles.Selector = new Selector();
+  private static handleNext(items: LinkElement[]): void {
+    if (items.length > 0) {
+      const item = items.pop();
+      BrowseFiles.insertElement(item.fileName, Number(item.uid));
+    }
+  }
 
+  constructor() {
     new RegularEvent('click', (evt: MouseEvent, targetEl: HTMLElement): void => {
       evt.preventDefault();
       BrowseFiles.insertElement(
@@ -54,87 +50,33 @@ class BrowseFiles {
       );
     }).delegateTo(document, '[data-close]');
 
-    new RegularEvent('change', BrowseFiles.Selector.toggleImportButton).delegateTo(document, Selectors.bulkItemSelector);
-    new RegularEvent('click', BrowseFiles.Selector.handle).delegateTo(document, Selectors.importSelectionSelector);
-    new RegularEvent('click', BrowseFiles.Selector.toggle).delegateTo(document, Selectors.selectionToggleSelector);
-    new RegularEvent('change', BrowseFiles.Selector.toggle).delegateTo(document, Selectors.bulkItemSelector);
+    // Handle import selection event, dispatched from MultiRecordSelection
+    new RegularEvent('multiRecordSelection:action:import', this.importSelection).bindTo(document);
   }
 
-}
-
-class Selector {
-  /**
-   * Either a toggle button (all/none/toggle) button was pressed, or a checkbox was switched
-   */
-  public toggle = (e: MouseEvent): void => {
+  private importSelection = (e: Event): void => {
     e.preventDefault();
-    const element = e.target as HTMLInputElement;
-    const action = element.dataset.action;
-    const items = this.getItems();
-
-    switch (action) {
-      case 'select-toggle':
-        items.forEach((item: HTMLInputElement) => {
-          item.checked = !item.checked;
-          item.closest('tr').classList.toggle('success');
-        });
-        break;
-      case 'select-all':
-        items.forEach((item: HTMLInputElement) => {
-          item.checked = true;
-          item.closest('tr').classList.add('success');
-        });
-        break;
-      case 'select-none':
-        items.forEach((item: HTMLInputElement) => {
-          item.checked = false;
-          item.closest('tr').classList.remove('success');
-        });
-        break;
-      default:
-        // the button itself was checked
-        if (element.classList.contains('typo3-list-check')) {
-          element.closest('tr').classList.toggle('success');
-        }
+    const targetEl: HTMLElement = e.target as HTMLElement;
+    const items: NodeListOf<HTMLInputElement> = document.querySelectorAll('.t3js-multi-record-selection-check');
+    if (!items.length) {
+      return;
     }
-    this.toggleImportButton();
-  }
 
-  /**
-   * Import selection button is pressed
-   */
-  public handle = (e: MouseEvent, targetEl: HTMLElement): void => {
-    e.preventDefault();
-    const items = this.getItems();
     const selectedItems: Array<LinkElement> = [];
-    if (items.length) {
-      items.forEach((item: HTMLInputElement) => {
-        if (item.checked && item.name && item.dataset.fileName && item.dataset.fileUid) {
-          selectedItems.unshift({uid: item.dataset.fileUid, fileName: item.dataset.fileName});
-        }
-      });
-      Icons.getIcon('spinner-circle', Icons.sizes.small, null, null, Icons.markupIdentifiers.inline).then((icon: string): void => {
-        targetEl.classList.add('disabled');
-        targetEl.innerHTML = icon;
-      });
-      this.handleSelection(selectedItems);
-    }
-  }
-
-  public getItems(): NodeList {
-    return document.querySelector(Selectors.listContainer).querySelectorAll(Selectors.bulkItemSelector);
-  }
-
-  public toggleImportButton(): void {
-    const hasCheckedElements = document.querySelector(Selectors.listContainer)?.querySelectorAll(Selectors.bulkItemSelector + ':checked').length > 0;
-    document.querySelector(Selectors.importSelectionSelector).classList.toggle('disabled', !hasCheckedElements);
-  }
+    items.forEach((item: HTMLInputElement) => {
+      if (item.checked && item.name && item.dataset.fileName && item.dataset.fileUid) {
+        selectedItems.unshift({uid: item.dataset.fileUid, fileName: item.dataset.fileName});
+      }
+    });
 
-  private handleSelection(items: LinkElement[]): void {
+    Icons.getIcon('spinner-circle', Icons.sizes.small, null, null, Icons.markupIdentifiers.inline).then((icon: string): void => {
+      targetEl.classList.add('disabled');
+      targetEl.innerHTML = icon;
+    });
     NProgress.configure({parent: '.element-browser-main-content', showSpinner: false});
     NProgress.start();
-    const stepping = 1 / items.length;
-    this.handleNext(items);
+    const stepping = 1 / selectedItems.length;
+    BrowseFiles.handleNext(selectedItems);
 
     new RegularEvent('message', (e: MessageEvent): void => {
       if (!MessageUtility.verifyOrigin(e.origin)) {
@@ -142,9 +84,9 @@ class Selector {
       }
 
       if (e.data.actionName === 'typo3:foreignRelation:inserted') {
-        if (items.length > 0) {
+        if (selectedItems.length > 0) {
           NProgress.inc(stepping);
-          this.handleNext(items);
+          BrowseFiles.handleNext(selectedItems);
         } else {
           NProgress.done();
           ElementBrowser.focusOpenerAndClose();
@@ -152,13 +94,6 @@ class Selector {
       }
     }).bindTo(window);
   }
-
-  private handleNext(items: LinkElement[]): void {
-    if (items.length > 0) {
-      const item = items.pop();
-      BrowseFiles.insertElement(item.fileName, Number(item.uid));
-    }
-  }
 }
 
 export = new BrowseFiles();
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/MultiRecordSelection.js b/typo3/sysext/backend/Resources/Public/JavaScript/MultiRecordSelection.js
new file mode 100644
index 0000000000000000000000000000000000000000..8bc39188051f386b0c7a40038d3ac1f56bc0f968
--- /dev/null
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/MultiRecordSelection.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!
+ */
+define(["require","exports","TYPO3/CMS/Backend/Notification","TYPO3/CMS/Core/DocumentService","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,c,o,n){"use strict";var i,s,a,l,r;!function(e){e.actionsSelector=".t3js-multi-record-selection-actions",e.checkboxSelector=".t3js-multi-record-selection-check",e.checkboxActionsSelector="#multi-record-selection-check-actions"}(i||(i={})),function(e){e.actionButton="button[data-multi-record-selection-action]",e.checkboxActionButton="button[data-multi-record-selection-check-action]",e.checkboxActionsToggleButton='button[data-bs-target="multi-record-selection-check-actions"]'}(s||(s={})),function(e){e.edit="edit"}(a||(a={})),function(e){e.checkAll="check-all",e.checkNone="check-none",e.toggle="toggle"}(l||(l={})),function(e){e.any="",e.checked=":checked",e.unchecked=":not(:checked)"}(r||(r={}));class d{static getCheckboxes(e=r.any){return document.querySelectorAll(i.checkboxSelector+e)}static changeCheckboxState(e,t){e.checked!==t&&(e.checked=t,e.dispatchEvent(new Event("checkbox:state:changed",{bubbles:!0,cancelable:!1})))}static getReturnUrl(e){return""===e&&(e=top.list_frame.document.location.pathname+top.list_frame.document.location.search),encodeURIComponent(e)}static restoreTemporaryState(){const e=d.getCheckboxes(r.checked);if(!e.length)return;e.forEach(e=>{e.closest("tr").classList.add("success")});const t=document.querySelector(i.actionsSelector);null!==t&&t.classList.remove("hidden")}static toggleActionsState(){const e=document.querySelectorAll([i.actionsSelector,s.actionButton].join(" "));e.length&&e.forEach(e=>{if(!e.dataset.multiRecordSelectionActionConfig)return;const t=JSON.parse(e.dataset.multiRecordSelectionActionConfig);if(!t.idField)return;e.classList.add("disabled");const c=d.getCheckboxes(r.checked);for(let o=0;o<c.length;o++)if(c[o].closest("tr").dataset[t.idField]){e.classList.remove("disabled");break}})}constructor(){o.ready().then(()=>{d.restoreTemporaryState(),this.registerActions(),this.registerCheckboxActions(),this.registerToggleCheckboxActions(),this.registerDispatchCheckboxStateChangedEvent(),this.registerCheckboxStateChangedEventHandler()})}registerActions(){new n("click",(e,t)=>{const o=d.getCheckboxes(r.checked);if(t.dataset.multiRecordSelectionAction&&o.length)switch(t.dataset.multiRecordSelectionAction){case a.edit:e.preventDefault();const n=JSON.parse(t.dataset.multiRecordSelectionActionConfig||"");if(!n||!n.idField||!n.table)break;const i=[];o.forEach(e=>{const t=e.closest("tr");null!==t&&t.dataset[n.idField]&&i.push(t.dataset[n.idField])}),i.length?window.location.href=top.TYPO3.settings.FormEngine.moduleUrl+"&edit["+n.table+"]["+i.join(",")+"]=edit&returnUrl="+d.getReturnUrl(n.returnUrl||""):c.warning("The selected elements can not be edited.");break;default:t.dispatchEvent(new Event("multiRecordSelection:action:"+t.dataset.multiRecordSelectionAction,{bubbles:!0,cancelable:!1}))}}).delegateTo(document,[i.actionsSelector,s.actionButton].join(" ")),d.toggleActionsState()}registerCheckboxActions(){new n("click",(e,t)=>{e.preventDefault();const o=d.getCheckboxes();if(t.dataset.multiRecordSelectionCheckAction&&o.length)switch(t.dataset.multiRecordSelectionCheckAction){case l.checkAll:o.forEach(e=>{d.changeCheckboxState(e,!0)});break;case l.checkNone:o.forEach(e=>{d.changeCheckboxState(e,!1)});break;case l.toggle:o.forEach(e=>{d.changeCheckboxState(e,!e.checked)});break;default:c.warning("Unknown checkbox action")}}).delegateTo(document,[i.checkboxActionsSelector,s.checkboxActionButton].join(" "))}registerDispatchCheckboxStateChangedEvent(){new n("change",(e,t)=>{t.dispatchEvent(new Event("checkbox:state:changed",{bubbles:!0,cancelable:!1}))}).delegateTo(document,i.checkboxSelector)}registerCheckboxStateChangedEventHandler(){new n("checkbox:state:changed",e=>{const t=e.target;t.checked?t.closest("tr").classList.add("success"):t.closest("tr").classList.remove("success");const c=document.querySelector(i.actionsSelector);null!==c&&(d.getCheckboxes(r.checked).length?c.classList.remove("hidden"):c.classList.add("hidden")),d.toggleActionsState()}).bindTo(document)}registerToggleCheckboxActions(){new n("click",()=>{const e=document.querySelector('button[data-multi-record-selection-check-action="'+l.checkAll+'"]');null!==e&&e.classList.toggle("disabled",!d.getCheckboxes(r.unchecked).length);const t=document.querySelector('button[data-multi-record-selection-check-action="'+l.checkNone+'"]');null!==t&&t.classList.toggle("disabled",!d.getCheckboxes(r.checked).length)}).delegateTo(document,s.checkboxActionsToggleButton)}}return new d}));
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-94906-MultiRecordSelectionInFilelist.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-94906-MultiRecordSelectionInFilelist.rst
new file mode 100644
index 0000000000000000000000000000000000000000..878630c119970efe1358d5360029c8c2a0eb076a
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-94906-MultiRecordSelectionInFilelist.rst
@@ -0,0 +1,42 @@
+.. include:: ../../Includes.txt
+
+====================================================
+Feature: #94906 - Multi record selection in filelist
+====================================================
+
+See :issue:`94906`
+
+Description
+===========
+
+With :issue:`94452` the file list in the file selector has been improved
+by introducing an optimized way of selecting the files to attach to a record.
+
+Those optimizations have now also been added to the filelist module. The
+checkboxes, previously only used for adding files / folders to the
+clipboard, are now always shown in front of each file / folder and are
+now independant of the current clipboard mode. Furthermore are the
+convenience actions, such as "check all", "uncheck all" and "toggle
+selection" now available in the filelist, too.
+
+By decoupling the selection from the clipboard logic, it's therefore
+now possible to directly work with the current selection without the
+need to transfer it top the clipboard first. This means, editing or
+deleting multiple files is now directly possible without any clipboard
+interaction. The available actions appear, once an element has been
+selected.
+
+As mentioned above, the "Edit marked" action has been added to the
+filelist, which might already be known from the recordlist module.
+This action allows to edit the :sql:`sys_file_metadata` records of
+all selected files at once.
+
+Impact
+======
+
+Selection of files and folder is now quicker to grasp for editors working
+in the filelist module. It's furthermore now also possible to directly
+execute actions, e.g. editing metadata of selected files, without
+transferring them to the clipboard first.
+
+.. index:: Backend, ext:filelist
diff --git a/typo3/sysext/core/Tests/Acceptance/Backend/FileList/FileClipboardCest.php b/typo3/sysext/core/Tests/Acceptance/Backend/FileList/FileClipboardCest.php
index d2e65e498206857bd6216f78f624077cfa28fd97..2ab430644a34d83e6efdb61c92aa00f071e0e33b 100644
--- a/typo3/sysext/core/Tests/Acceptance/Backend/FileList/FileClipboardCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Backend/FileList/FileClipboardCest.php
@@ -77,8 +77,9 @@ class FileClipboardCest extends AbstractFileCest
 
         $I->amGoingTo('add multiple elements to clipboard');
         $I->click('Clipboard #1 (multi-selection mode)');
-        $I->click('.t3js-toggle-all-checkboxes');
-        $I->click('span[title="Transfer the selection of files to clipboard"]');
+        $I->click('.dropdown-toggle');
+        $I->click('button[data-multi-record-selection-check-action="check-all"]');
+        $I->click('button[data-multi-record-selection-action="setCB"]');
 
         foreach ($expectedFiles as $file) {
             $I->see($file, '#clipboard_form');
diff --git a/typo3/sysext/filelist/Classes/Controller/FileListController.php b/typo3/sysext/filelist/Classes/Controller/FileListController.php
index 356f398462da9ef3bfb32e1a0fd385771a227e35..a9c24cacbe54f2d111ff08f4b2af3673e1ad2adc 100644
--- a/typo3/sysext/filelist/Classes/Controller/FileListController.php
+++ b/typo3/sysext/filelist/Classes/Controller/FileListController.php
@@ -269,7 +269,7 @@ class FileListController implements LoggerAwareInterface
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileList');
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Filelist/FileDelete');
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
-        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ClipboardComponent');
+        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/MultiRecordSelection');
         $this->pageRenderer->addInlineLanguageLabelFile(
             'EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf',
             'buttons'
@@ -368,8 +368,7 @@ class FileListController implements LoggerAwareInterface
             $this->folderObject,
             MathUtility::forceIntegerInRange($this->pointer, 0, 100000),
             (string)($this->MOD_SETTINGS['sort'] ?? ''),
-            (bool)($this->MOD_SETTINGS['reverse'] ?? false),
-            (bool)($this->MOD_SETTINGS['clipBoard'] ?? false)
+            (bool)($this->MOD_SETTINGS['reverse'] ?? false)
         );
     }
 
@@ -384,7 +383,10 @@ class FileListController implements LoggerAwareInterface
 
         // Generate the list, if accessible
         if ($this->folderObject->getStorage()->isBrowsable()) {
-            $this->view->assign('listHtml', $this->filelist->getTable($searchDemand));
+            $this->view->assignMultiple([
+                'listHtml' => $this->filelist->getTable($searchDemand),
+                'totalItems' => $this->filelist->totalItems
+            ]);
             if ($this->filelist->totalItems === 0 && $searchDemand !== null) {
                 // In case this is a search and no results were found, add a flash message
                 // @todo This info should in the future also be displayed for folders without any file.
@@ -393,6 +395,15 @@ class FileListController implements LoggerAwareInterface
                     $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang.xlf:flashmessage.no_results')
                 );
             }
+            // Assign meta information for the multi record selection
+            $this->view->assignMultiple([
+                'hasSelectedElements' => $this->filelist->getSelectedElements() !== [],
+                'editActionConfiguration' => json_encode([
+                    'idField' => 'metadataUid',
+                    'table' => 'sys_file_metadata',
+                    'returnUrl' => $this->filelist->listURL()
+                ])
+            ]);
         } else {
             $this->addFlashMessage(
                 $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:storageNotBrowsableMessage'),
@@ -450,6 +461,7 @@ class FileListController implements LoggerAwareInterface
         $this->view->assign('enableClipBoard', [
             'enabled' => $userTsConfig['options.']['file_list.']['enableClipBoard'] === 'selectable',
             'label' => htmlspecialchars($lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clipBoard')),
+            'mode' => $this->filelist->clipObj->current,
             'html' => BackendUtility::getFuncCheck(
                 $this->id,
                 'SET[clipBoard]',
diff --git a/typo3/sysext/filelist/Classes/FileList.php b/typo3/sysext/filelist/Classes/FileList.php
index 8b3390c5c7b0bf5a2258257874cc626bf9c084c6..ea0c28df7a1050accfaf1c9cfe8d1b1eb07c0681 100644
--- a/typo3/sysext/filelist/Classes/FileList.php
+++ b/typo3/sysext/filelist/Classes/FileList.php
@@ -135,7 +135,8 @@ class FileList
      */
     public $addElement_tdCssClass = [
         '_CONTROL_' => 'col-control',
-        '_CLIPBOARD_' => 'col-clipboard',
+        '_SELECTOR_' => 'col-selector',
+        'icon' => 'col-icon',
         'file' => 'col-title col-responsive',
         '_LOCALIZATION_' => 'col-localizationa',
     ];
@@ -177,6 +178,8 @@ class FileList
 
     protected ?FileSearchDemand $searchDemand = null;
 
+    protected array $selectedElements = [];
+
     public function __construct(?ServerRequestInterface $request = null)
     {
         // Setting the maximum length of the filenames to the user's settings or minimum 30 (= $this->fixedL)
@@ -204,9 +207,8 @@ class FileList
      * @param int $pointer Pointer
      * @param string $sort Sorting column
      * @param bool $sortRev Sorting direction
-     * @param bool $clipBoard
      */
-    public function start(Folder $folderObject, $pointer, $sort, $sortRev, $clipBoard = false)
+    public function start(Folder $folderObject, $pointer, $sort, $sortRev)
     {
         $this->folderObject = $folderObject;
         $this->counter = 0;
@@ -214,36 +216,9 @@ class FileList
         $this->sort = $sort;
         $this->sortRev = $sortRev;
         $this->firstElementNumber = $pointer;
-        // Cleaning rowlist for duplicates and place the $titleCol as the first column always!
-        $rowlist = 'file,_LOCALIZATION_,_CONTROL_,fileext,tstamp,size,rw,_REF_';
-        if ($clipBoard) {
-            $rowlist = str_replace('_CONTROL_,', '_CONTROL_,_CLIPBOARD_,', $rowlist);
-        }
-        $this->fieldArray = explode(',', $rowlist);
-    }
-
-    /**
-     * Wrapping input string in a link with clipboard command.
-     *
-     * @param string $string String to be linked - must be htmlspecialchar'ed / prepared before.
-     * @param string $cmd "cmd" value
-     * @param string $warning Warning for JS confirm message
-     * @return string Linked string
-     */
-    public function linkClipboardHeaderIcon($string, $cmd, $warning = '')
-    {
-        if ($warning) {
-            $attributes['class'] = 'btn btn-default t3js-modal-trigger';
-            $attributes['data-severity'] = 'warning';
-            $attributes['data-bs-content'] = $warning;
-            $attributes['data-event-name'] = 'filelist:clipboard:cmd';
-            $attributes['data-event-payload'] = $cmd;
-        } else {
-            $attributes['class'] = 'btn btn-default';
-            $attributes['data-filelist-clipboard-cmd'] = $cmd;
-        }
-
-        return '<button type="button" ' . GeneralUtility::implodeAttributes($attributes, true) . '>' . $string . '</button>';
+        $this->fieldArray = [
+            '_SELECTOR_', 'icon', 'file', '_LOCALIZATION_', '_CONTROL_', 'fileext', 'tstamp', 'size', 'rw', '_REF_'
+        ];
     }
 
     /**
@@ -285,7 +260,7 @@ class FileList
             $files = array_slice($files, $this->firstElementNumber, $filesNum);
 
             // Add special "Path" field for the search result
-            array_unshift($this->fieldArray, '_PATH_');
+            array_splice($this->fieldArray, 3, 0, '_PATH_');
         } else {
             // @todo use folder methods directly when they support filters
             $storage = $this->folderObject->getStorage();
@@ -353,14 +328,14 @@ class FileList
         // Header line is drawn
         $theData = [];
         foreach ($this->fieldArray as $v) {
-            if ($v === '_CLIPBOARD_') {
-                $theData[$v] = $this->renderClipboardHeaderRow(!empty($iOut));
+            if ($v === '_SELECTOR_') {
+                $theData[$v] = $this->renderCheckboxActions();
             } elseif ($v === '_REF_') {
                 $theData[$v] = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._REF_'));
             } elseif ($v === '_PATH_') {
                 $theData[$v] = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels._PATH_'));
-            } else {
-                // Normal row
+            } elseif ($v !== 'icon') {
+                // Normal row - except "icon", which does not need a table header col
                 $theData[$v]  = $this->linkWrapSort($v);
             }
         }
@@ -369,87 +344,25 @@ class FileList
             <div class="mb-4 mt-2">
                 <div class="table-fit mb-0">
                     <table class="table table-striped table-hover" id="typo3-filelist">
-                        <thead>' . $this->addElement('', $theData, true) . '</thead>
+                        <thead>' . $this->addElement($theData, [], true) . '</thead>
                         <tbody>' . $iOut . '</tbody>
                     </table>
                 </div>
             </div>';
     }
 
-    protected function renderClipboardHeaderRow(bool $hasContent): string
-    {
-        $cells = [];
-        $elFromTable = $this->clipObj->elFromTable('_FILE');
-        if (!empty($elFromTable) && $this->folderObject->checkActionPermission('write')) {
-            $clipboardMode = $this->clipObj->clipData[$this->clipObj->current]['mode'] ?? '';
-            $permission = $clipboardMode === 'copy' ? 'copy' : 'move';
-            $addPasteButton = $this->folderObject->checkActionPermission($permission);
-            $elToConfirm = [];
-            foreach ($elFromTable as $key => $element) {
-                $clipBoardElement = $this->resourceFactory->retrieveFileOrFolderObject($element);
-                if ($clipBoardElement instanceof Folder && $clipBoardElement->getStorage()->isWithinFolder($clipBoardElement, $this->folderObject)) {
-                    $addPasteButton = false;
-                }
-                $elToConfirm[$key] = $clipBoardElement->getName();
-            }
-            if ($addPasteButton) {
-                $cells[] = '<a class="btn btn-default t3js-modal-trigger"' .
-                    ' href="' . htmlspecialchars($this->clipObj->pasteUrl(
-                        '_FILE',
-                        $this->folderObject->getCombinedIdentifier()
-                    )) . '"'
-                    . ' data-bs-content="' . htmlspecialchars($this->clipObj->confirmMsgText(
-                        '_FILE',
-                        $this->folderObject->getReadablePath(),
-                        'into',
-                        $elToConfirm
-                    )) . '"'
-                    . ' data-severity="warning"'
-                    . ' data-title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_paste')) . '"'
-                    . ' title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_paste')) . '">'
-                    . $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)
-                        ->render()
-                    . '</a>';
-            } else {
-                $cells[] = $this->spaceIcon;
-            }
-        }
-        if ($this->clipObj->current !== 'normal' && $hasContent) {
-            $cells[] = $this->linkClipboardHeaderIcon('<span title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_selectMarked')) . '">' . $this->iconFactory->getIcon('actions-edit-copy', Icon::SIZE_SMALL)->render() . '</span>', 'setCB');
-            $cells[] = $this->linkClipboardHeaderIcon('<span title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_deleteMarked')) . '">' . $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render() . '</span>', 'delete', $this->getLanguageService()->getLL('clip_deleteMarkedWarning'));
-            $cells[] = '<a class="btn btn-default t3js-toggle-all-checkboxes" data-checkboxes-names="' . htmlspecialchars(implode(',', $this->CBnames)) . '" rel="" href="#" title="' . htmlspecialchars($this->getLanguageService()->getLL('clip_markRecords')) . '">' . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . '</a>';
-        }
-        if (!empty($cells)) {
-            return '<div class="btn-group">' . implode('', $cells) . '</div>';
-        }
-        return '';
-    }
-
     /**
      * Returns a table-row with the content from the fields in the input data array.
      * OBS: $this->fieldArray MUST be set! (represents the list of fields to display)
      *
-     * @param string $icon Is the <img>+<a> of the record. If not supplied the first 'join'-icon will be a 'line' instead
      * @param array $data Is the data array, record with the fields. Notice: These fields are (currently) NOT htmlspecialchar'ed before being wrapped in <td>-tags
+     * @param array $attributes Attributes for the table row. Values will be htmlspecialchar'ed!
      * @param bool $isTableHeader Whether the element to be added is a table header
      *
      * @return string HTML content for the table row
      */
-    public function addElement(string $icon, array $data, bool $isTableHeader = false): string
+    public function addElement(array $data, array $attributes = [], bool $isTableHeader = false): string
     {
-        // Initialize additional data attributes for the row
-        // Note: To be consistent with the other $data values, the additional data attributes
-        // are not htmlspecialchar'ed before being added to the table row. Therefore it
-        // has to be ensured they are properly escaped when applied to the $data array!
-        $dataAttributes = [];
-        foreach (['type', 'file-uid', 'metadata-uid', 'folder-identifier', 'combined-identifier'] as $dataAttribute) {
-            if (isset($data[$dataAttribute])) {
-                $dataAttributes['data-' . $dataAttribute] = $data[$dataAttribute];
-                // Unset as we don't need them anymore, when building the table cells
-                unset($data[$dataAttribute]);
-            }
-        }
-
         // Initialize rendering.
         $cols = [];
         $colType = $isTableHeader ? 'th' : 'td';
@@ -480,10 +393,9 @@ class FileList
 
         // Add the the table row
         return '
-            <tr ' . GeneralUtility::implodeAttributes($dataAttributes) . '>
-                <' . $colType . ' class="col-icon nowrap">' . ($icon ?: '') . '</' . $colType . '>'
-                . implode(PHP_EOL, $cols) .
-            '</tr>';
+            <tr ' . GeneralUtility::implodeAttributes($attributes, true) . '>
+                ' . implode(PHP_EOL, $cols) . '
+            </tr>';
     }
 
     /**
@@ -503,7 +415,7 @@ class FileList
                     'actions-move-up',
                     Icon::SIZE_SMALL
                 )->render() . ' <i>[' . (max(0, $currentItemCount - $this->iLimit) + 1) . ' - ' . $currentItemCount . ']</i></a>';
-                $code = $this->addElement('', $theData);
+                $code = $this->addElement($theData);
             }
             return $code;
         }
@@ -515,7 +427,7 @@ class FileList
                 'actions-move-down',
                 Icon::SIZE_SMALL
             )->render() . ' <i>[' . ($currentItemCount + 1) . ' - ' . $this->totalItems . ']</i></a>';
-            $code = $this->addElement('', $theData);
+            $code = $this->addElement($theData);
         }
         return $code;
     }
@@ -562,22 +474,28 @@ class FileList
             // Initialization
             $this->counter++;
 
-            // The icon with link
-            $theIcon = '<span title="' . htmlspecialchars($folderName) . '">' . $this->iconFactory->getIconForResource($folderObject, Icon::SIZE_SMALL)->render() . '</span>';
-            if (!$isLocked) {
-                $theIcon = (string)BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $folderObject->getCombinedIdentifier());
-            }
+            // The icon - will be linked later on, if not locked
+            $theIcon = $this->getFileOrFolderIcon($folderName, $folderObject);
 
             // Preparing and getting the data-array
-            $theData = [
-                'type' => 'folder',
-                'folder-identifier' => htmlspecialchars($folderObject->getIdentifier()),
-                'combined-identifier' => htmlspecialchars($folderObject->getCombinedIdentifier()),
+            $theData = [];
+
+            // Preparing table row attributes
+            $attributes = [
+                'data-type' => 'folder',
+                'data-folder-identifier' => $folderObject->getIdentifier(),
+                'data-combined-identifier' => $folderObject->getCombinedIdentifier(),
             ];
+            if ($this->clipObj->current !== 'normal'
+                && $this->clipObj->isSelected('_FILE', md5($folderObject->getCombinedIdentifier()))
+            ) {
+                $attributes['class'] = 'success';
+            }
             if ($isLocked) {
                 foreach ($this->fieldArray as $field) {
                     $theData[$field] = '';
                 }
+                $theData['icon'] = $theIcon;
                 $theData['file'] = $displayName;
             } else {
                 foreach ($this->fieldArray as $field) {
@@ -600,14 +518,17 @@ class FileList
                             $tstamp = $folderObject->getModificationTime();
                             $theData[$field] = $tstamp ? BackendUtility::date($tstamp) : '-';
                             break;
+                        case 'icon':
+                            $theData[$field] = (string)BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $folderObject->getCombinedIdentifier());
+                            break;
                         case 'file':
                             $theData[$field] = $this->linkWrapDir($displayName, $folderObject);
                             break;
                         case '_CONTROL_':
                             $theData[$field] = $this->makeEdit($folderObject);
                             break;
-                        case '_CLIPBOARD_':
-                            $theData[$field] = $this->makeClipboardCheckbox($folderObject);
+                        case '_SELECTOR_':
+                            $theData[$field] = $this->makeCheckbox($folderObject);
                             break;
                         case '_REF_':
                             $theData[$field] = $this->makeRef($folderObject);
@@ -620,7 +541,7 @@ class FileList
                     }
                 }
             }
-            $out .= $this->addElement($theIcon, $theData);
+            $out .= $this->addElement($theData, $attributes);
         }
         return $out;
     }
@@ -689,6 +610,11 @@ class FileList
         return (string)$this->uriBuilder->buildUriFromRoute('file_FilelistList', $params);
     }
 
+    public function getSelectedElements(): array
+    {
+        return $this->selectedElements;
+    }
+
     protected function getAvailableSystemLanguages(): array
     {
         // first two keys are "0" (default) and "-1" (multiple), after that comes the "other languages"
@@ -717,19 +643,22 @@ class FileList
             $ext = $fileObject->getExtension();
             $fileUid = $fileObject->getUid();
             $fileName = trim($fileObject->getName());
-            // The icon with link
-            $theIcon = '<span title="' . htmlspecialchars($fileName . ' [' . $fileUid . ']') . '">'
-                . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render() . '</span>';
-            $theIcon = (string)BackendUtility::wrapClickMenuOnIcon($theIcon, 'sys_file', $fileObject->getCombinedIdentifier());
             // Preparing and getting the data-array
-            $theData = [
-                'type' => 'file',
-                'file-uid' => $fileUid
+            $theData = [];
+            // Preparing table row attributes
+            $attributes = [
+                'data-type' => 'file',
+                'data-file-uid' => $fileUid
             ];
             if ($this->isEditMetadataAllowed($fileObject)
                 && ($metaDataUid = $fileObject->getMetaData()->offsetGet('uid'))
             ) {
-                $theData['metadata-uid'] = htmlspecialchars((string)$metaDataUid);
+                $attributes['data-metadata-uid'] = (string)$metaDataUid;
+            }
+            if ($this->clipObj->current !== 'normal'
+                && $this->clipObj->isSelected('_FILE', md5($fileObject->getCombinedIdentifier()))
+            ) {
+                $attributes['class'] = 'success';
             }
             foreach ($this->fieldArray as $field) {
                 switch ($field) {
@@ -748,8 +677,8 @@ class FileList
                     case '_CONTROL_':
                         $theData[$field] = $this->makeEdit($fileObject);
                         break;
-                    case '_CLIPBOARD_':
-                        $theData[$field] = $this->makeClipboardCheckbox($fileObject);
+                    case '_SELECTOR_':
+                        $theData[$field] = $this->makeCheckbox($fileObject);
                         break;
                     case '_LOCALIZATION_':
                         if (!empty($systemLanguages) && $fileObject->isIndexed() && $fileObject->checkActionPermission('editMeta') && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata') && !empty($GLOBALS['TCA']['sys_file_metadata']['ctrl']['languageField'] ?? null)) {
@@ -803,6 +732,9 @@ class FileList
                     case '_PATH_':
                         $theData[$field] = $this->makePath($fileObject);
                         break;
+                    case 'icon':
+                        $theData[$field] = (string)BackendUtility::wrapClickMenuOnIcon($this->getFileOrFolderIcon($fileName, $fileObject), 'sys_file', $fileObject->getCombinedIdentifier());
+                        break;
                     case 'file':
                         // Edit metadata of file
                         $theData[$field] = $this->linkWrapFile(htmlspecialchars($fileName), $fileObject);
@@ -833,7 +765,7 @@ class FileList
                         }
                 }
             }
-            $out .= $this->addElement($theIcon, $theData);
+            $out .= $this->addElement($theData, $attributes);
         }
         return $out;
     }
@@ -969,28 +901,32 @@ class FileList
     }
 
     /**
-     * Adds the clipboard checkbox for a file/folder in the listing
+     * Adds the checkbox to select a file/folder in the listing
      *
      * @param File|Folder $fileOrFolderObject
      * @return string
      */
-    protected function makeClipboardCheckbox($fileOrFolderObject): string
+    protected function makeCheckbox($fileOrFolderObject): string
     {
-        if ($this->clipObj->current === 'normal' || !$fileOrFolderObject->checkActionPermission('read')) {
+        if (!$fileOrFolderObject->checkActionPermission('read')) {
             return '';
         }
 
         $fullIdentifier = $fileOrFolderObject->getCombinedIdentifier();
         $md5 = md5($fullIdentifier);
         $identifier = '_FILE|' . $md5;
+        $isSelected = $this->clipObj->isSelected('_FILE', $md5) && $this->clipObj->current !== 'normal';
         $this->CBnames[] = $identifier;
 
+        if ($isSelected) {
+            $this->selectedElements[] = $identifier;
+        }
+
         return '
-            <label class="btn btn-default btn-checkbox">
-                <input type="checkbox" name="CBC[' . $identifier . ']" value="' . htmlspecialchars($fullIdentifier) . '" ' . ($this->clipObj->isSelected('_FILE', $md5) ? ' checked="checked"' : '') . ' />
-                <span class="t3-icon fa"></span>
+            <span class="form-check form-toggle">
+                <input class="form-check-input t3js-multi-record-selection-check" type="checkbox" name="CBC[' . $identifier . ']" value="' . htmlspecialchars($fullIdentifier) . '" ' . ($isSelected ? ' checked="checked"' : '') . ' />
                 <input type="hidden" name="CBH[' . $identifier . ']" value="0" />
-            </label>';
+            </span>';
     }
 
     /**
@@ -1245,26 +1181,6 @@ class FileList
         return htmlspecialchars($folder->$method());
     }
 
-    /**
-     * Returns an instance of LanguageService
-     *
-     * @return LanguageService
-     */
-    protected function getLanguageService()
-    {
-        return $GLOBALS['LANG'];
-    }
-
-    /**
-     * Returns the current BE user.
-     *
-     * @return BackendUserAuthentication
-     */
-    protected function getBackendUser()
-    {
-        return $GLOBALS['BE_USER'];
-    }
-
     /**
      * Generates HTML code for a Reference tooltip out of
      * sys_refindex records you hand over
@@ -1297,4 +1213,85 @@ class FileList
             && $file->checkActionPermission('editMeta')
             && $this->getBackendUser()->check('tables_modify', 'sys_file_metadata');
     }
+
+    /**
+     * Get the icon for a file or folder object
+     *
+     * @param string $title The icon title
+     * @param File|Folder $fileOrFolderObject
+     * @return string The wrapped icon for the file or folder
+     */
+    protected function getFileOrFolderIcon(string $title, $fileOrFolderObject): string
+    {
+        return '
+            <span title="' . htmlspecialchars($title) . '">
+                ' . $this->iconFactory->getIconForResource($fileOrFolderObject, Icon::SIZE_SMALL)->render() . '
+            </span>';
+    }
+
+    /**
+     * Render convenience actions, such as "check all"
+     *
+     * @return string HTML markup for the checkbox actions
+     */
+    protected function renderCheckboxActions(): string
+    {
+        // Early return in case there are no items
+        if (!$this->totalItems) {
+            return '';
+        }
+
+        $lang = $this->getLanguageService();
+
+        $dropdownItems['checkAll'] = '
+            <li>
+                <button type="button" class="btn btn-link dropdown-item disabled" data-multi-record-selection-check-action="check-all" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll')) . '">
+                    ' . $this->iconFactory->getIcon('actions-check-square', Icon::SIZE_SMALL)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll')) . '
+                </button>
+            </li>';
+
+        $dropdownItems['checkNone'] = '
+            <li>
+                <button type="button" class="btn btn-link dropdown-item disabled" data-multi-record-selection-check-action="check-none" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll')) . '">
+                    ' . $this->iconFactory->getIcon('actions-square', Icon::SIZE_SMALL)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll')) . '
+                </button>
+            </li>';
+
+        $dropdownItems['toggleSelection'] = '
+            <li>
+                <button type="button" class="btn btn-link dropdown-item" data-multi-record-selection-check-action="toggle" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection')) . '">
+                    ' . $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL)->render() . ' ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection')) . '
+                </button>
+            </li>';
+
+        return '
+            <div class="btn-group dropdown position-static">
+                 <button type="button" class="btn btn-borderless dropdown-toggle" data-bs-target="multi-record-selection-check-actions" data-bs-toggle="dropdown" data-bs-boundary="window" aria-expanded="false">
+                    ' . $this->iconFactory->getIcon('content-special-div', Icon::SIZE_SMALL) . '
+                </button>
+                <ul id="multi-record-selection-check-actions" class="dropdown-menu">
+                    ' . implode(PHP_EOL, $dropdownItems) . '
+                </ul>
+            </div>';
+    }
+
+    /**
+     * Returns an instance of LanguageService
+     *
+     * @return LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+
+    /**
+     * Returns the current BE user.
+     *
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser()
+    {
+        return $GLOBALS['BE_USER'];
+    }
 }
diff --git a/typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf b/typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf
index cda657c72030b1c37e5cdcc3fe2ee166244cefab..8749c476b2ce925758ec1b0bfb533ffa75edc67c 100644
--- a/typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf
+++ b/typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf
@@ -12,11 +12,14 @@
 			<trans-unit id="clip_pasteInto" resname="clip_pasteInto">
 				<source>Paste into: Clipboard content is inserted into this folder</source>
 			</trans-unit>
-			<trans-unit id="clip_markRecords" resname="clip_markRecords">
-				<source>Mark All/Mark none</source>
+			<trans-unit id="selection" resname="selection">
+				<source>Selection:</source>
+			</trans-unit>
+			<trans-unit id="editMarked" resname="editMarked">
+				<source>Edit Metadata</source>
 			</trans-unit>
 			<trans-unit id="clip_selectMarked" resname="clip_selectMarked">
-				<source>Transfer the selection of files to clipboard</source>
+				<source>Transfer to clipboard</source>
 			</trans-unit>
 			<trans-unit id="clip_deleteMarked" resname="clip_deleteMarked">
 				<source>Delete marked</source>
diff --git a/typo3/sysext/filelist/Resources/Private/Templates/File/List.html b/typo3/sysext/filelist/Resources/Private/Templates/File/List.html
index df703f9a31dc2ac1e28952ed7c2dc6776e2a8fde..c926a086ef23f044277d8553796059232130202d 100644
--- a/typo3/sysext/filelist/Resources/Private/Templates/File/List.html
+++ b/typo3/sysext/filelist/Resources/Private/Templates/File/List.html
@@ -29,7 +29,7 @@
 
 <f:section name="content">
     <form method="post" name="fileListForm">
-        <div class="row">
+        <div class="row mb-3">
             <div class="col-6">
                 <div class="input-group">
                     <input type="hidden" name="pointer" value="0" />
@@ -43,17 +43,47 @@
                 </div>
             </div>
         </div>
-        <div class="row justify-content-end">
-            <f:if condition="{listHtml} && {displayThumbs.enabled}">
-                <div class="col-6">
-                    <div class="float-end">
-                        <div class="form-check form-switch">
-                            {displayThumbs.html -> f:format.raw()}
-                            <label for="checkDisplayThumbs" class="form-check-label">
-                                {displayThumbs.label}
-                            </label>
+        <div class="row row-cols-auto justify-content-between">
+            <div class="col-auto">
+                <f:if condition="{listHtml} && {totalItems}">
+                    <div class="row row-cols-auto align-items-center g-2 t3js-multi-record-selection-actions {f:if(condition: '!{hasSelectedElements}', then: 'hidden')}">
+                        <div class="col">
+                            <strong><f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:selection"/></strong>
+                        </div>
+                        <div class="col">
+                            <button type="button" class="btn btn-default btn-sm disabled" data-multi-record-selection-action="edit" data-multi-record-selection-action-config="{editActionConfiguration}">
+                                <span title="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:editMarked')}">
+                                    <core:icon identifier="actions-open" size="small" /> <f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:editMarked" />
+                                </span>
+                            </button>
+                        </div>
+                        <f:if condition="{enableClipBoard.enabled}">
+                            <div class="col">
+                                <button type="button" class="btn btn-default btn-sm {f:if(condition: '{enableClipBoard.mode} == normal', then: 'disabled')}" data-multi-record-selection-action="setCB" data-filelist-clipboard-cmd="setCB">
+                                    <span title="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_selectMarked')}">
+                                        <core:icon identifier="actions-edit-copy" size="small" /> <f:translate key="LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_selectMarked" />
+                                    </span>
+                                </button>
+                            </div>
+                        </f:if>
+                        <div class="col">
+                            <button type="button" class="btn btn-default btn-sm t3js-modal-trigger" data-multi-record-selection-action="delete" data-event-name="filelist:clipboard:cmd" data-event-payload="delete" data-severity="warning" data-bs-content="{f:translate(key: 'LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:clip_deleteMarkedWarning')}">
+                                <span title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete')}">
+                                    <core:icon identifier="actions-edit-delete" size="small" /> <f:translate key="LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:cm.delete" />
+                                </span>
+                            </button>
                         </div>
                     </div>
+                </f:if>
+            </div>
+            <f:if condition="{listHtml} && {displayThumbs.enabled}">
+                <div class="col-auto">
+                    <div class="form-check form-switch">
+                        {displayThumbs.html -> f:format.raw()}
+                        <label for="checkDisplayThumbs" class="form-check-label">
+                            {displayThumbs.label}
+                        </label>
+                    </div>
                 </div>
             </f:if>
         </div>
diff --git a/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php b/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php
index ad0b85fd2e569bd2dbbfde1d52b8d2533a32f51b..f20758894ae1a9d6360802be835a394e1d2c63c5 100644
--- a/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php
+++ b/typo3/sysext/recordlist/Classes/Browser/FileBrowser.php
@@ -73,6 +73,7 @@ class FileBrowser extends AbstractElementBrowser implements ElementBrowserInterf
         parent::initialize();
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Recordlist/BrowseFiles');
         $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Tree/FileStorageBrowser');
+        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/MultiRecordSelection');
 
         $thumbnailConfig = $this->getBackendUser()->getTSConfig()['options.']['file_list.']['thumbnail.'] ?? [];
         if (isset($thumbnailConfig['width']) && MathUtility::canBeInterpretedAsInteger($thumbnailConfig['width'])) {
@@ -244,30 +245,27 @@ class FileBrowser extends AbstractElementBrowser implements ElementBrowserInterf
                 <tr>
                     <th colspan="3" class="nowrap">
                         <div class="btn-group dropdown position-static me-1">
-                            <button type="button" class="btn btn-borderless dropdown-toggle" data-bs-target="actions_filebrowser" data-bs-toggle="dropdown" data-bs-boundary="window" aria-expanded="false">' .
+                            <button type="button" class="btn btn-borderless dropdown-toggle" data-bs-target="multi-record-selection-check-actions" data-bs-toggle="dropdown" data-bs-boundary="window" aria-expanded="false">' .
                                 $this->iconFactory->getIcon('content-special-div', Icon::SIZE_SMALL) .
                             '</button>
-                            <ul id="actions_filebrowser" class="dropdown-menu">
+                            <ul id="multi-record-selection-check-actions" class="dropdown-menu">
                                 <li>
-                                    <button type="button" class="btn btn-link dropdown-item typo3-selection-toggle" data-action="select-all">' .
+                                    <button type="button" class="btn btn-link dropdown-item disabled" data-multi-record-selection-check-action="check-all" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll')) . '">' .
                                         $this->iconFactory->getIcon('actions-check-square', Icon::SIZE_SMALL) . ' ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.checkAll')) .
                                     '</button>
                                 </li>
                                 <li>
-                                    <button type="button" class="btn btn-link dropdown-item typo3-selection-toggle" data-action="select-none">' .
+                                    <button type="button" class="btn btn-link dropdown-item disabled" data-multi-record-selection-check-action="check-none" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll')) . '">' .
                                         $this->iconFactory->getIcon('actions-square', Icon::SIZE_SMALL) . ' ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.uncheckAll')) .
                                     '</button>
                                 </li>
                                 <li>
-                                    <button type="button" class="btn btn-link dropdown-item typo3-selection-toggle" data-action="select-toggle">' .
-                                        $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL) . ' ' . htmlspecialchars($lang->getLL('toggleSelection')) .
+                                    <button type="button" class="btn btn-link dropdown-item" data-multi-record-selection-check-action="toggle" title="' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection')) . '">' .
+                                        $this->iconFactory->getIcon('actions-document-select', Icon::SIZE_SMALL) . ' ' . htmlspecialchars($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.toggleSelection')) .
                                     '</button>
                                 </li>
                             </ul>
                         </div>
-                        <button type="button" class="btn btn-default disabled" data-action="import" title="' . htmlspecialchars($lang->getLL('importSelection')) . '">' .
-                            $this->iconFactory->getIcon('actions-document-import-t3d', Icon::SIZE_SMALL) . ' ' . htmlspecialchars($lang->getLL('importSelection')) .
-                        '</button>
                     </th>
                     <th class="col-control nowrap"></th>
                 </tr>
@@ -304,7 +302,7 @@ class FileBrowser extends AbstractElementBrowser implements ElementBrowserInterf
                 $ATag_e = '</a>';
                 $bulkCheckBox = '
                     <span class="form-check form-toggle">
-                        <input type="checkbox" data-file-name="' . htmlspecialchars($fileObject->getName()) . '" data-file-uid="' . $fileObject->getUid() . '" name="file_' . $fileObject->getUid() . '" value="0" autocomplete="off" class="form-check-input typo3-list-check"  />
+                        <input type="checkbox" data-file-name="' . htmlspecialchars($fileObject->getName()) . '" data-file-uid="' . $fileObject->getUid() . '" name="file_' . $fileObject->getUid() . '" value="0" autocomplete="off" class="form-check-input t3js-multi-record-selection-check"  />
                     </span>';
             } else {
                 $ATag = '';
@@ -343,8 +341,17 @@ class FileBrowser extends AbstractElementBrowser implements ElementBrowserInterf
         $markup = [];
         $markup[] = '<div class="mt-4 mb-4">' . $searchBox . '</div>';
         $markup[] = '<div id="filelist">';
-        $markup[] = '  <div class="list-header">';
-        $markup[] = '   ' . $this->getBulkSelector();
+        $markup[] = '  <div class="row row-cols-auto justify-content-between list-header">';
+        $markup[] = '      <div class="col-auto">';
+        $markup[] = '          <div class="row row-cols-auto align-items-center g-2 t3js-multi-record-selection-actions hidden">';
+        $markup[] = '              <div class="col">';
+        $markup[] = '                  <button type="button" class="btn btn-default btn-sm" data-multi-record-selection-action="import" title="' . htmlspecialchars($lang->getLL('importSelection')) . '">';
+        $markup[] = '                      ' . $this->iconFactory->getIcon('actions-document-import-t3d', Icon::SIZE_SMALL) . ' ' . htmlspecialchars($lang->getLL('importSelection'));
+        $markup[] = '                  </button>';
+        $markup[] = '              </div>';
+        $markup[] = '          </div>';
+        $markup[] = '      </div>';
+        $markup[] = '      ' . $this->getThumbnailSelector();
         $markup[] = '   </div>';
         $markup[] = '   <table class="mt-1 table table-sm table-responsive table-striped table-hover" id="typo3-filelist" data-list-container="files">';
         $markup[] = '       ' . $tableHeader;
@@ -375,38 +382,33 @@ class FileBrowser extends AbstractElementBrowser implements ElementBrowserInterf
     }
 
     /**
-     * Get the HTML data required for a bulk selection of files of the TYPO3 Element Browser.
+     * Get the HTML for the thumbnail selector, if enabled
      *
-     * @return string HTML data required for a bulk selection of files
+     * @return string HTML data required for the thumbnail selector
      */
-    protected function getBulkSelector(): string
+    protected function getThumbnailSelector(): string
     {
-        $lang = $this->getLanguageService();
-        $out = '';
-
         // Getting flag for showing/not showing thumbnails:
-        $noThumbsInEB = $this->getBackendUser()->getTSConfig()['options.']['noThumbsInEB'] ?? false;
-        if (!$noThumbsInEB && $this->selectedFolder) {
-            // MENU-ITEMS, fetching the setting for thumbnails from File>List module:
-            $_MOD_MENU = ['displayThumbs' => ''];
-            $_MOD_SETTINGS = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), 'file_list');
-            $addParams = HttpUtility::buildQueryString($this->getUrlParameters(['identifier' => $this->selectedFolder->getCombinedIdentifier()]), '&');
-            $thumbNailCheck = '<div class="form-check form-switch">'
-                . BackendUtility::getFuncCheck(
-                    '',
-                    'SET[displayThumbs]',
-                    $_MOD_SETTINGS['displayThumbs'] ?? true,
-                    $this->thisScript,
-                    $addParams,
-                    'id="checkDisplayThumbs"'
-                )
-                . '<label for="checkDisplayThumbs" class="form-check-label">'
-                . htmlspecialchars($lang->sL('LLL:EXT:recordlist/Resources/Private/Language/locallang_browse_links.xlf:displayThumbs')) . '</label></div>';
-            $out .= '<div class="float-end ps-2">' . $thumbNailCheck . '</div>';
-        } else {
-            $out .= '';
+        if (!$this->selectedFolder || ($this->getBackendUser()->getTSConfig()['options.']['noThumbsInEB'] ?? false)) {
+            return '';
         }
-        return $out;
+
+        $lang = $this->getLanguageService();
+
+        // MENU-ITEMS, fetching the setting for thumbnails from File>List module:
+        $_MOD_MENU = ['displayThumbs' => ''];
+        $currentValue = BackendUtility::getModuleData($_MOD_MENU, GeneralUtility::_GP('SET'), 'file_list')['displayThumbs'] ?? true;
+        $addParams = HttpUtility::buildQueryString($this->getUrlParameters(['identifier' => $this->selectedFolder->getCombinedIdentifier()]), '&');
+
+        return '
+            <div class="col-auto">
+                <div class="form-check form-switch">
+                    ' . BackendUtility::getFuncCheck('', 'SET[displayThumbs]', $currentValue, $this->thisScript, $addParams, 'id="checkDisplayThumbs"') . '
+                    <label for="checkDisplayThumbs" class="form-check-label">
+                        ' . htmlspecialchars($lang->sL('LLL:EXT:recordlist/Resources/Private/Language/locallang_browse_links.xlf:displayThumbs')) . '
+                    </label>
+                </div>
+            </div>';
     }
 
     /**
diff --git a/typo3/sysext/recordlist/Resources/Public/JavaScript/BrowseFiles.js b/typo3/sysext/recordlist/Resources/Public/JavaScript/BrowseFiles.js
index a2dd3ec92289fa063af26785fc7c6287a4270f77..e1bc77a820de387d814f09bbc4f9fca11d481bae 100644
--- a/typo3/sysext/recordlist/Resources/Public/JavaScript/BrowseFiles.js
+++ b/typo3/sysext/recordlist/Resources/Public/JavaScript/BrowseFiles.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-define(["require","exports","TYPO3/CMS/Backend/Utility/MessageUtility","./ElementBrowser","nprogress","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,l,s,n,o){"use strict";var c,i=TYPO3.Icons;!function(e){e.bulkItemSelector=".typo3-list-check",e.importSelectionSelector='button[data-action="import"]',e.selectionToggleSelector=".typo3-selection-toggle",e.listContainer='[data-list-container="files"]'}(c||(c={}));class r{constructor(){r.Selector=new a,new o("click",(e,t)=>{e.preventDefault(),r.insertElement(t.dataset.fileName,Number(t.dataset.fileUid),1===parseInt(t.dataset.close||"0",10))}).delegateTo(document,"[data-close]"),new o("change",r.Selector.toggleImportButton).delegateTo(document,c.bulkItemSelector),new o("click",r.Selector.handle).delegateTo(document,c.importSelectionSelector),new o("click",r.Selector.toggle).delegateTo(document,c.selectionToggleSelector),new o("change",r.Selector.toggle).delegateTo(document,c.bulkItemSelector)}static insertElement(e,t,l){return s.insertElement("sys_file",String(t),e,String(t),l)}}class a{constructor(){this.toggle=e=>{e.preventDefault();const t=e.target,l=t.dataset.action,s=this.getItems();switch(l){case"select-toggle":s.forEach(e=>{e.checked=!e.checked,e.closest("tr").classList.toggle("success")});break;case"select-all":s.forEach(e=>{e.checked=!0,e.closest("tr").classList.add("success")});break;case"select-none":s.forEach(e=>{e.checked=!1,e.closest("tr").classList.remove("success")});break;default:t.classList.contains("typo3-list-check")&&t.closest("tr").classList.toggle("success")}this.toggleImportButton()},this.handle=(e,t)=>{e.preventDefault();const l=this.getItems(),s=[];l.length&&(l.forEach(e=>{e.checked&&e.name&&e.dataset.fileName&&e.dataset.fileUid&&s.unshift({uid:e.dataset.fileUid,fileName:e.dataset.fileName})}),i.getIcon("spinner-circle",i.sizes.small,null,null,i.markupIdentifiers.inline).then(e=>{t.classList.add("disabled"),t.innerHTML=e}),this.handleSelection(s))}}getItems(){return document.querySelector(c.listContainer).querySelectorAll(c.bulkItemSelector)}toggleImportButton(){var e;const t=(null===(e=document.querySelector(c.listContainer))||void 0===e?void 0:e.querySelectorAll(c.bulkItemSelector+":checked").length)>0;document.querySelector(c.importSelectionSelector).classList.toggle("disabled",!t)}handleSelection(e){n.configure({parent:".element-browser-main-content",showSpinner:!1}),n.start();const t=1/e.length;this.handleNext(e),new o("message",o=>{if(!l.MessageUtility.verifyOrigin(o.origin))throw"Denied message sent by "+o.origin;"typo3:foreignRelation:inserted"===o.data.actionName&&(e.length>0?(n.inc(t),this.handleNext(e)):(n.done(),s.focusOpenerAndClose()))}).bindTo(window)}handleNext(e){if(e.length>0){const t=e.pop();r.insertElement(t.fileName,Number(t.uid))}}}return new r}));
\ No newline at end of file
+define(["require","exports","TYPO3/CMS/Backend/Utility/MessageUtility","./ElementBrowser","nprogress","TYPO3/CMS/Core/Event/RegularEvent"],(function(e,t,n,i,s,r){"use strict";var a=TYPO3.Icons;class l{constructor(){this.importSelection=e=>{e.preventDefault();const t=e.target,o=document.querySelectorAll(".t3js-multi-record-selection-check");if(!o.length)return;const c=[];o.forEach(e=>{e.checked&&e.name&&e.dataset.fileName&&e.dataset.fileUid&&c.unshift({uid:e.dataset.fileUid,fileName:e.dataset.fileName})}),a.getIcon("spinner-circle",a.sizes.small,null,null,a.markupIdentifiers.inline).then(e=>{t.classList.add("disabled"),t.innerHTML=e}),s.configure({parent:".element-browser-main-content",showSpinner:!1}),s.start();const d=1/c.length;l.handleNext(c),new r("message",e=>{if(!n.MessageUtility.verifyOrigin(e.origin))throw"Denied message sent by "+e.origin;"typo3:foreignRelation:inserted"===e.data.actionName&&(c.length>0?(s.inc(d),l.handleNext(c)):(s.done(),i.focusOpenerAndClose()))}).bindTo(window)},new r("click",(e,t)=>{e.preventDefault(),l.insertElement(t.dataset.fileName,Number(t.dataset.fileUid),1===parseInt(t.dataset.close||"0",10))}).delegateTo(document,"[data-close]"),new r("multiRecordSelection:action:import",this.importSelection).bindTo(document)}static insertElement(e,t,n){return i.insertElement("sys_file",String(t),e,String(t),n)}static handleNext(e){if(e.length>0){const t=e.pop();l.insertElement(t.fileName,Number(t.uid))}}}return new l}));
\ No newline at end of file