Vanilla JS — 두 항목 위치 교환
Vanilla JS에서 두 항목만 교환하는 swap과 완료 이벤트를 확인합니다.
삽입 정렬과 다른 결과
섹션 제목: “삽입 정렬과 다른 결과”[A, B, C, D]에서 A를 C 중앙에 놓으면 [C, B, A, D]가 됩니다. 두 항목만 위치를 교환하고 B와 D는 유지합니다. 일반 삽입 정렬처럼 중간 항목을 밀어내는 동작이 아닙니다.
- A 손잡이를 C 중앙까지 드래그하여 놓습니다.
- 결과 JSON에서
status: "dropped",change.operation: "swap",change.itemId: "A",change.swapItemId: "C"를 확인합니다. - 드래그를 시작한 뒤 Escape로 취소하면 순서가 유지됩니다.
- 초기화하면 A, B, C, D 순서로 돌아옵니다.
공개 옵션
섹션 제목: “공개 옵션”| 옵션 | 값/역할 |
|---|---|
swap |
true — 같은 영역에서 출발 항목과 대상 항목만 교환합니다. |
direction |
"vertical" — 이 예제의 단일 세로 목록 기준입니다. |
itemKey 또는 data-sortable-id |
이동 전후 유지되는 안정 ID를 지정합니다. |
handle |
".task-handle" — 포인터 입력을 손잡이로 제한합니다. |
Vanilla JS 상태 반영
섹션 제목: “Vanilla JS 상태 반영”createSortable · DOM
Vanilla가 DOM 교환을 적용합니다. 소비자 코드에서 onChange를 받아 두 요소를 다시 교환하면 결과가 뒤집히므로 이 예제는 최종 onAfterDrag에서 읽기만 수행합니다. 화면 해제 시 인스턴스를 destroy합니다.
적용 범위
섹션 제목: “적용 범위”이 예제는 같은 영역의 단일 항목 Swap입니다. Grid collision, multiDrag, threshold/invertSwap와의 조합, 여러 영역 사이의 전달은 별도 시나리오입니다. 포인터 기반 예제이며 드래그 handle의 focus가 전체 키보드 정렬 지원을 뜻하지 않습니다.
관련 문서
섹션 제목: “관련 문서”고급 정렬 가이드 · Core 생명주기 · Vanilla 인스턴스 · 일반 목록 이동
직접 실행
섹션 제목: “직접 실행”A
B
C
D
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>