Svelte

createPicker, use:attach, and use:portal.

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

$ npm install loopem
Gallery.sveltehtml
<script>
import { createPicker } from 'loopem/svelte'

let { photos } = $props()

const picker = createPicker({
  items: photos,
  layout: 'arc',
  radius: 900,
})

const { attach, portal, slots, selectedItem } = picker
</script>

<img src={$selectedItem?.full} alt="" />

<div class="picker" use:attach>
  {#each $slots as slot (slot.key)}
    <button class="thumb" use:portal={slot.element}>
      <img src={photos[slot.index].thumb} alt="" />
    </button>
  {/each}
</div>

use:attach on the container, use:portal={slot.element} on whatever should live inside a slot.

What you get back

attach · portal · slots · activeIndex · activeItem · selectedIndex · selectedItem · scrollTo · next · previous · select · seek · update · play · pause

Everything reactive is a store, so read it with $. Svelte is the one adapter with update(items), because its options are read once at creation.

Two-way binding

Svelte needs no extra option for this. initialIndex sets the start, and two effects keep it in step:

html
<script>
let { selected, onSelect } = $props()

const picker = createPicker({
  items: photos,
  // svelte-ignore state_referenced_locally
  initialIndex: selected,
})

const { selectedIndex } = picker

$effect(() => picker.select(selected))
$effect(() => onSelect($selectedIndex))
</script>

That is safe here because Svelte’s stores settle synchronously, so neither direction ever sees a stale index. React’s do not, which is why it needs an onSelect option and this does not.

Working code: the Svelte gallery.