From 82ac91d8866d40f0be56cb044ab433fab7a8cec6 Mon Sep 17 00:00:00 2001
From: Oliver Bartsch <bo@cedev.de>
Date: Tue, 18 Jun 2024 10:42:18 +0200
Subject: [PATCH] [BUGFIX] Properly handle checkbox state changes in FormEngine
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Using the multi record selection, it's possible
to change the state of checkboxes via multiple
ways, e.g. by clicking on the table row.

The central functionality for changing the
connected checkbox state now also considers
the disabled attribute and therefore only
changes the checked state in case the
checkbox is not disabled.

Additionally, next to the custom event, which
is triggered on checkbox state changes, the
multi record selection now also dispatches
the standard "change" event. FormEngine
validation for example is listening on
this event to mark the field as changed.

The JS component is also migrated to a proper
custom element (web component).

Resolves: #104142
Releases: main, 12.4
Change-Id: I9854709ee624d88821757fff3baf38c892e5f11d
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/84749
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: Oliver Bartsch <bo@cedev.de>
Tested-by: Ayke Halder <mail@ayke-halder.de>
Tested-by: core-ci <typo3@b13.com>
Tested-by: Stefan Bürk <stefan@buerk.tech>
Reviewed-by: Ayke Halder <mail@ayke-halder.de>
---
 .../element/select-check-box-element.ts       | 30 ++++++++++++-------
 .../backend/multi-record-selection.ts         |  7 +++--
 .../Form/Element/SelectCheckBoxElement.php    |  6 ++--
 .../element/select-check-box-element.js       |  2 +-
 .../JavaScript/multi-record-selection.js      |  2 +-
 5 files changed, 29 insertions(+), 18 deletions(-)

diff --git a/Build/Sources/TypeScript/backend/form-engine/element/select-check-box-element.ts b/Build/Sources/TypeScript/backend/form-engine/element/select-check-box-element.ts
index 066936a6d8a6..cfc366f9c447 100644
--- a/Build/Sources/TypeScript/backend/form-engine/element/select-check-box-element.ts
+++ b/Build/Sources/TypeScript/backend/form-engine/element/select-check-box-element.ts
@@ -11,7 +11,6 @@
  * The TYPO3 project - inspiring people to share!
  */
 
-import DocumentService from '@typo3/core/document-service';
 import RegularEvent from '@typo3/core/event/regular-event';
 import Icons from '@typo3/backend/icons';
 
@@ -24,18 +23,27 @@ enum IconIdentifier {
   expand = 'actions-view-list-expand',
 }
 
