Surface / LiveComponent - Handling an event in a component and then propagating it

I have a component which accepts an event as a prop. I want to trigger that event from within the handle_event of the component. Sometimes it will be triggered by JavaScript events, but sometimes it would be triggered from code.

For example, the parent is managing the state of various modal forms to ensure only one can be open at a time. It passes an event to its children to use to know when the modal should close (because it was closed via button click, escape, or successful form submission). The forms need to do cleanup in the case of cancel (clear the form), or in the case of submit (save the form and clear) prior to then sending this event to the parent component.

Code example:

defmodule ModalForm do
  use Surface.LiveComponent

  # This event closes the modal on clicks on the cancel,
  # close buttons, but the modal also needs to close
  # when the data gets saved.
  prop close, :event, required: true
  data changeset, :struct

  def handle_event("submit", form_data, socket) do
    # Do some things here, for example, save the data
    save(form_data)
    socket = clear_form(socket)
    
    # Now send the close event to the parent,
    # so we can close the modal.
    # How can we do this?
    pid = self()
    Task.async(fn ->
      # Psuedo-code. Event name and target are in socket.assigns[:close]
      propagate_event(pid, socket.assigns[:close])
    end)

    {:noreply, socket}
  end
end

defmodule ParentComponent do
  use Surface.LiveComponent

  data form, :string

  def handle_event("project-form-close", _, socket) do
    {:noreply, assign(socket, form: nil)}
  end

  def render(assigns) do
    ~H"""
    <ModalForm id="project-form" close="project-form-close" />
    <ModalForm id="another-modal-form" close="another-form-close" />
    """
  end
end

Is it possible to implement propagate_event psuedo-code above?

I realize I could send a regular message and then handle it, but trying to understand whether the existing events, handlers, and props can be used in this way to avoid highly coupling all the messages from different modules.