From 6bc608921949aa4bc3cd4dcf88d036d78b831e4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Wed, 15 Mar 2023 00:30:14 +0800 Subject: [PATCH] feat(useSortable): new function (#2763) Co-authored-by: Jelf <353742991@qq.com> Co-authored-by: Jelf Co-authored-by: Anthony Fu --- packages/add-ons.md | 1 + packages/integrations/README.md | 1 + packages/integrations/index.ts | 1 + packages/integrations/package.json | 16 +++ .../integrations/useSortable/component.ts | 35 ++++++ packages/integrations/useSortable/demo.vue | 22 ++++ packages/integrations/useSortable/index.md | 101 ++++++++++++++++++ packages/integrations/useSortable/index.ts | 67 ++++++++++++ pnpm-lock.yaml | 12 +++ 9 files changed, 256 insertions(+) create mode 100644 packages/integrations/useSortable/component.ts create mode 100644 packages/integrations/useSortable/demo.vue create mode 100644 packages/integrations/useSortable/index.md create mode 100644 packages/integrations/useSortable/index.ts diff --git a/packages/add-ons.md b/packages/add-ons.md index be5f3349e52..44bf29be2a0 100644 --- a/packages/add-ons.md +++ b/packages/add-ons.md @@ -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) diff --git a/packages/integrations/README.md b/packages/integrations/README.md index 3aa79f27eab..0eacca5558e 100644 --- a/packages/integrations/README.md +++ b/packages/integrations/README.md @@ -25,6 +25,7 @@ npm i @vueuse/integrations - [`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) diff --git a/packages/integrations/index.ts b/packages/integrations/index.ts index 8b2cca5830f..796af89fa5f 100644 --- a/packages/integrations/index.ts +++ b/packages/integrations/index.ts @@ -9,3 +9,4 @@ export * from './useIDBKeyval' export * from './useJwt' export * from './useNProgress' export * from './useQRCode' +export * from './useSortable' diff --git a/packages/integrations/package.json b/packages/integrations/package.json index 429dd6f7def..bad8df5aafb 100644 --- a/packages/integrations/package.json +++ b/packages/integrations/package.json @@ -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", @@ -109,6 +119,7 @@ "jwt-decode": "*", "nprogress": "*", "qrcode": "*", + "sortablejs": "*", "universal-cookie": "*" }, "peerDependenciesMeta": { @@ -144,6 +155,9 @@ }, "universal-cookie": { "optional": true + }, + "sortablejs": { + "optional": true } }, "dependencies": { @@ -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", @@ -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" } } diff --git a/packages/integrations/useSortable/component.ts b/packages/integrations/useSortable/component.ts new file mode 100644 index 00000000000..7363ba006d0 --- /dev/null +++ b/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, + required: true, + }, + tag: { + type: String, + default: 'div', + }, + options: { + type: Object as PropType, + 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)) + } + }, +}) diff --git a/packages/integrations/useSortable/demo.vue b/packages/integrations/useSortable/demo.vue new file mode 100644 index 00000000000..536393a098f --- /dev/null +++ b/packages/integrations/useSortable/demo.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/integrations/useSortable/index.md b/packages/integrations/useSortable/index.md new file mode 100644 index 00000000000..e846b0a31a0 --- /dev/null +++ b/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 + + + +``` + +### Use specifies the selector to operate on + +```vue + + + +``` + +### Use a selector to get the root element + +```vue + + + +``` + +### 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 + } +}) +``` diff --git a/packages/integrations/useSortable/index.ts b/packages/integrations/useSortable/index.ts new file mode 100644 index 00000000000..b494dd31ebf --- /dev/null +++ b/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(selector: string, list: MaybeComputedRef, + options?: UseSortableOptions): UseSortableReturn +export function useSortable(el: MaybeComputedRef, list: MaybeComputedRef, + options?: UseSortableOptions): UseSortableReturn +/** + * Wrapper for sortablejs. + * @param el + * @param list + * @param options + */ +export function useSortable( + el: MaybeComputedRef | string, + list: MaybeComputedRef, + 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( + list: MaybeComputedRef, + from: number, + to: number, +): void { + const array = resolveUnref(list) + if (to >= 0 && to < array.length) + array.splice(to, 0, array.splice(from, 1)[0]) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dede60bff09..0814aa9f705 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -199,6 +199,7 @@ importers: specifiers: '@types/nprogress': ^0.2.0 '@types/qrcode': ^1.5.0 + '@types/sortablejs': ^1.15.0 '@vueuse/core': workspace:* '@vueuse/shared': workspace:* async-validator: ^4.2.5 @@ -211,6 +212,7 @@ importers: jwt-decode: ^3.1.2 nprogress: ^0.2.0 qrcode: ^1.5.1 + sortablejs: ^1.15.0 universal-cookie: ^4.0.4 vue-demi: 0.13.11 dependencies: @@ -220,6 +222,7 @@ importers: 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 @@ -230,6 +233,7 @@ importers: jwt-decode: 3.1.2 nprogress: 0.2.0 qrcode: 1.5.1 + sortablejs: 1.15.0 universal-cookie: 4.0.4 packages/math: @@ -3040,6 +3044,10 @@ packages: '@types/node': 18.15.3 dev: true + /@types/sortablejs/1.15.0: + resolution: {integrity: sha512-qrhtM7M41EhH4tZQTNw2/RJkxllBx3reiJpTbgWCM2Dx0U1sZ6LwKp9lfNln9uqE26ZMKUaPEYaD4rzvOWYtZw==} + dev: true + /@types/trusted-types/2.0.2: resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} dev: true @@ -8817,6 +8825,10 @@ packages: tslib: 2.4.0 dev: true + /sortablejs/1.15.0: + resolution: {integrity: sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==} + dev: true + /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'}