11<script setup>
2- import { computed , ref } from " vue" ;
3- import BaseIcon from " ./BaseIcon.vue" ;
2+ import { ref , computed , watch } from ' vue' ;
3+ import BaseIcon from ' ./BaseIcon.vue' ;
44
55const props = defineProps ({
66 background: {
@@ -9,7 +9,7 @@ const props = defineProps({
99 },
1010 fontSize: {
1111 type: Number ,
12- default: 14 ,
12+ default: 14
1313 },
1414 labelLeft: {
1515 type: String ,
@@ -35,166 +35,208 @@ const props = defineProps({
3535 type: Number ,
3636 default: 0
3737 },
38+ selectColor: {
39+ type: String ,
40+ default: ' #4A4A4A'
41+ },
3842 useResetSlot: {
3943 type: Boolean ,
4044 default: false
4145 },
4246 valueStart: {
4347 type: [Number , String ],
44- default: 0 ,
48+ default: 0
4549 },
4650 valueEnd: {
4751 type: [Number , String ],
4852 default: 0
4953 }
5054});
5155
52- const emit = defineEmits ([
53- ' update:start' ,
54- ' update:end' ,
55- ' reset'
56- ]);
56+ const startValue = ref (props .min );
57+ const endValue = ref (props .max );
5758
58- const REF_SLICER = ref (null );
59- const REF_START = ref (null );
60- const REF_END = ref (null );
59+ const emit = defineEmits ([' update:start' , ' update:end' , ' reset' ]);
60+
61+ const highlightStyle = computed (() => {
62+ const range = props .max - props .min ;
63+ const startPercent = ((startValue .value - props .min ) / range) * 100 ;
64+ const endPercent = ((endValue .value - props .min ) / range) * 100 ;
65+ return {
66+ left: ` calc(${ startPercent} % + 5px)` ,
67+ width: ` ${ endPercent - startPercent - 1 } %` ,
68+ background: props .selectColor
69+ };
70+ });
6171
6272const slicerColor = computed (() => props .inputColor );
6373const backgroundColor = computed (() => props .background );
74+ const selectColorOpaque = computed (() => ` ${ props .selectColor } 33` )
6475
6576function reset () {
66- emit (' reset' )
77+ emit (' reset' );
6778}
6879
80+ function onStartInput () {
81+ if (Number (startValue .value ) > Number (endValue .value ) - 1 ) {
82+ startValue .value = Number (endValue .value ) - 1 ;
83+ }
84+ emit (' update:start' , Number (startValue .value ));
85+ }
86+
87+ function onEndInput () {
88+ if (Number (endValue .value ) < Number (startValue .value ) + 1 ) {
89+ endValue .value = Number (startValue .value ) + 1 ;
90+ }
91+ emit (' update:end' , Number (endValue .value ));
92+ }
93+
94+ watch (
95+ () => props .min ,
96+ (newMin ) => {
97+ if (Number (startValue .value ) < Number (newMin)) {
98+ startValue .value = Number (newMin);
99+ }
100+ if (Number (endValue .value ) < Number (newMin)) {
101+ endValue .value = Number (newMin);
102+ }
103+ }
104+ );
105+
106+ watch (
107+ () => props .max ,
108+ (newMax ) => {
109+ if (Number (startValue .value ) > Number (newMax)) {
110+ startValue .value = Number (newMax);
111+ }
112+ if (Number (endValue .value ) > Number (newMax)) {
113+ endValue .value = Number (newMax);
114+ }
115+ }
116+ );
69117 </script >
70118
71119<template >
72- <div class = " vue-data-ui-slicer " data-html2canvas-ignore >
120+ <div >
73121 <div class =" vue-data-ui-slicer-labels" >
74- <div class =" vue-data-ui-slicer-label-left" :style =" `font-size:${props.fontSize}px;color:${props.textColor}`" >
122+ <div class =" vue-data-ui-slicer-label-left"
123+ :style =" { fontSize: `${props.fontSize}px`, color: props.textColor }" >
75124 {{ labelLeft }}
76125 </div >
77126 <div v-if =" valueStart > 0 || valueEnd < max" >
78- <button v-if =" !useResetSlot" data-cy-reset tabindex =" 0" role =" button" class =" vue-data-ui-refresh-button" @click =" reset()" >
79- <BaseIcon name =" refresh" :stroke =" textColor" />
127+ <button v-if =" !useResetSlot" data-cy-reset tabindex =" 0" role =" button" class =" vue-data-ui-refresh-button"
128+ @click =" reset" >
129+ <BaseIcon name =" refresh" :stroke =" textColor" />
80130 </button >
81- <slot v-else name =" reset-action" v-bind = " { reset } " />
131+ <slot v-else name =" reset-action" :reset = " reset" />
82132 </div >
83- <div class =" vue-data-ui-slicer-label-right" :style =" `font-size:${props.fontSize}px;color:${props.textColor}`" >
133+ <div class =" vue-data-ui-slicer-label-right"
134+ :style =" { fontSize: `${props.fontSize}px`, color: props.textColor }" >
84135 {{ labelRight }}
85136 </div >
86137 </div >
87- <div class =" vue-data-ui-slicer-knobs" >
88- <div ref =" REF_SLICER" class =" vue-data-ui-slicer-track" />
89- <input
90- ref =" REF_START"
91- type =" range"
92- :value =" valueStart"
93- :style =" `border:none !important;accent-color:${slicerColor}`"
94- :min =" min"
95- :max =" max"
96- @input =" emit('update:start', $event.target.value)"
97- >
98- <input
99- ref =" REF_END"
100- type =" range"
101- :value =" valueEnd"
102- :style =" `border:none !important;accent-color:${slicerColor}`"
103- :min =" min"
104- :max =" max"
105- @input =" emit('update:end', $event.target.value)"
106- >
138+ <div class =" double-range-slider" >
139+ <div class =" slider-track" ></div >
140+ <div class =" range-highlight" :style =" highlightStyle" ></div >
141+ <input type =" range" :min =" min" :max =" max" v-model =" startValue" @input =" onStartInput" />
142+ <input type =" range" :min =" min" :max =" max" v-model =" endValue" @input =" onEndInput" />
107143 </div >
108144 </div >
109145</template >
110146
111- <style scoped lang="scss">
112- .vue-data-ui-slicer {
113- position : relative ;
114- display : flex ;
115- flex-direction : column ;
116- gap : 12px ;
117- padding : 0 24px ;
118- margin-bottom : 24px ;
119- }
120147
121- .vue-data-ui-slicer-knobs {
122- position : relative ;
148+ <style scoped lang="scss">
149+ .double-range-slider {
150+ position : relative !important ;
123151 width : calc (100% - 48px );
152+ height : 40px ;
124153 margin : 0 auto ;
125- height : 12px ;
126154}
127155
128- input [type = " range" ]{
129- -webkit-appearance : none ;
130- -moz-appearance : none ;
131- appearance : none ;
132- width : 100% ;
133- outline : none ;
156+ input [type = " range" ] {
134157 position : absolute ;
135- margin : auto ;
136- top : 0 ;
137- bottom : 0 ;
138158 left : 0 ;
139- background-color : transparent ;
140- pointer-events : none ;
141- }
142-
143- .vue-data-ui-slicer-track {
144159 width : 100% ;
145- height : 5px ;
146- position : absolute ;
147- margin : auto ;
148- top : 0 ;
149- bottom : 0 ;
150- border-radius : 5px ;
151- background : v-bind (slicerColor );
152- }
153- input [type = " range" ]::-webkit-slider-runnable-track {
154- -webkit-appearance : none ;
155- height : 5px ;
156- }
157- input [type = " range" ]::-moz-range-track {
158- -moz-appearance : none ;
159- height : 5px ;
160- }
161- input [type = " range" ]::-ms-track {
162160 appearance : none ;
163- height : 5px ;
161+ background : transparent ;
162+ pointer-events : none ;
163+ z-index : 3 ;
164164}
165- input [type = " range" ]::-webkit-slider-thumb {
165+
166+ input [type = " range" ]::-webkit-slider-thumb {
166167 -webkit-appearance : none ;
167- height : 1.3em ;
168- width : 1.3em ;
168+ pointer-events : auto ;
169+ width : 20px ;
170+ height : 20px ;
169171 background-color : v-bind (slicerColor );
172+ border-radius : 50% ;
170173 cursor : pointer ;
171- margin-top : -6px ;
174+ position : relative ;
175+ z-index : 2 ;
176+ outline : 2px solid v-bind (backgroundColor );
177+ transition : all 0.2s ease-in-out ;
178+ & :active ,
179+ & :hover {
180+ box-shadow : 0 0 0 10px v-bind (selectColorOpaque );
181+ background-color : v-bind (selectColor );
182+ }
183+ }
184+
185+ input [type = " range" ]::-moz-range-thumb {
172186 pointer-events : auto ;
187+ width : 20px ;
188+ height : 20px ;
189+ background-color : v-bind (slicerColor );
173190 border-radius : 50% ;
174- border : 1px solid v-bind (backgroundColor );
175- }
176- input [type = " range" ]::-moz-range-thumb {
177- -webkit-appearance : none ;
178- appearance : none ;
179- height : 1.3em ;
180- width : 1.3em ;
181191 cursor : pointer ;
182- border-radius : 50% ;
183- background-color : v-bind (slicerColor );
184- pointer-events : auto ;
192+ position : relative ;
193+ z-index : 2 ;
194+ outline : 2px solid v-bind (backgroundColor );
195+ transition : all 0.2s ease-in-out ;
196+ & :active ,
197+ & :hover {
198+ box-shadow : 0 0 0 10px v-bind (selectColorOpaque );
199+ background-color : v-bind (selectColor );
200+ }
185201}
186- input [type = " range" ]::-ms-thumb {
187- appearance : none ;
188- height : 1.3em ;
189- width : 1.3em ;
190- cursor : pointer ;
191- border-radius : 50% ;
192- background-color : v-bind (slicerColor );
202+
203+ input [type = " range" ]::-ms-thumb {
193204 pointer-events : auto ;
194- }
195- input [ type = " range " ] :active ::-webkit-slider-thumb {
205+ width : 20 px ;
206+ height : 20 px ;
196207 background-color : v-bind (slicerColor );
197- border : 2px solid v-bind (backgroundColor );
208+ border-radius : 50% ;
209+ cursor : pointer ;
210+ position : relative ;
211+ z-index : 2 ;
212+ outline : 2px solid v-bind (backgroundColor );
213+ transition : all 0.2s ease-in-out ;
214+ & :active ,
215+ & :hover {
216+ box-shadow : 0 0 0 10px v-bind (selectColorOpaque );
217+ background-color : v-bind (selectColor );
218+ }
219+ }
220+
221+ .slider-track {
222+ position : absolute ;
223+ width : 99% ;
224+ height : 8px ;
225+ border-radius : 4px ;
226+ background : #ddd ;
227+ top : 8px ;
228+ z-index : 1 ;
229+ left : 50% ;
230+ transform : translateX (-50% );
231+ -webkit-transform : translateX (-50% );
232+ }
233+
234+ .range-highlight {
235+ position : absolute ;
236+ height : 8px ;
237+ top : 8px ;
238+ z-index : 1 ;
239+ border-radius : 4px ;
198240}
199241
200242.vue-data-ui-refresh-button {
@@ -204,11 +246,12 @@ input[type="range"]:active::-webkit-slider-thumb{
204246 height : 36px ;
205247 width : 36px ;
206248 display : flex ;
207- align-items :center ;
208- justify-content :center ;
249+ align-items : center ;
250+ justify-content : center ;
209251 border-radius : 50% ;
210252 cursor : pointer ;
211253 transition : transform 0.2s ease-in-out ;
254+ transform-origin : center ;
212255 & :focus {
213256 outline : 1px solid v-bind (slicerColor );
214257 }
@@ -220,7 +263,7 @@ input[type="range"]:active::-webkit-slider-thumb{
220263.vue-data-ui-slicer-labels {
221264 display : flex ;
222265 flex-direction : row ;
223- align-items :center ;
266+ align-items : center ;
224267 padding : 0 24px ;
225268 height : 40px ;
226269}
@@ -229,9 +272,11 @@ input[type="range"]:active::-webkit-slider-thumb{
229272.vue-data-ui-slicer-label-right {
230273 width : 100% ;
231274}
275+
232276.vue-data-ui-slicer-label-left {
233277 text-align : left ;
234278}
279+
235280.vue-data-ui-slicer-label-right {
236281 text-align : right ;
237282}
0 commit comments