ElMassimo / iles

🏝 The joyful site generator

Home Page:https://iles.pages.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unable to partially hydrate components with scoped slots

ohuu opened this issue · comments

I've created a toy example to illustrate this issue. It's possible that I'm trying to use Iles incorrectly, apologies if that is the case.

Here is a simple component which exposes a scoped slot

<script lang="ts" setup>
const props = defineProps<{
  items: { data: string }[];
}>();
</script>

<template>
  <div v-for="item in items">
    <slot :item="item" />
  </div>
</template>

I use it within the index page like so:

<script lang="ts" setup>
const items = [{ data: "A" }, { data: "B" }];
</script>

<template>
  <my-component :items="items" client:only>
    <template #default="{ item }">
      <span>{{ item.data }}</span>
    </template>
  </my-component>
</template>

This results in the following error:

image

When I remove client:only the component works as expected.

Hi Oliver!

Scoped slots are not currently supported (some frameworks like Preact don't have a similar concept).

Because of the nature of scoped slots in Vue, the outer component would also need to be shipped client side in order for this to work.

Something that could be potentially supported is to allow pre-rendering scoped slots, but making them static at build time.

If you could share your use case (non toy example), it will help me to understand whether this enhancement is worth it, or whether it's a matter of documenting this better.

Thanks for the quick reply @ElMassimo. So, is this something we could support for Vue only without it affecting Preact or other frameworks? Or is there some fundamental reason why scoped slots can't be dynamic?

Because of the nature of scoped slots in Vue, the outer component would also need to be shipped client side in order for this to work.

That makes sense, isn't this something we could make iles js do?

Something that could be potentially supported is to allow pre-rendering scoped slots, but making them static at build time.

Not entirely sure what you mean by this.

Here's my non-toy example. It's basically a very simple gallery component that I want to reuse in a couple places on the site and style them quite differently.

<script setup lang="ts">
import { computed, ref, watchEffect } from "vue";

const props = withDefaults(
  defineProps<{ 
    items: (ProductFragment | ProductSummaryFragment)[]; 
    itemsToShow: 1 | 2 | 3; 
  }>(), { itemsToShow: 1 }
);

const colWidth = computed(() => {
  switch (props.itemsToShow) {
    case 1:
      return "w-full";
    case 2:
      return "w-1/2";
    case 3:
      return "w-1/3";
  }
});
const page = ref(0);
const totalPages = computed(() => props.items.length - props.itemsToShow);

const slider = ref<HTMLDivElement>();

function slide(dPage: number) {
  if (page.value + dPage >= 0 && page.value + dPage <= totalPages.value) {
    page.value += dPage;
  }
}

watchEffect(() => {
  if (slider.value) {
    const slideWidth = Math.round(slider.value.clientWidth / props.itemsToShow);
    const x = page.value * slideWidth;
    slider.value.style.transform = `translateX(-${x}px)`;
  }
});
</script>

<template>
  <div class="relative overflow-x-hidden whitespace-nowrap w-full">
    <div ref="slider" class="transition-transform">
      <div v-for="item in items" class="inline-block align-top" :class="colWidth">
        <slot name="gallery-item" :item="item" />
      </div>
    </div>

    <slot name="buttons" :slide="slide">
      <div class="absolute -translate-y-1/2 top-1/2 w-full">
        <button @click="slide(-1)" class="btn btn-primary btn-circle box-shadow-none">◀</button>
        <button @click="slide(+1)" class="btn btn-primary btn-circle box-shadow-none">▶</button>
      </div>
    </slot>
  </div>
</template>

One place I'm using this component is in the index page to display featured products. Like this:

<script lang="ts" setup>
...

const props = defineProps<{ products: ProductSummaryFragment[] }>();

...
</script>

<template>
  ...

  <d-carousel :items="products" :items-to-show="3">
    <template #gallery-item="{ item }">
      <div class="bg-white whitespace-normal w-full h-full p-4">
        <a class="link link-primary link-hover text-lg" href="">
          {{ item.name }}
        </a>
        <img class="mx-auto my-4 h-32" :src="item.thumbnail?.url" />
        <div v-html="shortDesc(item)" class="font-light" />
        <a class="absolute bottom-0 link link-primary link-hover align-baseline w-full">
          MORE INFO >
        </a>
      </div>
    </template>

    <template #buttons="{ slide }">
      <div class="absolute top-1/2 -translate-y-6 w-full">
        <button class="btn btn-primary btn-circle btn-sm box-shadow-none float-left !px-3" @click="slide(-1)">

        </button>
        <button
          class="btn btn-primary btn-circle btn-sm box-shadow-none float-right !px-3"
          @click="slide(+1)"
        >

        </button>
      </div>
    </template>
  </d-carousel>

  ...
</template>

For the time being I can just create separate components but it would be totally awesome if we could make Iles support scoped slots. They are a pretty useful thing in Vue.