UNPKG

@coreui/vue-pro

Version:

UI Components Library for Vue.js

320 lines (317 loc) 14.2 kB
import { defineComponent, ref, watch, onMounted, h } from 'vue'; import { getThumbSize, calculateMoveValue, updateValue, calculateTooltipPosition, updateGradient, calculateLabelPosition, getLabelValue, calculateClickValue, getNearestValueIndex, validateValue } from './utils.js'; import isRTL from '../../utils/isRTL.js'; const CRangeSlider = defineComponent({ name: 'CRangeSlider', props: { /** * Enable or disable clickable labels in the Vue Range Slider. * When set to `true`, users can click on labels to adjust the slider's value directly, enhancing interactivity and user experience. */ clickableLabels: { type: Boolean, default: true, }, /** * Control the interactive state of the Vue Range Slider with the `disabled` prop. * Setting it to `true` will disable all slider functionalities, preventing user interaction and visually indicating a non-interactive state. */ disabled: { type: Boolean, default: false, }, /** * Define the minimum distance between slider handles using the `distance` prop in the Vue Range Slider. * This ensures that the handles maintain a specified separation, preventing overlap and maintaining clear value distinctions. */ distance: { type: Number, default: 0, }, /** * Add descriptive labels to your Vue Range Slider by providing an array of `labels`. * These labels enhance the slider's usability by clearly indicating key values and providing contextual information to users. */ labels: { type: Array, default: () => [], }, /** * Specify the maximum value for the Vue Range Slider with the `max` prop. * This determines the upper limit of the slider's range, enabling precise control over the highest selectable value. */ max: { type: Number, default: 100, }, /** * Set the minimum value for the Vue Range Slider using the `min` prop. * This defines the lower bound of the slider's range, allowing you to control the starting point of user selection. */ min: { type: Number, default: 0, }, /** * The default name for a value passed using v-model. */ modelValue: [Number, Array], /** * Assign a `name` to the Vue Range Slider for form integration. * Whether using a single string or an array of strings, this prop ensures that the slider's values are correctly identified when submitting forms. */ name: { type: [String, Array], default: '', }, /** * Control the granularity of the Vue Range Slider by setting the `step` prop. * This defines the increment intervals between selectable values, allowing for precise adjustments based on your application's requirements. */ step: { type: Number, default: 1, }, /** * Toggle the visibility of tooltips in the Vue Range Slider with the `tooltips` prop. * When enabled, tooltips display the current value of the slider handles, providing real-time feedback to users. */ tooltips: { type: Boolean, default: true, }, /** * Customize the display format of tooltips in the Vue Range Slider using the `tooltipsFormat` function. * This allows you to format the tooltip values according to your specific requirements, enhancing the clarity and presentation of information. */ tooltipsFormat: { type: Function, default: null, }, /** * Controls the visual representation of the slider's track. When set to `'fill'`, the track is dynamically filled based on the slider's value(s). Setting it to `false` disables the filled track. */ track: { type: [Boolean, String], default: 'fill', validator: (value) => { return typeof value === 'boolean' || value === 'fill'; }, }, /** * Set the current value(s) of the Vue Range Slider using the `value` prop. * Whether you're using a single value or an array for multi-handle sliders, this prop controls the slider's position and ensures it reflects the desired state. */ value: { type: [Number, Array], default: () => [0], }, /** * Orient the Vue Range Slider vertically by setting the `vertical` prop to `true`. * This changes the slider's layout from horizontal to vertical, providing a different aesthetic and fitting various UI designs. */ vertical: { type: Boolean, default: false, }, }, emits: [ 'change', /** * Emit the new value whenever there’s a change event. */ 'update:modelValue', ], setup(props, { emit }) { const rangeSliderRef = ref(null); const inputsRef = ref([]); const labelsContainerRef = ref(null); const labelsRef = ref([]); const trackRef = ref(null); const currentValue = ref(props.modelValue ? Array.isArray(props.modelValue) ? props.modelValue : [props.modelValue] : Array.isArray(props.value) ? props.value : [props.value]); const isDragging = ref(false); const _isRTL = ref(false); const dragIndex = ref(0); const thumbSize = ref(); watch(() => props.value, (newVal) => { currentValue.value = Array.isArray(newVal) ? newVal : [newVal]; }); watch(() => props.modelValue, (newVal) => { if (newVal !== undefined) { currentValue.value = Array.isArray(newVal) ? newVal : [newVal]; } }); // Adjust labels container size based on labels onMounted(() => { const maxSize = Math.max(...labelsRef.value.map((label) => props.vertical ? label.offsetWidth : label.offsetHeight)); if (labelsContainerRef.value) { labelsContainerRef.value.style[props.vertical ? 'width' : 'height'] = `${maxSize}px`; } if (rangeSliderRef.value) { _isRTL.value = isRTL(rangeSliderRef.value); thumbSize.value = getThumbSize(rangeSliderRef.value, props.vertical); } }); watch(isDragging, (newVal) => { if (newVal) { globalThis.addEventListener('mousemove', handleMouseMove); globalThis.addEventListener('mouseup', handleMouseUp); } else { globalThis.removeEventListener('mousemove', handleMouseMove); globalThis.removeEventListener('mouseup', handleMouseUp); } }); const updateNearestValue = (value) => { const nearestIndex = getNearestValueIndex(value, currentValue.value); const newCurrentValue = [...currentValue.value]; newCurrentValue[nearestIndex] = validateValue(value, currentValue.value, props.distance, nearestIndex); setTimeout(() => { if (inputsRef.value[nearestIndex]) { inputsRef.value[nearestIndex].focus(); } }, 0); currentValue.value = newCurrentValue; emit('change', newCurrentValue); emit('update:modelValue', newCurrentValue); }; const handleInputChange = (event, index) => { if (props.disabled) return; const target = event.target; const value = Number(target.value); const newCurrentValue = updateValue(value, currentValue.value, props.distance, index); currentValue.value = newCurrentValue; emit('change', newCurrentValue); emit('update:modelValue', newCurrentValue); }; const handleInputsContainerMouseDown = (event) => { if (!trackRef.value || event.button !== 0 || props.disabled) return; const target = event.target; if (!(target instanceof HTMLInputElement) && target !== trackRef.value) { return; } const clickValue = calculateClickValue(event, trackRef.value, props.min, props.max, props.step, props.vertical, _isRTL.value); const index = getNearestValueIndex(clickValue, currentValue.value); isDragging.value = true; dragIndex.value = index; updateNearestValue(clickValue); }; const handleLabelClick = (event, value) => { if (!props.clickableLabels || props.disabled || event.button !== 0) return; updateNearestValue(value); }; const handleMouseMove = (event) => { if (!isDragging.value || !trackRef.value || props.disabled) return; const moveValue = calculateMoveValue(event, trackRef.value, props.min, props.max, props.step, props.vertical, _isRTL.value); const newCurrentValue = updateValue(moveValue, currentValue.value, props.distance, dragIndex.value); currentValue.value = newCurrentValue; emit('change', newCurrentValue); emit('update:modelValue', newCurrentValue); }; const handleMouseUp = () => { isDragging.value = false; }; return () => h('div', { class: [ 'range-slider', { 'range-slider-vertical': props.vertical, disabled: props.disabled, }, ], ref: rangeSliderRef, }, [ h('div', { class: 'range-slider-inputs-container', onMousedown: handleInputsContainerMouseDown, }, [ currentValue.value.map((value, index) => [ h('input', { class: 'range-slider-input', type: 'range', min: props.min, max: props.max, step: props.step, value: value, name: Array.isArray(props.name) ? props.name[index] : `${props.name || ''}-${index}`, role: 'slider', 'aria-valuemin': props.min, 'aria-valuemax': props.max, 'aria-valuenow': value, 'aria-orientation': props.vertical ? 'vertical' : 'horizontal', disabled: props.disabled, onInput: (e) => handleInputChange(e, index), ref: (el) => { inputsRef.value[index] = el; }, }), props.tooltips && h('div', { class: 'range-slider-tooltip', ...(thumbSize.value && { style: calculateTooltipPosition(props.min, props.max, value, thumbSize.value, props.vertical, _isRTL.value), }), }, [ h('div', { class: 'range-slider-tooltip-inner' }, [ props.tooltipsFormat ? props.tooltipsFormat(value) : value, ]), h('div', { class: 'range-slider-tooltip-arrow' }), ]), ]), h('div', { class: 'range-slider-track', ...(props.track && { style: updateGradient(props.min, props.max, currentValue.value, props.vertical, _isRTL.value), }), ref: trackRef, }), ]), Array.isArray(props.labels) && props.labels.length > 0 && h('div', { class: 'range-slider-labels-container', ref: labelsContainerRef, }, props.labels.map((label, index) => { const labelPosition = calculateLabelPosition(props.min, props.max, props.labels, label, index); const labelValue = getLabelValue(props.min, props.max, props.labels, label, index); const labelStyle = { ...(props.vertical ? { bottom: labelPosition } : _isRTL.value ? { right: labelPosition } : { left: labelPosition }), ...(typeof label === 'object' && 'style' in label ? label.style : {}), }; return h('div', { class: [ 'range-slider-label', { clickable: props.clickableLabels, }, typeof label === 'object' && 'className' in label ? label.className : '', ], style: labelStyle, onMousedown: (event) => handleLabelClick(event, labelValue), key: index, ref: (el) => { labelsRef.value[index] = el; }, }, typeof label === 'object' && 'label' in label ? label.label : label); })), ]); }, }); export { CRangeSlider }; //# sourceMappingURL=CRangeSlider.js.map