Vue

usePicker, Teleport, and two-way binding.

The adapter is a subpath of the same package. Nothing extra to install.

$ npm install loopem
Gallery.vuehtml
<script setup>
import { usePicker } from 'loopem/vue'

const props = defineProps(['photos'])

const { container, slots, selectedItem } = usePicker({
  items: () => props.photos,
  layout: 'arc',
  radius: 900,
})
</script>

<template>
  <img :src="selectedItem?.full" alt="" />

  <div class="picker" :ref="container">
    <Teleport v-for="slot in slots" :key="slot.key" :to="slot.element">
      <button class="thumb">
        <img :src="photos[slot.index].thumb" alt="" />
      </button>
    </Teleport>
  </div>
</template>

items takes a MaybeRefOrGetter, so a ref or a getter both work and the track follows the list. There is no update.

What you get back

container · slots · activeIndex · activeItem · selectedIndex · selectedItem · scrollTo · next · previous · select · seek · play · pause

Everything reactive is a ref or computed, so it works in a template unwrapped.

Two-way binding

ts
const chosen = ref(0)

const { container, slots } = usePicker({
  items: () => props.dates,
  selectedIndex: () => chosen.value,
  onSelect: (index) => (chosen.value = index),
})

Pass selectedIndex as a getter, not a plain number, or the track reads it once at setup and stops following. onSelect fires from the picker’s own event rather than from a watcher, which is what keeps it from reporting a stale index back over the value you just set.

Working code: the Vue gallery.