1. Framework Integration
  2. Svelte (Dynamic)

When Swapy is used in vanilla JavaScript, swapping happens by directly manipulating the DOM. This will not work properly in frameworks (like Svelte) for dynamic use cases, where slots and items can be added or removed by the user.

To make this work, Swapy needs to hand DOM updates over to the framework, by setting manualSwap: true. Each framework has its own way for that. But Swapy provides a few helpers to make it easy to set up.

Enabling manualSwap

First step is enabling manualSwap from the configs object.

<script>
  onMount(() => {
    swapy.value = createSwapy(container, {
      manualSwap: true
    })
  })

  onDestroy(() => {
    swapy.value?.destroy()
  })
</script>

Updating your template to use SlottedItems

Instead of displaying your data directly in the template, you need to wrap them with slottedItems (which we’ll see how to create later in this guide).

So if this is your template (let’s say you’re displaying an array of users):

<template>
  <div bind:this={container}>

    <div class="users">
      {#each users as user}
        {#key user.userId}
          <div
            class="slot"
            data-swapy-slot={user.userId}
          >

            {#key user.userId}
              <div class="user" data-swapy-item={user.userId}>
                <span>{user.name}</span>

                <button onclick={() => {
                  users = users.filter(u => u.userId !== user.userId)
                }}>Delete</button>
              </div>
            {/key}

          </div>
        {/key}

      {/each}
    </div>

    <button onclick={() => {
      users.push(/*newUser*/)
    }}>Add User</button>

  </div>
</template>

It will become:

<template>
  <div ref="container">

    <div class="users">
      {#each slottedItems as { slotId, itemId, item: user }}

        {#key slotId}
          <div class="slot" data-swapy-slot={slotId}>
            {#key itemId}
              <div class="user" data-swapy-item={itemId}>
                <span>{user.name}</span>

                <button onclick={() => {
                  users = users.filter(u => u.userId !== user.userId)
                }}>Delete</button>
              </div>
            {/key}
          </div>
        {/key}

      {/each}
    </div>

    <button onclick={() => {
      users.push(/*newUser*/)
    }}>Add User</button>

  </div>
</template>

Changes are:

  • Iterate over slottedItems instead of users.
  • Access the user object by destructuring slottedItems parameter.
  • Get access to new data along user: slotId and itemId.
  • Use slotId for the key and data-swapy-slot on the slot element.
  • Use itemId for the key and data-swapy-item on the item element.

Creating a state for the current slotItemMap

SlottedItems will be created based on the current slotItemMap of the Swapy instance. So let’s create a new state for slotItemMap and update it on swap events.

<script>
  import { createSwapy, utils } from 'swapy'

  let slotItemMap = $state(utils.initSlotItemMap(users, 'userId')) 
  // ...
  onMount(() => {
    swapy = createSwapy(container, {
      manualSwap: true
    })
    swapy.onSwap((event) => { 
      requestAnimationFrame(() => { 
        slotItemMap = event.newSlotItemMap.asArray 
      }) 
    }) 
  })
</script>

To initialize slotItemMap, we used Swapy’s helper function, utils.initSlotItemMap. It takes two parameters: your data array (e.g. users), and the name of the id field in your data (e.g. userId).

Creating SlottedItems

SlottedItems is a computed array based on the current slotItemMap. In Svelte, we can create computed values using $dervied.

<script>
  let slotItemMap = $state(utils.initSlotItemMap(users, 'userId'))
  let slottedItems = $derived(utils.toSlottedItems(users, 'userId', slotItemMap)) 
  // ...
</script>

We also used a helper function for that, utils.toSlottedItems.

Updating Swapy’s instance on add and remove

We can update the Swapy’s instance using swapy.update(). But we also need to update the current slotItemMap along with that. To save you all the work, there’s a helper function for that, utils.dynamicSwapy.

You need to use it in $effect, like this:

<script>
  let slotItemMap = $state(utils.initSlotItemMap(users, 'userId'))
  let slottedItems = $derived(utils.toSlottedItems(users, 'userId', slotItemMap))
  $effect(() => { 
    utils.dynamicSwapy( 
      swapy, 
      users, 
      'userId', 
      untrack(() => slotItemMap), 
      setSlotItemMap 
    ) 
  }) 
  // ...
</script>

Demo

1
2
3
+