From 386f59ab01f45757521924ef11ac09df10078916 Mon Sep 17 00:00:00 2001 From: Jay George Date: Tue, 12 May 2026 16:10:40 +0100 Subject: [PATCH 1/2] For conditionally hidden fields, add the option that when hidden, we can reserve the field's space in the layout --- lang/en/messages.php | 1 + .../components/field-conditions/Builder.vue | 33 +++++++++++++++---- resources/js/components/fields/Settings.vue | 7 ++++ resources/js/components/ui/Publish/Field.vue | 11 +++++-- src/Fields/Field.php | 6 ++++ tests/Fields/FieldTest.php | 1 + 6 files changed, 51 insertions(+), 8 deletions(-) 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..3210e9f0666 100644 --- a/resources/js/components/ui/Publish/Field.vue +++ b/resources/js/components/ui/Publish/Field.vue @@ -149,6 +149,12 @@ const shouldShowField = computed(() => { ).showField(props.config, fullPath.value); }); +const reserveSpaceWhenHidden = computed(() => props.config.reserve_space_when_hidden === true); + +const shouldHideFieldVisually = computed( + () => reserveSpaceWhenHidden.value && !shouldShowField.value, +); + const shouldShowLabelText = computed(() => !props.config.hide_display); const shouldShowLabel = computed( @@ -229,8 +235,9 @@ const fieldtypeComponentEvents = computed(() => ({ :shouldShowField="shouldShowField" > config, 'always_save', false); } + public function reserveSpaceWhenHidden() + { + return (bool) 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..b77792e1245 100644 --- a/tests/Fields/FieldTest.php +++ b/tests/Fields/FieldTest.php @@ -354,6 +354,7 @@ public function preProcess($data) 'required' => true, 'read_only' => false, // deprecated 'always_save' => false, + 'reserve_space_when_hidden' => false, ], $field->toPublishArray()); } From a3b7bf42ea074b4ddf554e3e1692eaf0b0977881 Mon Sep 17 00:00:00 2001 From: Jay George Date: Tue, 12 May 2026 16:18:50 +0100 Subject: [PATCH 2/2] Tidy up --- resources/js/components/ui/Publish/Field.vue | 9 +++++--- src/Fields/Field.php | 2 +- tests/Fields/FieldTest.php | 22 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/resources/js/components/ui/Publish/Field.vue b/resources/js/components/ui/Publish/Field.vue index 3210e9f0666..df69e1fdbe0 100644 --- a/resources/js/components/ui/Publish/Field.vue +++ b/resources/js/components/ui/Publish/Field.vue @@ -149,10 +149,13 @@ const shouldShowField = computed(() => { ).showField(props.config, fullPath.value); }); -const reserveSpaceWhenHidden = computed(() => props.config.reserve_space_when_hidden === true); +// 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( - () => reserveSpaceWhenHidden.value && !shouldShowField.value, + () => reserveSpaceWhenHiddenEnabled.value && !shouldShowField.value, ); const shouldShowLabelText = computed(() => !props.config.hide_display); @@ -235,7 +238,7 @@ const fieldtypeComponentEvents = computed(() => ({ :shouldShowField="shouldShowField" > config, 'reserve_space_when_hidden', false); + return Arr::get($this->config, 'reserve_space_when_hidden', false); } public function rules() diff --git a/tests/Fields/FieldTest.php b/tests/Fields/FieldTest.php index b77792e1245..38f3361593f 100644 --- a/tests/Fields/FieldTest.php +++ b/tests/Fields/FieldTest.php @@ -358,6 +358,28 @@ public function preProcess($data) ], $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() {