diff --git a/lang/en/messages.php b/lang/en/messages.php
index f5e7e230fb7..d581f6ad046 100644
--- a/lang/en/messages.php
+++ b/lang/en/messages.php
@@ -86,6 +86,7 @@
'entry_origin_instructions' => 'The new localization will inherit values from the entry in the selected site.',
'expect_root_instructions' => 'Consider the first page in the tree a "root" or "home" page.',
'field_conditions_always_save_instructions' => 'Always save field value, even if the field is hidden.',
+ 'field_conditions_reserve_space_when_hidden_instructions' => 'When hidden, reserve the field\'s space in the layout.',
'field_conditions_field_instructions' => 'You may enter any field handle. You are not limited to the options in the dropdown.',
'field_conditions_instructions' => 'When to show or hide this field.',
'field_desynced_from_origin' => 'Desynced from origin. Click to sync and revert to the origin\'s value.',
diff --git a/resources/js/components/field-conditions/Builder.vue b/resources/js/components/field-conditions/Builder.vue
index 895c9484e88..68fc16f954c 100644
--- a/resources/js/components/field-conditions/Builder.vue
+++ b/resources/js/components/field-conditions/Builder.vue
@@ -30,12 +30,23 @@
-
-
-
+
+
+
+
+
+
+
+
+
@@ -77,6 +88,7 @@ export default {
customMethod: null,
conditions: [],
alwaysSave: false,
+ reserveSpaceWhenHidden: false,
};
},
@@ -135,12 +147,17 @@ export default {
alwaysSave(alwaysSave) {
this.$emit('updated-always-save', alwaysSave);
},
+
+ reserveSpaceWhenHidden(reserveSpaceWhenHidden) {
+ this.$emit('updated-reserve-space-when-hidden', reserveSpaceWhenHidden);
+ },
},
created() {
this.add();
this.getInitialConditions();
this.getInitialAlwaysSaveState();
+ this.getInitialReserveSpaceWhenHiddenState();
},
methods: {
@@ -187,6 +204,10 @@ export default {
this.alwaysSave = data_get(this.config, 'always_save', false);
},
+ getInitialReserveSpaceWhenHiddenState() {
+ this.reserveSpaceWhenHidden = data_get(this.config, 'reserve_space_when_hidden', false);
+ },
+
prepareEditableConditions(conditions) {
return new Converter().fromBlueprint(conditions).map((condition) => {
condition._id = uniqid();
diff --git a/resources/js/components/fields/Settings.vue b/resources/js/components/fields/Settings.vue
index 5c02914bbee..1eaa3748c00 100644
--- a/resources/js/components/fields/Settings.vue
+++ b/resources/js/components/fields/Settings.vue
@@ -43,6 +43,7 @@
:suggestable-fields="suggestableConditionFields"
@updated="updateFieldConditions"
@updated-always-save="updateAlwaysSave"
+ @updated-reserve-space-when-hidden="updateReserveSpaceWhenHidden"
/>
@@ -263,6 +264,12 @@ export default {
this.markFieldEdited('always_save');
},
+ updateReserveSpaceWhenHidden(reserveSpaceWhenHidden) {
+ this.values.reserve_space_when_hidden = reserveSpaceWhenHidden;
+
+ this.markFieldEdited('reserve_space_when_hidden');
+ },
+
markFieldEdited(handle) {
if (this.editedFields.indexOf(handle) === -1) {
this.editedFields.push(handle);
diff --git a/resources/js/components/ui/Publish/Field.vue b/resources/js/components/ui/Publish/Field.vue
index 6918e403ae3..df69e1fdbe0 100644
--- a/resources/js/components/ui/Publish/Field.vue
+++ b/resources/js/components/ui/Publish/Field.vue
@@ -149,6 +149,15 @@ const shouldShowField = computed(() => {
).showField(props.config, fullPath.value);
});
+// Only applies when hidden by conditions; blueprint "hidden" visibility still removes the field from layout.
+const reserveSpaceWhenHiddenEnabled = computed(
+ () => props.config.reserve_space_when_hidden === true && props.config.visibility !== 'hidden',
+);
+
+const shouldHideFieldVisually = computed(
+ () => reserveSpaceWhenHiddenEnabled.value && !shouldShowField.value,
+);
+
const shouldShowLabelText = computed(() => !props.config.hide_display);
const shouldShowLabel = computed(
@@ -229,8 +238,9 @@ const fieldtypeComponentEvents = computed(() => ({
:shouldShowField="shouldShowField"
>
config, 'always_save', false);
}
+ public function reserveSpaceWhenHidden()
+ {
+ return Arr::get($this->config, 'reserve_space_when_hidden', false);
+ }
+
public function rules()
{
$rules = [$this->handle => $this->addNullableRule(array_merge(
@@ -277,6 +282,7 @@ public function toPublishArray()
'visibility' => $this->visibility(),
'read_only' => $this->visibility() === 'read_only', // Deprecated: Addon fieldtypes should now reference new `visibility` state.
'always_save' => $this->alwaysSave(),
+ 'reserve_space_when_hidden' => $this->reserveSpaceWhenHidden(),
]);
unset($array['validate']);
diff --git a/tests/Fields/FieldTest.php b/tests/Fields/FieldTest.php
index b38408923f7..38f3361593f 100644
--- a/tests/Fields/FieldTest.php
+++ b/tests/Fields/FieldTest.php
@@ -354,9 +354,32 @@ public function preProcess($data)
'required' => true,
'read_only' => false, // deprecated
'always_save' => false,
+ 'reserve_space_when_hidden' => false,
], $field->toPublishArray());
}
+ #[Test]
+ public function to_publish_array_passes_through_reserve_space_when_hidden()
+ {
+ FieldtypeRepository::partialMock();
+
+ FieldtypeRepository::shouldReceive('find')
+ ->with('example')
+ ->andReturn(new class extends Fieldtype
+ {
+ protected $component = 'example';
+
+ protected $configFields = [];
+ });
+
+ $field = new Field('test', [
+ 'type' => 'example',
+ 'reserve_space_when_hidden' => true,
+ ]);
+
+ $this->assertTrue($field->toPublishArray()['reserve_space_when_hidden']);
+ }
+
#[Test]
public function it_gets_the_value()
{