From f5294c75cd0c6667095589abd39b01211d3fdedf Mon Sep 17 00:00:00 2001 From: Rias Date: Thu, 1 Oct 2026 10:09:28 +0200 Subject: [PATCH 1/2] Add reusable server form modals Co-authored-by: Nathaniel Hammond --- CHANGELOG.md | 1 + .../composables/useElementActionMenu.ts | 18 +++ resources/js/modules/forms/FormModal.vue | 111 ++++++++++++++++++ resources/js/modules/forms/open-form-modal.ts | 60 ++++++++++ 4 files changed, 190 insertions(+) create mode 100644 resources/js/modules/forms/FormModal.vue create mode 100644 resources/js/modules/forms/open-form-modal.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3adee70813c..f7b0eebcb0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Improved the styling of control panel tables and lightswitches. ([#19766](https://github.com/craftcms/cms/pull/19766)) - Improved disabled secondary form actions with explanatory tooltips. - The “Assets” system settings nav item is now listed under “Content”, and “Globals”, “Categories”, and “Tags” are now listed under a new “Deprecated” heading, if those concepts are supported for the project. ([#19670](https://github.com/craftcms/cms/pull/19670)) +- Added reusable server-built form modals and the `formModal` element action behavior. - Added the `` web component. ([#19629](https://github.com/craftcms/cms/pull/19629)) - Added the `placeholder` property to ``, for an image to show while the thumbnail loads. ([#19750](https://github.com/craftcms/cms/pull/19750)) - Added the `--c-thumbnail-image-radius` custom property to ``, and its other custom properties can now be set on an ancestor. ([#19758](https://github.com/craftcms/cms/pull/19758)) diff --git a/resources/js/modules/elements/composables/useElementActionMenu.ts b/resources/js/modules/elements/composables/useElementActionMenu.ts index 825591458e3..fa29f7009c5 100644 --- a/resources/js/modules/elements/composables/useElementActionMenu.ts +++ b/resources/js/modules/elements/composables/useElementActionMenu.ts @@ -7,6 +7,7 @@ import {openSlideout, type SlideoutController} from '@/common/slideouts'; import type {ActionItem} from '@/common/types'; import {ElementDeletionManager} from '@/modules/element-deletion-manager'; import type {FormProperties, FormValues} from '@/modules/forms/types'; +import {openFormModal} from '@/modules/forms/open-form-modal'; import { openImageEditorDialog, type ImageEditorSettings, @@ -52,6 +53,13 @@ export type ElementActionBehavior = url: string; entryTypeFromField?: boolean; } + /** Loads and submits a server-built form, then reloads the page. */ + | { + type: 'formModal'; + modalUrl: string; + actionUrl: string; + params?: FormValues; + } // The asset behaviors below all hand off to a modal or uploader, and reload // the page afterwards rather than patching the file's details into it. | { @@ -200,6 +208,16 @@ export function createElementActionMenu({ return; } + case 'formModal': + void openFormModal({ + modalUrl: behavior.modalUrl, + actionUrl: behavior.actionUrl, + params: behavior.params, + onSubmitted: () => router.reload(), + }); + + return; + case 'previewFile': new Craft.PreviewFileModal(behavior.assetId, behavior.settings ?? {}); diff --git a/resources/js/modules/forms/FormModal.vue b/resources/js/modules/forms/FormModal.vue new file mode 100644 index 00000000000..951f1e77ec6 --- /dev/null +++ b/resources/js/modules/forms/FormModal.vue @@ -0,0 +1,111 @@ + + + diff --git a/resources/js/modules/forms/open-form-modal.ts b/resources/js/modules/forms/open-form-modal.ts new file mode 100644 index 00000000000..1b9992b5e3b --- /dev/null +++ b/resources/js/modules/forms/open-form-modal.ts @@ -0,0 +1,60 @@ +import type {App} from 'vue'; +import type {FormValues} from './types'; + +export interface FormModalOptions { + modalUrl: string; + actionUrl: string; + params?: FormValues; + title?: string; + submitLabel?: string; + width?: string; + onSubmitted?: (data: Record) => void; +} + +/** + * Opens a server-built form in a detached Vue app with registered CP controls. + * The app, host, and registry installation are removed on close or submission. + */ +export async function openFormModal({ + onSubmitted, + ...props +}: FormModalOptions): Promise { + const [{createApp, h}, {default: FormModal}, {cpComponentRegistry}] = + await Promise.all([ + import('vue'), + import('./FormModal.vue'), + import('@/bootstrap/components'), + ]); + + const host = document.createElement('div'); + document.body.append(host); + + let closed = false; + const close = () => { + if (closed) { + return; + } + + closed = true; + app.unmount(); + host.remove(); + }; + + const app: App = createApp({ + render: () => + h(FormModal, { + ...props, + onClose: close, + onSubmitted: (data: Record) => { + close(); + onSubmitted?.(data); + }, + }), + }); + + cpComponentRegistry.install(app); + app.onUnmount(() => cpComponentRegistry.uninstall(app)); + app.config.compilerOptions.isCustomElement = (tag: string) => + tag.includes('-'); + app.mount(host); +} From a910be6e3874da3183a3eaf24647fefeb3ee1bc8 Mon Sep 17 00:00:00 2001 From: Rias Date: Thu, 1 Oct 2026 10:09:46 +0200 Subject: [PATCH 2/2] Reference server form modal PR in changelog Co-authored-by: Nathaniel Hammond --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7b0eebcb0c..389b1501600 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ - Improved the styling of control panel tables and lightswitches. ([#19766](https://github.com/craftcms/cms/pull/19766)) - Improved disabled secondary form actions with explanatory tooltips. - The “Assets” system settings nav item is now listed under “Content”, and “Globals”, “Categories”, and “Tags” are now listed under a new “Deprecated” heading, if those concepts are supported for the project. ([#19670](https://github.com/craftcms/cms/pull/19670)) -- Added reusable server-built form modals and the `formModal` element action behavior. +- Added reusable server-built form modals and the `formModal` element action behavior. ([#19768](https://github.com/craftcms/cms/pull/19768)) - Added the `` web component. ([#19629](https://github.com/craftcms/cms/pull/19629)) - Added the `placeholder` property to ``, for an image to show while the thumbnail loads. ([#19750](https://github.com/craftcms/cms/pull/19750)) - Added the `--c-thumbnail-image-radius` custom property to ``, and its other custom properties can now be set on an ancestor. ([#19758](https://github.com/craftcms/cms/pull/19758))