From aff1a95c2fa4b9ef2581c5b573623d65865ad0b8 Mon Sep 17 00:00:00 2001 From: robonen Date: Sat, 19 Oct 2024 06:43:29 +0700 Subject: [PATCH] feat(packages/vue): add useClamp composable --- .../src/composables/useClamp/index.test.ts | 60 +++++++++++++++++++ .../vue/src/composables/useClamp/index.ts | 37 ++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 packages/vue/src/composables/useClamp/index.test.ts create mode 100644 packages/vue/src/composables/useClamp/index.ts diff --git a/packages/vue/src/composables/useClamp/index.test.ts b/packages/vue/src/composables/useClamp/index.test.ts new file mode 100644 index 0000000..e7daca8 --- /dev/null +++ b/packages/vue/src/composables/useClamp/index.test.ts @@ -0,0 +1,60 @@ +import { ref, readonly, computed } from 'vue'; +import { describe, it, expect } from 'vitest'; +import { useClamp } from '.'; + +describe('useClamp', () => { + it('non-reactive values should be clamped', () => { + const clampedValue = useClamp(10, 0, 5); + + expect(clampedValue.value).toBe(5); + }); + + it('clamp the value within the given range', () => { + const value = ref(10); + const clampedValue = useClamp(value, 0, 5); + + expect(clampedValue.value).toBe(5); + }); + + it('clamp the value within the given range using functions', () => { + const value = ref(10); + const clampedValue = useClamp(value, () => 0, () => 5); + + expect(clampedValue.value).toBe(5); + }); + + it('clamp readonly values', () => { + const computedValue = computed(() => 10); + const readonlyValue = readonly(ref(10)); + const clampedValue1 = useClamp(computedValue, 0, 5); + const clampedValue2 = useClamp(readonlyValue, 0, 5); + + expect(clampedValue1.value).toBe(5); + expect(clampedValue2.value).toBe(5); + }); + + it('update the clamped value when the original value changes', () => { + const value = ref(10); + const clampedValue = useClamp(value, 0, 5); + value.value = 3; + + expect(clampedValue.value).toBe(3); + }); + + it('update the clamped value when the min or max changes', () => { + const value = ref(10); + const min = ref(0); + const max = ref(5); + const clampedValue = useClamp(value, min, max); + + expect(clampedValue.value).toBe(5); + + max.value = 15; + + expect(clampedValue.value).toBe(10); + + min.value = 11; + + expect(clampedValue.value).toBe(11); + }); +}); \ No newline at end of file diff --git a/packages/vue/src/composables/useClamp/index.ts b/packages/vue/src/composables/useClamp/index.ts new file mode 100644 index 0000000..2ed2d47 --- /dev/null +++ b/packages/vue/src/composables/useClamp/index.ts @@ -0,0 +1,37 @@ +import { clamp, isFunction } from '@robonen/stdlib'; +import { computed, isReadonly, ref, toValue, type ComputedRef, type MaybeRef, type MaybeRefOrGetter, type WritableComputedRef } from 'vue'; + +/** + * @name useClamp + * @category Math + * @description Clamps a value between a minimum and maximum value + * + * @param {MaybeRefOrGetter} value The value to clamp + * @param {MaybeRefOrGetter} min The minimum value + * @param {MaybeRefOrGetter} max The maximum value + * @returns {ComputedRef} The clamped value + * + * @example + * const value = ref(10); + * const clampedValue = useClamp(value, 0, 5); + * + * @example + * const value = ref(10); + * const clampedValue = useClamp(value, () => 0, () => 5); + */ +export function useClamp(value: MaybeRef, min: MaybeRefOrGetter, max: MaybeRefOrGetter): WritableComputedRef; +export function useClamp(value: MaybeRefOrGetter, min: MaybeRefOrGetter, max: MaybeRefOrGetter): ComputedRef { + if (isFunction(value) || isReadonly(value)) + return computed(() => clamp(toValue(value), toValue(min), toValue(max))); + + const _value = ref(value); + + return computed({ + get() { + return clamp(_value.value, toValue(min), toValue(max)); + }, + set(newValue) { + _value.value = clamp(newValue, toValue(min), toValue(max)); + }, + }); +}