140 lines
3.6 KiB
Vue
140 lines
3.6 KiB
Vue
<template>
|
|
<TreeItem ref="el" v-bind="forward" v-slot="{ isExpanded, isSelected, isIndeterminate, handleToggle, handleSelect }">
|
|
<slot
|
|
:is-expanded="isExpanded"
|
|
:is-selected="isSelected"
|
|
:is-indeterminate="isIndeterminate"
|
|
:handle-select="handleSelect"
|
|
:handle-toggle="handleToggle"
|
|
:isDragging="isDragging"
|
|
:isDraggedOver="isDraggedOver"
|
|
/>
|
|
<div v-if="instruction">
|
|
<slot name="hint" :instruction="instruction" />
|
|
</div>
|
|
</TreeItem>
|
|
</template>
|
|
|
|
<script setup lang="ts" generic="T extends Record<string, any>">
|
|
import { useForwardPropsEmits, type FlattenedItem, type TreeItemEmits, type TreeItemProps } from 'radix-vue';
|
|
import { draggable, dropTargetForElements, monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
|
|
import { type Instruction, attachInstruction, extractInstruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item'
|
|
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine'
|
|
|
|
const props = defineProps<TreeItemProps<T> & FlattenedItem<T>>();
|
|
const emits = defineEmits<TreeItemEmits<T>>();
|
|
|
|
defineSlots<{
|
|
default: (props: {
|
|
isExpanded: boolean
|
|
isSelected: boolean
|
|
isIndeterminate: boolean | undefined
|
|
isDragging: boolean
|
|
isDraggedOver: boolean
|
|
handleToggle: () => void
|
|
handleSelect: () => void
|
|
}) => any,
|
|
hint: (props: {
|
|
instruction: Extract<Instruction, { type: 'reorder-above' | 'reorder-below' | 'make-child' }> | null
|
|
}) => any,
|
|
}>()
|
|
|
|
const forward = useForwardPropsEmits(props, emits);
|
|
|
|
const element = templateRef('el');
|
|
const isDragging = ref(false);
|
|
const isDraggedOver = ref(false);
|
|
const isInitialExpanded = ref(false);
|
|
const instruction = ref<Extract<Instruction, { type: 'reorder-above' | 'reorder-below' | 'make-child' }> | null>(null);
|
|
|
|
const mode = computed(() => {
|
|
if (props.hasChildren)
|
|
return 'expanded'
|
|
if (props.index + 1 === props.parentItem?.children?.length)
|
|
return 'last-in-group'
|
|
return 'standard'
|
|
});
|
|
|
|
watchEffect((onCleanup) => {
|
|
const currentElement = unrefElement(element) as HTMLElement;
|
|
|
|
if (!currentElement)
|
|
return
|
|
|
|
const item = { ...props.value, level: props.level, id: props._id }
|
|
|
|
const expandItem = () => {
|
|
if (!element.value?.isExpanded) {
|
|
element.value?.handleToggle()
|
|
}
|
|
}
|
|
|
|
const closeItem = () => {
|
|
if (element.value?.isExpanded) {
|
|
element.value?.handleToggle()
|
|
}
|
|
}
|
|
|
|
const dndFunction = combine(
|
|
draggable({
|
|
element: currentElement,
|
|
getInitialData: () => item,
|
|
onDragStart: () => {
|
|
isDragging.value = true
|
|
isInitialExpanded.value = element.value?.isExpanded ?? false
|
|
closeItem()
|
|
},
|
|
onDrop: () => {
|
|
isDragging.value = false
|
|
if (isInitialExpanded.value)
|
|
expandItem()
|
|
},
|
|
}),
|
|
|
|
dropTargetForElements({
|
|
element: currentElement,
|
|
getData: ({ input, element }) => {
|
|
const data = { id: item.id }
|
|
|
|
return attachInstruction(data, {
|
|
input,
|
|
element,
|
|
indentPerLevel: 16,
|
|
currentLevel: props.level,
|
|
mode: mode.value,
|
|
block: [],
|
|
})
|
|
},
|
|
canDrop: ({ source }) => {
|
|
return source.data.id !== item.id
|
|
},
|
|
onDrag: ({ self }) => {
|
|
instruction.value = extractInstruction(self.data) as typeof instruction.value
|
|
},
|
|
onDragEnter: ({ source }) => {
|
|
if (source.data.id !== item.id) {
|
|
isDraggedOver.value = true
|
|
}
|
|
},
|
|
onDragLeave: () => {
|
|
isDraggedOver.value = false
|
|
instruction.value = null
|
|
},
|
|
onDrop: ({ location }) => {
|
|
isDraggedOver.value = false
|
|
instruction.value = null
|
|
},
|
|
getIsSticky: () => true,
|
|
}),
|
|
|
|
monitorForElements({
|
|
canMonitor: ({ source }) => {
|
|
return source.data.id !== item.id
|
|
},
|
|
}),
|
|
)
|
|
|
|
// Cleanup dnd function
|
|
onCleanup(() => dndFunction())
|
|
})
|
|
</script> |