From eca26164692e3d4ddc3d834b3d3201a56cc31854 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Chris=20M=C3=BCller?= <typo3@krue.ml>
Date: Fri, 27 Jan 2023 15:20:52 +0100
Subject: [PATCH] [FEATURE] Add country select form element to form framework

With the introduction of the new country API and
the correspodning Fluid form viewhelper in #99618,
is the form framework extendend for a new form
element "country select".

The form element allows several configuration
options and renderes a single select element
in the frontend, using the corresponding form
viewhelper.

Following configuration options are available:

- First option: Defines the "empty option", i.e.
  the first element of the select.
- Prioritized countries: Defines a list of countries,
  which should be listed on top.
- Only countries: Defines the countries to be shown
  in the select list.
- Exclude countries: Defines the countries not to
  be shown in the list.

Resolves: #99735
Releases: main
Change-Id: I27f135bb38ba727d25fb6bdc684555972f1e8f1b
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/77623
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
---
 ...ture-99735-NewCountrySelectFormElement.rst |  38 ++++++
 .../Form/CountrySelectViewHelper.php          |   2 +-
 .../MultiValuePropertiesExtractor.php         |   1 +
 .../MultiValuePropertiesExtractor.php         |   1 +
 .../Yaml/FormElements/CountrySelect.yaml      | 113 ++++++++++++++++++
 .../form/Configuration/Yaml/FormSetup.yaml    |   2 +
 .../Documentation/E/FormElements/Index.rst    |  17 +++
 .../Inspector/CountrySelectEditor.html        |  14 +++
 .../Frontend/Partials/CountrySelect.html      |  36 ++++++
 .../Partials/CountrySelect.html               |  47 ++++++++
 .../Resources/Private/Language/Database.xlf   |  19 ++-
 .../form-editor/inspector-component.js        |  87 ++++++++++++++
 .../backend/form-editor/stage-component.js    |   2 +
 13 files changed, 377 insertions(+), 2 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/12.3/Feature-99735-NewCountrySelectFormElement.rst
 create mode 100644 typo3/sysext/form/Configuration/Yaml/FormElements/CountrySelect.yaml
 create mode 100644 typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/CountrySelectEditor.html
 create mode 100644 typo3/sysext/form/Resources/Private/Frontend/Partials/CountrySelect.html
 create mode 100644 typo3/sysext/form/Resources/Private/FrontendVersion2/Partials/CountrySelect.html

diff --git a/typo3/sysext/core/Documentation/Changelog/12.3/Feature-99735-NewCountrySelectFormElement.rst b/typo3/sysext/core/Documentation/Changelog/12.3/Feature-99735-NewCountrySelectFormElement.rst
new file mode 100644
index 000000000000..92a81e4578bc
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.3/Feature-99735-NewCountrySelectFormElement.rst
@@ -0,0 +1,38 @@
+.. include:: /Includes.rst.txt
+
+.. _feature-99735-1678701694:
+
+=================================================
+Feature: #99735 - New Country Select Form Element
+=================================================
+
+See :issue:`99735`
+
+Description
+===========
+
+Since :issue:`99618`, TYPO3 provides a list of countries, together with an API
+and a Fluid form viewhelper. A new "Country select" form element has now been
+added to the TYPO3 Form Framework for easily creating a country select in a
+form. The new form element features a couple of configuration options, which
+can either be configured via the :guilabel:`Forms` module or directly in the
+corresponding YAML file.
+
+Available options
+-----------------
+
+- `First option` (:yaml:`prependOptionLabel`): Define the "empty option", i.e. the first element of the select. You can use this to provide additional guidance for the user.
+- `Prioritized countries` (:yaml:`prioritizedCountries`): Define a list of countries which should be listed as first options in the form element.
+- `Only countries` (:yaml:`onlyCountries`): Restrict the countries to be rendered in the list.
+- `Exclude countries` (:yaml:`excludeCountries`): Define which countries should not be shown in the list.
+
+The new element will be rendered as single select (:html:`<select>`) HTML
+element in the frontend.
+
+Impact
+======
+
+With the new "Country select" form element is now available in the Form
+Framework with a couple of specific configuration options.
+
+.. index:: ext:form
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Form/CountrySelectViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Form/CountrySelectViewHelper.php
index 58d7916fd671..619544c87a75 100644
--- a/typo3/sysext/fluid/Classes/ViewHelpers/Form/CountrySelectViewHelper.php
+++ b/typo3/sysext/fluid/Classes/ViewHelpers/Form/CountrySelectViewHelper.php
@@ -170,7 +170,7 @@ final class CountrySelectViewHelper extends AbstractFormFieldViewHelper
         } else {
             ksort($options, SORT_NATURAL);
         }
