@v-c/slider 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/dist/Handles/Handle.cjs +1 -0
  3. package/dist/Handles/Handle.d.ts +107 -0
  4. package/dist/Handles/Handle.js +203 -0
  5. package/dist/Handles/index.cjs +1 -0
  6. package/dist/Handles/index.d.ts +98 -0
  7. package/dist/Handles/index.js +117 -0
  8. package/dist/Marks/Mark.cjs +1 -0
  9. package/dist/Marks/Mark.d.ts +9 -0
  10. package/dist/Marks/Mark.js +39 -0
  11. package/dist/Marks/index.cjs +1 -0
  12. package/dist/Marks/index.d.ts +15 -0
  13. package/dist/Marks/index.js +31 -0
  14. package/dist/Slider.cjs +1 -0
  15. package/dist/Slider.d.ts +253 -0
  16. package/dist/Slider.js +343 -0
  17. package/dist/Steps/Dot.cjs +1 -0
  18. package/dist/Steps/Dot.d.ts +9 -0
  19. package/dist/Steps/Dot.js +38 -0
  20. package/dist/Steps/index.cjs +1 -0
  21. package/dist/Steps/index.d.ts +11 -0
  22. package/dist/Steps/index.js +41 -0
  23. package/dist/Tracks/Track.cjs +1 -0
  24. package/dist/Tracks/Track.d.ts +61 -0
  25. package/dist/Tracks/Track.js +82 -0
  26. package/dist/Tracks/index.cjs +1 -0
  27. package/dist/Tracks/index.d.ts +47 -0
  28. package/dist/Tracks/index.js +83 -0
  29. package/dist/context.cjs +1 -0
  30. package/dist/context.d.ts +51 -0
  31. package/dist/context.js +27 -0
  32. package/dist/hooks/useDrag.cjs +1 -0
  33. package/dist/hooks/useDrag.d.ts +11 -0
  34. package/dist/hooks/useDrag.js +88 -0
  35. package/dist/hooks/useOffset.cjs +1 -0
  36. package/dist/hooks/useOffset.d.ts +10 -0
  37. package/dist/hooks/useOffset.js +98 -0
  38. package/dist/hooks/useRange.cjs +1 -0
  39. package/dist/hooks/useRange.d.ts +8 -0
  40. package/dist/hooks/useRange.js +10 -0
  41. package/dist/index.cjs +1 -0
  42. package/dist/index.d.ts +3 -0
  43. package/dist/index.js +4 -0
  44. package/dist/interface.cjs +1 -0
  45. package/dist/interface.d.ts +7 -0
  46. package/dist/interface.js +1 -0
  47. package/dist/util.cjs +1 -0
  48. package/dist/util.d.ts +6 -0
  49. package/dist/util.js +29 -0
  50. package/docs/TooltipSlider.tsx +94 -0
  51. package/docs/assets/anim.less +63 -0
  52. package/docs/assets/bootstrap.less +163 -0
  53. package/docs/assets/index.less +337 -0
  54. package/docs/debug.vue +60 -0
  55. package/docs/editable.vue +59 -0
  56. package/docs/handle.vue +45 -0
  57. package/docs/marks.vue +85 -0
  58. package/docs/multiple.vue +54 -0
  59. package/docs/range.vue +211 -0
  60. package/docs/slider.stories.vue +45 -0
  61. package/docs/sliderDemo.vue +267 -0
  62. package/docs/vertical.vue +122 -0
  63. package/package.json +35 -0
  64. package/src/Handles/Handle.tsx +226 -0
  65. package/src/Handles/index.tsx +124 -0
  66. package/src/Marks/Mark.tsx +40 -0
  67. package/src/Marks/index.tsx +40 -0
  68. package/src/Slider.tsx +582 -0
  69. package/src/Steps/Dot.tsx +40 -0
  70. package/src/Steps/index.tsx +54 -0
  71. package/src/Tracks/Track.tsx +89 -0
  72. package/src/Tracks/index.tsx +92 -0
  73. package/src/context.ts +65 -0
  74. package/src/hooks/useDrag.ts +244 -0
  75. package/src/hooks/useOffset.ts +264 -0
  76. package/src/hooks/useRange.ts +24 -0
  77. package/src/index.ts +8 -0
  78. package/src/interface.ts +17 -0
  79. package/src/util.ts +41 -0
  80. package/vite.config.ts +18 -0
  81. package/vitest.config.ts +11 -0
