Skip to content

Commit

Permalink
feat(useSortable): new function (#2763)
Browse files Browse the repository at this point in the history
Co-authored-by: Jelf <353742991@qq.com>
Co-authored-by: Jelf <okxiaoliang4@gmail.com>
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
4 people committed Mar 14, 2023
1 parent 12c2038 commit 6bc6089
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/add-ons.md
Expand Up @@ -81,6 +81,7 @@ Integration wrappers for utility libraries
- [`useJwt`](https://vueuse.org/integrations/useJwt/) — wrapper for [`jwt-decode`](https://github.com/auth0/jwt-decode)
- [`useNProgress`](https://vueuse.org/integrations/useNProgress/) — reactive wrapper for [`nprogress`](https://github.com/rstacruz/nprogress)
- [`useQRCode`](https://vueuse.org/integrations/useQRCode/) — wrapper for [`qrcode`](https://github.com/soldair/node-qrcode)
- [`useSortable`](https://vueuse.org/integrations/useSortable/) — wrapper for [`sortable`](https://github.com/SortableJS/Sortable)


## RxJS - [`@vueuse/rxjs`](https://vueuse.org/rxjs/README.html)
Expand Down
1 change: 1 addition & 0 deletions packages/integrations/README.md
Expand Up @@ -25,6 +25,7 @@ npm i <b>@vueuse/integrations</b>
- [`useJwt`](https://vueuse.org/integrations/useJwt/) — wrapper for [`jwt-decode`](https://github.com/auth0/jwt-decode)
- [`useNProgress`](https://vueuse.org/integrations/useNProgress/) — reactive wrapper for [`nprogress`](https://github.com/rstacruz/nprogress)
- [`useQRCode`](https://vueuse.org/integrations/useQRCode/) — wrapper for [`qrcode`](https://github.com/soldair/node-qrcode)
- [`useSortable`](https://vueuse.org/integrations/useSortable/) — wrapper for [`sortable`](https://github.com/SortableJS/Sortable)


<!--FUNCTIONS_LIST_ENDS-->
Expand Down
1 change: 1 addition & 0 deletions packages/integrations/index.ts
Expand Up @@ -9,3 +9,4 @@ export * from './useIDBKeyval'
export * from './useJwt'
export * from './useNProgress'
export * from './useQRCode'
export * from './useSortable'
16 changes: 16 additions & 0 deletions packages/integrations/package.json
Expand Up @@ -91,6 +91,16 @@
"types": "./useIDBKeyval.d.ts",
"require": "./useIDBKeyval.cjs",
"import": "./useIDBKeyval.mjs"
},
"./useSortable": {
"types": "./useSortable.d.ts",
"require": "./useSortable.cjs",
"import": "./useSortable.mjs"
},
"./useSortable/component": {
"types": "./useSortable/component.d.ts",
"require": "./useSortable/component.cjs",
"import": "./useSortable/component.mjs"
}
},
"main": "./index.cjs",
Expand All @@ -109,6 +119,7 @@
"jwt-decode": "*",
"nprogress": "*",
"qrcode": "*",
"sortablejs": "*",
"universal-cookie": "*"
},
"peerDependenciesMeta": {
Expand Down Expand Up @@ -144,6 +155,9 @@
},
"universal-cookie": {
"optional": true
},
"sortablejs": {
"optional": true
}
},
"dependencies": {
Expand All @@ -154,6 +168,7 @@
"devDependencies": {
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.5.0",
"@types/sortablejs": "^1.15.0",
"async-validator": "^4.2.5",
"axios": "^1.3.4",
"change-case": "^4.1.2",
Expand All @@ -164,6 +179,7 @@
"jwt-decode": "^3.1.2",
"nprogress": "^0.2.0",
"qrcode": "^1.5.1",
"sortablejs": "^1.15.0",
"universal-cookie": "^4.0.4"
}
}
35 changes: 35 additions & 0 deletions packages/integrations/useSortable/component.ts
@@ -0,0 +1,35 @@
import { type PropType, defineComponent, h, reactive, ref } from 'vue-demi'
import { useVModel } from '@vueuse/core'
import { type UseSortableOptions, useSortable } from '.'

export const UseSortable = /* #__PURE__ */ defineComponent({
name: 'UseSortable',
model: { // Compatible with vue2
prop: 'modelValue',
event: 'update:modelValue',
},
props: {
modelValue: {
type: Array as PropType<any[]>,
required: true,
},
tag: {
type: String,
default: 'div',
},
options: {
type: Object as PropType<UseSortableOptions>,
required: true,
},
},

setup(props, { slots }) {
const list = useVModel(props, 'modelValue')
const target = ref()
const data = reactive(useSortable(target, list, props.options))
return () => {
if (slots.default)
return h(props.tag, { ref: target }, slots.default(data))
}
},
})
22 changes: 22 additions & 0 deletions packages/integrations/useSortable/demo.vue
@@ -0,0 +1,22 @@
<script setup lang="ts">
import { useSortable } from '@vueuse/integrations/useSortable'
import { ref } from 'vue'
const el = ref<HTMLElement | null>(null)
const list = ref([{ id: 1, name: 'a' }, { id: 2, name: 'b' }, { id: 3, name: 'c' }])
useSortable(el, list, {
animation: 150,
})
</script>

<template>
<div ref="el" class="flex flex-col gap-2 p-4 w-300px h-200px m-auto bg-gray-500/5 rounded">
<div v-for="item in list" :key="item.id" class="h20 bg-gray-500/5 rounded p-3">
{{ item.name }}
</div>
</div>
<div class="text-center">
{{ JSON.stringify(list) }}
</div>
</template>
101 changes: 101 additions & 0 deletions packages/integrations/useSortable/index.md
@@ -0,0 +1,101 @@
---
category: '@Integrations'
---

# useSortable

Wrapper for [`sortable`](https://github.com/SortableJS/Sortable).

For more information on what options can be passed, see [`Sortable.options`](https://github.com/SortableJS/Sortable#options) in the `Sortable` documentation.

## Install

```bash
npm i sortablejs
```

## Usage

### Use template ref

```vue
<script setup lang="ts">
import { useSortable } from '@vueuse/integrations/useSortable'
import { ref } from 'vue'
const el = ref<HTMLElement | null>(null)
const list = ref([{ id: 1, name: 'a' }, { id: 2, name: 'b' }, { id: 3, name: 'c' }])
useSortable(el, list)
</script>
<template>
<div ref="el">
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
```

### Use specifies the selector to operate on

```vue
<script setup lang="ts">
import { useSortable } from '@vueuse/integrations/useSortable'
import { ref } from 'vue'
const el = ref<HTMLElement | null>(null)
const list = ref([{ id: 1, name: 'a' }, { id: 2, name: 'b' }, { id: 3, name: 'c' }])
useSortable(el, list, {
handle: '.handle'
})
</script>
<template>
<div ref="el">
<div v-for="item in list" :key="item.id">
<span>{{ item.name }}</span>
<span class="handle">*</span>
</div>
</div>
</template>
```

### Use a selector to get the root element

```vue
<script setup lang="ts">
import { useSortable } from '@vueuse/integrations/useSortable'
import { ref } from 'vue'
const list = ref([{ id: 1, name: 'a' }, { id: 2, name: 'b' }, { id: 3, name: 'c' }])
useSortable('#dv', list)
</script>
<template>
<div id="dv">
<div v-for="item in list" :key="item.id">
<span>{{ item.name }}</span>
</div>
</div>
</template>
```

### Tips

If you want to handle the onUpdate yourself, you can pass in onUpdate parameters, and we also exposed a function to move the item position.

```ts
import { moveArrayElement } from '@vueuse/integrations/useSortable'

useSortable(el, list, {
onUpdate: (e) => {
// do something
moveArrayElement(list.value, e.oldIndex, e.newIndex)
// do something
}
})
```
67 changes: 67 additions & 0 deletions packages/integrations/useSortable/index.ts
@@ -0,0 +1,67 @@
import { defaultDocument, resolveUnref, tryOnMounted, tryOnScopeDispose, unrefElement } from '@vueuse/core'
import type { ConfigurableDocument, MaybeComputedRef } from '@vueuse/core'
import Sortable, { type Options } from 'sortablejs'

export interface UseSortableReturn {
/**
* start sortable instance
*/
start: () => void
/**
* destroy sortable instance
*/
stop: () => void
}

export type UseSortableOptions = Options & ConfigurableDocument

export function useSortable<T>(selector: string, list: MaybeComputedRef<T[]>,
options?: UseSortableOptions): UseSortableReturn
export function useSortable<T>(el: MaybeComputedRef<HTMLElement | null | undefined>, list: MaybeComputedRef<T[]>,
options?: UseSortableOptions): UseSortableReturn
/**
* Wrapper for sortablejs.
* @param el
* @param list
* @param options
*/
export function useSortable<T>(
el: MaybeComputedRef<HTMLElement | null | undefined> | string,
list: MaybeComputedRef<T[]>,
options: UseSortableOptions = {},
): UseSortableReturn {
let sortable: Sortable

const { document = defaultDocument, ...resetOptions } = options

const defaultOptions: Options = {
onUpdate: (e) => {
moveArrayElement(list, e.oldIndex!, e.newIndex!)
},
}

const start = () => {
const target = (typeof el === 'string' ? document?.querySelector(el) : unrefElement(el))
if (!target)
return
sortable = new Sortable(target as HTMLElement, { ...defaultOptions, ...resetOptions })
}

const stop = () => sortable?.destroy()

tryOnMounted(start)

tryOnScopeDispose(stop)

return { stop, start }
}

export function moveArrayElement<T>(
list: MaybeComputedRef<T[]>,
from: number,
to: number,
): void {
const array = resolveUnref(list)
if (to >= 0 && to < array.length)
array.splice(to, 0, array.splice(from, 1)[0])
}
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6bc6089

Please sign in to comment.