symfony / ux

Symfony UX initiative: a JavaScript ecosystem for Symfony
https://ux.symfony.com/
MIT License
852 stars 313 forks source link

[Vue] Impossible to add slots when using vue_component() in twig?? #582

Open thomasmikkel opened 1 year ago

thomasmikkel commented 1 year ago

When using Vue 2 i Symfony, I used to add slots this way:

// Twig file
<div>
    <HeaderComponent>
        <template v-slot:content>
          <p>Some content...</p>
        </template>
    </HeaderComponent>
</div>

By adding vue components as suggested in Symfony UX Vue.js (supporting only Vue 3), how can I add slots now?

This doesn't seem to work:

// Twig file 
<div {{ vue_component('HeaderComponent') }}>
    <template v-slot:content>
      <p>Some content...</p>
    </template>
</div>
// In HeaderComponent.vue
<template>
    <header class="site-header">
        <slot name="content"></slot>
        ...
        ...
<template>
weaverryan commented 1 year ago

Hmm. I would have "guessed" that this would have already worked, though I am far from an expert on Vue+slots, etc. The code used to initialize the Vue component is very simple:

https://github.com/symfony/ux/blob/25b76357535f72f5d798e537decb55d812428220/src/Vue/assets/src/render_controller.ts#L46

So, we're passing the actual HTMLElement to Vue (in this case the <div ...> element from Twig). I had thought this would be enough for Vue to use the innerHTML. The documentation seems to suggest that: https://vuejs.org/guide/essentials/application.html#mounting-the-app

Does anyone know why this wouldn't work?

jdevinemt commented 1 year ago

So, we're passing the actual HTMLElement to Vue (in this case the <div ...> element from Twig). I had thought this would be enough for Vue to use the innerHTML. The documentation seems to suggest that: https://vuejs.org/guide/essentials/application.html#mounting-the-app

Does anyone know why this wouldn't work?

@weaverryan The issue is that because the root component for the Vue app that is being created has a template it will replace the DOM nodes inside the mount container. This is detailed in the Vue 3 documentation.

If the component has a template or a render function defined, it will replace any existing DOM nodes inside the container. Otherwise, if the runtime compiler is available, the innerHTML of the container will be used as the template.

The 'otherwise' part, which would handle the slots correctly, doesn't run because the first condition is true.

One possible, but potentially less eloquent solution would be to create an empty Vue app, register the specified component, and insert it into the mount container, and move the innerHtml of the mount container to the inserted component. So...

<div id="mount-container" {{ vue_component('ComponentName') }}>
   <p>I want to be slotted into the component!</p>
</div>

would be rendered as something like this...

<div id="mount-container">
   <app-component-name> <!-- would also need to pass props here -->
      <p>I want to be slotted into the component!</p>
   </app-component-name>
</div>

where app-component-name has been registered in the render controller with using something like app.component('app-component-name', component).

This would probably have to be an optional method of mounting the app since I believe this would only work when the runtime compiler is enabled.

It's probably best not to use slotted content with this UX package in general, but it would be a nice feature to have anyway.

carsonbot commented 6 months ago

Hey, thanks for your report! There has not been a lot of activity here for a while. Is this bug still relevant? Have you managed to find a workaround?

arturkarczmarczyk commented 6 months ago

Hey! I think the bug is still relevant. I faced it today. I rewrote the component to not use slots at this time, but it would be really amazing if there was a way to pass a default and/or named slots to a component initialized using vue_component().

carsonbot commented 5 days ago

Hey, thanks for your report! There has not been a lot of activity here for a while. Is this bug still relevant? Have you managed to find a workaround?