Skip to content

Commit

Permalink
type!: better type that relative to modelValue
Browse files Browse the repository at this point in the history
  • Loading branch information
wiidede committed Oct 17, 2023
1 parent 4eaa105 commit 8f7cca1
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 63 deletions.
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ interface VueRangeMultiResolverOptions {

## Props

generic="T"
generic="T = any, U = number | RangeData\<T>"

| Name | Type | Description | Default |
| --- | --- | --- | --- |
| v-model:modelValue* | number \| number[] \| RangeData\<T>[] | Model value. It will automatically detect the type of model and show the corresponding thumb(s) | [] |
| v-model:modelValue* | U | U[] | Model value. It will automatically detect the type of model and show the corresponding thumb(s) | [] |
| min | number | The minimum value allowed | 0 |
| max | number | The maximum value allowed | 100 |
| step | number | Step | 1 |
Expand All @@ -106,9 +106,9 @@ generic="T"
| size | 'small' \| 'medium' \| 'large' | Track size | 'small' |
| thumbType | 'circle' \| 'square' \| 'rect' | Thumb type(default 'rect' while size is 'large', otherwise 'small') | 'circle' \| 'rect' |
| thumbSize | 'small' \| 'medium' \| 'large' | Thumb size | 'medium' |
| renderTop | (data: RangeData\<T>) => VNode | A render function for displaying content above the thumb | undefined |
| renderTop | (data: U) => VNode | A render function for displaying content above the thumb | undefined |
| renderTopOnActive | boolean | Specifies whether to render only while the thumb is active | false |
| renderBottom | (data: RangeData\<T>) => VNode | A render function for displaying content below the thumb | undefined |
| renderBottom | (data: U) => VNode | A render function for displaying content below the thumb | undefined |
| renderBottomOnActive | boolean | Specifies whether to render only while the thumb is active | false |

## events
Expand All @@ -121,21 +121,22 @@ generic="T"

| Name | Type | Description |
| --- | --- | --- |
| top | { data: RangeData\<T> } | render above the thumb, only effect while renderTop is undefined |
| bottom | { data: RangeData\<T> } | render below the thumb, only effect while renderBottom is undefined |
| top | { data: U } | render above the thumb, only effect while renderTop is undefined |
| bottom | { data: U } | render below the thumb, only effect while renderBottom is undefined |

## types

```ts
export type RangeRenderFn<T = unknown> = (data: RangeData<T>) => VNode
export interface RangeData<T = unknown> {
export type RangeValueType<T> = number | RangeData<T>
export interface RangeData<T, U = RangeValueType<T>> {
value: number
data?: T
disabled?: boolean
renderTop?: RangeRenderFn<T>
renderBottom?: RangeRenderFn<T>
renderTop?: RangeRenderFn<T, U>
renderBottom?: RangeRenderFn<T, U>
}
export type RangeValue<T = unknown> = number | number[] | RangeData<T>[]
export type RangeRenderFn<T, U = RangeValueType<T>> = (data: U) => VNode
export type RangeValue<T, U = RangeValueType<T>> = U | U[]
```
## theme
Expand Down
30 changes: 15 additions & 15 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import { h, ref } from 'vue'
import type { RangeData } from 'vue-range-multi'
import { useDark, useToggle } from '@vueuse/core'
const modelSingle = ref<number>(3)
const modelNumber = ref<number>(3)
const modelNumbers = ref<number[]>([10, 20])
const modelNumberList = ref<number[]>([10, 20])
const modelNumbersAdd = ref<number[]>([10, 20, 30, 40, 50, 60, 70, 80, 90])
const modelNumberListAdd = ref<number[]>([10, 20, 30, 40, 50, 60, 70, 80, 90])
function handleAddNumbers(value: number) {
modelNumbersAdd.value.push(value)
modelNumberListAdd.value.push(value)
}
const modelData = ref<RangeData<string>[]>([
const modelDataList = ref<RangeData<string>[]>([
{ data: '00:00', value: 10, disabled: true },
{ data: '59:59', value: 90 },
])
function handleAddData(value: number) {
const date = new Date()
modelData.value.push({
modelDataList.value.push({
data: `${date.getMinutes()}:${date.getSeconds()}`,
value,
})
Expand All @@ -41,10 +41,10 @@ const toggleDark = useToggle(isDark)
<br>
<div class="flex items-baseline">
<span class="label">modelValue</span>
<pre class="value">{{ modelSingle }}</pre>
<pre class="value">{{ modelNumber }}</pre>
</div>
<Range
v-model="modelSingle"
v-model="modelNumber"
class="w-full py1"
:min="0"
:max="10"
Expand All @@ -59,10 +59,10 @@ const toggleDark = useToggle(isDark)
</div>
<div class="flex items-baseline">
<span class="label">modelValue</span>
<pre class="value">{{ JSON.stringify(modelNumbers) }}</pre>
<pre class="value">{{ JSON.stringify(modelNumberList) }}</pre>
</div>
<Range
v-model="modelNumbers"
v-model="modelNumberList"
class="w-full py1"
range-highlight
size="medium"
Expand All @@ -81,10 +81,10 @@ const toggleDark = useToggle(isDark)
</div>
<div class="flex items-baseline">
<span class="label">modelValue</span>
<pre class="value">{{ JSON.stringify(modelNumbersAdd) }}</pre>
<pre class="value">{{ JSON.stringify(modelNumberListAdd) }}</pre>
</div>
<Range
v-model="modelNumbersAdd"
v-model="modelNumberListAdd"
class="add-range w-full pb1 pt8"
:step="5"
addable
Expand All @@ -97,7 +97,7 @@ const toggleDark = useToggle(isDark)
>
<template #top="{ data }">
<div class="c-primary">
{{ data.value }}
{{ data }}
</div>
</template>
</Range>
Expand All @@ -114,10 +114,10 @@ const toggleDark = useToggle(isDark)
</div>
<div class="flex items-baseline">
<span class="label">modelValue</span>
<pre class="value">{{ JSON.stringify(modelData) }}</pre>
<pre class="value">{{ JSON.stringify(modelDataList) }}</pre>
</div>
<Range
v-model="modelData"
v-model="modelDataList"
class="data-range w-full py8"
addable
size="large"
Expand Down
49 changes: 28 additions & 21 deletions src/Range.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<script lang="ts" setup generic="T">
<script lang="ts" setup generic="T = any, U = RangeValueType<T>">
import { computed, nextTick, provide, ref, watch } from 'vue'
import { RangeContainerRefKey, RangeTrackRefKey } from './Range'
import RangeThumb from './RangeThumb.vue'
import type { RangeData, RangeRenderFn, RangeValue } from './type'
import type { RangeData, RangeRenderFn, RangeValue, RangeValueType } from './type'
import { percentage2value, swap, value2percentage } from './utils'
const props = withDefaults(defineProps<{
modelValue: RangeValue<T>
modelValue: RangeValue<T, U>
min?: number
max?: number
step?: number
Expand All @@ -19,9 +19,9 @@ const props = withDefaults(defineProps<{
size?: 'small' | 'medium' | 'large'
thumbType?: 'circle' | 'square' | 'rect'
thumbSize?: 'small' | 'medium' | 'large'
renderTop?: RangeRenderFn<T>
renderTop?: RangeRenderFn<T, U>
renderTopOnActive?: boolean
renderBottom?: RangeRenderFn<T>
renderBottom?: RangeRenderFn<T, U>
renderBottomOnActive?: boolean
}>(), {
modelValue: () => [],
Expand All @@ -40,41 +40,47 @@ const emits = defineEmits<{
}>()
defineSlots<{
top(props: { data: RangeData<T> }): any
bottom(props: { data: RangeData<T> }): any
top(props: { data: U }): any
bottom(props: { data: U }): any
}>()
const modelType = computed<'single' | 'numbers' | 'data'>(() => {
const modelType = computed<'number' | 'data' | 'numberList' | 'dataList'>(() => {
const value = props.modelValue
if (Array.isArray(value) && typeof value[0] === 'number')
return 'numbers'
return 'numberList'
else if (Array.isArray(value))
return 'data'
return 'dataList'
else if (typeof value === 'number')
return 'number'
else
return 'single'
return 'data'
})
const model = computed<RangeData<T>[]>({
const model = computed<RangeData<T, U>[]>({
get: () => {
const value = props.modelValue
if (Array.isArray(value))
return value.map(item => typeof item === 'number' ? { value: item } : item)
return value.map(item => typeof item === 'number' ? { value: (item as number) } : (item as RangeData<T, U>))
else
return [{ key: 0, value }]
return [typeof value === 'number' ? { value: (value as number) } : (value as RangeData<T, U>)]
},
set: (value) => {
if (modelType.value === 'single')
emits('update:modelValue', value[0]?.value)
else if (modelType.value === 'numbers')
emits('update:modelValue', value.map(item => item.value))
let res: any
if (modelType.value === 'number')
res = value[0].value
else if (modelType.value === 'data')
res = value[0]
else if (modelType.value === 'numberList')
res = value.map(item => (item.value))
else
emits('update:modelValue', value)
res = value
emits('update:modelValue', res)
},
})
const allowAdd = computed(() =>
props.addable
&& (!props.limit || model.value.length < props.limit)
&& modelType.value !== 'single',
&& !['data', 'number'].includes(modelType.value),
)
const stops = computed(() => {
const stops = Math.floor((props.max - props.min) / props.step) + 1
Expand All @@ -87,7 +93,7 @@ const stops = computed(() => {
})
const indexMap = ref<number[]>([])
function sort(val: RangeData<T>[]) {
function sort(val: RangeData<T, U>[]) {
let changed = false
for (let i = val.length; i > 0; i--) {
for (let j = 0; j < i - 1; j++) {
Expand Down Expand Up @@ -233,6 +239,7 @@ provide(RangeContainerRefKey, containerRef)
:active="current === idx"
:disabled="model[index].disabled"
:data="model[index]"
:model-type="modelType"
:render-top="model[index].renderTop || renderTop"
:render-top-on-active="renderTopOnActive"
:render-bottom="model[index].renderBottom || renderBottom"
Expand Down
23 changes: 12 additions & 11 deletions src/RangeThumb.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
<script lang="ts" setup generic="T">
<script lang="ts" setup generic="T = any, U = RangeValueType<T>">
import { inject, nextTick, ref } from 'vue'
import { RangeContainerRefKey, RangeTrackRefKey } from './Range'
import Render from './Render.vue'
import type { RangeData, RangeRenderFn } from './type'
import type { RangeData, RangeRenderFn, RangeValueType } from './type'
const props = defineProps<{
position: number
data: RangeData<T>
data: RangeData<T, U>
modelType: 'number' | 'data' | 'numberList' | 'dataList'
active?: boolean
disabled?: boolean
addable?: boolean
thumbType?: 'circle' | 'square' | 'rect'
thumbSize?: 'small' | 'medium' | 'large'
renderTop?: RangeRenderFn<T>
renderTop?: RangeRenderFn<T, U>
renderTopOnActive?: boolean
renderBottom?: RangeRenderFn<T>
renderBottom?: RangeRenderFn<T, U>
renderBottomOnActive?: boolean
}>()
Expand All @@ -27,8 +28,8 @@ const emits = defineEmits<{
}>()
defineSlots<{
top(props: { data: RangeData<T> }): any
bottom(props: { data: RangeData<T> }): any
top(props: { data: U }): any
bottom(props: { data: U }): any
}>()
const thumbRef = ref<HTMLElement>()
Expand Down Expand Up @@ -107,16 +108,16 @@ function onPointerDown(e: PointerEvent) {
<Transition name="fade">
<div v-if="!renderTopOnActive || active" class="m-range-transition-container">
<div class="m-range-thumb-top-container">
<Render v-if="renderTop" :render="() => renderTop?.(data)" />
<slot v-else name="top" :data="data" />
<Render v-if="renderTop" :render="() => renderTop?.((['data', 'dataList'].includes(modelType) ? data : data.value) as U)" />
<slot v-else name="top" :data="(['data', 'dataList'].includes(modelType) ? data : data.value) as U" />
</div>
</div>
</Transition>
<Transition name="fade">
<div v-if="!renderBottomOnActive || active" class="m-range-transition-container">
<div class="m-range-thumb-bottom-container">
<Render v-if="renderBottom" :render="() => renderBottom?.(data)" />
<slot v-else name="bottom" :data="data" />
<Render v-if="renderBottom" :render="() => renderBottom?.((['data', 'dataList'].includes(modelType) ? data : data.value) as U)" />
<slot v-else name="bottom" :data="(['data', 'dataList'].includes(modelType) ? data : data.value) as U" />
</div>
</div>
</Transition>
Expand Down
11 changes: 6 additions & 5 deletions src/type.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import type { VNode } from 'vue'

export type RangeRenderFn<T = unknown> = (data: RangeData<T>) => VNode
export interface RangeData<T = unknown> {
export type RangeValueType<T> = number | RangeData<T>
export interface RangeData<T, U = RangeValueType<T>> {
value: number
data?: T
disabled?: boolean
renderTop?: RangeRenderFn<T>
renderBottom?: RangeRenderFn<T>
renderTop?: RangeRenderFn<T, U>
renderBottom?: RangeRenderFn<T, U>
}
export type RangeValue<T = unknown> = number | number[] | RangeData<T>[]
export type RangeRenderFn<T, U = RangeValueType<T>> = (data: U) => VNode
export type RangeValue<T, U = RangeValueType<T>> = U | U[]

0 comments on commit 8f7cca1

Please sign in to comment.