1 | import { Component, Model, Prop, Watch, Vue } from 'vue-property-decorator'
|
2 | import {
|
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'
|
19 | import VueSliderDot from './vue-slider-dot'
|
20 | import VueSliderMark from './vue-slider-mark'
|
21 |
|
22 | import { getSize, getPos, getKeyboardHandleFunc, HandleFunction } from './utils'
|
23 | import Decimal from './utils/decimal'
|
24 | import Control, { ERROR_TYPE } from './utils/control'
|
25 | import State, { StateMap } from './utils/state'
|
26 |
|
27 | import './styles/slider.scss'
|
28 |
|
29 | export const SliderState: StateMap = {
|
30 | None: 0,
|
31 | Drag: 1 << 1,
|
32 | Focus: 1 << 2,
|
33 | }
|
34 |
|
35 | const 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 | })
|
49 | export default class VueSlider extends Vue {
|
50 | control!: Control
|
51 | states: State = new State(SliderState)
|
52 |
|
53 | scale: number = 1
|
54 |
|
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 |
|
81 | @Prop({ default: 14 })
|
82 | dotSize!: [number, number] | number
|
83 |
|
84 |
|
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 |
|
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 |
|
139 | @Prop({ type: Boolean, default: true })
|
140 | useKeyboard?: boolean
|
141 |
|
142 |
|
143 | @Prop(Function)
|
144 | keydownHook!: (e: KeyboardEvent) => HandleFunction | boolean
|
145 |
|
146 |
|
147 | @Prop({ type: Boolean, default: true })
|
148 | enableCross!: boolean
|
149 |
|
150 |
|
151 | @Prop({ type: Boolean, default: false })
|
152 | fixed!: boolean
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | @Prop({ type: Boolean, default: true })
|
158 | order!: boolean
|
159 |
|
160 |
|
161 | @Prop(Number) minRange?: number
|
162 |
|
163 |
|
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 |
|
176 | @Prop(Boolean) included?: boolean
|
177 |
|
178 |
|
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 |
|
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 |
|
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 |
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
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 |
|
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 |
|
637 | this.control.syncDotsPos()
|
638 | }
|
639 | this.states.delete(SliderState.Drag)
|
640 |
|
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 | {}
|
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 | {}
|
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 | {}
|
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 | }
|