-        if ($this->arguments['prioritizedCountries'] !== []) {
+        if (($this->arguments['prioritizedCountries'] ?? []) !== []) {
             $finalOptions = [];
             foreach ($this->arguments['prioritizedCountries'] as $countryCode) {
                 if (isset($options[$countryCode])) {
diff --git a/typo3/sysext/form/Classes/Domain/Configuration/FrameworkConfiguration/Extractors/FormElement/MultiValuePropertiesExtractor.php b/typo3/sysext/form/Classes/Domain/Configuration/FrameworkConfiguration/Extractors/FormElement/MultiValuePropertiesExtractor.php
index 58eae2b37933..bb0bb9b5127c 100644
--- a/typo3/sysext/form/Classes/Domain/Configuration/FrameworkConfiguration/Extractors/FormElement/MultiValuePropertiesExtractor.php
+++ b/typo3/sysext/form/Classes/Domain/Configuration/FrameworkConfiguration/Extractors/FormElement/MultiValuePropertiesExtractor.php
@@ -35,6 +35,7 @@ class MultiValuePropertiesExtractor extends AbstractExtractor
         if (
             $value !== 'Inspector-PropertyGridEditor'
             && $value !== 'Inspector-MultiSelectEditor'
+            && $value !== 'Inspector-CountrySelectEditor'
             && $value !== 'Inspector-ValidationErrorMessageEditor'
             && $value !== 'Inspector-RequiredValidatorEditor'
         ) {
diff --git a/typo3/sysext/form/Classes/Domain/Configuration/FrameworkConfiguration/Extractors/PropertyCollectionElement/MultiValuePropertiesExtractor.php b/typo3/sysext/form/Classes/Domain/Configuration/FrameworkConfiguration/Extractors/PropertyCollectionElement/MultiValuePropertiesExtractor.php
index 6131fbd6b55a..85cad00301b2 100644
--- a/typo3/sysext/form/Classes/Domain/Configuration/FrameworkConfiguration/Extractors/PropertyCollectionElement/MultiValuePropertiesExtractor.php
+++ b/typo3/sysext/form/Classes/Domain/Configuration/FrameworkConfiguration/Extractors/PropertyCollectionElement/MultiValuePropertiesExtractor.php
@@ -35,6 +35,7 @@ class MultiValuePropertiesExtractor extends AbstractExtractor
         if (
             $value !== 'Inspector-PropertyGridEditor'
             && $value !== 'Inspector-MultiSelectEditor'
+            && $value !== 'Inspector-CountrySelectEditor'
             && $value !== 'Inspector-ValidationErrorMessageEditor'
         ) {
             return;
diff --git a/typo3/sysext/form/Configuration/Yaml/FormElements/CountrySelect.yaml b/typo3/sysext/form/Configuration/Yaml/FormElements/CountrySelect.yaml
new file mode 100644
index 000000000000..105091b6aa4a
--- /dev/null
+++ b/typo3/sysext/form/Configuration/Yaml/FormElements/CountrySelect.yaml
@@ -0,0 +1,113 @@
+prototypes:
+  standard:
+    formElementsDefinition:
+      CountrySelect:
+        formEditor:
+          editors:
+            100:
+              identifier: header
+              templateName: Inspector-FormElementHeaderEditor
+            200:
+              identifier: label
+              templateName: Inspector-TextEditor
+              label: formEditor.elements.FormElement.editor.label.label
+              propertyPath: label
+            230:
+              identifier: elementDescription
+              templateName: Inspector-TextEditor
+              label: formEditor.elements.FormElement.editor.elementDescription.label
+              propertyPath: properties.elementDescription
+            250:
+              identifier: inactiveOption
+              templateName: Inspector-TextEditor
+              label: formEditor.elements.SelectionMixin.editor.inactiveOption.label
+              propertyPath: properties.prependOptionLabel
+              fieldExplanationText: formEditor.elements.SelectionMixin.editor.inactiveOption.fieldExplanationText
+              doNotSetIfPropertyValueIsEmpty: true
+            300:
+              identifier: prioritizedCountries
+              templateName: Inspector-CountrySelectEditor
+              label: formEditor.elements.CountrySelect.editor.prioritizedCountries.label
+              propertyPath: properties.prioritizedCountries
+            310:
+              identifier: onlyCountries
+              templateName: Inspector-CountrySelectEditor
+              label: formEditor.elements.CountrySelect.editor.onlyCountries.label
+              propertyPath: properties.onlyCountries
+            320:
+              identifier: excludeCountries
+              templateName: Inspector-CountrySelectEditor
+              label: formEditor.elements.CountrySelect.editor.excludeCountries.label
+              propertyPath: properties.excludeCountries
+            700:
+              identifier: gridColumnViewPortConfiguration
+              templateName: Inspector-GridColumnViewPortConfigurationEditor
+              label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.label
+              configurationOptions:
+                viewPorts:
+                  10:
+                    viewPortIdentifier: xs
+                    label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xs.label
+                  20:
+                    viewPortIdentifier: sm
+                    label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.sm.label
+                  30:
+                    viewPortIdentifier: md
+                    label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.md.label
+                  40:
+                    viewPortIdentifier: lg
+                    label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.lg.label
+                  50:
+                    viewPortIdentifier: xl
+                    label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xl.label
+                  60:
+                    viewPortIdentifier: xxl
+                    label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.xxl.label
+                numbersOfColumnsToUse:
+                  label: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.label
+                  propertyPath: 'properties.gridColumnClassAutoConfiguration.viewPorts.{@viewPortIdentifier}.numbersOfColumnsToUse'
+                  fieldExplanationText: formEditor.elements.FormElement.editor.gridColumnViewPortConfiguration.numbersOfColumnsToUse.fieldExplanationText
+            800:
+              identifier: requiredValidator
+              templateName: Inspector-RequiredValidatorEditor
+              label: formEditor.elements.FormElement.editor.requiredValidator.label
+              validatorIdentifier: NotEmpty
+              propertyPath: properties.fluidAdditionalAttributes.required
+              propertyValue: required
+              configurationOptions:
+                validationErrorMessage:
+                  label: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.label
+                  propertyPath: properties.validationErrorMessages
+                  fieldExplanationText: formEditor.elements.FormElement.editor.requiredValidator.validationErrorMessage.fieldExplanationText
+                  errorCodes:
+                    10: 1221560910
+                    20: 1221560718
+                    30: 1347992400
+                    40: 1347992453
+            9999:
+              identifier: removeButton
+              templateName: Inspector-RemoveElementEditor
+          predefinedDefaults:
+            properties:
+              options: {  }
+          label: formEditor.elements.CountrySelect.label
+          group: select
+          groupSorting: 200
+          iconIdentifier: form-multi-select
+        implementationClassName: TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement
+        properties:
+          containerClassAttribute: input
+          elementClassAttribute: ''
+          elementErrorClassAttribute: error
+        variants:
+          -
+            identifier: template-variant
+            condition: 'getRootFormProperty("renderingOptions.templateVariant") == "version2"'
+            properties:
+              containerClassAttribute: 'form-element form-element-select mb-3'
+              elementClassAttribute: form-control
+              elementErrorClassAttribute: ~
+              labelClassAttribute: form-label
+    formEditor:
+      formEditorPartials:
+        FormElement-CountrySelect: Stage/SelectTemplate
diff --git a/typo3/sysext/form/Configuration/Yaml/FormSetup.yaml b/typo3/sysext/form/Configuration/Yaml/FormSetup.yaml
index 90af87635656..a0886ad83e35 100644
--- a/typo3/sysext/form/Configuration/Yaml/FormSetup.yaml
+++ b/typo3/sysext/form/Configuration/Yaml/FormSetup.yaml
@@ -35,6 +35,7 @@ imports:
     - { resource: './FormElements/MultiSelect.yaml' }
     - { resource: './FormElements/RadioButton.yaml' }
     - { resource: './FormElements/SingleSelect.yaml' }
+    - { resource: './FormElements/CountrySelect.yaml' }
     - { resource: './FormElements/DatePicker.yaml' }
     - { resource: './FormElements/StaticText.yaml' }
     - { resource: './FormElements/ContentElement.yaml' }
@@ -102,6 +103,7 @@ prototypes:
         Inspector-ValidationErrorMessageEditor: Inspector/ValidationErrorMessageEditor
         Inspector-Typo3WinBrowserEditor: Inspector/Typo3WinBrowserEditor
         Inspector-MaximumFileSizeEditor: Inspector/MaximumFileSizeEditor
+        Inspector-CountrySelectEditor: Inspector/CountrySelectEditor
 
       formElementPropertyValidatorsDefinition:
         NotEmpty:
diff --git a/typo3/sysext/form/Documentation/E/FormElements/Index.rst b/typo3/sysext/form/Documentation/E/FormElements/Index.rst
index c9dc6cca856b..cc647a83ee73 100644
--- a/typo3/sysext/form/Documentation/E/FormElements/Index.rst
+++ b/typo3/sysext/form/Documentation/E/FormElements/Index.rst
@@ -389,6 +389,23 @@ to the :ref:`default<form-elements-settings>`. Additional settings:
    Settings for the 'Multi select' element.
 
 
+.. _form-elements-select-elements-country-select:
+
+Country select
+============
+
+An element to create a country select. The settings for this element adhere
+to the :ref:`default<form-elements-settings>`. Additional settings:
+
+- **First option**: Define the "empty option", i.e. the first element of the
+  select. You can use this to provide additional guidance for the user.
+- **Prioritized countries**: A multi-selection of country names, which should
+  always be listed as first options in the form element.
+- **Only countries**: Restrict the countries to be rendered in the selection.
+- **Exclude countries**: Define which countries should not be shown in the
+  selection.
+
+
 .. _form-elements-advanced-elements:
 
 Advanced elements
diff --git a/typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/CountrySelectEditor.html b/typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/CountrySelectEditor.html
new file mode 100644
index 000000000000..ef246f932b2c
--- /dev/null
+++ b/typo3/sysext/form/Resources/Private/Backend/Partials/FormEditor/Inspector/CountrySelectEditor.html
@@ -0,0 +1,14 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers" data-namespace-typo3-fluid="true">
+<div class="form-editor">
+    <div class="t3-form-control-group form-group">
+        <label data-random-id data-random-id-attribute="for" data-random-id-number="1"><span data-template-property="label"></span></label>
+        <div class="formengine-field-item">
+            <div class="form-control-wrap">
+                <div class="t3-form-controls" data-identifier="inspectorEditorControlsWrapper">
+                    <f:form.countrySelect data="{template-property: 'selectOptions', random-id: '', random-id-attribute: 'id', random-id-number: '1'}" class="form-select" multiple="multiple" sortByOptionLabel="1"></f:form.countrySelect>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+</html>
diff --git a/typo3/sysext/form/Resources/Private/Frontend/Partials/CountrySelect.html b/typo3/sysext/form/Resources/Private/Frontend/Partials/CountrySelect.html
new file mode 100644
index 000000000000..e29a2d3afb13
--- /dev/null
+++ b/typo3/sysext/form/Resources/Private/Frontend/Partials/CountrySelect.html
@@ -0,0 +1,36 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers" data-namespace-typo3-fluid="true">
+<formvh:renderRenderable renderable="{element}">
+    <f:render partial="Field/Field" arguments="{element: element}" contentAs="elementContent">
+        <f:if condition="{element.properties.prependOptionLabel}">
+            <f:then>
+                <f:form.countrySelect
+                        property="{element.identifier}"
+                        id="{element.uniqueIdentifier}"
+                        class="{element.properties.elementClassAttribute} form-control"
+                        errorClass="{element.properties.elementErrorClassAttribute}"
+                        additionalAttributes="{formvh:translateElementProperty(element: element, property: 'fluidAdditionalAttributes')}"
+                        prependOptionLabel="{formvh:translateElementProperty(element: element, property: 'prependOptionLabel')}"
+                        prependOptionValue="{formvh:translateElementProperty(element: element, property: 'prependOptionValue')}"
+                        sortByOptionLabel="true"
+                        prioritizedCountries="{element.properties.prioritizedCountries}"
+                        onlyCountries="{element.properties.onlyCountries}"
+                        excludeCountries="{element.properties.excludeCountries}"
+                />
+            </f:then>
+            <f:else>
+                <f:form.countrySelect
+                        property="{element.identifier}"
+                        id="{element.uniqueIdentifier}"
+                        class="{element.properties.elementClassAttribute} form-control"
+                        errorClass="{element.properties.elementErrorClassAttribute}"
+                        additionalAttributes="{formvh:translateElementProperty(element: element, property: 'fluidAdditionalAttributes')}"
+                        sortByOptionLabel="true"
+                        prioritizedCountries="{element.properties.prioritizedCountries}"
+                        onlyCountries="{element.properties.onlyCountries}"
+                        excludeCountries="{element.properties.excludeCountries}"
+                />
+            </f:else>
+        </f:if>
+    </f:render>
+</formvh:renderRenderable>
+</html>
diff --git a/typo3/sysext/form/Resources/Private/FrontendVersion2/Partials/CountrySelect.html b/typo3/sysext/form/Resources/Private/FrontendVersion2/Partials/CountrySelect.html
new file mode 100644
index 000000000000..f6bf9f496502
--- /dev/null
+++ b/typo3/sysext/form/Resources/Private/FrontendVersion2/Partials/CountrySelect.html
@@ -0,0 +1,47 @@
+<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers" data-namespace-typo3-fluid="true">
+<formvh:renderRenderable renderable="{element}">
+    <f:form.validationResults for="{element.rootForm.identifier}.{element.identifier}">
+        <f:if condition="{element.properties.elementDescription}">
+            <f:variable name="aria" value="{describedby: '{element.uniqueIdentifier}-desc'}"/>
+        </f:if>
+        <f:if condition="{validationResults.errors}">
+            <f:variable name="aria" value="{invalid: 'true', describedby: '{element.uniqueIdentifier}-errors'}"/>
+        </f:if>
+
+        <f:render partial="Field/Field" arguments="{element: element}" contentAs="elementContent">
+            <f:if condition="{element.properties.prependOptionLabel}">
+                <f:then>
+                    <f:form.countrySelect
+                        property="{element.identifier}"
+                        id="{element.uniqueIdentifier}"
+                        class="{element.properties.elementClassAttribute}"
+                        errorClass="{element.rootForm.renderingOptions.fieldProperties.errorClassAttribute}"
+                        additionalAttributes="{formvh:translateElementProperty(element: element, property: 'fluidAdditionalAttributes')}"
+                        prependOptionLabel="{formvh:translateElementProperty(element: element, property: 'prependOptionLabel')}"
+                        prependOptionValue="{formvh:translateElementProperty(element: element, property: 'prependOptionValue')}"
+                        sortByOptionLabel="true"
+                        prioritizedCountries="{element.properties.prioritizedCountries}"
+                        onlyCountries="{element.properties.onlyCountries}"
+                        excludeCountries="{element.properties.excludeCountries}"
+                        aria="{aria}"
+                    />
+                </f:then>
+                <f:else>
+                    <f:form.countrySelect
+                        property="{element.identifier}"
+                        id="{element.uniqueIdentifier}"
+                        class="{element.properties.elementClassAttribute}"
+                        errorClass="{element.rootForm.renderingOptions.fieldProperties.errorClassAttribute}"
+                        additionalAttributes="{formvh:translateElementProperty(element: element, property: 'fluidAdditionalAttributes')}"
+                        sortByOptionLabel="true"
+                        prioritizedCountries="{element.properties.prioritizedCountries}"
+                        onlyCountries="{element.properties.onlyCountries}"
+                        excludeCountries="{element.properties.excludeCountries}"
+                        aria="{aria}"
+                    />
+                </f:else>
+            </f:if>
+        </f:render>
+    </f:form.validationResults>
+</formvh:renderRenderable>
+</html>
diff --git a/typo3/sysext/form/Resources/Private/Language/Database.xlf b/typo3/sysext/form/Resources/Private/Language/Database.xlf
index 4d844d320518..c1cfe8ce3388 100644
--- a/typo3/sysext/form/Resources/Private/Language/Database.xlf
+++ b/typo3/sysext/form/Resources/Private/Language/Database.xlf
@@ -955,7 +955,11 @@
                 <source>Checkbox</source>
             </trans-unit>
 
-            <trans-unit id="formEditor.elements.MultiCheckbox.label" resname="formEditor.elements.MultiCheckbox.label" xml:space="preserve">
+            <trans-unit id="formEditor.elements.CountrySelect.label" resname="formEditor.elements.CountrySelect.label" xml:space="preserve">
+                <source>Country select</source>
+            </trans-unit>
+
+			<trans-unit id="formEditor.elements.MultiCheckbox.label" resname="formEditor.elements.MultiCheckbox.label" xml:space="preserve">
                 <source>Multi checkbox</source>
             </trans-unit>
 
@@ -1083,6 +1087,19 @@
                 <source>Images (bmp)</source>
             </trans-unit>
 
+            <trans-unit id="formEditor.elements.CountrySelect" resname="formEditor.elements.CountrySelect" xml:space="preserve">
+                <source>Country Select</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.CountrySelect.editor.prioritizedCountries.label" resname="formEditor.elements.CountrySelect.editor.prioritizedCountries.label" xml:space="preserve">
+                <source>Prioritized countries</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.CountrySelect.editor.onlyCountries.label" resname="formEditor.elements.CountrySelect.editor.onlyCountries.label" xml:space="preserve">
+                <source>Only countries</source>
+            </trans-unit>
+            <trans-unit id="formEditor.elements.CountrySelect.editor.excludeCountries.label" resname="formEditor.elements.CountrySelect.editor.excludeCountries.label" xml:space="preserve">
+                <source>Exclude countries</source>
+            </trans-unit>
+
             <trans-unit id="formEditor.elements.UnknownElement" resname="formEditor.elements.UnknownElement" xml:space="preserve">
                 <source>Unknown element</source>
             </trans-unit>
diff --git a/typo3/sysext/form/Resources/Public/JavaScript/backend/form-editor/inspector-component.js b/typo3/sysext/form/Resources/Public/JavaScript/backend/form-editor/inspector-component.js
index 9c3841b5c1bf..411ef0e6b149 100644
--- a/typo3/sysext/form/Resources/Public/JavaScript/backend/form-editor/inspector-component.js
+++ b/typo3/sysext/form/Resources/Public/JavaScript/backend/form-editor/inspector-component.js
@@ -33,6 +33,7 @@ const {
   renderCheckboxEditor,
   renderCollectionElementEditors,
   renderCollectionElementHeaderEditor,
+  renderCountrySelectEditor,
   renderFileMaxSizeEditor,
   renderCollectionElementSelectionEditor,
   renderEditors,
@@ -362,6 +363,14 @@ function factory($, Helper, Icons, Notification, Modal, MessageUtility) {
             collectionName
           );
           break;
+        case 'Inspector-CountrySelectEditor':
+          renderCountrySelectEditor(
+            editorConfiguration,
+            editorHtml,
+            collectionElementIdentifier,
+            collectionName
+          );
+          break;
         case 'Inspector-SingleSelectEditor':
           renderSingleSelectEditor(
             editorConfiguration,
@@ -1476,6 +1485,83 @@ function factory($, Helper, Icons, Notification, Modal, MessageUtility) {
       });
     };
 
+    /**
+     * @public
+     *
+     * @param object editorConfiguration
+     * @param object editorHtml
+     * @param string collectionElementIdentifier
+     * @param string collectionName
+     * @return void
+     * @throws 1674826430
+     * @throws 1674826431
+     * @throws 1674826432
+     */
+    function renderCountrySelectEditor(editorConfiguration, editorHtml, collectionElementIdentifier, collectionName) {
+      var propertyData, propertyPath, selectElement, options;
+      assert(
+        'object' === $.type(editorConfiguration),
+        'Invalid parameter "editorConfiguration"',
+        1674826430
+      );
+      assert(
+        'object' === $.type(editorHtml),
+        'Invalid parameter "editorHtml"',
+        1674826431
+      );
+      assert(
+        getUtility().isNonEmptyString(editorConfiguration['label']),
+        'Invalid configuration "label"',
+        1674826432
+      );
+
+      propertyPath = getFormEditorApp().buildPropertyPath(
+        editorConfiguration['propertyPath'],
+        collectionElementIdentifier,
+        collectionName
+      );
+
+      getHelper()
+        .getTemplatePropertyDomElement('label', editorHtml)
+        .append(editorConfiguration['label']);
+
+      selectElement = getHelper()
+        .getTemplatePropertyDomElement('selectOptions', editorHtml);
+
+      propertyData = getCurrentlySelectedFormElement().get(propertyPath);
+
+      options = $('option', selectElement);
+      selectElement.empty();
+
+      for (var i = 0, len = options.length; i < len; ++i) {
+        var option, selected = false;
+
+        for (var propertyDataKey in propertyData) {
+          if (!propertyData.hasOwnProperty(propertyDataKey)) {
+            continue;
+          }
+
+          if (options[i].value === propertyData[propertyDataKey]) {
+            selected = true;
+            break;
+          }
+        }
+
+        option = new Option(options[i].text, i, false, selected);
+        $(option).data({value: options[i].value});
+        selectElement.append(option);
+      }
+
+      selectElement.on('change', function() {
+        var selectValues = [];
+        $('option:selected', $(this)).each(function(i) {
+          selectValues.push($(this).data('value'));
+        });
+
+        getCurrentlySelectedFormElement().set(propertyPath, selectValues);
+      });
+    };
+
     /**
      * @public
      *
@@ -2704,6 +2790,7 @@ function factory($, Helper, Icons, Notification, Modal, MessageUtility) {
       renderPropertyGridEditor: renderPropertyGridEditor,
       renderRemoveElementEditor: renderRemoveElementEditor,
       renderRequiredValidatorEditor: renderRequiredValidatorEditor,
+      renderCountrySelectEditor: renderCountrySelectEditor,
       renderSingleSelectEditor: renderSingleSelectEditor,
       renderMultiSelectEditor: renderMultiSelectEditor,
       renderTextareaEditor: renderTextareaEditor,
diff --git a/typo3/sysext/form/Resources/Public/JavaScript/backend/form-editor/stage-component.js b/typo3/sysext/form/Resources/Public/JavaScript/backend/form-editor/stage-component.js
index 52ecd183fc75..b5eefdeea23d 100644
--- a/typo3/sysext/form/Resources/Public/JavaScript/backend/form-editor/stage-component.js
+++ b/typo3/sysext/form/Resources/Public/JavaScript/backend/form-editor/stage-component.js
@@ -122,6 +122,7 @@ function factory($, Helper, Icons) {
         'FormElement-AdvancedPassword': 'FormElement-AdvancedPassword',
         'FormElement-Checkbox': 'FormElement-Checkbox',
         'FormElement-ContentElement': 'FormElement-ContentElement',
+        'FormElement-CountrySelect': 'FormElement-CountrySelect',
         'FormElement-DatePicker': 'FormElement-DatePicker',
         'FormElement-Fieldset': 'FormElement-Fieldset',
         'FormElement-GridRow': 'FormElement-GridRow',
@@ -307,6 +308,7 @@ function factory($, Helper, Icons) {
         case 'ImageUpload':
           renderFileUploadTemplates(formElement, template);
           break;
+        case 'CountrySelect':
         case 'SingleSelect':
         case 'RadioButton':
         case 'MultiSelect':
-- 
GitLab