Skip to content

Commit

Permalink
feat: support vertical
Browse files Browse the repository at this point in the history
  • Loading branch information
wiidede committed Oct 20, 2023
1 parent d4bdd2a commit 0a129f7
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 31 deletions.
33 changes: 22 additions & 11 deletions src/Range.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const props = withDefaults(defineProps<{
min?: number
max?: number
step?: number
vertical?: boolean
addable?: boolean
limit?: number
smooth?: boolean
Expand Down Expand Up @@ -200,8 +201,8 @@ function addThumb(e: MouseEvent) {
if (!trackRef?.value || !allowAdd.value)
return
const trackRect = trackRef.value.getBoundingClientRect()
const offset = e.clientX - trackRect.left
const percent = offset / trackRect.width * 100
const offset = props.vertical ? e.clientY - trackRect.top : e.clientX - trackRect.left
const percent = offset / (props.vertical ? trackRect.height : trackRect.width) * 100
const value = getValue(percent)
if (model.value.some(item => item.value === value))
return
Expand All @@ -216,33 +217,42 @@ provide(RangeContainerRefKey, containerRef)
<div
ref="containerRef"
class="dark:m-range-theme-dark m-range-theme m-range"
:class="[`m-range-${size}`, `m-range-thumb-${thumbSize}`]"
:class="[`m-range-${vertical ? 'v-' : ''}${size}`, `m-range-${vertical ? 'v-' : ''}thumb-${thumbSize}`]"
>
<div
ref="trackRef"
class="m-range-track"
:class="{ 'cursor-copy': allowAdd }"
:class="[vertical ? 'm-range-v-track' : 'm-range-track', { 'cursor-copy': allowAdd }]"
@pointerdown="addTiming = true"
@pointerleave="addTiming = false"
@pointerup.prevent="addThumb"
>
<div v-show="rangeHighlight && model.length === 2" class="m-range-highlight-container">
<div
class="m-range-highlight"
:style="{ left: `${Math.min(...position)}%`, right: `${100 - Math.max(...position)}%` }"
:class="vertical ? 'm-range-v-highlight' : 'm-range-highlight'"
:style="vertical
? { top: `${Math.min(...position)}%`, bottom: `${100 - Math.max(...position)}%` }
: { left: `${Math.min(...position)}%`, right: `${100 - Math.max(...position)}%` }"
/>
</div>
<div v-if="stops > 0" class="m-range-points-area">
<div class="m-range-points-container">
<div :class="vertical ? 'm-range-v-points-container' : 'm-range-points-container'">
<div v-for="index in stops" :key="index" class="m-range-points" />
</div>
</div>
<div v-if="marks" class="m-range-marks">
<div v-if="marks" :class="vertical ? 'm-range-v-marks' : 'm-range-marks'">
<template v-for="mark, key in marks" :key="key">
<div v-if="(typeof mark === 'string')" class="m-range-mark-item" :style="{ left: `${key}%` }">
<div
v-if="(typeof mark === 'string')"
:class="vertical ? 'm-range-v-mark-item' : 'm-range-mark-item'"
:style="vertical ? { top: `${key}%` } : { left: `${key}%` }"
>
{{ mark }}
</div>
<div v-else class="m-range-mark-item" :class="mark.class" :style="{ left: `${key}%`, ...mark.style }">
<div
v-else
:class="[vertical ? 'm-range-v-mark-item' : 'm-range-mark-item', mark.class]"
:style="vertical ? { top: `${key}%` } : { left: `${key}%`, ...mark.style }"
>
{{ mark.label }}
</div>
</template>
Expand All @@ -260,6 +270,7 @@ provide(RangeContainerRefKey, containerRef)
:render-top-on-active="renderTopOnActive"
:render-bottom="model[index].renderBottom || renderBottom"
:render-bottom-on-active="renderBottomOnActive"
:vertical="vertical"
:addable="addable"
:thumb-type="thumbType || (size === 'large' ? 'rect' : 'circle')"
:thumb-size="thumbSize"
Expand Down
38 changes: 23 additions & 15 deletions src/RangeThumb.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const props = defineProps<{
active?: boolean
disabled?: boolean
unremovable?: boolean
vertical?: boolean
addable?: boolean
thumbType?: 'circle' | 'square' | 'rect'
thumbSize?: 'small' | 'medium' | 'large'
Expand Down Expand Up @@ -41,30 +42,37 @@ const removable = computed(() => props.addable && !props.unremovable)
const deleting = ref(false)
function shouldDelete(clientY: number) {
function shouldDelete(offset: number) {
if (!containerRef?.value || !trackRef?.value)
return false
const containerRect = containerRef.value.getBoundingClientRect()
const trackRect = trackRef.value.getBoundingClientRect()
const top = Math.min(containerRect.top, trackRect.top - 32)
const bottom = Math.max(containerRect.bottom, trackRect.bottom + 32)
return clientY < top || clientY > bottom
if (props.vertical) {
const left = Math.min(containerRect.left, trackRect.left - 32)
const right = Math.max(containerRect.right, trackRect.right + 32)
return offset < left || offset > right
}
else {
const top = Math.min(containerRect.top, trackRect.top - 32)
const bottom = Math.max(containerRect.bottom, trackRect.bottom + 32)
return offset < top || offset > bottom
}
}
function onPointerMove(e: PointerEvent) {
if (!thumbRef.value || !trackRef?.value || props.disabled)
return
const trackRect = trackRef.value.getBoundingClientRect()
const offset = e.clientX - trackRect.left
const percent = offset / trackRect.width * 100
const offset = props.vertical ? e.clientY - trackRect.top : e.clientX - trackRect.left
const percent = offset / (props.vertical ? trackRect.height : trackRect.width) * 100
if (percent < 0)
emits('update', 0)
else if (percent > 100)
emits('update', 100)
else if (!Number.isNaN(percent))
emits('update', percent)
if (removable.value)
deleting.value = shouldDelete(e.clientY)
deleting.value = shouldDelete(props.vertical ? e.clientX : e.clientY)
}
async function onPointerUp(e: PointerEvent) {
Expand All @@ -76,7 +84,7 @@ async function onPointerUp(e: PointerEvent) {
return
}
if (removable.value) {
if (shouldDelete(e.clientY)) {
if (shouldDelete(props.vertical ? e.clientX : e.clientY)) {
deleting.value = false
emits('delete')
}
Expand All @@ -97,32 +105,32 @@ function onPointerDown(e: PointerEvent) {
<template>
<div
ref="thumbRef"
class="m-range-thumb"
:class="[
vertical ? 'm-range-v-thumb' : 'm-range-thumb',
{
'm-range-thumb-active': active,
'op-20': removable && deleting && active,
},
disabled ? 'cursor-not-allowed' : removable ? 'cursor-move' : 'cursor-ew-resize',
`m-range-thumb-${thumbType}`,
`m-range-thumb-${thumbSize}`,
disabled ? 'cursor-not-allowed' : removable ? 'cursor-move' : vertical ? 'cursor-ns-resize' : 'cursor-ew-resize',
`m-range-${vertical ? 'v-' : ''}thumb-${thumbType}`,
`m-range-${vertical ? 'v-' : ''}thumb-${thumbSize}`,
]"
:style="{ left: `${position}%` }"
:style="vertical ? { top: `${position}%` } : { left: `${position}%` }"
@pointerdown="onPointerDown"
@mousedown.prevent="() => {}"
@touchstart="(e) => { e.cancelable === true && e.preventDefault() }"
>
<Transition name="fade">
<div v-if="!renderTopOnActive || active" class="m-range-transition-container">
<div class="m-range-thumb-top-container">
<div :class="vertical ? 'm-range-v-thumb-top-container' : 'm-range-thumb-top-container'">
<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">
<div :class="vertical ? 'm-range-v-thumb-bottom-container' : 'm-range-thumb-bottom-container'">
<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>
Expand Down
33 changes: 28 additions & 5 deletions unocss.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,34 @@ export default defineConfig({
// common
['m-range-border', 'border-2 border-primary border-solid'],
// classes
['m-range', 'min-h-1 box-content'],
['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]'],
// track
['m-range-small', 'h2 [--m-range-h:0.5rem]'],
['m-range-medium', 'h4 [--m-range-h:1rem]'],
['m-range-large', 'h8 [--m-range-h:2rem]'],
['m-range-track', 'relative h-full bg-fill select-none rd-full'],
['m-range-v-small', 'w2 [--m-range-w:0.5rem]'],
['m-range-v-medium', 'w4 [--m-range-w:1rem]'],
['m-range-v-large', 'w8 [--m-range-w:2rem]'],
['m-range-v-track', 'relative w-full h-full bg-fill select-none rd-full'],
// highlight
['m-range-highlight-container', 'absolute h-full w-full overflow-hidden'],
['m-range-highlight', 'h-full bg-primary absolute rd-1px'],
['m-range-v-highlight', 'w-full bg-primary absolute rd-1px'],
// points
['m-range-points-area', 'absolute h-full w-full rd-inherit overflow-hidden'],
['m-range-points-container', 'absolute h-full left--3px right--3px flex justify-between items-center'],
['m-range-points', 'w-6px h-6px rd-3px bg-fill-stop'],
['m-range-marks', 'absolute h-full w-full left-0 bottom-0 translate-y-[calc(var(--m-range-h)_+_var(--m-range-thumb-h))]'],
['m-range-v-points-container', 'absolute w-full top--3px bottom--3px flex flex-col justify-between items-center'],
// marks
['m-range-marks', 'absolute h-full w-full left-0 top-0 translate-y-[calc(var(--m-range-h)_+_var(--m-range-thumb-h))]'],
['m-range-mark-item', 'absolute top-0 translate-x--50%'],
['m-range-v-marks', 'absolute h-full w-full left-0 top-0 translate-x-[calc(var(--m-range-w)_+_var(--m-range-thumb-w))]'],
['m-range-v-mark-item', 'absolute left-0 translate-y--50%'],
// thumb
['m-range-thumb', 'm-range-border absolute bg-fill-thumb translate-x--50% transform-origin-center transition transition-property-[opacity,transform]'],
['m-range-thumb-circle', 'w-[calc(var(--m-range-h)_+_var(--m-range-thumb-h)_*_2)] rd-full'],
['m-range-thumb-square', 'w-[calc(var(--m-range-h)_+_var(--m-range-thumb-h)_*_2)]'],
Expand All @@ -38,6 +52,15 @@ export default defineConfig({
['m-range-transition-container', 'absolute h-full w-full'],
['m-range-thumb-top-container', 'absolute left-50% top-0 translate-x--50% translate-y--110%'],
['m-range-thumb-bottom-container', 'absolute left-50% bottom-0 translate-x--50% translate-y-110%'],
['m-range-v-thumb', 'm-range-border absolute bg-fill-thumb translate-y--50% transform-origin-center transition transition-property-[opacity,transform]'],
['m-range-v-thumb-circle', 'h-[calc(var(--m-range-w)_+_var(--m-range-thumb-w)_*_2)] rd-full'],
['m-range-v-thumb-square', 'h-[calc(var(--m-range-w)_+_var(--m-range-thumb-w)_*_2)]'],
['m-range-v-thumb-rect', 'h3 rd-full'],
['m-range-v-thumb-small', 'left--0 right--0 [--m-range-thumb-w:0rem]'],
['m-range-v-thumb-medium', 'left--1 right--1 [--m-range-thumb-w:0.25rem]'],
['m-range-v-thumb-large', 'left--2 right--2 [--m-range-thumb-w:0.5rem]'],
['m-range-v-thumb-top-container', 'absolute top-50% left-0 translate-y--50% translate-x--110%'],
['m-range-v-thumb-bottom-container', 'absolute top-50% right-0 translate-y--50% translate-x-110%'],
],
theme: {
colors: {
Expand All @@ -50,9 +73,9 @@ export default defineConfig({
},
},
safelist: [
...['small', 'medium', 'large'].map(size => `m-range-${size}`),
...['circle', 'square', 'rect'].map(size => `m-range-thumb-${size}`),
...['small', 'medium', 'large'].map(size => `m-range-thumb-${size}`),
...['small', 'medium', 'large'].flatMap(size => [`m-range-${size}`, `m-range-v-${size}`]),
...['circle', 'square', 'rect'].flatMap(size => [`m-range-thumb-${size}`, `m-range-v-thumb-${size}`]),
...['small', 'medium', 'large'].flatMap(size => [`m-range-thumb-${size}`, `m-range-v-thumb-${size}`]),
],
presets: [
presetUno(),
Expand Down

0 comments on commit 0a129f7

Please sign in to comment.