Svelte — 항목 복제
Svelte에서 원본을 유지하며 고유 ID의 복제본을 생성하고, 취소·역방향 거부를 확인합니다.
조작 순서와 예상 결과
섹션 제목: “조작 순서와 예상 결과”- 원본의 메모 손잡이를 잡고 오른쪽 빈 영역에 놓습니다. 원본 2개는 유지되고 복제본 1개가 생깁니다.
- 같은 메모를 다시 복사합니다. 복제본은 2개가 되며 두 ID는 달라야 합니다.
- 복제본을 원본으로 되돌리려는 드래그는 거부합니다. 이 예제는 원본에서 복제본으로만 전달합니다.
- 원본을 드래그하다 Escape로 취소하면 목록 구성은 유지합니다.
- 초기화하면 원본 순서를 복원하고 복제본을 비웁니다. ID 순번은 재사용하지 않습니다.
이 예제의 드래그는 포인터 입력을 사용합니다. onAfterDrag 상태와 아래 ID 목록으로 결과를 확인하십시오.
복사 정책
섹션 제목: “복사 정책”| 위치 | 설정 | 의미 |
|---|---|---|
| 원본 | group: { name: "templates", pull: "copy", put: false } |
외부로 복사하며 다른 영역의 항목을 받지 않습니다. |
| 목적지 | group: { name: "templates", pull: false } |
같은 group을 받고 다른 영역으로 내보내지 않습니다. |
| 두 영역 | 서로 다른 areaId |
scope 내 영역 identity를 구분합니다. |
pull: "copy"는 영역 간 전달에서 원본을 유지합니다. 동일 영역 안의 재정렬을 자동으로 금지하는 설정은 아닙니다. 드래그 중 원본은 남고 preview와 placeholder가 별도로 표시됩니다.
Svelte 상태 계약
섹션 제목: “Svelte 상태 계약”comins-sortable/svelte의 scope를 만들고 두 요소에 use:sortable을 연결합니다. 반응형 options의 items와 onItemsChange가 상태를 왕복 전달합니다. {#each ... (item.id)}로 DOM identity를 유지하고 소유 컴포넌트 해제 시 scope를 destroy합니다.
type CopyItem<T> = (item: T, context: CopyItemContext) => T;복사 결과는 새 객체와 새 ID를 사용합니다. 예제 데이터는 얕은 객체이므로 spread를 사용합니다. 중첩 객체를 독립적으로 수정할 계획이면 애플리케이션의 복제 정책을 추가하십시오.
완료와 실패 처리
섹션 제목: “완료와 실패 처리”목록 갱신 알림만으로 완료를 확정하지 않고 onAfterDrag.status === "dropped"를 확인합니다. Escape·영역 밖 드롭·정책 거부는 완료와 구분합니다. 새 ID 누락·중복 또는 잘못된 복사 결과는 유효한 복제로 취급하지 않습니다.
이 예제의 ID 순번은 한 인스턴스 안의 데모용입니다. 서버 저장이나 여러 클라이언트 간 공유에는 애플리케이션 전체에서 충돌하지 않는 ID 정책이 필요합니다. Alt/Option 복제, 사용자 정의 preview, 실패 입력 전수 검증은 별도 시나리오입니다.
관련 문서
섹션 제목: “관련 문서”복제 가이드 · Core 생명주기 · Vanilla 인스턴스 · 같은 어댑터의 이동 예제
직접 실행
섹션 제목: “직접 실행”<script lang="ts"> import { onDestroy } from 'svelte'; import { createSortableScope, sortable } from 'comins-sortable/svelte'; import 'comins-sortable/styles.css'; export let lang = 'ko'; const ko = lang === 'ko'; type Item = { id: string; label: string }; const initial: Item[] = [{ id: 'template-note', label: ko ? '메모' : 'Note' }, { id: 'template-chart', label: ko ? '차트' : 'Chart' }]; let catalog: Item[] = [...initial]; let board: Item[] = []; let result = ''; let sequence = 0; const scope = createSortableScope<Item>({ onAfterDrag: event => { result = `${event.status}: ${event.reason}`; } }); onDestroy(() => scope.destroy()); const copyItem = (item: Item): Item => ({ ...item, id: `copy-${++sequence}` }); $: sourceOptions = { scope, areaId: 'catalog', group: { name: 'templates', pull: 'copy' as const, put: false }, items: catalog, itemKey: 'id' as const, copyItem, handle: '.task-handle', onItemsChange: (next: readonly Item[]) => { catalog = [...next]; } }; $: targetOptions = { scope, areaId: 'board', group: { name: 'templates', pull: false as const }, items: board, itemKey: 'id' as const, handle: '.task-handle', onItemsChange: (next: readonly Item[]) => { board = [...next]; } };</script><div class="demo" data-demo="copy-svelte"> <div class="demo-toolbar"><span>{ko ? '왼쪽 항목을 오른쪽으로 여러 번 드래그하십시오.' : 'Drag an item from the catalog to the board more than once.'}</span><button on:click={() => { scope.cancel(); catalog = [...initial]; board = []; result = ''; }}>{ko ? '초기화' : 'Reset'}</button></div> <div class="transfer-board"> <section class="transfer-column"><div class="list-heading">{ko ? '원본' : 'Catalog'}<span>{catalog.length}</span></div> <div class="task-list copy-catalog" data-copy-area="catalog" use:sortable={sourceOptions}> {#each catalog as item (item.id)}<div class="task" data-copy-id={item.id}><button class="task-handle" aria-label={`${ko ? '드래그' : 'Drag'}: ${item.label}`}>⠿</button><span>{item.label}</span></div>{/each} </div> </section> <section class="transfer-column"><div class="list-heading">{ko ? '복제본' : 'Copies'}<span>{board.length}</span></div> <div class="task-list copy-board" data-copy-area="board" use:sortable={targetOptions}> {#each board as item (item.id)}<div class="task" data-copy-id={item.id}><button class="task-handle" aria-label={`${ko ? '드래그' : 'Drag'}: ${item.label}`}>⠿</button><span>{item.label}</span></div>{/each} </div> </section> </div> <p role="status">{result || (ko ? '복제 대기' : 'Ready to copy')}</p> <pre data-copy-state>{JSON.stringify({ catalog: catalog.map(item => item.id), board: board.map(item => item.id) }, null, 2)}</pre></div>