package/src/Slider.tsx ADDED
@@ -0,0 +1,582 @@
1
+ import type { ComputedRef, CSSProperties, ExtractPropTypes, PropType, Ref } from 'vue'
2
+ import type { HandlesRef } from './Handles'
3
+ import type {
4
+ AriaValueFormat,
5
+ Direction,
6
+ OnStartMove,
7
+ SliderClassNames,
8
+ SliderStyles,
9
+ } from './interface'
10
+ import type { InternalMarkObj, MarkObj } from './Marks'
11
+ import isEqual from '@v-c/util/dist/isEqual'
12
+ import warning from '@v-c/util/dist/warning'
13
+ import cls from 'classnames'
14
+ import { computed, defineComponent, isVNode, ref, shallowRef, watch, watchEffect } from 'vue'
15
+ import { useProviderSliderContext } from './context'
16
+ import Handles from './Handles'
17
+ import useDrag from './hooks/useDrag'
18
+ import useOffset from './hooks/useOffset'
19
+ import useRange from './hooks/useRange'
20
+ import Marks from './Marks'
21
+ import Steps from './Steps'
22
+ import Tracks from './Tracks'
23
+
24
+ export interface RangeConfig {
25
+ editable?: boolean
26
+ draggableTrack?: boolean
27
+ /** Set min count when `editable` */
28
+ minCount?: number
29
+ /** Set max count when `editable` */
30
+ maxCount?: number
31
+ }
32
+
33
+ function sliderProps() {
34
+ return {
35
+ prefixCls: { type: String, default: 'vc-slider' },
36
+ className: String,
37
+ classNames: Object as PropType<SliderClassNames>,
38
+ styles: Object as PropType<SliderStyles>,
39
+ id: String,
40
+ disabled: { type: Boolean, default: false },
41
+ keyboard: { type: Boolean, default: true },
42
+ autoFocus: Boolean,
43
+ min: { type: Number, default: 0 },
44
+ max: { type: Number, default: 100 },
45
+ step: { type: Number, default: 1 },
46
+ value: [Number, Array] as PropType<number | number[]>,
47
+ defaultValue: [Number, Array] as PropType<number | number[]>,
48
+ range: [Boolean, Object] as PropType<boolean | RangeConfig>,
49
+ count: Number,
50
+ allowCross: { type: Boolean, default: true },
51
+ pushable: { type: [Boolean, Number], default: false },
52
+ reverse: Boolean,
53
+ vertical: Boolean,
54
+ included: { type: Boolean, default: true },
55
+ startPoint: Number,
56
+ trackStyle: [Object, Array] as PropType<Record<string, any> | Record<string, any>[]>,
57
+ handleStyle: [Object, Array] as PropType<Record<string, any> | Record<string, any>[]>,
58
+ railStyle: Object as PropType<Record<string, any>>,
59
+ dotStyle: [Object, Function] as PropType<Record<string, any> | ((dotValue: number) => Record<string, any>)>,
60
+ activeDotStyle: [Object, Function] as PropType<Record<string, any> | ((dotValue: number) => Record<string, any>)>,
61
+ marks: Object as PropType<Record<string | number, any | MarkObj>>,
62
+ dots: Boolean,
63
+ handleRender: Function,
64
+ activeHandleRender: Function,
65
+ track: { type: Boolean, default: true },
66
+ tabIndex: { type: [Number, Array] as PropType<number | number[]>, default: 0 },
67
+ ariaLabelForHandle: [String, Array] as PropType<string | string[]>,
68
+ ariaLabelledByForHandle: [String, Array] as PropType<string | string[]>,
69
+ ariaRequired: Boolean,
70
+ ariaValueTextFormatterForHandle: [Function, Array] as PropType<AriaValueFormat | AriaValueFormat[]>,
71
+ }
72
+ }
73
+ export type SliderProps = Partial<ExtractPropTypes<ReturnType<typeof sliderProps>>>
74
+
75
+ export interface SliderRef {
76
+ focus: () => void
77
+ blur: () => void
78
+ }
79
+
80
+ export default defineComponent({
81
+ name: 'Slider',
82
+ props: {
83
+ ...sliderProps(),
84
+ },
85
+ emits: ['focus', 'blur', 'change', 'beforeChange', 'afterChange', 'changeComplete'],
86
+ setup(props, { attrs, emit, expose }) {
87
+ const handlesRef = ref<HandlesRef>()
88
+ const containerRef = ref<HTMLDivElement>()
89
+
90
+ const direction = shallowRef<Direction>('ltr')
91
+ watch([() => props.reverse, () => props.vertical], ([newReverse, newVertical]) => {
92
+ if (newVertical) {
93
+ direction.value = newReverse ? 'ttb' : 'btt'
94
+ }
95
+ else {
96
+ direction.value = newReverse ? 'rtl' : 'ltr'
97
+ }
98
+ }, { immediate: true })
99
+
100
+ const mergedMin = shallowRef(0)
101
+ const mergedMax = shallowRef(100)
102
+ const mergedStep = shallowRef(1)
103
+ const markList = ref<InternalMarkObj[]>([])
104
+
105
+ const mergedValue = ref<number | number[]>(props.defaultValue! || props.value!)
106
+ const rawValues = ref<number[] | ComputedRef<number[]>>([])
107
+ const getRange = ref()
108
+ const getOffset = ref()
109
+
110
+ watchEffect(() => {
111
+ const {
112
+ range,
113
+ min,
114
+ max,
115
+ step,
116
+ pushable,
117
+ marks,
118
+ allowCross,
119
+ value,
120
+ count,
121
+ } = props
122
+ // ============================ Range =============================
123
+ const [rangeEnabled, rangeEditable, rangeDraggableTrack, minCount, maxCount] = useRange(range)
124
+ getRange.value = {
125
+ rangeEnabled,
126
+ rangeEditable,
127
+ rangeDraggableTrack,
128
+ minCount,
129
+ maxCount,
130
+ }
131
+
132
+ mergedMin.value = isFinite(min) ? min : 0
133
+ mergedMax.value = isFinite(max) ? max : 100
134
+
135
+ // ============================= Step =============================
136
+ mergedStep.value = step !== null && step <= 0 ? 1 : step
137
+
138
+ // ============================= Push =============================
139
+ const mergedPush = computed(() => {
140
+ if (typeof pushable === 'boolean') {
141
+ return pushable ? mergedStep.value : false
142
+ }
143
+ return pushable >= 0 ? pushable : false
144
+ })
145
+
146
+ // ============================ Marks =============================
147
+ markList.value = Object.keys(marks || {})
148
+ .map<InternalMarkObj>((key) => {
149
+ const mark = marks?.[key]
150
+ const markObj: InternalMarkObj = {
151
+ value: Number(key),
152
+ }
153
+
154
+ if (
155
+ mark
156
+ && typeof mark === 'object'
157
+ && !isVNode(mark)
158
+ && ('label' in mark || 'style' in mark)
159
+ ) {
160
+ markObj.style = mark.style
161
+ markObj.label = mark.label
162
+ }
163
+ else {
164
+ markObj.label = mark
165
+ }
166
+
167
+ return markObj
168
+ })
169
+ .filter(({ label }) => label || typeof label === 'number')
170
+ .sort((a, b) => a.value - b.value)
171
+
172
+ // ============================ Format ============================
173
+ const [formatValue, offsetValues] = useOffset(
174
+ mergedMin.value,
175
+ mergedMax.value,
176
+ mergedStep.value,
177
+ markList.value,
178
+ allowCross,
179
+ mergedPush.value,
180
+ )
181
+ getOffset.value = {
182
+ formatValue,
183
+ offsetValues,
184
+ }
185
+
186
+ // ============================ Values ============================
187
+ if (value !== undefined) {
188
+ mergedValue.value = value
189
+ }
190
+
191
+ const getRawValues = computed(() => {
192
+ const valueList
193
+ = mergedValue.value === null || mergedValue.value === undefined
194
+ ? []
195
+ : Array.isArray(mergedValue.value)
196
+ ? mergedValue.value
197
+ : [mergedValue.value]
198
+
199
+ const [val0 = mergedMin.value] = valueList
200
+ let returnValues = mergedValue.value === null ? [] : [val0]
201
+
202
+ // Format as range
203
+ if (rangeEnabled) {
204
+ returnValues = [...valueList]
205
+
206
+ // When count provided or value is `undefined`, we fill values
207
+ if (count || mergedValue.value === undefined) {
208
+ const pointCount = count! >= 0 ? count! + 1 : 2
209
+ returnValues = returnValues.slice(0, pointCount)
210
+
211
+ // Fill with count
212
+ while (returnValues.length < pointCount) {
213
+ returnValues.push(returnValues[returnValues.length - 1] ?? mergedMin.value)
214
+ }
215
+ }
216
+ returnValues.sort((a, b) => a - b)
217
+ }
218
+
219
+ // Align in range
220
+ returnValues.forEach((val, index) => {
221
+ returnValues[index] = formatValue(val)
222
+ })
223
+
224
+ return returnValues
225
+ })
226
+
227
+ rawValues.value = getRawValues.value
228
+ })
229
+
230
+ // =========================== onChange ===========================
231
+ const getTriggerValue = (triggerValues: number[]) => {
232
+ return getRange.value.rangeEnabled ? triggerValues : triggerValues[0]
233
+ }
234
+
235
+ const triggerChange = (nextValues: number[]) => {
236
+ // Order first
237
+ const cloneNextValues = [...nextValues].sort((a, b) => a - b)
238
+
239
+ // Trigger event if needed
240
+ if (!isEqual(cloneNextValues, rawValues.value, true)) {
241
+ emit('change', getTriggerValue(cloneNextValues))
242
+ }
243
+
244
+ // We set this later since it will re-render component immediately
245
+ mergedValue.value = cloneNextValues
246
+ }
247
+
248
+ const finishChange = (draggingDelete?: boolean) => {
249
+ // Trigger from `useDrag` will tell if it's a delete action
250
+ if (draggingDelete) {
251
+ handlesRef.value?.hideHelp()
252
+ }
253
+
254
+ const finishValue = getTriggerValue(rawValues.value)
255
+ if (props.onAfterChange) {
256
+ emit('afterChange', finishValue)
257
+ warning(
258
+ false,
259
+ '[vc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.',
260
+ )
261
+ }
262
+ emit('changeComplete', finishValue)
263
+ }
264
+
265
+ const onDelete = (index: number) => {
266
+ if (props.disabled || !getRange.value.rangeEditable || rawValues.value.length <= getRange.value.minCount) {
267
+ return
268
+ }
269
+
270
+ const cloneNextValues = [...rawValues.value]
271
+ cloneNextValues.splice(index, 1)
272
+
273
+ emit('beforeChange', getTriggerValue(cloneNextValues))
274
+ triggerChange(cloneNextValues)
275
+
276
+ const nextFocusIndex = Math.max(0, index - 1)
277
+ handlesRef.value?.hideHelp()
278
+ handlesRef.value?.focus(nextFocusIndex)
279
+ }
280
+ const [draggingIndex, draggingValue, draggingDelete, cacheValues, onStartDrag] = useDrag(
281
+ containerRef as Ref<HTMLDivElement>,
282
+ direction,
283
+ rawValues,
284
+ mergedMin,
285
+ mergedMax,
286
+ getOffset.value.formatValue,
287
+ triggerChange,
288
+ finishChange,
289
+ getOffset.value.offsetValues,
290
+ getRange.value.rangeEditable,
291
+ getRange.value.minCount,
292
+ )
293
+
294
+ /**
295
+ * When `rangeEditable` will insert a new value in the values array.
296
+ * Else it will replace the value in the values array.
297
+ */
298
+ const changeToCloseValue = (newValue: number, e?: MouseEvent) => {
299
+ if (!props.disabled) {
300
+ // Create new values
301
+ const cloneNextValues = [...rawValues.value]
302
+
303
+ let valueIndex = 0
304
+ let valueBeforeIndex = 0 // Record the index which value < newValue
305
+ let valueDist = mergedMax.value - mergedMin.value
306
+
307
+ rawValues.value.forEach((val, index) => {
308
+ const dist = Math.abs(newValue - val)
309
+ if (dist <= valueDist) {
310
+ valueDist = dist
311
+ valueIndex = index
312
+ }
313
+
314
+ if (val < newValue) {
315
+ valueBeforeIndex = index
316
+ }
317
+ })
318
+
319
+ let focusIndex = valueIndex
320
+
321
+ if (getRange.value.rangeEditable && valueDist !== 0 && (!getRange.value.maxCount || rawValues.value.length < getRange.value.maxCount)) {
322
+ cloneNextValues.splice(valueBeforeIndex + 1, 0, newValue)
323
+ focusIndex = valueBeforeIndex + 1
324
+ }
325
+ else {
326
+ cloneNextValues[valueIndex] = newValue
327
+ }
328
+ // Fill value to match default 2 (only when `rawValues` is empty)
329
+ if (getRange.value.rangeEnabled && !rawValues.value.length && props.count === undefined) {
330
+ cloneNextValues.push(newValue)
331
+ }
332
+
333
+ const nextValue = getTriggerValue(cloneNextValues)
334
+ emit('beforeChange', nextValue)
335
+ triggerChange(cloneNextValues)
336
+
337
+ if (e) {
338
+ (document.activeElement as HTMLElement)?.blur?.()
339
+ handlesRef.value?.focus(focusIndex)
340
+ onStartDrag(e, focusIndex, cloneNextValues)
341
+ }
342
+ else {
343
+ if (props.onAfterChange) {
344
+ // https://github.com/ant-design/ant-design/issues/49997
345
+ emit('afterChange', nextValue)
346
+ warning(
347
+ false,
348
+ '[vc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.',
349
+ )
350
+ }
351
+ emit('changeComplete', nextValue)
352
+ }
353
+ }
354
+ }
355
+
356
+ // ============================ Click =============================
357
+ const onSliderMouseDown = (e: MouseEvent) => {
358
+ e.preventDefault()
359
+ const { width, height, left, top, bottom, right }
360
+ = containerRef.value!.getBoundingClientRect()
361
+ const { clientX, clientY } = e
362
+
363
+ let percent: number
364
+ switch (direction.value) {
365
+ case 'btt':
366
+ percent = (bottom - clientY) / height
367
+ break
368
+
369
+ case 'ttb':
370
+ percent = (clientY - top) / height
371
+ break
372
+
373
+ case 'rtl':
374
+ percent = (right - clientX) / width
375
+ break
376
+
377
+ default:
378
+ percent = (clientX - left) / width
379
+ }
380
+
381
+ const nextValue = mergedMin.value + percent * (mergedMax.value - mergedMin.value)
382
+ console.log('click', nextValue, getOffset.value.formatValue(nextValue))
383
+ changeToCloseValue(getOffset.value.formatValue(nextValue), e)
384
+ }
385
+
386
+ // =========================== Keyboard ===========================
387
+ const keyboardValue = ref<number | null>(null)
388
+
389
+ const onHandleOffsetChange = (offset: number | 'min' | 'max', valueIndex: number) => {
390
+ if (!props.disabled) {
391
+ const next = getOffset.value.offsetValues(rawValues.value, offset, valueIndex)
392
+
393
+ emit('beforeChange', getTriggerValue(rawValues.value))
394
+ triggerChange(next.values)
395
+
396
+ keyboardValue.value = next.value
397
+ }
398
+ }
399
+
400
+ watchEffect(() => {
401
+ if (keyboardValue.value !== null) {
402
+ const valueIndex = rawValues.value.indexOf(keyboardValue.value)
403
+ if (valueIndex >= 0) {
404
+ handlesRef.value?.focus(valueIndex)
405
+ }
406
+ }
407
+
408
+ keyboardValue.value = null
409
+ })
410
+
411
+ // ============================= Drag =============================
412
+ const mergedDraggableTrack = computed(() => {
413
+ if (getRange.value.rangeDraggableTrack && mergedStep.value === null) {
414
+ if (process.env.NODE_ENV !== 'production') {
415
+ warning(false, '`draggableTrack` is not supported when `step` is `null`.')
416
+ }
417
+ return false
418
+ }
419
+ return getRange.value.rangeDraggableTrack
420
+ })
421
+
422
+ const onStartMove: OnStartMove = (e, valueIndex) => {
423
+ console.log('onStartMove-valueIndex', valueIndex)
424
+ onStartDrag(e, valueIndex)
425
+
426
+ emit('beforeChange', getTriggerValue(rawValues.value))
427
+ }
428
+
429
+ // Auto focus for updated handle
430
+ const dragging = computed(() => draggingIndex.value !== -1)
431
+ watchEffect(() => {
432
+ if (!dragging.value) {
433
+ const valueIndex = rawValues.value.lastIndexOf(draggingValue.value)
434
+ handlesRef.value?.focus(valueIndex)
435
+ }
436
+ })
437
+
438
+ // =========================== Included ===========================
439
+ const sortedCacheValues = computed(
440
+ () => [...cacheValues.value].sort((a, b) => a - b),
441
+ )
442
+
443
+ // Provide a range values with included [min, max]
444
+ // Used for Track, Mark & Dot
445
+ const [includedStart, includedEnd] = computed(() => {
446
+ if (!getRange.value.rangeEnabled) {
447
+ return [mergedMin.value, sortedCacheValues.value[0]]
448
+ }
449
+
450
+ return [sortedCacheValues.value[0], sortedCacheValues.value[sortedCacheValues.value.length - 1]]
451
+ }).value
452
+
453
+ // ============================= Refs =============================
454
+ expose({
455
+ focus: () => {
456
+ handlesRef.value?.focus(0)
457
+ },
458
+ blur: () => {
459
+ const { activeElement } = document
460
+ if (containerRef.value?.contains(activeElement)) {
461
+ (activeElement as HTMLElement)?.blur()
462
+ }
463
+ },
464
+ })
465
+
466
+ // ========================== Auto Focus ==========================
467
+ watchEffect(() => {
468
+ if (props.autoFocus) {
469
+ handlesRef.value?.focus(0)
470
+ }
471
+ })
472
+ // =========================== Context ============================
473
+ const context = computed(() => ({
474
+ min: mergedMin,
475
+ max: mergedMax,
476
+ direction,
477
+ disabled: props.disabled,
478
+ keyboard: props.keyboard,
479
+ step: mergedStep,
480
+ included: props.included,
481
+ includedStart,
482
+ includedEnd,
483
+ range: getRange.value.rangeEnabled,
484
+ tabIndex: props.tabIndex,
485
+ ariaLabelForHandle: props.ariaLabelForHandle,
486
+ ariaLabelledByForHandle: props.ariaLabelledByForHandle,
487
+ ariaRequired: props.ariaRequired,
488
+ ariaValueTextFormatterForHandle: props.ariaValueTextFormatterForHandle,
489
+ styles: props.styles || {},
490
+ classNames: props.classNames || {},
491
+ }))
492
+ useProviderSliderContext(context.value)
493
+
494
+ // ============================ Render ============================
495
+ return () => {
496
+ const {
497
+ prefixCls = 'vc-slider',
498
+ id,
499
+
500
+ // Status
501
+ disabled = false,
502
+ vertical,
503
+
504
+ // Style
505
+ startPoint,
506
+ trackStyle,
507
+ handleStyle,
508
+ railStyle,
509
+ dotStyle,
510
+ activeDotStyle,
511
+
512
+ // Decorations
513
+ dots,
514
+ handleRender,
515
+ activeHandleRender,
516
+
517
+ // Components
518
+ track,
519
+ classNames,
520
+ styles,
521
+ } = props
522
+ return (
523
+ <div
524
+ ref={containerRef}
525
+ class={cls(prefixCls, [attrs.class], {
526
+ [`${prefixCls}-disabled`]: disabled,
527
+ [`${prefixCls}-vertical`]: vertical,
528
+ [`${prefixCls}-horizontal`]: !vertical,
529
+ [`${prefixCls}-with-marks`]: markList.value.length,
530
+ })}
531
+ style={attrs.style as CSSProperties}
532
+ onMousedown={onSliderMouseDown}
533
+ id={id}
534
+ >
535
+ <div
536
+ class={cls(`${prefixCls}-rail`, classNames?.rail)}
537
+ style={{ ...railStyle, ...styles?.rail }}
538
+ />
539
+
540
+ {track && (
541
+ <Tracks
542
+ prefixCls={prefixCls}
543
+ // 将style换成trackStyle,因为vue通过attrs取style,数组会合并,相同的样式名如backgroundColor后一个会覆盖前面的
544
+ trackStyle={trackStyle}
545
+ values={rawValues.value}
546
+ startPoint={startPoint}
547
+ onStartMove={mergedDraggableTrack.value ? onStartMove : undefined}
548
+ />
549
+ )}
550
+
551
+ <Steps
552
+ prefixCls={prefixCls}
553
+ marks={markList.value}
554
+ dots={dots}
555
+ style={dotStyle}
556
+ activeStyle={activeDotStyle}
557
+ />
558
+
559
+ <Handles
560
+ ref={handlesRef}
561
+ prefixCls={prefixCls}
562
+ // 原因如⬆️trackStyle
563
+ handleStyle={handleStyle}
564
+ values={cacheValues.value}
565
+ draggingIndex={draggingIndex.value}
566
+ draggingDelete={draggingDelete.value}
567
+ onStartMove={onStartMove}
568
+ onOffsetChange={onHandleOffsetChange}
569
+ onFocus={(e: FocusEvent) => emit('focus', e)}
570
+ onBlur={(e: FocusEvent) => emit('blur', e)}
571
+ handleRender={handleRender}
572
+ activeHandleRender={activeHandleRender}
573
+ onChangeComplete={finishChange}
574
+ onDelete={getRange.value.rangeEditable ? onDelete : () => {}}
575
+ />
576
+
577
+ <Marks prefixCls={prefixCls} marks={markList.value} onClick={changeToCloseValue} />
578
+ </div>
579
+ )
580
+ }
581
+ },
582
+ })
@@ -0,0 +1,40 @@
1
+ import type { CSSProperties, FunctionalComponent } from 'vue'
2
+ import classNames from 'classnames'
3
+ import { useInjectSlider } from '../context'
4
+ import { getDirectionStyle } from '../util'
5
+
6
+ export interface DotProps {
7
+ prefixCls: string
8
+ value: number
9
+ style?: CSSProperties | ((dotValue: number) => CSSProperties)
10
+ activeStyle?: CSSProperties | ((dotValue: number) => CSSProperties)
11
+ }
12
+
13
+ const Dot: FunctionalComponent<DotProps> = (props, { attrs }) => {
14
+ const { prefixCls, value, activeStyle } = props
15
+ const { min, max, direction, included, includedStart, includedEnd } = useInjectSlider()
16
+
17
+ const dotClassName = `${prefixCls}-dot`
18
+ const active = included && includedStart <= value && value <= includedEnd
19
+
20
+ // ============================ Offset ============================
21
+ let mergedStyle: CSSProperties = {
22
+ ...getDirectionStyle(direction.value, value, min.value, max.value),
23
+ }
24
+
25
+ if (active) {
26
+ mergedStyle = {
27
+ ...mergedStyle,
28
+ ...(typeof activeStyle === 'function' ? activeStyle(value) : activeStyle),
29
+ }
30
+ }
31
+
32
+ return (
33
+ <span
34
+ class={classNames(dotClassName, { [`${dotClassName}-active`]: active })}
35
+ style={{ ...mergedStyle, ...attrs.style as CSSProperties }}
36
+ />
37
+ )
38
+ }
39
+
40
+ export default Dot
@@ -0,0 +1,54 @@
1
+ import type { CSSProperties, FunctionalComponent } from 'vue'
2
+ import type { InternalMarkObj } from '../Marks'
3
+ import { computed } from 'vue'
4
+ import { useInjectSlider } from '../context'
5
+ import Dot from './Dot'
6
+
7
+ export interface StepsProps {
8
+ prefixCls: string
9
+ marks: InternalMarkObj[]
10
+ dots?: boolean
11
+ style?: CSSProperties | ((dotValue: number) => CSSProperties)
12
+ activeStyle?: CSSProperties | ((dotValue: number) => CSSProperties)
13
+ }
14
+
15
+ const Steps: FunctionalComponent<StepsProps> = (props, { attrs }) => {
16
+ const { prefixCls, marks, dots, activeStyle } = props
17
+ const { min, max, step } = useInjectSlider()
18
+
19
+ const stepDots = computed<number[]>(() => {
20
+ const dotSet = new Set<number>()
21
+
22
+ // Add marks
23
+ marks.forEach((mark) => {
24
+ dotSet.add(mark.value)
25
+ })
26
+
27
+ // Fill dots
28
+ if (dots && step !== null) {
29
+ let current = min.value
30
+ while (current <= max.value) {
31
+ dotSet.add(current)
32
+ current += step.value!
33
+ }
34
+ }
35
+
36
+ return Array.from(dotSet)
37
+ })
38
+
39
+ return (
40
+ <div class={`${prefixCls}-step`}>
41
+ {stepDots.value.map(dotValue => (
42
+ <Dot
43
+ prefixCls={prefixCls}
44
+ key={dotValue}
45
+ value={dotValue}
46
+ style={{ ...attrs.style as CSSProperties }}
47
+ activeStyle={activeStyle}
48
+ />
49
+ ))}
50
+ </div>
51
+ )
52
+ }
53
+
54
+ export default Steps