UNPKG

25.1 kBTypeScriptView Raw
1import { Component, Model, Prop, Watch, Vue } from 'vue-property-decorator'
2import {
3 Value,
4 DataObject,
5 Mark,
6 Marks,
7 MarksProp,
8 Styles,
9 DotOption,
10 DotStyle,
11 Dot,
12 Direction,
13 Position,
14 ProcessProp,
15 Process,
16 TooltipProp,
17 TooltipFormatter,
18} from './typings'
19import VueSliderDot from './vue-slider-dot'
20import VueSliderMark from './vue-slider-mark'
21
22import { getSize, getPos, getKeyboardHandleFunc, HandleFunction } from './utils'
23import Decimal from './utils/decimal'
24import Control, { ERROR_TYPE } from './utils/control'
25import State, { StateMap } from './utils/state'
26
27import './styles/slider.scss'
28
29export const SliderState: StateMap = {
30 None: 0,
31 Drag: 1 << 1,
32 Focus: 1 << 2,
33}
34
35const DEFAULT_SLIDER_SIZE = 4
36
37@Component({
38 name: 'VueSlider',
39 data() {
40 return {
41 control: null,
42 }
43 },
44 components: {
45 VueSliderDot,
46 VueSliderMark,
47 },
48})
49export default class VueSlider extends Vue {
50 control!: Control
51 states: State = new State(SliderState)
52 // The width of the component is divided into one hundred, the width of each one.
53 scale: number = 1
54 // Currently dragged slider index
55 focusDotIndex: number = 0
56
57 $refs!: {
58 container: HTMLDivElement
59 rail: HTMLDivElement
60 }
61
62 $el!: HTMLDivElement
63
64 @Model('change', { default: 0 })
65 value!: Value | Value[]
66
67 @Prop({ type: Boolean, default: false })
68 silent!: boolean
69
70 @Prop({
71 default: 'ltr',
72 validator: dir => ['ltr', 'rtl', 'ttb', 'btt'].indexOf(dir) > -1,
73 })
74 direction!: Direction
75
76 @Prop({ type: [Number, String] }) width?: number | string
77
78 @Prop({ type: [Number, String] }) height?: number | string
79
80 // The size of the slider, optional [width, height] | size
81 @Prop({ default: 14 })
82 dotSize!: [number, number] | number
83
84 // whether or not the slider should be fully contained within its containing element
85 @Prop({ default: false })
86 contained!: boolean
87
88 @Prop({ type: Number, default: 0 })
89 min!: number
90
91 @Prop({ type: Number, default: 100 })
92 max!: number
93
94 @Prop({ type: Number, default: 1 })
95 interval!: number
96
97 @Prop({ type: Boolean, default: false })
98 disabled!: boolean
99
100 @Prop({ type: Boolean, default: true })
101 clickable!: boolean
102
103 @Prop({ type: Boolean, default: false })
104 dragOnClick!: boolean
105
106 // The duration of the slider slide, Unit second
107 @Prop({ type: Number, default: 0.5 })
108 duration!: number
109
110 @Prop({ type: [Object, Array] }) data?: Value[] | object[] | DataObject
111
112 @Prop({ type: String, default: 'value' }) dataValue!: string
113
114 @Prop({ type: String, default: 'label' }) dataLabel!: string
115
116 @Prop({ type: Boolean, default: false })
117 lazy!: boolean
118
119 @Prop({
120 type: String,
121 validator: val => ['none', 'always', 'focus', 'hover', 'active'].indexOf(val) > -1,
122 default: 'active',
123 })
124 tooltip!: TooltipProp
125
126 @Prop({
127 type: [String, Array],
128 validator: data =>
129 (Array.isArray(data) ? data : [data]).every(
130 val => ['top', 'right', 'bottom', 'left'].indexOf(val) > -1,
131 ),
132 })
133 tooltipPlacement?: Position | Position[]
134
135 @Prop({ type: [String, Array, Function] })
136 tooltipFormatter?: TooltipFormatter | TooltipFormatter[]
137
138 // Keyboard control
139 @Prop({ type: Boolean, default: true })
140 useKeyboard?: boolean
141
142 // Keyboard controlled hook function
143 @Prop(Function)
144 keydownHook!: (e: KeyboardEvent) => HandleFunction | boolean
145
146 // Whether to allow sliders to cross, only in range mode
147 @Prop({ type: Boolean, default: true })
148 enableCross!: boolean
149
150 // Whether to fix the slider interval, only in range mode
151 @Prop({ type: Boolean, default: false })
152 fixed!: boolean
153
154 // Whether to sort values, only in range mode
155 // When order is false, the parameters [minRange, maxRange, fixed, enableCross] are invalid
156 // e.g. When order = false, [50, 30] will not be automatically sorted into [30, 50]
157 @Prop({ type: Boolean, default: true })
158 order!: boolean
159
160 // Minimum distance between sliders, only in range mode
161 @Prop(Number) minRange?: number
162
163 // Maximum distance between sliders, only in range mode
164 @Prop(Number) maxRange?: number
165
166 @Prop({ type: [Boolean, Object, Array, Function], default: false })
167 marks?: MarksProp
168
169 @Prop({ type: [Boolean, Function], default: true })
170 process?: ProcessProp
171
172 @Prop({ type: [Number] })
173 zoom?: number
174
175 // If the value is true , mark will be an independent value
176 @Prop(Boolean) included?: boolean
177
178 // If the value is true , dot will automatically adsorb to the nearest value
179 @Prop(Boolean) adsorb?: boolean
180
181 @Prop(Boolean) hideLabel?: boolean
182
183 @Prop() dotOptions?: DotOption | DotOption[]
184
185 @Prop() dotAttrs?: object
186
187 @Prop() railStyle?: Styles
188
189 @Prop() processStyle?: Styles
190
191 @Prop() dotStyle?: Styles
192
193 @Prop() tooltipStyle?: Styles
194
195 @Prop() stepStyle?: Styles
196
197 @Prop() stepActiveStyle?: Styles
198
199 @Prop() labelStyle?: Styles
200
201 @Prop() labelActiveStyle?: Styles
202
203 get tailSize() {
204 return getSize((this.isHorizontal ? this.height : this.width) || DEFAULT_SLIDER_SIZE)
205 }
206
207 get containerClasses() {
208 return [
209 'vue-slider',
210 [`vue-slider-${this.direction}`],
211 {
212 'vue-slider-disabled': this.disabled,
213 },
214 ]
215 }
216
217 get containerStyles() {
218 const [dotWidth, dotHeight] = Array.isArray(this.dotSize)
219 ? this.dotSize
220 : [this.dotSize, this.dotSize]
221 const containerWidth = this.width
222 ? getSize(this.width)
223 : this.isHorizontal
224 ? 'auto'
225 : getSize(DEFAULT_SLIDER_SIZE)
226 const containerHeight = this.height
227 ? getSize(this.height)
228 : this.isHorizontal
229 ? getSize(DEFAULT_SLIDER_SIZE)
230 : 'auto'
231 return {
232 padding: this.contained
233 ? `${dotHeight / 2}px ${dotWidth / 2}px`
234 : this.isHorizontal
235 ? `${dotHeight / 2}px 0`
236 : `0 ${dotWidth / 2}px`,
237 width: containerWidth,
238 height: containerHeight,
239 }
240 }
241
242 get processArray(): Process[] {
243 return this.control.processArray.map(([start, end, style], index) => {
244 if (start > end) {
245 /* tslint:disable:semicolon */
246 ;[start, end] = [end, start]
247 }
248 const sizeStyleKey = this.isHorizontal ? 'width' : 'height'
249 return {
250 start,
251 end,
252 index,
253 style: {
254 [this.isHorizontal ? 'height' : 'width']: '100%',
255 [this.isHorizontal ? 'top' : 'left']: 0,
256 [this.mainDirection]: `${start}%`,
257 [sizeStyleKey]: `${end - start}%`,
258 transitionProperty: `${sizeStyleKey},${this.mainDirection}`,
259 transitionDuration: `${this.animateTime}s`,
260 ...this.processStyle,
261 ...style,
262 },
263 }
264 })
265 }
266
267 get dotBaseStyle() {
268 const [dotWidth, dotHeight] = Array.isArray(this.dotSize)
269 ? this.dotSize
270 : [this.dotSize, this.dotSize]
271 let dotPos: { [key: string]: string }
272 if (this.isHorizontal) {
273 dotPos = {
274 transform: `translate(${this.isReverse ? '50%' : '-50%'}, -50%)`,
275 '-WebkitTransform': `translate(${this.isReverse ? '50%' : '-50%'}, -50%)`,
276 top: '50%',
277 [this.direction === 'ltr' ? 'left' : 'right']: '0',
278 }
279 } else {
280 dotPos = {
281 transform: `translate(-50%, ${this.isReverse ? '50%' : '-50%'})`,
282 '-WebkitTransform': `translate(-50%, ${this.isReverse ? '50%' : '-50%'})`,
283 left: '50%',
284 [this.direction === 'btt' ? 'bottom' : 'top']: '0',
285 }
286 }
287 return {
288 width: `${dotWidth}px`,
289 height: `${dotHeight}px`,
290 ...dotPos,
291 }
292 }
293
294 get mainDirection(): string {
295 switch (this.direction) {
296 case 'ltr':
297 return 'left'
298 case 'rtl':
299 return 'right'
300 case 'btt':
301 return 'bottom'
302 case 'ttb':
303 return 'top'
304 }
305 }
306
307 get isHorizontal(): boolean {
308 return this.direction === 'ltr' || this.direction === 'rtl'
309 }
310
311 get isReverse(): boolean {
312 return this.direction === 'rtl' || this.direction === 'btt'
313 }
314
315 get tooltipDirections(): Position[] {
316 const dir = this.tooltipPlacement || (this.isHorizontal ? 'top' : 'left')
317 if (Array.isArray(dir)) {
318 return dir
319 } else {
320 return this.dots.map(() => dir)
321 }
322 }
323
324 get dots(): Dot[] {
325 return this.control.dotsPos.map((pos, index) => ({
326 pos,
327 index,
328 value: this.control.dotsValue[index],
329 focus: this.states.has(SliderState.Focus) && this.focusDotIndex === index,
330 disabled: this.disabled,
331 style: this.dotStyle,
332 ...((Array.isArray(this.dotOptions) ? this.dotOptions[index] : this.dotOptions) || {}),
333 }))
334 }
335
336 get animateTime(): number {
337 if (this.states.has(SliderState.Drag)) {
338 return 0
339 }
340 return this.duration
341 }
342
343 get canSort(): boolean {
344 return this.order && !this.minRange && !this.maxRange && !this.fixed && this.enableCross
345 }
346
347 isObjectData(data?: Value[] | object[] | DataObject): data is DataObject {
348 return !!data && Object.prototype.toString.call(data) === '[object Object]'
349 }
350
351 isObjectArrayData(data?: Value[] | object[] | DataObject): data is object[] {
352 return !!data && Array.isArray(data) && data.length > 0 && typeof data[0] === 'object'
353 }
354
355 get sliderData(): undefined | Value[] {
356 if (this.isObjectArrayData(this.data)) {
357 return (this.data as any[]).map(obj => obj[this.dataValue])
358 } else if (this.isObjectData(this.data)) {
359 return Object.keys(this.data)
360 } else {
361 return this.data as Value[]
362 }
363 }
364
365 get sliderMarks(): undefined | MarksProp {
366 if (this.marks) {
367 return this.marks
368 } else if (this.isObjectArrayData(this.data)) {
369 return val => {
370 const mark = { label: val }
371 ;(this.data as any[]).some(obj => {
372 if (obj[this.dataValue] === val) {
373 mark.label = obj[this.dataLabel]
374 return true
375 }
376 return false
377 })
378 return mark
379 }
380 } else if (this.isObjectData(this.data)) {
381 return this.data
382 }
383 }
384
385 get sliderTooltipFormatter(): undefined | TooltipFormatter | TooltipFormatter[] {
386 if (this.tooltipFormatter) {
387 return this.tooltipFormatter
388 } else if (this.isObjectArrayData(this.data)) {
389 return val => {
390 let tooltipText = '' + val
391 ;(this.data as any[]).some(obj => {
392 if (obj[this.dataValue] === val) {
393 tooltipText = obj[this.dataLabel]
394 return true
395 }
396 return false
397 })
398 return tooltipText
399 }
400 } else if (this.isObjectData(this.data)) {
401 const data = this.data
402 return val => data[val]
403 }
404 }
405
406 @Watch('value')
407 onValueChanged() {
408 if (this.control && !this.states.has(SliderState.Drag) && this.isNotSync) {
409 this.control.setValue(this.value)
410 this.syncValueByPos()
411 }
412 }
413
414 created() {
415 this.initControl()
416 }
417
418 mounted() {
419 this.bindEvent()
420 }
421
422 beforeDestroy() {
423 this.unbindEvent()
424 }
425
426 bindEvent() {
427 document.addEventListener('touchmove', this.dragMove, { passive: false })
428 document.addEventListener('touchend', this.dragEnd, { passive: false })
429 document.addEventListener('mousedown', this.blurHandle)
430 document.addEventListener('mousemove', this.dragMove, { passive: false })
431 document.addEventListener('mouseup', this.dragEnd)
432 document.addEventListener('mouseleave', this.dragEnd)
433 document.addEventListener('keydown', this.keydownHandle)
434 }
435
436 unbindEvent() {
437 document.removeEventListener('touchmove', this.dragMove)
438 document.removeEventListener('touchend', this.dragEnd)
439 document.removeEventListener('mousedown', this.blurHandle)
440 document.removeEventListener('mousemove', this.dragMove)
441 document.removeEventListener('mouseup', this.dragEnd)
442 document.removeEventListener('mouseleave', this.dragEnd)
443 document.removeEventListener('keydown', this.keydownHandle)
444 }
445
446 setScale() {
447 const decimal = new Decimal(
448 Math.floor(this.isHorizontal ? this.$refs.rail.offsetWidth : this.$refs.rail.offsetHeight),
449 )
450 if (this.zoom !== void 0) {
451 decimal.multiply(this.zoom)
452 }
453 decimal.divide(100)
454 this.scale = decimal.toNumber()
455 }
456
457 initControl() {
458 this.control = new Control({
459 value: this.value,
460 data: this.sliderData,
461 enableCross: this.enableCross,
462 fixed: this.fixed,
463 max: this.max,
464 min: this.min,
465 interval: this.interval,
466 minRange: this.minRange,
467 maxRange: this.maxRange,
468 order: this.order,
469 marks: this.sliderMarks,
470 included: this.included,
471 process: this.process,
472 adsorb: this.adsorb,
473 dotOptions: this.dotOptions,
474 onError: this.emitError,
475 })
476 this.syncValueByPos()
477 ;[
478 'data',
479 'enableCross',
480 'fixed',
481 'max',
482 'min',
483 'interval',
484 'minRange',
485 'maxRange',
486 'order',
487 'marks',
488 'process',
489 'adsorb',
490 'included',
491 'dotOptions',
492 ].forEach(name => {
493 this.$watch(name, (val: any) => {
494 if (
495 name === 'data' &&
496 Array.isArray(this.control.data) &&
497 Array.isArray(val) &&
498 this.control.data.length === val.length &&
499 val.every((item, index) => item === (this.control.data as Value[])[index])
500 ) {
501 return false
502 }
503 switch (name) {
504 case 'data':
505 case 'dataLabel':
506 case 'dataValue':
507 this.control.data = this.sliderData
508 break
509 case 'mark':
510 this.control.marks = this.sliderMarks
511 break
512 default:
513 ;(this.control as any)[name] = val
514 }
515 if (['data', 'max', 'min', 'interval'].indexOf(name) > -1) {
516 this.control.syncDotsPos()
517 }
518 })
519 })
520 }
521
522 private syncValueByPos() {
523 const values = this.control.dotsValue
524 if (this.isDiff(values, Array.isArray(this.value) ? this.value : [this.value])) {
525 this.$emit('change', values.length === 1 ? values[0] : [...values], this.focusDotIndex)
526 }
527 }
528
529 // Slider value and component internal value are inconsistent
530 private get isNotSync() {
531 const values = this.control.dotsValue
532 return Array.isArray(this.value)
533 ? this.value.length !== values.length ||
534 this.value.some((val, index) => val !== values[index])
535 : this.value !== values[0]
536 }
537
538 private isDiff(value1: Value[], value2: Value[]) {
539 return value1.length !== value2.length || value1.some((val, index) => val !== value2[index])
540 }
541
542 private emitError(type: ERROR_TYPE, message: string) {
543 if (!this.silent) {
544 console.error(`[VueSlider error]: ${message}`)
545 }
546 this.$emit('error', type, message)
547 }
548
549 /**
550 * Get the drag range of the slider
551 *
552 * @private
553 * @param {number} index slider index
554 * @returns {[number, number]} range [start, end]
555 * @memberof VueSlider
556 */
557 private get dragRange(): [number, number] {
558 const prevDot = this.dots[this.focusDotIndex - 1]
559 const nextDot = this.dots[this.focusDotIndex + 1]
560 return [prevDot ? prevDot.pos : -Infinity, nextDot ? nextDot.pos : Infinity]
561 }
562
563 private dragStartOnProcess(e: MouseEvent | TouchEvent) {
564 if (this.dragOnClick) {
565 this.setScale()
566 const pos = this.getPosByEvent(e)
567 const index = this.control.getRecentDot(pos)
568 if (this.dots[index].disabled) {
569 return
570 }
571 this.dragStart(index)
572 this.control.setDotPos(pos, this.focusDotIndex)
573 if (!this.lazy) {
574 this.syncValueByPos()
575 }
576 }
577 }
578
579 private dragStart(index: number) {
580 this.focusDotIndex = index
581 this.setScale()
582 this.states.add(SliderState.Drag)
583 this.states.add(SliderState.Focus)
584 this.$emit('drag-start', this.focusDotIndex)
585 }
586
587 private dragMove(e: MouseEvent | TouchEvent) {
588 if (!this.states.has(SliderState.Drag)) {
589 return false
590 }
591 e.preventDefault()
592 const pos = this.getPosByEvent(e)
593 this.isCrossDot(pos)
594 this.control.setDotPos(pos, this.focusDotIndex)
595 if (!this.lazy) {
596 this.syncValueByPos()
597 }
598 const value = this.control.dotsValue
599 this.$emit('dragging', value.length === 1 ? value[0] : [...value], this.focusDotIndex)
600 }
601
602 // If the component is sorted, then when the slider crosses, toggle the currently selected slider index
603 private isCrossDot(pos: number) {
604 if (this.canSort) {
605 const curIndex = this.focusDotIndex
606 let curPos = pos
607 if (curPos > this.dragRange[1]) {
608 curPos = this.dragRange[1]
609 this.focusDotIndex++
610 } else if (curPos < this.dragRange[0]) {
611 curPos = this.dragRange[0]
612 this.focusDotIndex--
613 }
614 if (curIndex !== this.focusDotIndex) {
615 const dotVm = (this.$refs as any)[`dot-${this.focusDotIndex}`]
616 if (dotVm && dotVm.$el) {
617 dotVm.$el.focus()
618 }
619 this.control.setDotPos(curPos, curIndex)
620 }
621 }
622 }
623
624 private dragEnd(e: MouseEvent | TouchEvent) {
625 if (!this.states.has(SliderState.Drag)) {
626 return false
627 }
628
629 setTimeout(() => {
630 if (this.lazy) {
631 this.syncValueByPos()
632 }
633 if (this.included && this.isNotSync) {
634 this.control.setValue(this.value)
635 } else {
636 // Sync slider position
637 this.control.syncDotsPos()
638 }
639 this.states.delete(SliderState.Drag)
640 // If useKeyboard is true, keep focus status after dragging
641 if (!this.useKeyboard || 'targetTouches' in e) {
642 this.states.delete(SliderState.Focus)
643 }
644 this.$emit('drag-end', this.focusDotIndex)
645 })
646 }
647
648 private blurHandle(e: MouseEvent) {
649 if (
650 !this.states.has(SliderState.Focus) ||
651 !this.$refs.container ||
652 this.$refs.container.contains(e.target as Node)
653 ) {
654 return false
655 }
656 this.states.delete(SliderState.Focus)
657 }
658
659 private clickHandle(e: MouseEvent | TouchEvent) {
660 if (!this.clickable || this.disabled) {
661 return false
662 }
663 if (this.states.has(SliderState.Drag)) {
664 return
665 }
666 this.setScale()
667 const pos = this.getPosByEvent(e)
668 this.setValueByPos(pos)
669 }
670
671 focus(index: number = 0) {
672 this.states.add(SliderState.Focus)
673 this.focusDotIndex = index
674 }
675
676 blur() {
677 this.states.delete(SliderState.Focus)
678 }
679
680 getValue() {
681 const values = this.control.dotsValue
682 return values.length === 1 ? values[0] : values
683 }
684
685 getIndex() {
686 const indexes = this.control.dotsIndex
687 return indexes.length === 1 ? indexes[0] : indexes
688 }
689
690 setValue(value: Value | Value[]) {
691 this.control.setValue(Array.isArray(value) ? [...value] : [value])
692 this.syncValueByPos()
693 }
694
695 setIndex(index: number | number[]) {
696 const value = Array.isArray(index)
697 ? index.map(n => this.control.getValueByIndex(n))
698 : this.control.getValueByIndex(index)
699 this.setValue(value)
700 }
701
702 setValueByPos(pos: number) {
703 const index = this.control.getRecentDot(pos)
704 if (this.disabled || this.dots[index].disabled) {
705 return false
706 }
707 this.focusDotIndex = index
708 this.control.setDotPos(pos, index)
709 this.syncValueByPos()
710
711 if (this.useKeyboard) {
712 this.states.add(SliderState.Focus)
713 }
714
715 setTimeout(() => {
716 if (this.included && this.isNotSync) {
717 this.control.setValue(this.value)
718 } else {
719 this.control.syncDotsPos()
720 }
721 })
722 }
723
724 keydownHandle(e: KeyboardEvent) {
725 if (!this.useKeyboard || !this.states.has(SliderState.Focus)) {
726 return false
727 }
728
729 const isInclude = this.included && this.marks
730 const handleFunc = getKeyboardHandleFunc(e, {
731 direction: this.direction,
732 max: isInclude ? this.control.markList.length - 1 : this.control.total,
733 min: 0,
734 hook: this.keydownHook,
735 })
736
737 if (handleFunc) {
738 e.preventDefault()
739 let newIndex = -1
740 let pos = 0
741 if (isInclude) {
742 this.control.markList.some((mark, index) => {
743 if (mark.value === this.control.dotsValue[this.focusDotIndex]) {
744 newIndex = handleFunc(index)
745 return true
746 }
747 return false
748 })
749 if (newIndex < 0) {
750 newIndex = 0
751 } else if (newIndex > this.control.markList.length - 1) {
752 newIndex = this.control.markList.length - 1
753 }
754 pos = this.control.markList[newIndex].pos
755 } else {
756 newIndex = handleFunc(
757 this.control.getIndexByValue(this.control.dotsValue[this.focusDotIndex]),
758 )
759 pos = this.control.parseValue(this.control.getValueByIndex(newIndex))
760 }
761 this.isCrossDot(pos)
762 this.control.setDotPos(pos, this.focusDotIndex)
763 this.syncValueByPos()
764 }
765 }
766
767 private getPosByEvent(e: MouseEvent | TouchEvent): number {
768 return (
769 getPos(e, this.$refs.rail, this.isReverse, this.zoom)[this.isHorizontal ? 'x' : 'y'] /
770 this.scale
771 )
772 }
773
774 private renderSlot<T>(name: string, data: T, defaultSlot: any, isDefault?: boolean): any {
775 const scopedSlot = this.$scopedSlots[name]
776 return scopedSlot ? (
777 isDefault ? (
778 scopedSlot(data)
779 ) : (
780 <template slot={name}>{scopedSlot(data)}</template>
781 )
782 ) : (
783 defaultSlot
784 )
785 }
786
787 render() {
788 return (
789 <div
790 ref="container"
791 class={this.containerClasses}
792 style={this.containerStyles}
793 onClick={this.clickHandle}
794 onTouchstart={this.dragStartOnProcess}
795 onMousedown={this.dragStartOnProcess}
796 {...this.$attrs}
797 >
798 {/* rail */}
799 <div ref="rail" class="vue-slider-rail" style={this.railStyle}>
800 {this.processArray.map((item, index) =>
801 this.renderSlot<Process>(
802 'process',
803 item,
804 <div class="vue-slider-process" key={`process-${index}`} style={item.style} />,
805 true,
806 ),
807 )}
808 {/* mark */}
809 {this.sliderMarks ? (
810 <div class="vue-slider-marks">
811 {this.control.markList.map((mark, index) =>
812 this.renderSlot<Mark>(
813 'mark',
814 mark,
815 <vue-slider-mark
816 key={`mark-${index}`}
817 mark={mark}
818 hideLabel={this.hideLabel}
819 style={{
820 [this.isHorizontal ? 'height' : 'width']: '100%',
821 [this.isHorizontal ? 'width' : 'height']: this.tailSize,
822 [this.mainDirection]: `${mark.pos}%`,
823 }}
824 stepStyle={this.stepStyle}
825 stepActiveStyle={this.stepActiveStyle}
826 labelStyle={this.labelStyle}
827 labelActiveStyle={this.labelActiveStyle}
828 onPressLabel={(pos: number) => this.clickable && this.setValueByPos(pos)}
829 >
830 {this.renderSlot<Mark>('step', mark, null)}
831 {this.renderSlot<Mark>('label', mark, null)}
832 </vue-slider-mark>,
833 true,
834 ),
835 )}
836 </div>
837 ) : null}
838 {/* dot */}
839 {this.dots.map((dot, index) => (
840 <vue-slider-dot
841 ref={`dot-${index}`}
842 key={`dot-${index}`}
843 value={dot.value}
844 disabled={dot.disabled}
845 focus={dot.focus}
846 dot-style={[
847 dot.style,
848 dot.disabled ? dot.disabledStyle : null,
849 dot.focus ? dot.focusStyle : null,
850 ]}
851 tooltip={dot.tooltip || this.tooltip}
852 tooltip-style={[
853 this.tooltipStyle,
854 dot.tooltipStyle,
855 dot.disabled ? dot.tooltipDisabledStyle : null,
856 dot.focus ? dot.tooltipFocusStyle : null,
857 ]}
858 tooltip-formatter={
859 Array.isArray(this.sliderTooltipFormatter)
860 ? this.sliderTooltipFormatter[index]
861 : this.sliderTooltipFormatter
862 }
863 tooltip-placement={this.tooltipDirections[index]}
864 style={[
865 this.dotBaseStyle,
866 {
867 [this.mainDirection]: `${dot.pos}%`,
868 transition: `${this.mainDirection} ${this.animateTime}s`,
869 },
870 ]}
871 onDrag-start={() => this.dragStart(index)}
872 role="slider"
873 aria-valuenow={dot.value}
874 aria-valuemin={this.min}
875 aria-valuemax={this.max}
876 aria-orientation={this.isHorizontal ? 'horizontal' : 'vertical'}
877 tabindex="0"
878 nativeOnFocus={() => !dot.disabled && this.focus(index)}
879 nativeOnBlur={() => this.blur()}
880 {...{ attrs: this.dotAttrs }}
881 >
882 {this.renderSlot<Dot>('dot', dot, null)}
883 {this.renderSlot<Dot>('tooltip', dot, null)}
884 </vue-slider-dot>
885 ))}
886 {this.renderSlot<any>('default', { value: this.getValue() }, null, true)}
887 </div>
888 </div>
889 )
890 }
891}