From d7c32f2f4533cae6e0bd6e381e13b04a41984b55 Mon Sep 17 00:00:00 2001 From: robonen Date: Thu, 30 May 2024 04:16:45 +0700 Subject: [PATCH] feat(packages/vue): add useCounter composable --- .../vue/src/composables/useCounter/demo.vue | 8 ++ .../src/composables/useCounter/index.test.ts | 76 +++++++++++++++++++ .../vue/src/composables/useCounter/index.ts | 58 ++++++++++++++ packages/vue/src/index.ts | 1 + 4 files changed, 143 insertions(+) create mode 100644 packages/vue/src/composables/useCounter/demo.vue create mode 100644 packages/vue/src/composables/useCounter/index.test.ts create mode 100644 packages/vue/src/composables/useCounter/index.ts create mode 100644 packages/vue/src/index.ts diff --git a/packages/vue/src/composables/useCounter/demo.vue b/packages/vue/src/composables/useCounter/demo.vue new file mode 100644 index 0000000..a94d1cb --- /dev/null +++ b/packages/vue/src/composables/useCounter/demo.vue @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/packages/vue/src/composables/useCounter/index.test.ts b/packages/vue/src/composables/useCounter/index.test.ts new file mode 100644 index 0000000..6ad2196 --- /dev/null +++ b/packages/vue/src/composables/useCounter/index.test.ts @@ -0,0 +1,76 @@ +import { it, expect, describe } from 'vitest'; +import { useCounter } from '.'; +import { ref } from 'vue'; + +describe('useCounter', () => { + it('initialize count with the provided initial value', () => { + const { count } = useCounter(5); + expect(count.value).toBe(5); + }); + + it('initialize count with the provided initial value from a ref', () => { + const { count } = useCounter(ref(5)); + expect(count.value).toBe(5); + }); + + it('increment count by 1 by default', () => { + const { count, increment } = useCounter(0); + increment(); + expect(count.value).toBe(1); + }); + + it('increment count by the specified delta', () => { + const { count, increment } = useCounter(0); + increment(5); + expect(count.value).toBe(5); + }); + + it('decrement count by 1 by default', () => { + const { count, decrement } = useCounter(5); + decrement(); + expect(count.value).toBe(4); + }); + + it('decrement count by the specified delta', () => { + const { count, decrement } = useCounter(10); + decrement(5); + expect(count.value).toBe(5); + }); + + it('set count to the specified value', () => { + const { count, set } = useCounter(0); + set(10); + expect(count.value).toBe(10); + }); + + it('get the current count value', () => { + const { get } = useCounter(5); + expect(get()).toBe(5); + }); + + it('reset count to the initial value', () => { + const { count, reset } = useCounter(10); + count.value = 5; + reset(); + expect(count.value).toBe(10); + }); + + it('reset count to the specified value', () => { + const { count, reset } = useCounter(10); + count.value = 5; + reset(20); + expect(count.value).toBe(20); + }); + + it('clamp count to the minimum value', () => { + const { count, decrement } = useCounter(Number.MIN_SAFE_INTEGER); + decrement(); + expect(count.value).toBe(Number.MIN_SAFE_INTEGER); + }); + + it('clamp count to the maximum value', () => { + const { count, increment } = useCounter(Number.MAX_SAFE_INTEGER); + increment(); + expect(count.value).toBe(Number.MAX_SAFE_INTEGER); + }); +}); \ No newline at end of file diff --git a/packages/vue/src/composables/useCounter/index.ts b/packages/vue/src/composables/useCounter/index.ts new file mode 100644 index 0000000..3f27868 --- /dev/null +++ b/packages/vue/src/composables/useCounter/index.ts @@ -0,0 +1,58 @@ +import { ref, unref, type MaybeRef } from 'vue'; +import { clamp } from '@robonen/stdlib'; + +export interface UseCounterOptions { + min?: number; + max?: number; +} + +/** + * @name useCounter + * @category Utilities + * @description A composable that provides a counter with increment, decrement, set, get, and reset functions + * + * @param {MaybeRef} [initialValue=0] The initial value of the counter + * @param {UseCounterOptions} [options={}] The options for the counter + * @param {number} [options.min=Number.MIN_SAFE_INTEGER] The minimum value of the counter + * @param {number} [options.max=Number.MAX_SAFE_INTEGER] The maximum value of the counter + * + * @example + * const { count, increment } = useCounter(0); + * + * @example + * const { count, increment, decrement, set, get, reset } = useCounter(0, { min: 0, max: 10 }); + */ +export function useCounter(initialValue: MaybeRef = 0, options: UseCounterOptions = {}) { + let _initialValue = unref(initialValue); + const count = ref(initialValue); + + const { + min = Number.MIN_SAFE_INTEGER, + max = Number.MAX_SAFE_INTEGER, + } = options; + + const increment = (delta = 1) => + count.value = clamp(count.value + delta, min, max); + + const decrement = (delta = 1) => + count.value = clamp(count.value - delta, min, max); + + const set = (value: number) => + count.value = clamp(value, min, max); + + const get = () => count.value; + + const reset = (value = _initialValue) => { + _initialValue = value; + return set(value); + }; + + return { + count, + increment, + decrement, + set, + get, + reset, + }; +}; diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts new file mode 100644 index 0000000..65eee94 --- /dev/null +++ b/packages/vue/src/index.ts @@ -0,0 +1 @@ +export * from './composables'; \ No newline at end of file