diff --git a/CHANGELOG.md b/CHANGELOG.md index 3adee70813c..2a7bc9ff362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - Fixed a bug where pressing Ctrl/Cmd + S in a slideout could also save the page behind it. ([#19705](https://github.com/craftcms/cms/pull/19705)) - Fixed a bug where Shift-selecting a shorter range didn’t deselect items outside the new range. ([#19727](https://github.com/craftcms/cms/pull/19727)) - Fixed a bug where select inputs could lose their selected value if their options were loaded asynchronously. ([#19728](https://github.com/craftcms/cms/pull/19728)) +- Fixed a bug where reactive controls in element editors didn’t refresh their field layouts, including nested form scopes. ([#19769](https://github.com/craftcms/cms/pull/19769)) ### Assets - Migrated Control Panel uploads to the native Uppy picker and shared upload sessions, including user photos. diff --git a/resources/js/modules/elements/components/ElementEditor.vue b/resources/js/modules/elements/components/ElementEditor.vue index 33872602d8e..49faaec31ac 100644 --- a/resources/js/modules/elements/components/ElementEditor.vue +++ b/resources/js/modules/elements/components/ElementEditor.vue @@ -59,6 +59,7 @@ renderer, refreshAfterNestedChange, refreshForm, + refreshLayout, save, sidebarErrors, sidebarPayload, @@ -383,6 +384,7 @@ ref="renderer" :payload="formPayload" :errors="errors" + :refresh="formPayload.refreshable ? refreshLayout : undefined" :modified="autosave.modified.value" :disabled="workflowReviewLocked" @update:mutation="onMutation" diff --git a/resources/js/modules/elements/composables/useElementEditor.ts b/resources/js/modules/elements/composables/useElementEditor.ts index 2e5847196e8..d31d03c159d 100644 --- a/resources/js/modules/elements/composables/useElementEditor.ts +++ b/resources/js/modules/elements/composables/useElementEditor.ts @@ -508,10 +508,14 @@ export function useElementEditor({saveData, root}: Options = {}) { return; } - applyingSavedPayload = true; - savedForm.value = response.form; - await nextTick(); - applyingSavedPayload = false; + // A nested scope's payload is only that form; the renderer that asked for + // it reconciles it into the layout. + if (JSON.stringify(scope) === JSON.stringify(rootScope)) { + applyingSavedPayload = true; + savedForm.value = response.form; + await nextTick(); + applyingSavedPayload = false; + } if (generation !== refreshGeneration) { return; @@ -522,6 +526,22 @@ export function useElementEditor({saveData, root}: Options = {}) { return response; } + /** + * Refreshes the field layout for the renderer's reactive controls. + */ + async function refreshLayout( + _values: FormValues, + scope?: string[] + ): Promise { + const response = await refreshForm(scope); + + if (!response) { + throw new Error('A newer refresh superseded this one.'); + } + + return response.form as FormPayload; + } + // Set for the duration of one submission when an alternate action owns it, // so the shared save pipeline (elevated sessions, error handling, the // processing flag) is reused rather than reimplemented per action. @@ -899,6 +919,7 @@ export function useElementEditor({saveData, root}: Options = {}) { renderer, refreshAfterNestedChange, refreshForm, + refreshLayout, save, sidebarErrors, sidebarPayload,