Skip to content

Commit 01f8dec

Browse files
committed
Atoms - Add drag selection feature to Slicer component
1 parent 8b550d3 commit 01f8dec

File tree

1 file changed

+130
-4
lines changed

1 file changed

+130
-4
lines changed

src/atoms/Slicer.vue

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,18 @@ const props = defineProps({
9797
refreshEndPoint: {
9898
type: Number,
9999
default: null
100+
},
101+
enableRangeHandles: {
102+
type: Boolean,
103+
default: false
104+
},
105+
enableSelectionDrag: {
106+
type: Boolean,
107+
default: true
100108
}
101109
});
102110
111+
const zoomWrapper = ref(null);
103112
const startValue = ref(props.min);
104113
const endValue = ref(props.max);
105114
const hasMinimap = computed(() => !!props.minimap.length);
@@ -296,6 +305,87 @@ function setEndValue(value) {
296305
emit('update:end', Number(endValue.value));
297306
}
298307
308+
const currentRange = computed(() => {
309+
return props.valueEnd - props.valueStart;
310+
});
311+
312+
const isDragging = ref(false);
313+
let initialMouseX = ref(null);
314+
315+
const dragThreshold = computed(() => {
316+
if (!zoomWrapper.value) return 20;
317+
const w = zoomWrapper.value.getBoundingClientRect().width - 48;
318+
return w / (props.max - props.min);
319+
});
320+
321+
const startDragging = (event) => {
322+
if (!props.enableSelectionDrag) {
323+
return;
324+
}
325+
const isTouch = event.type === 'touchstart';
326+
const target = isTouch ? event.targetTouches[0].target : event.target;
327+
if (target.classList.contains('range-handle')) {
328+
return;
329+
}
330+
isDragging.value = true;
331+
initialMouseX.value = isTouch ? event.targetTouches[0].clientX : event.clientX;
332+
333+
const moveHandler = isTouch ? handleTouchDragging : handleDragging;
334+
const endHandler = isTouch ? stopTouchDragging : stopDragging;
335+
336+
window.addEventListener(isTouch ? 'touchmove' : 'mousemove', moveHandler, { passive: false });
337+
window.addEventListener(isTouch ? 'touchend' : 'mouseup', endHandler);
338+
};
339+
340+
function handleDragging(event) {
341+
updateDragging(event.clientX);
342+
};
343+
344+
function handleTouchDragging(event) {
345+
if (event.target.classList.contains('range-handle')) {
346+
return;
347+
}
348+
event.preventDefault();
349+
updateDragging(event.targetTouches[0].clientX);
350+
};
351+
352+
function updateDragging(currentX) {
353+
if (!isDragging.value) return;
354+
355+
const deltaX = currentX - initialMouseX.value;
356+
357+
if (Math.abs(deltaX) > dragThreshold.value) {
358+
if (deltaX > 0) {
359+
if (Number(endValue.value) + 1 <= props.max) {
360+
const v = Number(endValue.value) + 1;
361+
setEndValue(v);
362+
setStartValue(v - currentRange.value);
363+
}
364+
} else {
365+
if (Number(startValue.value) - 1 >= props.min) {
366+
const v = Number(startValue.value) - 1;
367+
setStartValue(v);
368+
setEndValue(v + currentRange.value);
369+
}
370+
}
371+
initialMouseX.value = currentX;
372+
}
373+
};
374+
375+
function stopDragging() {
376+
endDragging('mousemove', 'mouseup');
377+
};
378+
379+
function stopTouchDragging () {
380+
endDragging('touchmove', 'touchend');
381+
};
382+
383+
function endDragging(moveEvent, endEvent) {
384+
isDragging.value = false;
385+
window.removeEventListener(moveEvent, handleDragging);
386+
window.removeEventListener(endEvent, stopDragging);
387+
};
388+
299389
defineExpose({
300390
setStartValue,
301391
setEndValue
@@ -304,7 +394,14 @@ defineExpose({
304394
</script>
305395

306396
<template>
307-
<div data-html2canvas-ignore data-cy="slicer" style="padding: 0 24px" class="vue-data-ui-zoom">
397+
<div
398+
data-html2canvas-ignore data-cy="slicer"
399+
style="padding: 0 24px"
400+
class="vue-data-ui-zoom"
401+
ref="zoomWrapper"
402+
@mousedown="startDragging"
403+
@touchstart="startDragging"
404+
>
308405
<div class="vue-data-ui-slicer-labels" style="position: relative; z-index: 1; pointer-events: none;">
309406
<div v-if="valueStart !== refreshStartPoint || valueEnd !== endpoint" style="width: 100%; position: relative">
310407
<button
@@ -443,19 +540,44 @@ defineExpose({
443540
:width="unitWidthX < 0 ? 0 : unitWidthX"
444541
fill="transparent"
445542
style="pointer-events: all !important;"
543+
:style="{
544+
cursor: trap >= valueStart && trap < valueEnd && enableSelectionDrag ? 'move' : 'default',
545+
}"
446546
@mouseenter="trapMouse(trap)"
447547
@mouseleave="selectedTrap = null; emit('trapMouse', null)"
448548
/>
449549
</svg>
450550
</div>
451551
</template>
452552
<div class="slider-track"></div>
453-
<div class="range-highlight" :style="highlightStyle"></div>
454-
<input ref="rangeStart" :key="`range-min${inputStep}`" type="range" class="range-left" :min="min" :max="max" v-model="startValue" @input="onStartInput" />
553+
<div :class="{
554+
'range-highlight': true,
555+
'move': enableSelectionDrag
556+
}" :style="highlightStyle"></div>
557+
<input
558+
v-if="enableRangeHandles"
559+
ref="rangeStart"
560+
:key="`range-min${inputStep}`"
561+
type="range"
562+
class="range-left range-handle"
563+
:min="min"
564+
:max="max"
565+
v-model="startValue"
566+
@input="onStartInput"
567+
/>
455568
<div class="thumb-label thumb-label-left" :style="leftLabelPosition">
456569
{{ labelLeft }}
457570
</div>
458-
<input ref="rangeEnd" type="range" class="range-right" :min="min" :max="max" v-model="endValue" @input="onEndInput" />
571+
<input
572+
v-if="enableRangeHandles"
573+
ref="rangeEnd"
574+
type="range"
575+
class="range-right range-handle"
576+
:min="min"
577+
:max="max"
578+
v-model="endValue"
579+
@input="onEndInput"
580+
/>
459581
<div class="thumb-label thumb-label-right" :style="rightLabelPosition">
460582
{{ labelRight }}
461583
</div>
@@ -571,6 +693,10 @@ input[type="range"]::-ms-thumb {
571693
border-radius: 4px;
572694
}
573695
696+
.move {
697+
cursor: move;
698+
}
699+
574700
.vue-data-ui-refresh-button {
575701
position: absolute;
576702
left: 50%;

0 commit comments

Comments
 (0)