Implementing Drag-and-Drop using Svelte 5
Tags: svelte, webdev, javascript, programming
Published: 2023-11-25 18:18:51

It's been two weeks since Svelte 5 was published on npm as the next version.
During that time, I had the chance to try out Svelte 5 in my free time, and working with runes was quite a fun task.
In this post, I'll introduce drag-and-drop functionality using Svelte 5.
For projects using the CLI, as Dev.to's embed feature doesn't seem to fully support StackBlitz, I'll provide a REPL link instead.
[Svelte 5 preview](https://svelte-5-preview.vercel.app/#H4sIAAAAAAAACs0aDW_bxvWvnNWgkBJRltN1A2hLXpukS7d0KAoDaycJCi2ebLYUyZGUbU3Rf997775PpKw4CDIksMi79969e9_vjtvOMkl51Qkn204WrXgn7HxXFJ1-p94U-FLd8bTm8F7l63KBIxfVokyKmi3yrOYP9WjaWeXxOuXTzniawWAFUyWPaj6v8zifpwkMjFi3x0Zjtp1m0zrlNUtqvqqewfizqgbQLo5P6634mdZxnvGQTdi0cxVVf7B_Tzts1leTSBYm1Sv8E1DfTTv9vcHvmwZfNQ2-bhp80zT4Q9Pg35oG3zYN_tg0-PemwX80Db6bdtTYTDzs8KeHf4T4b3lyc1tXIF0pUCHO0-fP2V9RrWybrVfXvJzMduz5aXcy68HUqVxJSPcRWATdaV1W87iMbm6S7MbS6DJKK05MtdBS6HUZZdUyL1cWMizjYU47uAmwgw9gFMjjtGMTQQbmCGnblETpaUazmD9YAEM9A-9lPW-cV2yUvF6XWcW2d3kSq6WFvHkWkwRcM5_WyZJ1beH0mDZwnHIWPRlJ9iwgRR-dBWgLn5mYrc4GVZEmC-5Q6rOz3mQ4MzTa0RTCsE-0ewpnZ5kV4LvqJb1q_YNo8IeBgIqojFZKxTuxG2-uUYnIk4JzhSxGHUHXvNSiphX6hO9LnWwGRGo23WOr_I7PcZ8OosDhsCdG6sIpxLRF2hM2mtRJnlE8E2C9LyQFvZEDQvhUw5EoCO2ai28tnue4DzL62L6p9gmBxvb7ibMuRi42GAzk2-4LCVrs7RFza95GgxiaJHVQQJ7j1eXa8btmETgb8MxWreo7i97BhKZnXpySLI89n7DCFCCzLkVYbQLn8DyWL_AcBA6CLbZJMgOsoZnc6TjEyC8fX-jCLPTixVMW0qWFo0w0QWtEZVuPq0YDsCR3tOAcA3nBzj52Z4HM-7ajT2yiMxawsycI2mEsEIx9jG4f5-tFC19uItpzHZoQTqw4kGvJYuYGdvBML9u1-JRYhiW5lI1oOWADqu2eTcgYu5rQKKY1IFgSaUCz5dWArEXegGrU4SKqskW_quTatwMWDkhZw3_-UORlDdF5r8SHyYtT0RqMsWy60M_JinBeA6EfMSEty3wFAXlwqkYGosnAutaD_TmNFvxtnsa83EezJpspvIIOJUoyH1cPW1iqc7FbFn-LXSoHqVzkQi5znvIVyA2ArVK1u0gTGPw1ZCIy95kY-M0MIHLI3l799O6NoEAZJco2VGTL8rMnS21XrHFyB8awSTk0XnECqTnahMsUHfH3dQVRZxNQY5bV4YI0ek592bS-cCVyDaYUwj5wVPIw2vr70o6YZzgMBjPaanEMlP3sxgLsAGv4N7gvoyLEP4qlY5DipOQLzGDhIk_Xq-yc3QCVs2HxYBEBMlURZZrOErYZVMl_efiSr4AQvt5TYAivwV5w_aucxTlIFtBsMjY3IpSEg2-BBmBcnMKkBbv9ikeLW5kltFCE309UvTHrg9lh98qiit1B5bSzFtMeQW492tqTtIIm60-0KkoGYLDDkWKBndpMnyLTO3sXkKhs9k1UY19_bc_oUInlsa6nGrZjO63LibUhj6tkqSm5cnY0UkRxDIyFL1Eneyr5Uqb0GorMz2lJooj9opYk6-jPY0mC-JGWJDn5aEsyzxduEhhjgMW5Th8PspJlwuNOiMX2rq-PxBRDR5yLAT3qUpjOB1t6YSxelxGa36XKA3KcdOaN-WoJ2ZOzCvY2kqoWWsh-odrgChi8QC7zJROJswtZcnB6BYDvAO7NQ7QqUq4TZW_gJ8WxIg1EQtbQcdH8boc_pyrLUoWixAHJ80_DIdUcosXCJ3__NKgXFW_UKO3wuKYo86IS6dlK_9F1yt3MjyMNSd-2VKAG1pbc8bjrLOnYM864Jm2VZ2w0skrUNstHILGDnq49VJ12HBOtTmVIo-FftrNp96AeoOZlUPJ4Dc1_twCDI3tasEtWQBuwYCGDwaE-NgobCcieUgCF2H6RnrSPyCb2p3xd8Td3GIEYxx8x63folhHdRlmc8jl4weIPkBgh6c42SjnaMi1qlWaWb1ClRUgDEAoU0VRtXQ7wTPuVqKAQvUdlr7OiKIWgtHLP--zCSFXS3lFTEyVSiaZkiJjq2yPiloPa0GMO6W60_WY4lPFP1GxEZrT1F5QwjxaAImJiLqOCMV8uAeQtpTO7FFQdHp0XSY3rMKyy4HVeglUHZRQn60okQ3afxPVt-Gd8zNd1CgE5HJxBHqvyNInPGSojiNLkJlPVLPMS8mJdVnkZFnkiprXdhfSUQrD6rbvVo7vioXeuGNt-tGORDnD-vTlaCZ9tVSjbrar3O0Peoznt5EW0SOpNOHhpbfel2i77KooiSISGwMleNWALWNBUhU4msui5uR3IM_INrXx62yvpQWqOedCIKum3BxnYiXSqEyuVPGmOV0LUNXZ0ASTUTBo_rFJhFVKpeNAJL3kKz18Nh0PTNxzmi70AuZycyJpCMXihU8BRqd6qPT4p6x-Zcz97yjWMiEjC_Mz55Bgnz0EbNTJIeXZT36q7JZmVRCh0A9lJEDCx5wBsIy85i87ONkGWB9gNJ4tAhqSAHD2iQr5iQTD2emJII2VQAfCiJp9ojzFxVN3yo4KM9tu_fNsQc5THHeFRaFzslpf85NiqU9epn2SEizSqqgoKz6ouqX4RBakJ_ZdO3Xh5LiF03w_zIlmGVFpRovarSwGMTnsMtJt6jq5vTUHcWudqy8ejGjJbsX02og6KbNHaOgxn6zSlYb1f6w03pGpSi2O_9HRn0W0E__MHtZP5xt2CcSuXGxtmqs_yF8oQ5tjF4vGUPsaC4uX7fJ1ha_yKVvoFIMip6wcElHxgzeYQAedc0gobA7XZh6rzQrIh14uKAkTUtbimtdwyWlxX7HzByNqvpSATOtMrydcHcWxPzxvxbJG4zvOUR5lBdwr1vctnA1fy_6x5BeVw3MbSpQEGFy6BJ_FiMST5kRRkMbtljxkzGKmsWPcK2hVWwmh0aEOakqbAdsZuvAvmnnv7B2GqFBYFBjKXG-iq_N-8Y3nNH-eLNb4OpBlWP0Ab9TPGu67PT09_ECC_QRBsqcWxlhIrY7MvR70LJgng3CLIsQGWN1G9uKUY0s34vYkoXVFmgPLvME_619eaBtPrehNHEKfYbYhL0gvjVr_KEeNCv8lcp8rPbs9ODxQeB41r4ZRZSlknhPY8Tef3SX0r0u1roDnI8vtuT11A-21Fna8Xt-Chbu62zWtddEUOtq32CtHsFmzfNomytE234fKNV7VWxEo1Gc4a2h_Jistmm03bMRKMykSjktZ7dZuk-yGpzb6ONQCIfJZhGXvChOFYxH4-kcs7MVF_O2FlmhZjgBm1tNCwqyvPZSVnlaUxy-cxGisAJWYBJKoza17WawjhXAEO6TJSwruXkShhZ_uG2iSZNVxH6Um89rM_YbKv8zQUfpGwc45I0Klcm_mUDOmx32RvA6otB_b5zHvj3s-2C0ytLekVGs4-AxDMq825FVvS9216VjmrTlbcUm5bcEBNIqTtTCbN9ZgQrhCzGgUc-fJdlqyoj_0B1uZdIeC9-IML9HrH8_oYjw4ndAtMKbayFajt8fzcsTuEAnvT6eo6jzeWxV1D5_SH46cCaSB4-hc2BmwMaw1E6KYBLzlp6HegTSdFWdSbrtAdRNjMmZk6THcfdWhPOsu2X5aLJUA1sC_ftnEFIOvP-65I9c1Retg8RQ8btUtxngSK2ChFiBFPExr-Ki8-RhE23r4eWqnuIT5RC5t2LZBw_XlfC8KLqCmQcc3u2x4LbScaUa9rgoBF_0ERB6M4QFIAbxTw5iCwCq0mGuJnIw_WKyRyA1CSFVxoCPHugEC4RBIb8-ZMX-d1na-QxMYeOLxzKKVgJ6aqonMOClA0FVC0atnOhb0bvYx23-83utz2MQMX87mIiafsm6H-4ndoFdXSql0WSuk2tryOZULgBh7uo2zsywGVcGFpRDOwaWDAUPUoBA4Fi43HZCBVPm7R-NFMSDqBT6eFFTISt8JvLdjBipoOvMQxTnifZHF-r4pCXT_rEyQ9snNg1oULsS70vC7SNYQecWHoew6vZ5DXqZk5869vk2q01SlAtUB4pALDdLKibhPUNX0uT8ZLDuJJ7rj-BgVPh8cgBvw59gQMT2w_x-EX3pf4V7HWncn_yRmWdWHacIqFe7COqojxI86q9s47Xid3h05hjoCmfd0l_N6B3j_xBsauwKHydT1mH9jQOmARo35R-elHKeBd2XFHKSTOnuIEP3zUzHbNdVxfmE5_jxxFAQpR1t1dA9DRbbBkxBzrpDwqFUNqUp42aJ6HppV4SnOvriPbu3uU5zHd_cedQjiXwaqTUCeXhyY3jqO4n2zvn8D6PWP7V3z7ht7taiMfLPC4AYo22Dr78MH4Sg8K2Dzj_8xj3sVYZk7H7JVkQ6niJLl0dF3l6Vp-mNAEXVMRanpacReLpSled-FHTC2IqWgifEzqLWxUN2Y8fnztxZ2WgwyaE0cZh1MgO5zZPnPOIlqBuFYS90Z7F0niHmmZ1OoLS-vmR3uGyzuOuDnXvY83Q7v2BLm_MW2Hu73v3YhTO90yTKX6Rvbx5Dvb_Q-psrT8EzgAAA==)


## Synergy between Runes and Module Context

The new feature in Svelte 5, Rune, is a kind of macro that allows you to write reactive programs outside Svelte files in js files, just like in Svelte files.
This is particularly convenient when leveraging Svelte's module context, which is compiled in the following way.
```html
<script context="module">
// scripts in module
// scripts in module
</script>
<script>
// scripts in default
// scripts in default
</script>

<template></template>
```
```js
// scripts in module
// scripts in module
export default function App() {
  // scripts in default
  // scripts in default
}
```
The module context is a great space for writing reactive instance creation functions using runes.
```html
<script context="module">
export function create_counter(count = 0) {
  let count$ = $state(count)
  return {
    get $count() {
      return count$
    },
    increment() {
      count$ += 1
    }
  }
}
</script>
<script>
let counter = create_counter()
</script>

<button onclick={counter.increment}>
  clicks: {counter.$count}
</button>
```


## Considerations for mouse move and mouse up events

To implement drag and drop, I used mousedown, mousemove, and mouseup events.
I chose this approach instead of using `draggable="true"` because I wanted to display an HTML element instead of a dragImage.
One thing to note here is that the mousemove and mouseup events need to be connected to the window.
```html
<svelte:window
    onmousemove={handle_mousemove}
    onmouseup={handle_mouseup}
    ontouchmove={handle_touchmove}
    ontouchend={handle_touchend} />
```


## DragContainer and Draggable

When dragging begins, the draggable passes the drag target to the drag container.
Since there is only one drag target, a drag container with drag-related logic can control multiple draggables, even with just one instance.
```html
<DragContainer bind:setDragElement={set_drag_element}
    ondragend={todo_list.end_drag}>
  <DragItem index={i}
      {todo_list}
      {set_drag_element}
      type="todo" />
```


## Conclusion

While Svelte 5 hasn't been officially released yet, the current preview version is already quite intriguing.
What's astonishing is how convenient and rational the syntax of runes becomes the more you use it.
Declarative frontend frameworks, originating from React, seem to already have a familiar feel to the implicit magic executed by runes, given their nature of meta-programming.