Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Vue Transition with Combobox #821

Closed
nethriis opened this issue Apr 4, 2024 · 18 comments
Closed

[Bug]: Vue Transition with Combobox #821

nethriis opened this issue Apr 4, 2024 · 18 comments
Labels
bug Something isn't working

Comments

@nethriis
Copy link

nethriis commented Apr 4, 2024

Environment

Developement/Production OS: Windows 11
Node version: 20.10.0
Package manager: pnpm@8.15.4
Radix Vue version: 1.6.1
Vue version: 3.4.21
Nuxt version: 3.11.1
Nuxt mode: universal
Nuxt target: server
CSS framework: tailwindcss@3.4.3
Client OS: Windows 11
Browser: Firefox

Link to minimal reproduction

https://stackblitz.com/edit/nuxt-starter-chghww

Steps to reproduce

Try to open the combobox

Describe the bug

Hello everyone ! I've tried to make a combobox with an appear animation of the popper but I have the error:

[Vue warn]: Component inside <Transition> renders non-element root node that cannot be animated. 
  at <Teleport disabled=false forceMount=true to=undefined > 
  at <ComboboxPortal forceMount="" > 
  at <BaseTransition appear=false persisted=false mode=undefined  ... > 
  at <Transition name="fade" > 
  at <Primitive ref=fn<s> style= 
Object { pointerEvents: undefined }
 as=undefined  ... > 
  at <PopperRoot class="w-[200px]" > 
  at <ComboboxRoot modelValue="" onUpdate:modelValue=fn class="w-[200px]" > 
  at <Combobox modelValue="" onUpdate:modelValue=fn options= 
Array(3) [ "Option 1", "Option 2", "Option 3" ]
  ... > 
  at <App key=3 > 
  at <NuxtRoot>

Here is the layout of my component :

<ComboboxRoot>
  <ComboboxAnchor>
    <ComboboxTrigger>
      ...
    </ComboboxTrigger>
  </ComboboxAnchor>
    
  <Transition name="fade">
    <ComboboxPortal forceMount>
      <ComboboxContent position="popper">
    <ComboboxViewport>
      <div>
        <ComboboxInput />
      </div>
    <ComboboxGroup>
      <ComboboxItem>
        <span>...</span>
        <ComboboxItemIndicator>
            </ComboboxItemIndicator>
          </ComboboxItem>
        </ComboboxGroup>

        <ComboboxEmpty />
        </ComboboxViewport>
      </ComboboxContent>
    </ComboboxPortal>
  </Transition>
</ComboboxRoot>

Expected behavior

A fade animation

Context & Screenshots (if applicable)

No response

@nethriis nethriis added the bug Something isn't working label Apr 4, 2024
@sadeghbarati
Copy link
Collaborator

sadeghbarati commented Apr 4, 2024

Kinda related issue with Transition and Teleport (Portal) 😅

unovue/shadcn-vue#440

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

Related issue with Transition and Teleport (Portal)

radix-vue/shadcn-vue#440

no

@ChrisGV04
Copy link
Contributor

Hi @nethriis! Can you please try wrapping the Transition on the ComboboxContent instead of the ComboboxPortal?

Usually the transition must be applied to the HTML element that will be animated, but in this case the ComboboxPortal is a Teleport so it doesn't render an actual HTML element.

I have done it like this and it does seem to be working.

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

@ChrisGV04 Thanks for your response I tried it and I receive the same error :

[Vue warn]: Component inside <Transition> renders non-element root node that cannot be animated. 
  at <PrimitiveSlot ref=Ref< undefined > class="border border-gray-300 rounded-md bg-white shadow-md w-[--radix-combobox-trigger-width] z-10" > 
  at <CollectionSlot class="border border-gray-300 rounded-md bg-white shadow-md w-[--radix-combobox-trigger-width] z-10" > 
  at <ComboboxContentImpl position="popper" onEscapeKeyDown=fn onPointerDownOutside=fn  ... > 
  at <Presence present=true class="border border-gray-300 rounded-md bg-white shadow-md w-[--radix-combobox-trigger-width] z-10" > 
  at <ComboboxContent position="popper" class="border border-gray-300 rounded-md bg-white shadow-md w-[--radix-combobox-trigger-width] z-10" > 
  at <BaseTransition appear=false persisted=false mode=undefined  ... > 
  at <Transition name="fade" > 
  at <Teleport disabled=false forceMount=true to=undefined > 
  at <ComboboxPortal forceMount="" > 
  at <Primitive ref=fn<s> style= 
Object { pointerEvents: "auto" }
 as=undefined  ... > 
  at <PopperRoot class="w-[200px]" > 
  at <ComboboxRoot modelValue="" onUpdate:modelValue=fn class="w-[200px]" > 
  at <Combobox modelValue="" onUpdate:modelValue=fn options= 