-class SelectCheckBoxElement {
-  constructor() {
-    DocumentService.ready().then((): void => {
-      this.registerEventHandler();
-    });
+/**
+ * Module: @typo3/backend/form-engine/element/select-check-box-element
+ *
+ * Functionality for the selectCheckBox element
+ *
+ * @example
+ * <typo3-formengine-element-select-check-box>
+ *   ...
+ * </typo3-formengine-element-select-check-box>
+ *
+ * This is based on W3C custom elements ("web components") specification, see
+ * https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
+ */
+class SelectCheckBoxElement extends HTMLElement {
+
+  public connectedCallback(): void {
+    this.registerEventHandler();
   }
 
-  /**
-   * Registers the events for the header collapse icon toggling
-   */
   private registerEventHandler(): void {
-    new RegularEvent('click', this.toggleGroup).delegateTo(document, Identifier.toggleGroup);
+    new RegularEvent('click', this.toggleGroup).delegateTo(this, Identifier.toggleGroup);
   }
 
   private toggleGroup(e: MouseEvent, targetEl: HTMLElement): void {
@@ -51,4 +59,4 @@ class SelectCheckBoxElement {
   }
 }
 
-export default SelectCheckBoxElement;
+window.customElements.define('typo3-formengine-element-select-check-box', SelectCheckBoxElement);
diff --git a/Build/Sources/TypeScript/backend/multi-record-selection.ts b/Build/Sources/TypeScript/backend/multi-record-selection.ts
index 77f0386f33d6..38b728b436ac 100644
--- a/Build/Sources/TypeScript/backend/multi-record-selection.ts
+++ b/Build/Sources/TypeScript/backend/multi-record-selection.ts
@@ -77,11 +77,14 @@ class MultiRecordSelection {
   }
 
   private static changeCheckboxState(checkbox: HTMLInputElement, check: boolean): void {
-    if (checkbox.checked === check || checkbox.dataset.manuallyChanged) {
-      // Return in case state did not change or another component has already changed it
+    if (checkbox.disabled || checkbox.checked === check || checkbox.dataset.manuallyChanged) {
+      // Return in case the checkbox is disabled, the state did not change or another component has already changed it
       return;
     }
     checkbox.checked = check;
+    // Dispatch the standard "change" event, which might be used by form components, e.g. FormEngine
+    checkbox.dispatchEvent(new CustomEvent('change', { bubbles: true }));
+    // Dispatch custom event, which might be used by components to keep track of external state changes
     checkbox.dispatchEvent(new CustomEvent('multiRecordSelection:checkbox:state:changed',{
       detail: { identifier: MultiRecordSelection.getIdentifier(checkbox) }, bubbles: true, cancelable: false
     }));
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php
index 7346990f6c54..7adb7e44d805 100644
--- a/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php
@@ -152,7 +152,7 @@ class SelectCheckBoxElement extends AbstractFormElement
         $fieldWizardHtml = $fieldWizardResult['html'];
         $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
 
-        $html[] = '<div class="formengine-field-item t3js-formengine-field-item" data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '">';
+        $html[] = '<typo3-formengine-element-select-check-box class="formengine-field-item t3js-formengine-field-item" data-formengine-validation-rules="' . htmlspecialchars($this->getValidationDataAsJsonString($config)) . '">';
         $html[] = $fieldInformationHtml;
         $html[] =   '<div class="form-wizards-wrap">';
         $html[] =       '<div class="form-wizards-element">';
@@ -249,7 +249,7 @@ class SelectCheckBoxElement extends AbstractFormElement
 
                     // Add JavaScript module. This is only needed, in case the element
                     // is not readOnly, since otherwise no checkbox changes take place.
-                    $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@typo3/backend/form-engine/element/select-check-box-element.js')->instance();
+                    $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@typo3/backend/form-engine/element/select-check-box-element.js');
                     $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::create('@typo3/backend/multi-record-selection.js');
                 }
                 $html[] =            '<tbody>' . implode(LF, $tableRows) . '</tbody>';
@@ -269,7 +269,7 @@ class SelectCheckBoxElement extends AbstractFormElement
             $html[] =   '</div>';
         }
         $html[] =   '</div>';
-        $html[] = '</div>';
+        $html[] = '</typo3-formengine-element-select-check-box>';
 
         $resultArray['html'] = $this->wrapWithFieldsetAndLegend(implode(LF, $html));
         return $resultArray;
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-check-box-element.js b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-check-box-element.js
index 02483bb57680..e0d1cf03b835 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-check-box-element.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/form-engine/element/select-check-box-element.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import DocumentService from"@typo3/core/document-service.js";import RegularEvent from"@typo3/core/event/regular-event.js";import Icons from"@typo3/backend/icons.js";var Identifier,IconIdentifier;!function(e){e.toggleGroup=".t3js-toggle-selectcheckbox-group"}(Identifier||(Identifier={})),function(e){e.collapse="actions-view-list-collapse",e.expand="actions-view-list-expand"}(IconIdentifier||(IconIdentifier={}));class SelectCheckBoxElement{constructor(){DocumentService.ready().then((()=>{this.registerEventHandler()}))}registerEventHandler(){new RegularEvent("click",this.toggleGroup).delegateTo(document,Identifier.toggleGroup)}toggleGroup(e,t){e.preventDefault();const n="true"===t.ariaExpanded,o=t.querySelector(".collapseIcon"),r=n?IconIdentifier.collapse:IconIdentifier.expand;Icons.getIcon(r,Icons.sizes.small).then((e=>{o.innerHTML=e}))}}export default SelectCheckBoxElement;
\ No newline at end of file
+import RegularEvent from"@typo3/core/event/regular-event.js";import Icons from"@typo3/backend/icons.js";var Identifier,IconIdentifier;!function(e){e.toggleGroup=".t3js-toggle-selectcheckbox-group"}(Identifier||(Identifier={})),function(e){e.collapse="actions-view-list-collapse",e.expand="actions-view-list-expand"}(IconIdentifier||(IconIdentifier={}));class SelectCheckBoxElement extends HTMLElement{connectedCallback(){this.registerEventHandler()}registerEventHandler(){new RegularEvent("click",this.toggleGroup).delegateTo(this,Identifier.toggleGroup)}toggleGroup(e,n){e.preventDefault();const t="true"===n.ariaExpanded,o=n.querySelector(".collapseIcon"),i=t?IconIdentifier.collapse:IconIdentifier.expand;Icons.getIcon(i,Icons.sizes.small).then((e=>{o.innerHTML=e}))}}window.customElements.define("typo3-formengine-element-select-check-box",SelectCheckBoxElement);
\ No newline at end of file
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/multi-record-selection.js b/typo3/sysext/backend/Resources/Public/JavaScript/multi-record-selection.js
index 2c91339118d8..bb9bb9a98cc1 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/multi-record-selection.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/multi-record-selection.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import Notification from"@typo3/backend/notification.js";import DocumentService from"@typo3/core/document-service.js";import RegularEvent from"@typo3/core/event/regular-event.js";import{selector}from"@typo3/core/literals.js";export var MultiRecordSelectionSelectors;var Buttons,CheckboxActions,CheckboxState;!function(e){e.actionsSelector=".t3js-multi-record-selection-actions",e.checkboxSelector=".t3js-multi-record-selection-check",e.checkboxActionsSelector=".t3js-multi-record-selection-check-actions",e.checkboxActionsToggleSelector=".t3js-multi-record-selection-check-actions-toggle",e.elementSelector="[data-multi-record-selection-element]"}(MultiRecordSelectionSelectors||(MultiRecordSelectionSelectors={})),function(e){e.actionButton="button[data-multi-record-selection-action]",e.checkboxActionButton="button[data-multi-record-selection-check-action]"}(Buttons||(Buttons={})),function(e){e.checkAll="check-all",e.checkNone="check-none",e.toggle="toggle"}(CheckboxActions||(CheckboxActions={})),function(e){e.any="",e.checked=":checked",e.unchecked=":not(:checked)"}(CheckboxState||(CheckboxState={}));class MultiRecordSelection{constructor(){this.lastChecked=null,DocumentService.ready().then((()=>{MultiRecordSelection.restoreTemporaryState(),this.registerActions(),this.registerActionsEventHandlers(),this.registerCheckboxActions(),this.registerCheckboxKeyboardActions(),this.registerCheckboxTableRowSelectionAction(),this.registerToggleCheckboxActions(),this.registerDispatchCheckboxStateChangedEvent(),this.registerCheckboxStateChangedEventHandler()}))}static getCheckboxes(e=CheckboxState.any,t=""){return document.querySelectorAll(MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.checkboxSelector+e,t))}static getCombinedSelector(e,t){return""!==t?[selector`[data-multi-record-selection-identifier="${t}"]`,e].join(" "):e}static getIdentifier(e){return e.closest("[data-multi-record-selection-identifier]")?.dataset.multiRecordSelectionIdentifier||""}static changeCheckboxState(e,t){e.checked===t||e.dataset.manuallyChanged||(e.checked=t,e.dispatchEvent(new CustomEvent("multiRecordSelection:checkbox:state:changed",{detail:{identifier:MultiRecordSelection.getIdentifier(e)},bubbles:!0,cancelable:!1})))}static restoreTemporaryState(){const e=MultiRecordSelection.getCheckboxes(CheckboxState.checked);if(!e.length)return;let t=!1;const c=[];e.forEach((e=>{e.closest(MultiRecordSelectionSelectors.elementSelector)?.classList.add(MultiRecordSelection.activeClass);const o=MultiRecordSelection.getIdentifier(e);""===o||c.includes(o)||(c.push(o),t=!0,MultiRecordSelection.toggleActionsState(o))})),t||MultiRecordSelection.toggleActionsState()}static toggleActionsState(e=""){const t=document.querySelectorAll(MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.actionsSelector,e));if(!t.length)return;if(!MultiRecordSelection.getCheckboxes(CheckboxState.checked,e).length)return void t.forEach((e=>MultiRecordSelection.changeActionContainerVisibility(e,!1)));t.forEach((e=>MultiRecordSelection.changeActionContainerVisibility(e)));const c=document.querySelectorAll([MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.actionsSelector,e),Buttons.actionButton].join(" "));c.length&&c.forEach((t=>{if(!t.dataset.multiRecordSelectionActionConfig)return;const c=JSON.parse(t.dataset.multiRecordSelectionActionConfig);if(!c.idField)return;t.classList.add(this.disabledClass);const o=MultiRecordSelection.getCheckboxes(CheckboxState.checked,e);for(let e=0;e<o.length;e++)if(o[e].closest(MultiRecordSelectionSelectors.elementSelector)?.dataset[c.idField]){t.classList.remove(this.disabledClass);break}}))}static changeActionContainerVisibility(e,t=!0){const c=e.closest(".multi-record-selection-panel")?.children;if(t){if(c)for(let e=0;e<c.length;e++)c[e].classList.add("hidden");e.classList.remove("hidden")}else{if(c)for(let e=0;e<c.length;e++)c[e].classList.remove("hidden");e.classList.add("hidden")}}static unsetManuallyChangedAttribute(e){MultiRecordSelection.getCheckboxes(CheckboxState.any,e).forEach((e=>{e.removeAttribute("data-manually-changed")}))}registerActions(){new RegularEvent("click",((e,t)=>{t.dataset.multiRecordSelectionAction;const c=MultiRecordSelection.getIdentifier(t),o=JSON.parse(t.dataset.multiRecordSelectionActionConfig||"{}"),i=MultiRecordSelection.getCheckboxes(CheckboxState.checked,c);i.length&&t.dispatchEvent(new CustomEvent("multiRecordSelection:action:"+t.dataset.multiRecordSelectionAction,{detail:{identifier:c,checkboxes:i,configuration:o},bubbles:!0,cancelable:!1}))})).delegateTo(document,[MultiRecordSelectionSelectors.actionsSelector,Buttons.actionButton].join(" "))}registerActionsEventHandlers(){new RegularEvent("multiRecordSelection:actions:show",(e=>{const t=e.detail?.identifier||"",c=document.querySelectorAll(MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.actionsSelector,t));c.length&&c.forEach((e=>MultiRecordSelection.changeActionContainerVisibility(e)))})).bindTo(document),new RegularEvent("multiRecordSelection:actions:hide",(e=>{const t=e.detail?.identifier||"",c=document.querySelectorAll(MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.actionsSelector,t));c.length&&c.forEach((e=>MultiRecordSelection.changeActionContainerVisibility(e,!1)))})).bindTo(document)}registerCheckboxActions(){new RegularEvent("click",((e,t)=>{if(e.preventDefault(),!t.dataset.multiRecordSelectionCheckAction)return;const c=MultiRecordSelection.getIdentifier(t),o=MultiRecordSelection.getCheckboxes(CheckboxState.any,c);if(o.length){switch(MultiRecordSelection.unsetManuallyChangedAttribute(c),t.dataset.multiRecordSelectionCheckAction){case CheckboxActions.checkAll:o.forEach((e=>{MultiRecordSelection.changeCheckboxState(e,!0)}));break;case CheckboxActions.checkNone:o.forEach((e=>{MultiRecordSelection.changeCheckboxState(e,!1)}));break;case CheckboxActions.toggle:o.forEach((e=>{MultiRecordSelection.changeCheckboxState(e,!e.checked)}));break;default:Notification.warning("Unknown checkbox action")}MultiRecordSelection.unsetManuallyChangedAttribute(c)}})).delegateTo(document,[MultiRecordSelectionSelectors.checkboxActionsSelector,Buttons.checkboxActionButton].join(" "))}registerCheckboxKeyboardActions(){new RegularEvent("click",((e,t)=>this.handleCheckboxKeyboardActions(e,t))).delegateTo(document,MultiRecordSelectionSelectors.checkboxSelector)}registerCheckboxTableRowSelectionAction(){new RegularEvent("click",((e,t)=>{const c=e.target.tagName;if("TH"!==c&&"TD"!==c)return;const o=t.querySelector(MultiRecordSelectionSelectors.checkboxSelector);null!==o&&(MultiRecordSelection.changeCheckboxState(o,!o.checked),this.handleCheckboxKeyboardActions(e,o,!1))})).delegateTo(document,MultiRecordSelectionSelectors.elementSelector),new RegularEvent("mousedown",(e=>(e.shiftKey||e.altKey||e.ctrlKey)&&e.preventDefault())).delegateTo(document,MultiRecordSelectionSelectors.elementSelector)}registerDispatchCheckboxStateChangedEvent(){new RegularEvent("change",((e,t)=>{t.dispatchEvent(new CustomEvent("multiRecordSelection:checkbox:state:changed",{detail:{identifier:MultiRecordSelection.getIdentifier(t)},bubbles:!0,cancelable:!1}))})).delegateTo(document,MultiRecordSelectionSelectors.checkboxSelector)}registerCheckboxStateChangedEventHandler(){new RegularEvent("multiRecordSelection:checkbox:state:changed",(e=>{const t=e.target,c=e.detail?.identifier||"";t.checked?t.closest(MultiRecordSelectionSelectors.elementSelector).classList.add(MultiRecordSelection.activeClass):t.closest(MultiRecordSelectionSelectors.elementSelector).classList.remove(MultiRecordSelection.activeClass),MultiRecordSelection.toggleActionsState(c)})).bindTo(document)}registerToggleCheckboxActions(){new RegularEvent("click",((e,t)=>{const c=MultiRecordSelection.getIdentifier(t),o=document.querySelector([MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.checkboxActionsSelector,c),'button[data-multi-record-selection-check-action="'+CheckboxActions.checkAll+'"]'].join(" "));null!==o&&o.classList.toggle("disabled",!MultiRecordSelection.getCheckboxes(CheckboxState.unchecked,c).length);const i=document.querySelector([MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.checkboxActionsSelector,c),'button[data-multi-record-selection-check-action="'+CheckboxActions.checkNone+'"]'].join(" "));null!==i&&i.classList.toggle("disabled",!MultiRecordSelection.getCheckboxes(CheckboxState.checked,c).length);const l=document.querySelector([MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.checkboxActionsSelector,c),'button[data-multi-record-selection-check-action="'+CheckboxActions.toggle+'"]'].join(" "));null!==l&&l.classList.toggle("disabled",!MultiRecordSelection.getCheckboxes(CheckboxState.any,c).length)})).delegateTo(document,MultiRecordSelectionSelectors.checkboxActionsToggleSelector)}handleCheckboxKeyboardActions(e,t,c=!0){const o=MultiRecordSelection.getIdentifier(t);if(this.lastChecked&&document.body.contains(this.lastChecked)&&MultiRecordSelection.getIdentifier(this.lastChecked)===o&&(e.shiftKey||e.altKey||e.ctrlKey)){if(c&&MultiRecordSelection.unsetManuallyChangedAttribute(o),e.shiftKey){const e=Array.from(MultiRecordSelection.getCheckboxes(CheckboxState.any,o)),c=e.indexOf(t),i=e.indexOf(this.lastChecked);e.slice(Math.min(c,i),Math.max(c,i)+1).forEach((e=>{e!==t&&MultiRecordSelection.changeCheckboxState(e,t.checked)}))}this.lastChecked=t,(e.altKey||e.ctrlKey)&&MultiRecordSelection.getCheckboxes(CheckboxState.any,o).forEach((e=>{e!==t&&MultiRecordSelection.changeCheckboxState(e,!e.checked)})),MultiRecordSelection.unsetManuallyChangedAttribute(o)}else this.lastChecked=t}}MultiRecordSelection.activeClass="active",MultiRecordSelection.disabledClass="disabled";export default new MultiRecordSelection;
\ No newline at end of file
+import Notification from"@typo3/backend/notification.js";import DocumentService from"@typo3/core/document-service.js";import RegularEvent from"@typo3/core/event/regular-event.js";import{selector}from"@typo3/core/literals.js";export var MultiRecordSelectionSelectors;var Buttons,CheckboxActions,CheckboxState;!function(e){e.actionsSelector=".t3js-multi-record-selection-actions",e.checkboxSelector=".t3js-multi-record-selection-check",e.checkboxActionsSelector=".t3js-multi-record-selection-check-actions",e.checkboxActionsToggleSelector=".t3js-multi-record-selection-check-actions-toggle",e.elementSelector="[data-multi-record-selection-element]"}(MultiRecordSelectionSelectors||(MultiRecordSelectionSelectors={})),function(e){e.actionButton="button[data-multi-record-selection-action]",e.checkboxActionButton="button[data-multi-record-selection-check-action]"}(Buttons||(Buttons={})),function(e){e.checkAll="check-all",e.checkNone="check-none",e.toggle="toggle"}(CheckboxActions||(CheckboxActions={})),function(e){e.any="",e.checked=":checked",e.unchecked=":not(:checked)"}(CheckboxState||(CheckboxState={}));class MultiRecordSelection{constructor(){this.lastChecked=null,DocumentService.ready().then((()=>{MultiRecordSelection.restoreTemporaryState(),this.registerActions(),this.registerActionsEventHandlers(),this.registerCheckboxActions(),this.registerCheckboxKeyboardActions(),this.registerCheckboxTableRowSelectionAction(),this.registerToggleCheckboxActions(),this.registerDispatchCheckboxStateChangedEvent(),this.registerCheckboxStateChangedEventHandler()}))}static getCheckboxes(e=CheckboxState.any,t=""){return document.querySelectorAll(MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.checkboxSelector+e,t))}static getCombinedSelector(e,t){return""!==t?[selector`[data-multi-record-selection-identifier="${t}"]`,e].join(" "):e}static getIdentifier(e){return e.closest("[data-multi-record-selection-identifier]")?.dataset.multiRecordSelectionIdentifier||""}static changeCheckboxState(e,t){e.disabled||e.checked===t||e.dataset.manuallyChanged||(e.checked=t,e.dispatchEvent(new CustomEvent("change",{bubbles:!0})),e.dispatchEvent(new CustomEvent("multiRecordSelection:checkbox:state:changed",{detail:{identifier:MultiRecordSelection.getIdentifier(e)},bubbles:!0,cancelable:!1})))}static restoreTemporaryState(){const e=MultiRecordSelection.getCheckboxes(CheckboxState.checked);if(!e.length)return;let t=!1;const c=[];e.forEach((e=>{e.closest(MultiRecordSelectionSelectors.elementSelector)?.classList.add(MultiRecordSelection.activeClass);const o=MultiRecordSelection.getIdentifier(e);""===o||c.includes(o)||(c.push(o),t=!0,MultiRecordSelection.toggleActionsState(o))})),t||MultiRecordSelection.toggleActionsState()}static toggleActionsState(e=""){const t=document.querySelectorAll(MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.actionsSelector,e));if(!t.length)return;if(!MultiRecordSelection.getCheckboxes(CheckboxState.checked,e).length)return void t.forEach((e=>MultiRecordSelection.changeActionContainerVisibility(e,!1)));t.forEach((e=>MultiRecordSelection.changeActionContainerVisibility(e)));const c=document.querySelectorAll([MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.actionsSelector,e),Buttons.actionButton].join(" "));c.length&&c.forEach((t=>{if(!t.dataset.multiRecordSelectionActionConfig)return;const c=JSON.parse(t.dataset.multiRecordSelectionActionConfig);if(!c.idField)return;t.classList.add(this.disabledClass);const o=MultiRecordSelection.getCheckboxes(CheckboxState.checked,e);for(let e=0;e<o.length;e++)if(o[e].closest(MultiRecordSelectionSelectors.elementSelector)?.dataset[c.idField]){t.classList.remove(this.disabledClass);break}}))}static changeActionContainerVisibility(e,t=!0){const c=e.closest(".multi-record-selection-panel")?.children;if(t){if(c)for(let e=0;e<c.length;e++)c[e].classList.add("hidden");e.classList.remove("hidden")}else{if(c)for(let e=0;e<c.length;e++)c[e].classList.remove("hidden");e.classList.add("hidden")}}static unsetManuallyChangedAttribute(e){MultiRecordSelection.getCheckboxes(CheckboxState.any,e).forEach((e=>{e.removeAttribute("data-manually-changed")}))}registerActions(){new RegularEvent("click",((e,t)=>{t.dataset.multiRecordSelectionAction;const c=MultiRecordSelection.getIdentifier(t),o=JSON.parse(t.dataset.multiRecordSelectionActionConfig||"{}"),i=MultiRecordSelection.getCheckboxes(CheckboxState.checked,c);i.length&&t.dispatchEvent(new CustomEvent("multiRecordSelection:action:"+t.dataset.multiRecordSelectionAction,{detail:{identifier:c,checkboxes:i,configuration:o},bubbles:!0,cancelable:!1}))})).delegateTo(document,[MultiRecordSelectionSelectors.actionsSelector,Buttons.actionButton].join(" "))}registerActionsEventHandlers(){new RegularEvent("multiRecordSelection:actions:show",(e=>{const t=e.detail?.identifier||"",c=document.querySelectorAll(MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.actionsSelector,t));c.length&&c.forEach((e=>MultiRecordSelection.changeActionContainerVisibility(e)))})).bindTo(document),new RegularEvent("multiRecordSelection:actions:hide",(e=>{const t=e.detail?.identifier||"",c=document.querySelectorAll(MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.actionsSelector,t));c.length&&c.forEach((e=>MultiRecordSelection.changeActionContainerVisibility(e,!1)))})).bindTo(document)}registerCheckboxActions(){new RegularEvent("click",((e,t)=>{if(e.preventDefault(),!t.dataset.multiRecordSelectionCheckAction)return;const c=MultiRecordSelection.getIdentifier(t),o=MultiRecordSelection.getCheckboxes(CheckboxState.any,c);if(o.length){switch(MultiRecordSelection.unsetManuallyChangedAttribute(c),t.dataset.multiRecordSelectionCheckAction){case CheckboxActions.checkAll:o.forEach((e=>{MultiRecordSelection.changeCheckboxState(e,!0)}));break;case CheckboxActions.checkNone:o.forEach((e=>{MultiRecordSelection.changeCheckboxState(e,!1)}));break;case CheckboxActions.toggle:o.forEach((e=>{MultiRecordSelection.changeCheckboxState(e,!e.checked)}));break;default:Notification.warning("Unknown checkbox action")}MultiRecordSelection.unsetManuallyChangedAttribute(c)}})).delegateTo(document,[MultiRecordSelectionSelectors.checkboxActionsSelector,Buttons.checkboxActionButton].join(" "))}registerCheckboxKeyboardActions(){new RegularEvent("click",((e,t)=>this.handleCheckboxKeyboardActions(e,t))).delegateTo(document,MultiRecordSelectionSelectors.checkboxSelector)}registerCheckboxTableRowSelectionAction(){new RegularEvent("click",((e,t)=>{const c=e.target.tagName;if("TH"!==c&&"TD"!==c)return;const o=t.querySelector(MultiRecordSelectionSelectors.checkboxSelector);null!==o&&(MultiRecordSelection.changeCheckboxState(o,!o.checked),this.handleCheckboxKeyboardActions(e,o,!1))})).delegateTo(document,MultiRecordSelectionSelectors.elementSelector),new RegularEvent("mousedown",(e=>(e.shiftKey||e.altKey||e.ctrlKey)&&e.preventDefault())).delegateTo(document,MultiRecordSelectionSelectors.elementSelector)}registerDispatchCheckboxStateChangedEvent(){new RegularEvent("change",((e,t)=>{t.dispatchEvent(new CustomEvent("multiRecordSelection:checkbox:state:changed",{detail:{identifier:MultiRecordSelection.getIdentifier(t)},bubbles:!0,cancelable:!1}))})).delegateTo(document,MultiRecordSelectionSelectors.checkboxSelector)}registerCheckboxStateChangedEventHandler(){new RegularEvent("multiRecordSelection:checkbox:state:changed",(e=>{const t=e.target,c=e.detail?.identifier||"";t.checked?t.closest(MultiRecordSelectionSelectors.elementSelector).classList.add(MultiRecordSelection.activeClass):t.closest(MultiRecordSelectionSelectors.elementSelector).classList.remove(MultiRecordSelection.activeClass),MultiRecordSelection.toggleActionsState(c)})).bindTo(document)}registerToggleCheckboxActions(){new RegularEvent("click",((e,t)=>{const c=MultiRecordSelection.getIdentifier(t),o=document.querySelector([MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.checkboxActionsSelector,c),'button[data-multi-record-selection-check-action="'+CheckboxActions.checkAll+'"]'].join(" "));null!==o&&o.classList.toggle("disabled",!MultiRecordSelection.getCheckboxes(CheckboxState.unchecked,c).length);const i=document.querySelector([MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.checkboxActionsSelector,c),'button[data-multi-record-selection-check-action="'+CheckboxActions.checkNone+'"]'].join(" "));null!==i&&i.classList.toggle("disabled",!MultiRecordSelection.getCheckboxes(CheckboxState.checked,c).length);const l=document.querySelector([MultiRecordSelection.getCombinedSelector(MultiRecordSelectionSelectors.checkboxActionsSelector,c),'button[data-multi-record-selection-check-action="'+CheckboxActions.toggle+'"]'].join(" "));null!==l&&l.classList.toggle("disabled",!MultiRecordSelection.getCheckboxes(CheckboxState.any,c).length)})).delegateTo(document,MultiRecordSelectionSelectors.checkboxActionsToggleSelector)}handleCheckboxKeyboardActions(e,t,c=!0){const o=MultiRecordSelection.getIdentifier(t);if(this.lastChecked&&document.body.contains(this.lastChecked)&&MultiRecordSelection.getIdentifier(this.lastChecked)===o&&(e.shiftKey||e.altKey||e.ctrlKey)){if(c&&MultiRecordSelection.unsetManuallyChangedAttribute(o),e.shiftKey){const e=Array.from(MultiRecordSelection.getCheckboxes(CheckboxState.any,o)),c=e.indexOf(t),i=e.indexOf(this.lastChecked);e.slice(Math.min(c,i),Math.max(c,i)+1).forEach((e=>{e!==t&&MultiRecordSelection.changeCheckboxState(e,t.checked)}))}this.lastChecked=t,(e.altKey||e.ctrlKey)&&MultiRecordSelection.getCheckboxes(CheckboxState.any,o).forEach((e=>{e!==t&&MultiRecordSelection.changeCheckboxState(e,!e.checked)})),MultiRecordSelection.unsetManuallyChangedAttribute(o)}else this.lastChecked=t}}MultiRecordSelection.activeClass="active",MultiRecordSelection.disabledClass="disabled";export default new MultiRecordSelection;
\ No newline at end of file
-- 
GitLab