diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md
index 4b9f60d5724..33586e16554 100644
--- a/CHANGELOG-WIP.md
+++ b/CHANGELOG-WIP.md
@@ -50,6 +50,7 @@
- Deprecated `craft\helpers\ElementHelper::rootElement()`. `craft\base\ElementInterface::getRootOwner()` should be used instead.
- Added `Craft.cp.announce()`, simplifying live region announcements for screen readers. ([#15569](https://github.com/craftcms/cms/pull/15569))
- Element action menu items returned by `craft\base\Element::safeActionMenuItems()` and `destructiveActionMenuItems()` can now include a `showInChips` key to explicitly opt into/out of being shown within element chips and cards.
+- Element select inputs now support `allowAdd` and `allowRemove` settings. ([#15639](https://github.com/craftcms/cms/discussions/15639))
- Control panel CSS selectors that take orientation into account now use logical properties. ([#15522](https://github.com/craftcms/cms/pull/15522))
### System
diff --git a/src/templates/_includes/forms/elementSelect.twig b/src/templates/_includes/forms/elementSelect.twig
index 786baef1d71..2110e676293 100644
--- a/src/templates/_includes/forms/elementSelect.twig
+++ b/src/templates/_includes/forms/elementSelect.twig
@@ -22,6 +22,9 @@
{% set maintainHierarchy = maintainHierarchy ?? false %}
{% set registerJs = registerJs ?? true %}
+{% set allowAdd = allowAdd ?? true %}
+{% set allowRemove = allowRemove ?? true %}
+
{% set containerAttributes = {
id: id,
class: ['elementselect']|merge((class ?? [])|explodeClass),
@@ -63,23 +66,25 @@
{% endif %}
- {{ tag('button', {
- type: 'button',
- class: [
- 'btn',
- 'add',
- 'icon',
- 'dashed',
- disabled ? 'disabled',
- limit and elements|length >= limit ? 'hidden',
- ]|filter,
- text: selectionLabel ?? 'Choose'|t('app'),
- disabled: disabled,
- aria: {
- label: selectionLabel ?? 'Choose'|t('app'),
- describedby: describedBy ?? false,
- }
- }) }}
+ {% if allowAdd %}
+ {{ tag('button', {
+ type: 'button',
+ class: [
+ 'btn',
+ 'add',
+ 'icon',
+ 'dashed',
+ disabled ? 'disabled',
+ limit and elements|length >= limit ? 'hidden',
+ ]|filter,
+ text: selectionLabel ?? 'Choose'|t('app'),
+ disabled: disabled,
+ aria: {
+ label: selectionLabel ?? 'Choose'|t('app'),
+ describedby: describedBy ?? false,
+ }
+ }) }}
+ {% endif %}
{% endtag %}
@@ -94,6 +99,8 @@
referenceElementId: referenceElement.id ?? null,
referenceElementSiteId: referenceElement.siteId ?? null,
criteria: criteria,
+ allowAdd: allowAdd,
+ allowRemove: allowRemove,
allowSelfRelations: allowSelfRelations ?? false,
maintainHierarchy: maintainHierarchy,
branchLimit: branchLimit ?? null,
diff --git a/src/web/assets/cp/dist/cp.js b/src/web/assets/cp/dist/cp.js
index 0ceb5ae4133..1666db3f98a 100644
--- a/src/web/assets/cp/dist/cp.js
+++ b/src/web/assets/cp/dist/cp.js
@@ -1,3 +1,3 @@
/*! For license information please see cp.js.LICENSE.txt */
-(function(){var __webpack_modules__={463:function(){Craft.Accordion=Garnish.Base.extend({$trigger:null,targetSelector:null,_$target:null,init:function(t){var e=this;this.$trigger=$(t),this.$trigger.data("accordion")&&(console.warn("Double-instantiating an accordion trigger on an element"),this.$trigger.data("accordion").destroy()),this.$trigger.data("accordion",this),this.targetSelector=this.$trigger.attr("aria-controls")?"#".concat(this.$trigger.attr("aria-controls")):null,this.targetSelector&&(this._$target=$(this.targetSelector)),this.addListener(this.$trigger,"click","onTriggerClick"),this.addListener(this.$trigger,"keypress",(function(t){var n=t.keyCode;n!==Garnish.SPACE_KEY&&n!==Garnish.RETURN_KEY||(t.preventDefault(),e.onTriggerClick())}))},onTriggerClick:function(){"true"===this.$trigger.attr("aria-expanded")?this.hideTarget(this._$target):this.showTarget(this._$target)},showTarget:function(t){var e=this;if(t&&t.length){this.showTarget._currentHeight=t.height(),t.removeClass("hidden"),this.$trigger.removeClass("collapsed").addClass("expanded").attr("aria-expanded","true");for(var n=0;n