feat: enable drag reorder for skeleton requirements
This commit is contained in:
@@ -20,8 +20,22 @@
|
||||
<div
|
||||
v-for="(requirement, index) in requirements"
|
||||
:key="requirement.id || index"
|
||||
class="border border-base-200 rounded-lg p-4 space-y-3"
|
||||
class="relative border border-base-200 rounded-lg p-4 pl-12 space-y-3 transition-colors"
|
||||
:class="requirementReorderClass(index)"
|
||||
@dragenter="onRequirementDragEnter(index)"
|
||||
@dragover="onRequirementDragOver"
|
||||
@drop="onRequirementDrop(index)"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute left-3 top-4 btn btn-ghost btn-xs btn-square cursor-grab active:cursor-grabbing"
|
||||
draggable="true"
|
||||
title="Réorganiser"
|
||||
@dragstart="onRequirementDragStart(index, $event)"
|
||||
@dragend="onRequirementDragEnd"
|
||||
>
|
||||
<IconLucideGripVertical class="w-4 h-4" aria-hidden="true" />
|
||||
</button>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 flex-1">
|
||||
<div class="form-control">
|
||||
@@ -120,11 +134,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import IconLucidePlus from '~icons/lucide/plus'
|
||||
import IconLucideTrash2 from '~icons/lucide/trash-2'
|
||||
import SearchSelect from '~/components/common/SearchSelect.vue'
|
||||
import IconLucideGripVertical from '~icons/lucide/grip-vertical'
|
||||
|
||||
type Option = {
|
||||
id: string | number
|
||||
@@ -139,6 +154,7 @@ type Requirement = Record<string, unknown> & {
|
||||
maxCount?: number | null
|
||||
required?: boolean | null
|
||||
allowNewModels?: boolean | null
|
||||
orderIndex?: number | null
|
||||
}
|
||||
|
||||
type Labels = {
|
||||
@@ -221,20 +237,30 @@ const optionDescription = (option: Option) => {
|
||||
return ''
|
||||
}
|
||||
|
||||
const applyOrderIndex = (list: Requirement[]): Requirement[] =>
|
||||
list.map((item, index) => ({
|
||||
...item,
|
||||
orderIndex: index,
|
||||
}))
|
||||
|
||||
const addRequirement = () => {
|
||||
requirements.value = [
|
||||
requirements.value = applyOrderIndex([
|
||||
...requirements.value,
|
||||
props.defaultRequirement(),
|
||||
]
|
||||
])
|
||||
}
|
||||
|
||||
const removeRequirement = (index: number) => {
|
||||
requirements.value = requirements.value.filter((_, i) => i !== index)
|
||||
requirements.value = applyOrderIndex(
|
||||
requirements.value.filter((_, i) => i !== index),
|
||||
)
|
||||
}
|
||||
|
||||
const updateRequirement = (index: number, patch: Partial<Requirement>) => {
|
||||
requirements.value = requirements.value.map((item, i) =>
|
||||
i === index ? { ...item, ...patch } : item
|
||||
requirements.value = applyOrderIndex(
|
||||
requirements.value.map((item, i) =>
|
||||
i === index ? { ...item, ...patch } : item,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -251,6 +277,76 @@ const parseOptionalNumber = (value: string) => {
|
||||
return Number.isFinite(parsed) ? parsed : null
|
||||
}
|
||||
|
||||
const draggingRequirementIndex = ref<number | null>(null)
|
||||
const requirementDropTargetIndex = ref<number | null>(null)
|
||||
|
||||
const resetRequirementDragState = () => {
|
||||
draggingRequirementIndex.value = null
|
||||
requirementDropTargetIndex.value = null
|
||||
}
|
||||
|
||||
const reorderRequirements = (from: number, to: number) => {
|
||||
const list = requirements.value
|
||||
if (!Array.isArray(list)) {
|
||||
resetRequirementDragState()
|
||||
return
|
||||
}
|
||||
if (from === to || from < 0 || to < 0 || from >= list.length || to >= list.length) {
|
||||
resetRequirementDragState()
|
||||
return
|
||||
}
|
||||
const updated = list.slice() as Requirement[]
|
||||
const [moved] = updated.splice(from, 1)
|
||||
updated.splice(to, 0, moved)
|
||||
requirements.value = applyOrderIndex(updated)
|
||||
resetRequirementDragState()
|
||||
}
|
||||
|
||||
const onRequirementDragStart = (index: number, event: DragEvent) => {
|
||||
draggingRequirementIndex.value = index
|
||||
requirementDropTargetIndex.value = index
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
const onRequirementDragEnter = (index: number) => {
|
||||
if (draggingRequirementIndex.value === null) {
|
||||
return
|
||||
}
|
||||
requirementDropTargetIndex.value = index
|
||||
}
|
||||
|
||||
const onRequirementDragOver = (event: DragEvent) => {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const onRequirementDrop = (index: number) => {
|
||||
if (draggingRequirementIndex.value === null) {
|
||||
resetRequirementDragState()
|
||||
return
|
||||
}
|
||||
reorderRequirements(draggingRequirementIndex.value, index)
|
||||
}
|
||||
|
||||
const onRequirementDragEnd = () => {
|
||||
resetRequirementDragState()
|
||||
}
|
||||
|
||||
const requirementReorderClass = (index: number) => {
|
||||
if (draggingRequirementIndex.value === index) {
|
||||
return 'border-dashed border-primary'
|
||||
}
|
||||
if (
|
||||
draggingRequirementIndex.value !== null &&
|
||||
requirementDropTargetIndex.value === index &&
|
||||
draggingRequirementIndex.value !== index
|
||||
) {
|
||||
return 'border-primary border-dashed bg-primary/5'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const normalizeTypeModel = (value: unknown) => {
|
||||
if (value === null || value === undefined) {
|
||||
return ''
|
||||
|
||||
Reference in New Issue
Block a user