Vanilla JS — Swap two items
Use swap in Vanilla JS to exchange only two items and inspect the completion event.
Different from insertion sorting
Section titled “Different from insertion sorting”Dropping A on the center of C in [A, B, C, D] produces [C, B, A, D]. Only the two items exchange positions; B and D remain. Intervening items are not shifted as they would be for insertion.
- Drag the A handle to the center of C and release.
- Check JSON for
status: "dropped",change.operation: "swap",change.itemId: "A", andchange.swapItemId: "C". - Start another drag and cancel with Escape; order remains unchanged.
- Reset returns to A, B, C, D.
Public options
Section titled “Public options”| Option | Value/purpose |
|---|---|
swap |
true — exchanges source and target within the same area. |
direction |
"vertical" — this example uses one vertical list. |
itemKey or data-sortable-id |
Supplies identity retained across movement. |
handle |
".task-handle" — restricts pointer input to handles. |
Vanilla JS state commit
Section titled “Vanilla JS state commit”createSortable · DOM
Vanilla applies the DOM swap. Swapping the elements again in a consumer onChange callback would undo it, so this example only reads the final result in onAfterDrag. Destroy the instance when its host disconnects.
This is a same-area, single-item swap. Grid collision, multiDrag, threshold/invertSwap combinations, and cross-area delivery remain separate scenarios. This pointer-based example does not claim full keyboard reordering simply because its handle is focusable.
Related documentation
Section titled “Related documentation”Advanced sorting guide · Core lifecycle · Vanilla instance · Ordinary list transfer
Try it live
Section titled “Try it live”A, B, C, D
null
---const { lang = 'ko' } = Astro.props;const ko = lang === 'ko';---<vanilla-swap class="demo" data-demo="swap-vanilla"> <div class="demo-toolbar"><span>{ko ? 'A의 손잡이를 C의 중앙에 놓으십시오.' : 'Drop the A handle onto the center of C.'}</span><button data-reset>{ko ? '초기화' : 'Reset'}</button></div> <div class="task-list swap-list">{['A','B','C','D'].map(id => <div class="task" data-sortable-id={id} data-swap-id={id}><button class="task-handle" aria-label={`Drag ${id}`}>⠿</button><span>{id}</span></div>)}</div> <p role="status" data-swap-order>A, B, C, D</p> <pre data-swap-result>null</pre></vanilla-swap><script> import { createSortable } from 'comins-sortable'; import 'comins-sortable/styles.css'; class VanillaSwap extends HTMLElement { cleanup?: () => void; connectedCallback() { if (this.cleanup) return; const list = this.querySelector<HTMLElement>('.swap-list')!; const initial = [...list.children]; const update = () => { this.querySelector('[data-swap-order]')!.textContent = [...list.children].map(item => item.getAttribute('data-sortable-id')).join(', '); }; const sortable = createSortable(list, { areaId: 'items', item: ':scope > .task', handle: '.task-handle', swap: true, direction: 'vertical', onAfterDrag: result => { update(); this.querySelector('[data-swap-result]')!.textContent = JSON.stringify(result, null, 2); }, }); const reset = () => { sortable.cancel(); list.replaceChildren(...initial); sortable.refreshArea('items'); update(); this.querySelector('[data-swap-result]')!.textContent = 'null'; }; const button = this.querySelector('[data-reset]')!; button.addEventListener('click', reset); this.cleanup = () => { button.removeEventListener('click', reset); sortable.destroy(); }; } disconnectedCallback() { this.cleanup?.(); this.cleanup = undefined; } } if (!customElements.get('vanilla-swap')) customElements.define('vanilla-swap', VanillaSwap);</script>