Array(4) [ "Nuxt", "Vue", "Svelte", "Tauri" ]
  ... > 
  at <App key=3 > 
  at <NuxtRoot>

But only when the Combobox popper shows

@ChrisGV04
Copy link
Contributor

I see. Let me give it a try on a fresh demo

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

Here is minimal repro :
https://stackblitz.com/edit/nuxt-starter-chghww

@ChrisGV04
Copy link
Contributor

Okay, so there are a few things to consider to make it work:

  1. Upgrade to radix-vue@1.6.2 because v1.6.1 had broken transitions (fix: slot prevents out transition for non-modal dropdown and select components #801)
  2. When using the Transition on the ComboboxContent, you don't actually need forceMount. It should work just fine.
  3. In the minimal repro, the animation classes are wrong. It should be .fade-enter-from but it just says .fade-enter so enter animation doesn't work.

I got it working by updating Radix Vue to v1.6.2 and the file looking like this:

<template>
  <div>
    <ComboboxRoot v-model="selected">
      <ComboboxAnchor>
        <ComboboxTrigger>
          <span v-if="selected.length === 0">{{ placeholder }}</span>
          <span v-else>{{ selected }}</span>
          <Icon name="radix-icons:caret-sort" />
        </ComboboxTrigger>
      </ComboboxAnchor>

      <div>
        <ComboboxPortal>
          <Transition name="fade">
            <ComboboxContent position="popper">
              <ComboboxViewport class="flex flex-col">
                <div>
                  <Icon name="radix-icons:magnifying-glass" />
                  <ComboboxInput
                    :placeholder="placeholder ? placeholder : 'Search...'"
                  />
                </div>
                <ComboboxGroup>
                  <ComboboxItem
                    v-for="(option, item) of options"
                    :key="item"
                    :value="option"
                  >
                    <span>{{ option }}</span>
                    <ComboboxItemIndicator>
                      <Icon name="radix-icons:check" />
                    </ComboboxItemIndicator>
                  </ComboboxItem>
                </ComboboxGroup>

                <ComboboxEmpty />
              </ComboboxViewport>
            </ComboboxContent>
          </Transition>
        </ComboboxPortal>
      </div>
    </ComboboxRoot>
  </div>
</template>

<script setup lang="ts">
const selected = ref("");
const placeholder = ref("Placeholder...");
const options = ref(["Nuxt", "Vue", "Svelte", "Tauri"]);
</script>

<style>
div[data-radix-popper-content-wrapper] {
  top: 4px !important;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}

.fade-enter-from, .fade-leave-to {
  opacity: 0;
}
</style>

Also, it might be irrelevant for this particular issue, but when you want to use the Radix Vue module for Nuxt, it should be:

- modules: ["radix-vue"]
+ modules: ["radix-vue/nuxt"]

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

@ChrisGV04 It's weird now I have updated to 1.6.2, I have an hydration mismatch error :

[Vue warn]: Hydration node mismatch:
- rendered on server: 
<div class="w-[inherit]"> <empty string> 
- expected on client: Symbol("v-fgt") 
  at <PrimitiveSlot class="w-[inherit]" > 
  at <Primitive ref=fn<s> as=undefined as-child=true  ... > 
  at <PopperAnchor as-child="" class="w-[inherit]" > 
  at <ComboboxAnchor class="w-[inherit]" > 
  at <Primitive ref=fn<s> style= 
Object { pointerEvents: undefined }
 as=undefined  ... > 
  at <PopperRoot class="w-[200px]" > 
  at <ComboboxRoot modelValue="" onUpdate:modelValue=fn class="w-[200px]" > 
  at <Combobox modelValue="" onUpdate:modelValue=fn options= 
Array(4) [ "Nuxt", "Vue", "Svelte", "Tauri" ]
  ... > 
  at <App key=3 > 
  at <NuxtRoot>

@ChrisGV04
Copy link
Contributor

I wasn't able to reproduce the hydration mismatch on the minimal repro with the corrections applied. Did you try the 3 suggestions to get it working?

Also, you must remove forceMount from the ComboboxPortal.

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

Yes I've done all 3 suggestions, it works on the stackblitz but not in my code. Idk why.

@ChrisGV04
Copy link
Contributor

Oh I see. Do you mind sharing the file that doesn't work?

And even with the hydration mismatch, does the animation work?

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

No the animation doesn't work and the popper not appear anymore.
Here is the file :

<script setup lang="ts">
import {
  ComboboxAnchor,
  ComboboxContent,
  ComboboxEmpty,
  ComboboxGroup,
  ComboboxInput,
  ComboboxItem,
  ComboboxItemIndicator,
  ComboboxPortal,
  ComboboxViewport,
  ComboboxTrigger,
  ComboboxRoot
} from 'radix-vue'

const selected = defineModel<string>({
  required: true
})

defineProps<{
  id?: string
  options: string[]
  placeholder?: string
	emptyText?: string
}>()
</script>

<template>
  <ComboboxRoot v-model="selected">
    <ComboboxAnchor class="w-[inherit]">
      <ComboboxTrigger
        class="inline-flex items-center justify-between whitespace-nowrap transition-colors border border-gray-300 rounded-md hover:bg-gray-100 w-full px-2.5 py-1.5"
      >
        <span v-if="selected.length === 0">{{ placeholder }}</span>
        <span v-else>{{ selected }}</span>
        <Icon name="radix-icons:caret-sort" class="w-4 h-4 text-gray-500" />
      </ComboboxTrigger>
    </ComboboxAnchor>

		<div>
			<ComboboxPortal>
				<Transition name="fade">
					<ComboboxContent
						position="popper"
						class="border border-gray-300 rounded-md bg-white shadow-md w-[--radix-combobox-trigger-width] z-10"
					>
						<ComboboxViewport class="flex flex-col">
							<div class="flex items-center space-x-1.5 border-b border-b-gray-200 px-2.5 w-full">
								<Icon
									name="radix-icons:magnifying-glass"
									class="min-w-4 min-h-4 text-gray-500"
								/>
								<ComboboxInput
									:placeholder="placeholder ? placeholder : 'Search...'"
									class="py-1.5 w-full focus:outline-none bg-transparent placeholder:text-gray-500"
								/>
							</div>
							<ComboboxGroup class="p-1">
								<ComboboxItem
									v-for="(option, item) of options"
									:key="item"
									:value="option"
									class="flex items-center justify-between rounded-md px-2 py-1 data-[highlighted]:bg-gray-100 transition-colors cursor-pointer"
								>
									<span>{{ option }}</span>
									<ComboboxItemIndicator class="flex items-center">
										<Icon name="radix-icons:check" class="w-4 h-4" />
									</ComboboxItemIndicator>
								</ComboboxItem>
							</ComboboxGroup>

							<ComboboxEmpty class="p-3">
								{{ emptyText ? emptyText : 'No option' }}
							</ComboboxEmpty>
						</ComboboxViewport>
					</ComboboxContent>
				</Transition>
			</ComboboxPortal>
		</div>
  </ComboboxRoot>
</template>

<style>
div[data-radix-popper-content-wrapper] {
  top: 4px !important;
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter-from, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
  opacity: 0;
}
</style>

@ChrisGV04
Copy link
Contributor

I cannot replicate the hydration mismatch with your file either. Maybe the issue is on the way you used the component, not from Radix Vue.

Do you mind sharing the file where you used this component that's giving you the hydration mismatch? Maybe one of the props is causing the mismatch

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

Here is the file where the component is called:

<script setup lang="ts">
const darkMode = ref(false)
const selectedCombobox = ref('')
const optionsCombobox = ref(['Nuxt', 'Vue', 'Svelte', 'Tauri'])
const accept = ref(false)
const selectedRadio = ref('')
const optionsRadio = ref(['Default', 'Comfortable', 'Compact'])
</script>

<template>
  <div class="p-2 space-y-2">
    Nuxt module playground!
    <div class="flex items-center space-x-2">
      <label for="airplane">Dark mode</label>
      <Switch
        id="airplane"
        offIcon="radix-icons:sun"
        onIcon="radix-icons:moon"
        v-model="darkMode"
      />
    </div>
    <Avatar src="https://avatars.githubusercontent.com/u/72009265?v=4" fallback="DA" />
    <Combobox
      v-model="selectedCombobox"
      :options="optionsCombobox"
      placeholder="Select framework..."
      emptyText="No framework found."
      class="w-[200px]"
    />
    <label class="flex flex-row gap-2 items-center">
      <Checkbox v-model="accept" />
      <span class="select-none">Accept terms and conditions.</span>
    </label>
    <RadioGroup v-model="selectedRadio" :options="optionsRadio" />
  </div>
</template>

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

For your information I develop this in the context of nuxt module idk if this context affect the behavior of radix-vue element.
You said that the error is not due to radix but my code. BUT it worked before the 1.6.2

@ChrisGV04
Copy link
Contributor

That's very strange...

I also have my own Nuxt module using Radix Vue and the combobox doesn't give me hydration mismatches and animations work great. I do get hydration mismatches for the DropdownMenu, but that is normal as of today (See why here: #577)

I added your component to my module and used it in another SSR app and it worked without issues. Even the animations are working.

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

That's very strange I will try to recreate a nuxt module project an retry to see if it works

@nethriis
Copy link
Author

nethriis commented Apr 4, 2024

I've recreate my module from scratch and now it works well, thanks everyone for your help !

@nethriis nethriis closed this as completed Apr 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants