@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
JavaScript
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
};