Skip to content

Multi-Select

click to select item

item 1
item 2
item 3
item 4
item 5
item 6
item 7
item 8
item 9
item 10
<template>
  <div ref="listRef" class="flex-1">
    <div
      v-for="item in list"
      :key="item.id"
      :class="['item', item.selected ? 'selected' : '']"
      :data-id="item.id"
    >
      <input v-model="item.selected" type="checkbox" />
      <span>{{ item.name }}</span>
      <span class="handle"></span>
    </div>
  </div>
</template>

<script setup>
import { inject, onMounted, onUnmounted, ref } from 'vue';

const dnd = ref(null);
const listRef = ref();

const isChoose = ref(false);

const list = ref(
  new Array(10)
    .fill(0)
    .map((v, i) => ({ id: i + 1, name: `item ${i + 1}`, selected: i < 2 ? true : false }))
);

onMounted(() => {
  const Sortable = inject('Sortable');
  dnd.value = new Sortable(listRef.value, {
    handle: '.handle',
    multiple: true,
    chosenClass: 'chosen',
    animation: 500,
    swapOnDrop: false,
    dropOnAnimationEnd: true,
    onChoose: (evt) => {
      console.log('choose', evt);

      isChoose.value = true;
    },
    onUnchoose: (evt) => {
      console.log('unchoose', evt);

      isChoose.value = false;
    },
    onDrag: (evt) => {
      console.log('drag', evt);
    },
    onDrop: (evt) => {
      console.log('drop', evt);

      if (evt.relative === 0) {
        return;
      }

      const tempList = [...list.value];

      const nodeItem = tempList[evt.oldIndex];
      const targetId = tempList[evt.newIndex].id;

      let selectedItems = [];
      // multi-drag
      if (nodeItem.selected) {
        selectedItems = tempList.filter((item) => item.selected);

        if (selectedItems.find((item) => item.id === targetId)) {
          return;
        }
      } else {
        // single-drag
        selectedItems = [nodeItem];
      }

      selectedItems.forEach((item) => {
        tempList.splice(tempList.indexOf(item), 1);
      });

      let dropIndex = tempList.findIndex((item) => item.id === targetId);

      if (evt.oldIndex < evt.newIndex && evt.relative === 1) {
        dropIndex += evt.relative;
      }

      tempList.splice(dropIndex, 0, ...selectedItems);

      list.value = tempList;
    },
  });
});

onUnmounted(() => {
  dnd.value.destroy();
});
</script>

<style scoped>
.wrap {
  display: flex;
  justify-content: space-between;
  gap: 20px;
}

.flex-1 {
  flex: 1;
}

.item {
  border-radius: 5px;
  box-shadow: 0px 2px 5px -2px #57bbb4;
  padding: 8px;
  margin-bottom: 5px;
}

.selected {
  background-color: #57bbb4;
}

.handle {
  float: right;
  cursor: move;
}

.chosen {
  box-shadow: 0px 0px 5px 1px #1984ff;
}
</style>

Released under the MIT License.