diff --git a/resources/js/common/slideouts/useElementEditor.ts b/resources/js/common/slideouts/useElementEditor.ts index 85fbeed48d3..9ac67f3088d 100644 --- a/resources/js/common/slideouts/useElementEditor.ts +++ b/resources/js/common/slideouts/useElementEditor.ts @@ -445,7 +445,8 @@ export function useElementEditor( panel.nestedOwnerId = await panel.prepareNestedOwner(); } - if (panel?.nestedOwnerId) { + // Element types without drafts (e.g. variants) save in place. + if (panel?.nestedOwnerId && instance.settings.canCreateDrafts) { if (!instance.settings.draftId) { await instance.saveDraft(); } diff --git a/resources/js/modules/elements/composables/useElementEditor.test.ts b/resources/js/modules/elements/composables/useElementEditor.test.ts index ddcf9bc1355..0d620574f5f 100644 --- a/resources/js/modules/elements/composables/useElementEditor.test.ts +++ b/resources/js/modules/elements/composables/useElementEditor.test.ts @@ -1937,6 +1937,32 @@ describe('useElementEditor', () => { }); }); + it('saves draft-less elements in place when the opener prepares an owner draft', async () => { + const slideout = handledSlideout(); + const prepareNestedOwner = vi.fn().mockResolvedValue(73); + Object.assign(slideout.instance, {prepareNestedOwner}); + const request = stubSaveRequest(() => Promise.resolve({data: {}})); + const {editor} = mount( + payload({ + canAutosave: false, + nestedContext, + saveForDerivativeUrl: + '/actions/elements/save-nested-element-for-derivative', + }), + slideout + ); + + editor.save(); + + await vi.waitFor(() => expect(request).toHaveBeenCalledOnce()); + expect(prepareNestedOwner).toHaveBeenCalledOnce(); + expect(postSpy).not.toHaveBeenCalled(); + expect(request.mock.calls[0]![0]).toMatchObject({ + url: '/actions/entries/save-entry', + data: expect.not.objectContaining({newOwnerId: expect.anything()}), + }); + }); + it('announces invalid nested elements when a save fails', async () => { const displayError = vi.fn(); vi.stubGlobal('Craft', {cp: {displayError}}); diff --git a/resources/js/modules/elements/composables/useElementEditor.ts b/resources/js/modules/elements/composables/useElementEditor.ts index c20a546df4a..0f5afcd58ad 100644 --- a/resources/js/modules/elements/composables/useElementEditor.ts +++ b/resources/js/modules/elements/composables/useElementEditor.ts @@ -671,7 +671,8 @@ export function useElementEditor({saveData, root, transform}: Options = {}) { try { const ownerId = await slideout!.instance.prepareNestedOwner!(); - if (!ownerId) { + // Element types without drafts (e.g. variants) save in place. + if (!ownerId || !props.canAutosave) { return true; } diff --git a/resources/js/modules/forms/nested-elements/NestedElementsControl.test.ts b/resources/js/modules/forms/nested-elements/NestedElementsControl.test.ts index 0d004f0d031..a38c33e7e99 100644 --- a/resources/js/modules/forms/nested-elements/NestedElementsControl.test.ts +++ b/resources/js/modules/forms/nested-elements/NestedElementsControl.test.ts @@ -163,6 +163,7 @@ describe('NestedElementsControl', () => { ) { const legacyElementEditor = { settings: { + canCreateDrafts: true, draftId: 6, saveParams: null as Record | null, }, @@ -590,6 +591,33 @@ describe('NestedElementsControl', () => { }); }); + it('saves draft-less elements in place in the legacy editor', async () => { + const {createElementEditor, legacyElementEditor} = mount({ + vueSlideout: false, + cards: [ + nestedElement({ + id: 18, + siteId: 1, + ownerId: 31, + editUrl: + '/admin/actions/elements/edit?elementId=18&siteId=1&fieldId=7&ownerId=31', + cardAttributes: {data: {editable: true}}, + }), + ], + }); + legacyElementEditor.settings.canCreateDrafts = false; + legacyElementEditor.settings.draftId = null as unknown as number; + await nextTick(); + + root.querySelector('a[href]')!.click(); + await vi.waitFor(() => expect(createElementEditor).toHaveBeenCalledOnce()); + + const settings = createElementEditor.mock.lastCall![1]; + await expect(settings.onBeforeSubmit()).resolves.toBeUndefined(); + expect(legacyElementEditor.saveDraft).not.toHaveBeenCalled(); + expect(legacyElementEditor.settings.saveParams).toBeNull(); + }); + it('opens the entry’s own edit page in a new tab on a modified click', async () => { const open = vi.spyOn(window, 'open').mockReturnValue(null); mount({ diff --git a/resources/js/modules/forms/nested-elements/useNestedElementOperations.ts b/resources/js/modules/forms/nested-elements/useNestedElementOperations.ts index db0e7a9d70b..43b86d5378c 100644 --- a/resources/js/modules/forms/nested-elements/useNestedElementOperations.ts +++ b/resources/js/modules/forms/nested-elements/useNestedElementOperations.ts @@ -43,6 +43,7 @@ export interface LegacyElementEditorSlideout { ): void; elementEditor: { settings: { + canCreateDrafts?: boolean; draftId?: number | null; saveParams?: Record | null; }; @@ -335,7 +336,10 @@ export function useNestedElementOperations( prepareNestedOwner ? async (slideout) => { const preparedOwnerId = await prepareNestedOwner(); - if (!preparedOwnerId) { + if ( + !preparedOwnerId || + !slideout.elementEditor.settings.canCreateDrafts + ) { return; }