diff --git a/packages/core/index.ts b/packages/core/index.ts index c0da16188dd..872b92d91c8 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -96,6 +96,7 @@ export * from './useScroll' export * from './useScrollLock' export * from './useSessionStorage' export * from './useShare' +export * from './useSorted' export * from './useSpeechRecognition' export * from './useSpeechSynthesis' export * from './useStepper' diff --git a/packages/core/useSorted/demo.vue b/packages/core/useSorted/demo.vue new file mode 100644 index 00000000000..8b12ce32b5f --- /dev/null +++ b/packages/core/useSorted/demo.vue @@ -0,0 +1,58 @@ + + + diff --git a/packages/core/useSorted/index.md b/packages/core/useSorted/index.md new file mode 100644 index 00000000000..e2df0c3172e --- /dev/null +++ b/packages/core/useSorted/index.md @@ -0,0 +1,45 @@ +--- +category: Array +--- + +# useSorted + +reactive sort array + +## Usage + +```ts +import { useSorted } from '@vueuse/core' + +// general sort +const source = [10, 3, 5, 7, 2, 1, 8, 6, 9, 4] +const sorted = useSorted(source) +console.log(sorted.value) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +console.log(source) // [10, 3, 5, 7, 2, 1, 8, 6, 9, 4] + +// object sort +const objArr = [{ + name: 'John', + age: 40, +}, { + name: 'Jane', + age: 20, +}, { + name: 'Joe', + age: 30, +}, { + name: 'Jenny', + age: 22, +}] +const objSorted = useSorted(objArr, (a, b) => a.age - b.age) +``` +### dirty mode + +dirty mode will change the source array. +```ts +const source = ref([10, 3, 5, 7, 2, 1, 8, 6, 9, 4]) +const sorted = useSorted(source, (a, b) => a - b, { + dirty: true, +}) +console.log(source)// output: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 +``` diff --git a/packages/core/useSorted/index.test.ts b/packages/core/useSorted/index.test.ts new file mode 100644 index 00000000000..8683aa9e60f --- /dev/null +++ b/packages/core/useSorted/index.test.ts @@ -0,0 +1,94 @@ +import { unref } from 'vue' +import { useSorted } from '.' + +interface User { + name: string + age: number +} + +const arr = [10, 3, 5, 7, 2, 1, 8, 6, 9, 4] +const arrSorted = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +const objArr: User[] = [ + { + name: 'John', + age: 40, + }, + { + name: 'Jane', + age: 20, + }, + { + name: 'Joe', + age: 30, + }, + { + name: 'Jenny', + age: 22, + }, +] +const objectSorted: User[] = [ + { + name: 'Jane', + age: 20, + }, + { + name: 'Jenny', + age: 22, + }, + { + name: 'Joe', + age: 30, + }, + { + name: 'John', + age: 40, + }, +] + +describe('useSorted', () => { + it('should be defined', () => { + expect(useSorted).toBeDefined() + }) + + it('should pure sort function', () => { + const sorted = useSorted(arr) + expect(unref(sorted)).toMatchObject(arrSorted) + expect(unref(arr)).toMatchInlineSnapshot(` + [ + 10, + 3, + 5, + 7, + 2, + 1, + 8, + 6, + 9, + 4, + ] + `) + }) + + it('should dirty sort', () => { + const dirtyArr = [...arr] + const sorted = useSorted(dirtyArr, (a, b) => a - b, { dirty: true }) + + expect(unref(sorted)).toMatchObject(arrSorted) + expect(unref(dirtyArr)).toMatchObject(unref(sorted)) + }) + + it('should sort object', () => { + const sorted = useSorted(objArr, (a, b) => a.age - b.age) + + expect(unref(sorted)).toMatchObject(objectSorted) + }) + + it('should sort object by options.compareFn', () => { + const sorted = useSorted(objArr, { + compareFn: (a, b) => a.age - b.age, + }) + + expect(unref(sorted)).toMatchObject(objectSorted) + }) +}) diff --git a/packages/core/useSorted/index.ts b/packages/core/useSorted/index.ts new file mode 100644 index 00000000000..aebffa3a909 --- /dev/null +++ b/packages/core/useSorted/index.ts @@ -0,0 +1,75 @@ +import type { Ref } from 'vue-demi' +import type { MaybeRef } from '@vueuse/shared' +import { computed, isRef, unref, watchEffect } from 'vue-demi' + +export type UseSortedCompareFn = (a: T, b: T) => number + +export type UseSortedFn = (arr: T[], compareFn: UseSortedCompareFn) => T[] + +export interface UseSortedOptions { + /** + * sort algorithm + */ + sortFn?: UseSortedFn + /** + * compare function + */ + compareFn?: UseSortedCompareFn + /** + * change the value of the source array + * @default false + */ + dirty?: boolean +} + +const defaultSortFn: UseSortedFn = (source: T[], compareFn: UseSortedCompareFn): T[] => source.sort(compareFn) +const defaultCompare: UseSortedCompareFn = (a, b) => a - b + +export function useSorted(source: MaybeRef, compareFn?: UseSortedCompareFn): Ref +export function useSorted(source: MaybeRef, options?: UseSortedOptions): Ref +export function useSorted(source: MaybeRef, compareFn?: UseSortedCompareFn, options?: Omit, 'compareFn'>): Ref +/** + * reactive sort array + * + * @see https://vueuse.org/useSorted + * @param source source array + * @param options + */ +export function useSorted(...args: any[]) { + const [source] = args + let compareFn: UseSortedCompareFn = defaultCompare + let options: UseSortedOptions = {} + + if (args.length === 2) { + if (typeof args[1] === 'object') { + options = args[1] + compareFn = options.compareFn ?? defaultCompare + } + else { + compareFn = args[1] ?? defaultCompare + } + } + else if (args.length > 2) { + compareFn = args[1] ?? defaultCompare + options = args[2] ?? {} + } + + const { + dirty = false, + sortFn = defaultSortFn, + } = options + + if (!dirty) + return computed(() => sortFn([...unref(source)], compareFn)) + + // dirty + watchEffect(() => { + const result = sortFn(unref(source), compareFn) + if (isRef(source)) + source.value = result + else + source.splice(0, source.length, ...result) + }) + + return source +}