From bd529c6f52f9fb02e6e922082c3b4a3ea6b5c441 Mon Sep 17 00:00:00 2001 From: wiidede Date: Sat, 21 Oct 2023 12:22:37 +0800 Subject: [PATCH] refactor!: indexMap to object; remove add event --- README.md | 9 +-- playground/src/Count.vue | 24 +++++++ playground/src/ranges/01Number.vue | 6 +- playground/src/ranges/02NumberList.vue | 6 +- .../{03RangeData.vue => 03NumberList.vue} | 13 ++-- playground/src/ranges/04RangeDataList.vue | 12 ++-- playground/src/ranges/05Vertical.vue | 9 ++- src/Range.vue | 63 ++++++++++++------- src/utils.ts | 2 +- test/utils.test.ts | 5 ++ unocss.config.ts | 7 ++- 11 files changed, 100 insertions(+), 56 deletions(-) create mode 100644 playground/src/Count.vue rename playground/src/ranges/{03RangeData.vue => 03NumberList.vue} (75%) diff --git a/README.md b/README.md index 37551bc..d4a44dd 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,8 @@ generic="T = any, U = number | RangeData\" | max | number | The maximum value allowed | 100 | | step | number | Step | 1 | | vertical | boolean | Determines if the range is vertical. Note that it will generate new classes like 'm-range-v-xxx' | false | -| addable | boolean | Determines if new data can be added/deleted. This will emit event 'addThumb' | false | +| addable | boolean | Determines if new data can be added/deleted. You can specific the data to be add by `addData` prop | false | +| addData | (value: number) => RangeData | Data to be added. This will only effect while modelValue is RangeData[]. It will return { value } by default | undefined | | limit | number | the limit can be add | undefined | | smooth | boolean | Determines if the thumb(s) should only be displayed on the stop points or not | false | | deduplicate | boolean | Determines if the thumb(s) can be duplicated | true | @@ -113,12 +114,6 @@ generic="T = any, U = number | RangeData\" | renderBottomOnActive | boolean | Specifies whether to render only while the thumb is active | false | | marks | RangeMarks | Show marks under the track | undefined | -## events - -| Name | Type| Description | -| --- | --- | --- | -| add | (value: number): void | add event while click the track | - ## slots | Name | Type | Description | diff --git a/playground/src/Count.vue b/playground/src/Count.vue new file mode 100644 index 0000000..5d38b27 --- /dev/null +++ b/playground/src/Count.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/playground/src/ranges/01Number.vue b/playground/src/ranges/01Number.vue index 1311c6a..f254a43 100644 --- a/playground/src/ranges/01Number.vue +++ b/playground/src/ranges/01Number.vue @@ -1,7 +1,7 @@ diff --git a/playground/src/ranges/05Vertical.vue b/playground/src/ranges/05Vertical.vue index 6e2c3d6..6e081be 100644 --- a/playground/src/ranges/05Vertical.vue +++ b/playground/src/ranges/05Vertical.vue @@ -2,13 +2,13 @@ import { h, ref } from 'vue' import type { RangeData } from 'vue-range-multi' -const modelDataList = ref[]>([ +const model = ref[]>([ { data: 'disabled', value: 1, disabled: true }, { data: 'random1', value: 3 }, { data: 'unremovable', value: 6, unremovable: true }, ]) function handleAddData(value: number) { - modelDataList.value.push({ + model.value.push({ data: `random${Math.floor(Math.random() * 10)}`, value, }) @@ -25,7 +25,7 @@ function handleAddData(value: number) { slot diff --git a/src/Range.vue b/src/Range.vue index 0dbd0e5..610c320 100644 --- a/src/Range.vue +++ b/src/Range.vue @@ -12,6 +12,7 @@ const props = withDefaults(defineProps<{ step?: number vertical?: boolean addable?: boolean + addData?: (value: number) => RangeData limit?: number smooth?: boolean deduplicate?: boolean @@ -38,7 +39,6 @@ const props = withDefaults(defineProps<{ const emits = defineEmits<{ (e: 'update:modelValue', modelValue: typeof props.modelValue): void - (e: 'add', value: number): void }>() defineSlots<{ @@ -94,30 +94,41 @@ const stops = computed(() => { return -1 }) -const indexMap = ref([]) +const indexMap = ref>({}) +const indexMapReversed = computed(() => Object.fromEntries(Object.entries(indexMap.value).map(([k, v]) => [v, Number.parseInt(k)]))) function sort(val: RangeData[]) { let changed = false for (let i = val.length; i > 0; i--) { for (let j = 0; j < i - 1; j++) { if (val[j].value > val[j + 1].value) { swap(val, j, j + 1) - swap(indexMap.value, j, j + 1) + swap(indexMap.value, indexMapReversed.value[j], indexMapReversed.value[j + 1]) changed = true } } } changed && nextTick(() => model.value = val) } +function getAddableIndex(arr: number[]) { + [...arr].sort() + let i = 0 + for (; i < arr.length; i++) { + if (i !== arr[i]) + return i + } + return i +} watch(model, (val) => { - if (val.length > indexMap.value.length) { - for (let i = indexMap.value.length; i < val.length; i++) - indexMap.value.push(i) + const length = Object.keys(indexMap.value).length + if (val.length > length) { + for (let i = length; i < val.length; i++) + indexMap.value[getAddableIndex(Object.keys(indexMap.value).map(key => Number.parseInt(key)))] = i sort(val) } - else if (val.length < indexMap.value.length) { - for (let i = indexMap.value.length - 1; i >= val.length; i--) { - const index = indexMap.value.indexOf(i) - index > -1 && indexMap.value.splice(index, 1) + else if (val.length < length) { + for (let i = length - 1; i >= val.length; i--) { + const index = indexMapReversed.value[i] + index > -1 && delete indexMap.value[index] } sort(val) } @@ -144,11 +155,11 @@ function setCurrentPercentage(val: number) { currentPercentage.value = -1 }, 300) } -const position = computed(() => indexMap.value.map( - (index, idx) => (props.smooth && current.value === idx && currentPercentage.value > -1) - ? currentPercentage.value - : getPercentage(model.value[index].value), -)) +const position = computed(() => Object.fromEntries(Object.entries(indexMap.value).map( + ([idx, index]) => (props.smooth && current.value === Number.parseInt(idx) && currentPercentage.value > -1) + ? [idx, currentPercentage.value] + : [idx, getPercentage(model.value[index].value)], +))) function onUpdate(percentage: number) { setCurrentPercentage(percentage) @@ -164,7 +175,7 @@ function onUpdate(percentage: number) { const prev = values[i - 1] if (value < prev) { swap(modelValue, i, i - 1) - swap(indexMap.value, indexMap.value.indexOf(i), indexMap.value.indexOf(i - 1)) + swap(indexMap.value, indexMapReversed.value[i], indexMapReversed.value[i - 1]) index -= 1 } } @@ -174,7 +185,7 @@ function onUpdate(percentage: number) { const next = values[i + 1] if (value > next) { swap(modelValue, i, i + 1) - swap(indexMap.value, indexMap.value.indexOf(i), indexMap.value.indexOf(i + 1)) + swap(indexMap.value, indexMapReversed.value[i], indexMapReversed.value[i + 1]) index += 1 } } @@ -188,8 +199,8 @@ function onDelete() { const modelValue = model.value modelValue.splice(valueIndex, 1) model.value = modelValue - indexMap.value.splice(current.value, 1) - indexMap.value = indexMap.value.map(idx => idx >= valueIndex ? idx - 1 : idx) + delete indexMap.value[current.value] + indexMap.value = Object.fromEntries(Object.entries(indexMap.value).map(([idx, index]) => index >= valueIndex ? [idx, index - 1] : [idx, index])) } let addTiming = false @@ -205,7 +216,15 @@ function addThumb(e: MouseEvent) { const value = getValue(percent) if (model.value.some(item => item.value === value)) return - emits('add', value) + const modelValue = model.value + if (modelType.value === 'numberList') { + modelValue.push({ value }) + model.value = modelValue + } + else if (modelType.value === 'dataList') { + modelValue.push(props.addData ? props.addData(value) : { value }) + model.value = modelValue + } } provide(RangeTrackRefKey, trackRef) @@ -227,8 +246,8 @@ provide(RangeTrackRefKey, trackRef)
diff --git a/src/utils.ts b/src/utils.ts index 3585722..8b0f077 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -export function swap(arr: T[], i: number, j: number): void { +export function swap(arr: T, i: keyof T, j: keyof T): void { [arr[i], arr[j]] = [arr[j], arr[i]] } diff --git a/test/utils.test.ts b/test/utils.test.ts index 646cbfc..1e4c16e 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -7,6 +7,11 @@ describe('swap', () => { swap(arr, 0, 2) expect(arr).toEqual([3, 2, 1]) }) + it('should swap elements in an object', () => { + const obj = { a: 1, b: 2, c: 3 } + swap(obj, 'a', 'c') + expect(obj).toEqual({ a: 3, b: 2, c: 1 }) + }) }) describe('value and percentage conversion', () => { diff --git a/unocss.config.ts b/unocss.config.ts index 36d3619..a6757ba 100644 --- a/unocss.config.ts +++ b/unocss.config.ts @@ -15,8 +15,8 @@ export default defineConfig({ // classes ['m-range', 'min-h-1 min-w-1 box-content'], // theme - ['m-range-theme', '[--c-primary:#409EFF] [--c-fill:#E4E7ED] [--c-fill-stop:#F5F5F5] [--c-fill-thumb:#fff]'], - ['m-range-theme-dark', '[--c-primary:#3070ED] [--c-fill:#444] [--c-fill-stop:#555] [--c-fill-thumb:#333]'], + ['m-range-theme', '[--c-primary:#409EFF] [--c-fill:#E4E7ED] [--c-fill-stop:#F5F5F5] [--c-fill-thumb:#fff] [--c-drop:var(--c-primary)]'], + ['m-range-theme-dark', '[--c-primary:#3070ED] [--c-fill:#444] [--c-fill-stop:#555] [--c-fill-thumb:#333] [--c-drop:var(--c-primary)]'], // track ['m-range-small', 'h2 [--m-range-h:0.5rem]'], ['m-range-medium', 'h4 [--m-range-h:1rem]'], @@ -48,7 +48,7 @@ export default defineConfig({ ['m-range-thumb-small', 'top--0 bottom--0 [--m-range-thumb-h:0rem]'], ['m-range-thumb-medium', 'top--1 bottom--1 [--m-range-thumb-h:0.25rem]'], ['m-range-thumb-large', 'top--2 bottom--2 [--m-range-thumb-h:0.5rem]'], - ['m-range-thumb-active', 'z-1 drop-shadow-[0.1rem_0.15rem_0.25rem_var(--c-primary)]'], + ['m-range-thumb-active', 'z-1 drop-shadow-[0.1rem_0.15rem_0.25rem_var(--c-drop)]'], ['m-range-transition-container', 'absolute h-full w0 left-50%'], ['m-range-thumb-top-container', 'absolute left-50% top-0 translate-x--50% translate-y-[calc(-100%_-_4px)]'], ['m-range-thumb-bottom-container', 'absolute left-50% bottom-0 translate-x--50% translate-y-[calc(100%_+_4px)]'], @@ -70,6 +70,7 @@ export default defineConfig({ stop: 'var(--c-fill-stop)', thumb: 'var(--c-fill-thumb)', }, + drop: 'var(--c-drop)', primary: 'var(--c-primary)', }, },