UNPKG

@varlet/ui

Version:

A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.

566 lines (565 loc) • 20.8 kB
import { computed, defineComponent, nextTick, onBeforeUnmount, reactive, ref, watch } from "vue"; import { call, clamp, error, getRect, hasOwn, isArray, isNumber, preventDefault, toNumber, warn } from "@varlet/shared"; import { onSmartMounted, onWindowResize, useEventListener } from "@varlet/use"; import VarFormDetails from "../form-details/index.mjs"; import { useForm } from "../form/provide.mjs"; import Hover from "../hover/index.mjs"; import VarHoverOverlay, { useHoverOverlay } from "../hover-overlay/index.mjs"; import { createNamespace, useValidation } from "../utils/components.mjs"; import { getLeft, toSizeUnit } from "../utils/elements.mjs"; import { props, Thumbs } from "./props.mjs"; const { name, n, classes } = createNamespace("slider"); import { normalizeClass as _normalizeClass, normalizeStyle as _normalizeStyle, createElementVNode as _createElementVNode, renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, renderSlot as _renderSlot, resolveDirective as _resolveDirective, withDirectives as _withDirectives, resolveComponent as _resolveComponent, createVNode as _createVNode, toDisplayString as _toDisplayString, withModifiers as _withModifiers } from "vue"; const _hoisted_1 = ["tabindex", "aria-valuemin", "aria-valuemax", "aria-valuenow", "aria-disabled", "aria-valuetext", "onTouchstart", "onFocusin", "onFocusout"]; function __render__(_ctx, _cache) { const _component_var_hover_overlay = _resolveComponent("var-hover-overlay"); const _component_var_form_details = _resolveComponent("var-form-details"); const _directive_hover = _resolveDirective("hover"); return _openBlock(), _createElementBlock( "div", { class: _normalizeClass(_ctx.classes(_ctx.n(_ctx.direction), _ctx.n("$--box"))) }, [ _createElementVNode( "div", { ref: "sliderEl", class: _normalizeClass(_ctx.classes(_ctx.n(`${_ctx.direction}-block`), [_ctx.isDisabled, _ctx.n("--disabled")], [_ctx.errorMessage, _ctx.n(`${_ctx.direction}--error`)])), onClick: _cache[0] || (_cache[0] = (...args) => _ctx.handleClick && _ctx.handleClick(...args)) }, [ _createElementVNode( "div", { class: _normalizeClass(_ctx.n(`${_ctx.direction}-track`)) }, [ _createElementVNode( "div", { class: _normalizeClass(_ctx.n(`${_ctx.direction}-track-background`)), style: _normalizeStyle({ background: _ctx.trackColor, height: _ctx.isVertical ? "100%" : _ctx.toSizeUnit(_ctx.trackHeight), width: _ctx.isVertical ? _ctx.toSizeUnit(_ctx.trackHeight) : "100%" }) }, null, 6 /* CLASS, STYLE */ ), _createElementVNode( "div", { class: _normalizeClass(_ctx.n(`${_ctx.direction}-track-fill`)), style: _normalizeStyle(_ctx.getFillStyle) }, null, 6 /* CLASS, STYLE */ ) ], 2 /* CLASS */ ), (_openBlock(true), _createElementBlock( _Fragment, null, _renderList(_ctx.thumbList, (item) => { return _openBlock(), _createElementBlock("div", { key: item.enumValue, class: _normalizeClass(_ctx.n(`${_ctx.direction}-thumb`)), style: _normalizeStyle(_ctx.thumbStyle(item)), tabindex: _ctx.isDisabled ? void 0 : "0", role: "slider", "aria-valuemin": _ctx.min, "aria-valuemax": _ctx.max, "aria-valuenow": item.value, "aria-disabled": _ctx.isDisabled, "aria-valuetext": `${item.text}`, onTouchstart: _withModifiers(($event) => _ctx.start($event, item.enumValue), ["stop"]), onFocusin: ($event) => _ctx.handleFocus(item), onFocusout: ($event) => _ctx.handleBlur(item) }, [ _renderSlot(_ctx.$slots, "button", { currentValue: item.text }, () => [ _withDirectives(_createElementVNode( "div", { class: _normalizeClass(_ctx.n(`${_ctx.direction}-thumb-block`)), style: _normalizeStyle({ background: _ctx.thumbColor }) }, null, 6 /* CLASS, STYLE */ ), [ [_directive_hover, (value) => _ctx.hover(value, item), "desktop"] ]), _createElementVNode( "div", { class: _normalizeClass( _ctx.classes(_ctx.n(`${_ctx.direction}-thumb-ripple`), [ _ctx.thumbsProps[item.enumValue].active, _ctx.n(`${_ctx.direction}-thumb-ripple--active`) ]) ), style: _normalizeStyle({ background: _ctx.thumbsProps[item.enumValue].active ? _ctx.thumbColor : void 0 }) }, [ _createVNode(_component_var_hover_overlay, { hovering: !_ctx.isDisabled && item.hovering, focusing: !_ctx.isDisabled && item.focusing }, null, 8, ["hovering", "focusing"]) ], 6 /* CLASS, STYLE */ ), _createElementVNode( "div", { class: _normalizeClass( _ctx.classes(_ctx.n(`${_ctx.direction}-thumb-label`), [_ctx.showLabel(item.enumValue), _ctx.n(`${_ctx.direction}-thumb-label--active`)]) ), style: _normalizeStyle({ background: _ctx.labelColor, color: _ctx.labelTextColor, height: _ctx.toSizeUnit(_ctx.thumbSize), width: _ctx.toSizeUnit(_ctx.thumbSize) }) }, [ _createElementVNode( "span", null, _toDisplayString(item.text), 1 /* TEXT */ ) ], 6 /* CLASS, STYLE */ ) ]) ], 46, _hoisted_1); }), 128 /* KEYED_FRAGMENT */ )) ], 2 /* CLASS */ ), _createVNode(_component_var_form_details, { "error-message": _ctx.errorMessage, class: _normalizeClass(_ctx.n("form")), "var-slider-cover": "" }, null, 8, ["error-message", "class"]) ], 2 /* CLASS */ ); } const __sfc__ = defineComponent({ name, components: { VarFormDetails, VarHoverOverlay }, directives: { Hover }, props, setup(props2) { const maxDistance = ref(0); const sliderEl = ref(null); const isScroll = ref(false); const scope = computed(() => toNumber(props2.max) - toNumber(props2.min)); const unitWidth = computed(() => maxDistance.value / scope.value * toNumber(props2.step)); const isDisabled = computed(() => props2.disabled || (form == null ? void 0 : form.disabled.value)); const isReadonly = computed(() => props2.readonly || (form == null ? void 0 : form.readonly.value)); const isVertical = computed(() => props2.direction === "vertical"); const focusingFirst = ref(false); const focusingSecond = ref(false); const { bindForm, form } = useForm(); const { errorMessage, validateWithTrigger: vt, validate: v, resetValidation } = useValidation(); const { hovering: hoveringFirst, handleHovering: handleHoveringFirst } = useHoverOverlay(); const { hovering: hoveringSecond, handleHovering: handleHoveringSecond } = useHoverOverlay(); const thumbList = computed(() => { const { modelValue, range } = props2; let list = []; if (range && isArray(modelValue)) { list = [ { value: getValue(modelValue[0]), enumValue: Thumbs.First, text: toPrecision(modelValue[0]), hovering: hoveringFirst.value, focusing: focusingFirst.value, handleHovering: handleHoveringFirst, handleFocusing(value) { focusingFirst.value = value; } }, { value: getValue(modelValue[1]), enumValue: Thumbs.Second, text: toPrecision(modelValue[1]), hovering: hoveringSecond.value, focusing: focusingSecond.value, handleHovering: handleHoveringSecond, handleFocusing(value) { focusingSecond.value = value; } } ]; } else if (isNumber(modelValue)) { list = [ { value: getValue(modelValue), enumValue: Thumbs.First, text: toPrecision(modelValue), hovering: hoveringFirst.value, focusing: focusingFirst.value, handleHovering: handleHoveringFirst, handleFocusing(value) { focusingFirst.value = value; } } ]; } return list; }); const getFillStyle = computed(() => { const { activeColor, range, modelValue } = props2; const gap = range && isArray(modelValue) ? getValue(Math.min(modelValue[0], modelValue[1])) : 0; const fillLength = range && isArray(modelValue) ? getValue(Math.max(modelValue[0], modelValue[1])) - gap : getValue(modelValue); return isVertical.value ? { left: "0px", height: `${fillLength}%`, bottom: `${gap}%`, background: activeColor } : { top: "0px", width: `${fillLength}%`, left: `${gap}%`, background: activeColor }; }); const thumbsProps = reactive({ [Thumbs.First]: getThumbProps(), [Thumbs.Second]: getThumbProps() }); let activeThumb; const sliderProvider = { reset, validate, resetValidation }; call(bindForm, sliderProvider); watch([() => props2.modelValue, () => props2.step], ([modelValue, step]) => { if (!stepValidator() || !valueValidator() || isScroll.value) { return; } setProps(modelValue, toNumber(step)); }); watch(maxDistance, () => setProps()); onSmartMounted(() => { if (!stepValidator() || !valueValidator()) { return; } resizeMaxDistance(); }); onBeforeUnmount(removeDocumentEvents); useEventListener(() => window, "keydown", handleKeydown); onWindowResize(resizeMaxDistance); function resizeMaxDistance() { maxDistance.value = sliderEl.value[isVertical.value ? "offsetHeight" : "offsetWidth"]; } function validate() { return v(props2.rules, props2.modelValue); } function getThumbProps() { return { startPosition: 0, currentOffset: 0, active: false, percentValue: 0 }; } function validateWithTrigger() { nextTick(() => vt(["onChange"], "onChange", props2.rules, props2.modelValue)); } function getOffset(e) { const currentTarget = e.currentTarget; if (!currentTarget) { return 0; } if (!isVertical.value) { return e.clientX - getLeft(currentTarget); } return maxDistance.value - (e.clientY - getRect(currentTarget).top); } function thumbStyle(thumb) { const key = isVertical.value ? "bottom" : "left"; return { [key]: `${thumb.value}%`, zIndex: thumbsProps[thumb.enumValue].active ? 1 : void 0 }; } function showLabel(type) { if (props2.labelVisible === "always") { return true; } if (props2.labelVisible === "never") { return false; } return thumbsProps[type].active; } function getValue(value) { const { min, max } = props2; if (value < toNumber(min)) { return 0; } if (value > toNumber(max)) { return 100; } return (value - toNumber(min)) / scope.value * 100; } function toPrecision(value) { if (!isNumber(value)) { return 0; } const num = clamp(value, toNumber(props2.min), toNumber(props2.max)); const isInteger = parseInt(`${num}`, 10) === num; return isInteger ? num : toNumber(num.toPrecision(5)); } function hover(value, item) { if (isDisabled.value) { return; } item.handleHovering(value); } function emitChange(value) { call(props2.onChange, value); call(props2["onUpdate:modelValue"], value); validateWithTrigger(); } function setPercent(moveDistance, type) { let rangeValue = []; const { step, range, modelValue, min } = props2; const stepNumber = toNumber(step); const roundDistance = Math.round(moveDistance / unitWidth.value); const curValue = roundDistance * stepNumber + toNumber(min); const prevValue = thumbsProps[type].percentValue * stepNumber + toNumber(min); thumbsProps[type].percentValue = roundDistance; if (range && isArray(modelValue)) { rangeValue = type === Thumbs.First ? [curValue, modelValue[1]] : [modelValue[0], curValue]; } if (prevValue !== curValue) { const value = range ? rangeValue.map((value2) => toPrecision(value2)) : toPrecision(curValue); emitChange(value); } } function getType(offset) { if (!props2.range) { return Thumbs.First; } const thumb1Distance = thumbsProps[Thumbs.First].percentValue * unitWidth.value; const thumb2Distance = thumbsProps[Thumbs.Second].percentValue * unitWidth.value; const offsetToThumb1 = Math.abs(offset - thumb1Distance); const offsetToThumb2 = Math.abs(offset - thumb2Distance); return offsetToThumb1 <= offsetToThumb2 ? Thumbs.First : Thumbs.Second; } function addDocumentEvents() { document.addEventListener("touchmove", move, { passive: false }); document.addEventListener("touchend", end); document.addEventListener("touchcancel", end); } function removeDocumentEvents() { document.removeEventListener("touchmove", move); document.removeEventListener("touchend", end); document.removeEventListener("touchcancel", end); } function start(event, type) { resizeMaxDistance(); if (!isDisabled.value) { thumbsProps[type].active = true; } activeThumb = type; addDocumentEvents(); if (isDisabled.value || isReadonly.value) { return; } call(props2.onStart); isScroll.value = true; const { clientX, clientY } = event.touches[0]; thumbsProps[type].startPosition = isVertical.value ? clientY : clientX; } function move(event) { preventDefault(event); if (isDisabled.value || isReadonly.value || !isScroll.value) { return; } const { startPosition, currentOffset } = thumbsProps[activeThumb]; const { clientX, clientY } = event.touches[0]; let moveDistance = (isVertical.value ? startPosition - clientY : clientX - startPosition) + currentOffset; if (moveDistance <= 0) { moveDistance = 0; } else if (moveDistance >= maxDistance.value) { moveDistance = maxDistance.value; } setPercent(moveDistance, activeThumb); } function end() { removeDocumentEvents(); const { range, modelValue, onEnd, step, min } = props2; if (!isDisabled.value) { thumbsProps[activeThumb].active = false; } if (isDisabled.value || isReadonly.value) { return; } let rangeValue = []; thumbsProps[activeThumb].currentOffset = thumbsProps[activeThumb].percentValue * unitWidth.value; const curValue = thumbsProps[activeThumb].percentValue * toNumber(step) + toNumber(min); if (range && isArray(modelValue)) { rangeValue = activeThumb === Thumbs.First ? [curValue, modelValue[1]] : [modelValue[0], curValue]; } call(onEnd, range ? rangeValue : curValue); isScroll.value = false; } function handleClick(event) { if (isDisabled.value || isReadonly.value) { return; } if (event.target.closest(`.${n("thumb")}`)) { return; } const offset = getOffset(event); const type = getType(offset); activeThumb = type; setPercent(offset, type); end(); } function stepValidator() { if (toNumber(props2.step) <= 0) { warn("Slider", '"step" should be > 0'); return false; } return true; } function valueValidator() { const { range, modelValue } = props2; if (range && !isArray(modelValue)) { error("Slider", '"modelValue" should be an Array'); return false; } if (!range && isArray(modelValue)) { error("Slider", '"modelValue" should be a Number'); return false; } if (range && isArray(modelValue) && modelValue.length < 2) { error("Slider", '"modelValue" should have two value'); return false; } return true; } function setProps(modelValue = props2.modelValue, step = toNumber(props2.step)) { const getPercent = (value) => { const { min, max } = props2; if (value < toNumber(min)) { return 0; } if (value > toNumber(max)) { return scope.value / step; } return (value - toNumber(min)) / step; }; if (props2.range && isArray(modelValue)) { thumbsProps[Thumbs.First].percentValue = getPercent(modelValue[0]); thumbsProps[Thumbs.First].currentOffset = thumbsProps[Thumbs.First].percentValue * unitWidth.value; thumbsProps[Thumbs.Second].percentValue = getPercent(modelValue[1]); thumbsProps[Thumbs.Second].currentOffset = thumbsProps[Thumbs.Second].percentValue * unitWidth.value; } else if (isNumber(modelValue)) { thumbsProps[Thumbs.First].currentOffset = getPercent(modelValue) * unitWidth.value; } } function reset() { const resetValue = props2.range ? [0, 0] : 0; call(props2["onUpdate:modelValue"], resetValue); resetValidation(); } function moveFocusingThumb(offset, value) { const stepValue = toNumber(props2.step); if (isArray(value)) { const updatedFirstValue = value[0] + (focusingFirst.value ? offset * stepValue : 0); const updatedSecondValue = value[1] + (focusingSecond.value ? offset * stepValue : 0); return [updatedFirstValue, updatedSecondValue].map(toPrecision); } return toPrecision(value + offset * stepValue); } function handleKeydown(event) { const keyToOffset = { ArrowRight: 1, ArrowUp: 1, ArrowLeft: -1, ArrowDown: -1 }; const { key } = event; if (!hasOwn(keyToOffset, key) || isReadonly.value || isDisabled.value) { return; } if (props2.range && !focusingFirst.value && !focusingSecond.value) { return; } if (!props2.range && !focusingFirst.value) { return; } preventDefault(event); const offset = keyToOffset[key]; const value = moveFocusingThumb(offset, props2.modelValue); emitChange(value); } function handleFocus(item) { if (isDisabled.value) { return; } item.handleFocusing(true); } function handleBlur(item) { item.handleFocusing(false); } return { sliderEl, getFillStyle, isDisabled, isVertical, errorMessage, thumbsProps, thumbList, handleFocus, handleBlur, n, classes, thumbStyle, hover, toSizeUnit, toNumber, showLabel, start, move, end, handleClick }; } }); __sfc__.render = __render__; var stdin_default = __sfc__; export { stdin_default as default };