Vue — 항목 복제
Vue에서 원본을 유지하며 고유 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가 별도로 표시됩니다.
Vue 상태 계약
섹션 제목: “Vue 상태 계약”0.1.6의 Vue SFC 제네릭 추론을 사용합니다. SortableArea는 modelValue에서 항목·슬롯 타입을 추론하므로 별도 컴포넌트 타입 단언이 필요하지 않습니다.
type CopyItem<T> = (item: T, context: CopyItemContext) => T;복사 결과는 새 객체와 새 ID를 사용합니다. 예제 데이터는 얕은 객체이므로 spread를 사용합니다. 중첩 객체를 독립적으로 수정할 계획이면 애플리케이션의 복제 정책을 추가하십시오.
완료와 실패 처리
섹션 제목: “완료와 실패 처리”목록 갱신 알림만으로 완료를 확정하지 않고 onAfterDrag.status === "dropped"를 확인합니다. Escape·영역 밖 드롭·정책 거부는 완료와 구분합니다. 새 ID 누락·중복 또는 잘못된 복사 결과는 유효한 복제로 취급하지 않습니다.
이 예제의 ID 순번은 한 인스턴스 안의 데모용입니다. 서버 저장이나 여러 클라이언트 간 공유에는 애플리케이션 전체에서 충돌하지 않는 ID 정책이 필요합니다. Alt/Option 복제, 사용자 정의 preview, 실패 입력 전수 검증은 별도 시나리오입니다.
관련 문서
섹션 제목: “관련 문서”복제 가이드 · Core 생명주기 · Vanilla 인스턴스 · 같은 어댑터의 이동 예제
직접 실행
섹션 제목: “직접 실행”<script setup lang="ts">import { ref } from 'vue';import { SortableRoot, SortableArea } from 'comins-sortable/vue';import type { AfterDragResult } from 'comins-sortable/core';import 'comins-sortable/styles.css';const props = withDefaults(defineProps<{ lang?: string }>(), { lang: 'ko' });const ko = props.lang === 'ko';type Item = { id: string; label: string };const initial: Item[] = [{ id: 'template-note', label: ko ? '메모' : 'Note' }, { id: 'template-chart', label: ko ? '차트' : 'Chart' }];const catalog = ref<Item[]>([...initial]);const board = ref<Item[]>([]);const result = ref('');let sequence = 0;const sourceGroup = { name: 'templates', pull: 'copy' as const, put: false };const targetGroup = { name: 'templates', pull: false as const };const copyItem = (item: Item): Item => ({ ...item, id: `copy-${++sequence}` });function afterDrag(event: AfterDragResult) { result.value = `${event.status}: ${event.reason}`; }function reset() { catalog.value = [...initial]; board.value = []; result.value = ''; }</script><template> <div class="demo" data-demo="copy-vue"> <div class="demo-toolbar"><span>{{ ko ? '왼쪽 항목을 오른쪽으로 여러 번 드래그하십시오.' : 'Drag an item from the catalog to the board more than once.' }}</span><button @click="reset">{{ ko ? '초기화' : 'Reset' }}</button></div> <SortableRoot :on-after-drag="afterDrag"> <div class="transfer-board"> <section class="transfer-column"><div class="list-heading">{{ ko ? '원본' : 'Catalog' }}<span>{{ catalog.length }}</span></div> <SortableArea v-model="catalog" area-id="catalog" :group="sourceGroup" item-key="id" :copy-item="copyItem" handle=".task-handle" :component-props="{ class: 'task-list copy-catalog', 'data-copy-area': 'catalog' }"> <template #item="{ item }"><div class="task" :data-copy-id="item.id"><button class="task-handle" :aria-label="`${ko ? '드래그' : 'Drag'}: ${item.label}`">⠿</button><span>{{ item.label }}</span></div></template> </SortableArea> </section> <section class="transfer-column"><div class="list-heading">{{ ko ? '복제본' : 'Copies' }}<span>{{ board.length }}</span></div> <SortableArea v-model="board" area-id="board" :group="targetGroup" item-key="id" handle=".task-handle" :component-props="{ class: 'task-list copy-board', 'data-copy-area': 'board' }"> <template #item="{ item }"><div class="task" :data-copy-id="item.id"><button class="task-handle" :aria-label="`${ko ? '드래그' : 'Drag'}: ${item.label}`">⠿</button><span>{{ item.label }}</span></div></template> </SortableArea> </section> </div> </SortableRoot> <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></template>