@varlet/ui
Version:
A Vue3 component library based on Material Design 2 and 3, supporting mobile and desktop.
503 lines (502 loc) • 19.2 kB
JavaScript
import { computed, defineComponent, ref, Transition, watch } from "vue";
import { call, clamp, clampArrayRange, toNumber } from "@varlet/shared";
import { useTouch, useVModel } from "@varlet/use";
import VarButton from "../button/index.mjs";
import { t } from "../locale/index.mjs";
import { injectLocaleProvider } from "../locale-provider/provide.mjs";
import VarPopup from "../popup/index.mjs";
import { createNamespace } from "../utils/components.mjs";
import { getTranslateY, toPxNum } from "../utils/elements.mjs";
import { props } from "./props.mjs";
const { name, n, classes } = createNamespace("picker");
const MOMENTUM_RECORD_TIME = 300;
const MOMENTUM_ALLOW_DISTANCE = 15;
const TRANSITION_DURATION = 200;
const MOMENTUM_TRANSITION_DURATION = 1e3;
let sid = 0;
import { renderSlot as _renderSlot, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, resolveComponent as _resolveComponent, normalizeClass as _normalizeClass, withCtx as _withCtx, createVNode as _createVNode, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode, renderList as _renderList, Fragment as _Fragment, normalizeStyle as _normalizeStyle, withModifiers as _withModifiers, mergeProps as _mergeProps, resolveDynamicComponent as _resolveDynamicComponent, createBlock as _createBlock } from "vue";
const _hoisted_1 = ["onTouchstartPassive", "onTouchmove", "onTouchend"];
const _hoisted_2 = ["onTransitionend"];
const _hoisted_3 = ["onClick"];
function __render__(_ctx, _cache) {
const _component_var_button = _resolveComponent("var-button");
return _openBlock(), _createBlock(
_resolveDynamicComponent(_ctx.dynamic ? _ctx.n("$-popup") : _ctx.Transition),
_mergeProps(
_ctx.dynamic ? {
onOpen: _ctx.onOpen,
onOpened: _ctx.onOpened,
onClose: _ctx.onClose,
onClosed: _ctx.onClosed,
onClickOverlay: _ctx.onClickOverlay,
onRouteChange: _ctx.onRouteChange,
onKeyEscape: _ctx.onKeyEscape,
closeOnClickOverlay: _ctx.closeOnClickOverlay,
closeOnKeyEscape: _ctx.closeOnKeyEscape,
teleport: _ctx.teleport,
show: _ctx.show,
safeArea: _ctx.safeArea,
"onUpdate:show": _ctx.handlePopupUpdateShow,
position: "bottom",
class: _ctx.n("popup")
} : null,
{ "var-picker-cover": "" }
),
{
default: _withCtx(() => [
_createElementVNode(
"div",
_mergeProps({
class: _ctx.n()
}, _ctx.$attrs),
[
_ctx.toolbar ? (_openBlock(), _createElementBlock(
"div",
{
key: 0,
class: _normalizeClass(_ctx.n("toolbar"))
},
[
_renderSlot(_ctx.$slots, "cancel", {}, () => [
_createVNode(_component_var_button, {
class: _normalizeClass(_ctx.n("cancel-button")),
"var-picker-cover": "",
text: "",
"text-color": _ctx.cancelButtonTextColor,
onClick: _ctx.cancel
}, {
default: _withCtx(() => {
var _a;
return [
_createTextVNode(
_toDisplayString((_a = _ctx.cancelButtonText) != null ? _a : (_ctx.pt ? _ctx.pt : _ctx.t)("pickerCancelButtonText")),
1
/* TEXT */
)
];
}),
_: 1
/* STABLE */
}, 8, ["class", "text-color", "onClick"])
]),
_renderSlot(_ctx.$slots, "title", {}, () => {
var _a;
return [
_createElementVNode(
"div",
{
class: _normalizeClass(_ctx.n("title"))
},
_toDisplayString((_a = _ctx.title) != null ? _a : (_ctx.pt ? _ctx.pt : _ctx.t)("pickerTitle")),
3
/* TEXT, CLASS */
)
];
}),
_renderSlot(_ctx.$slots, "confirm", {}, () => [
_createVNode(_component_var_button, {
class: _normalizeClass(_ctx.n("confirm-button")),
text: "",
"var-picker-cover": "",
"text-color": _ctx.confirmButtonTextColor,
onClick: _ctx.confirm
}, {
default: _withCtx(() => {
var _a;
return [
_createTextVNode(
_toDisplayString((_a = _ctx.confirmButtonText) != null ? _a : (_ctx.pt ? _ctx.pt : _ctx.t)("pickerConfirmButtonText")),
1
/* TEXT */
)
];
}),
_: 1
/* STABLE */
}, 8, ["class", "text-color", "onClick"])
])
],
2
/* CLASS */
)) : _createCommentVNode("v-if", true),
_createElementVNode(
"div",
{
class: _normalizeClass(_ctx.n("columns")),
style: _normalizeStyle({ height: `${_ctx.columnHeight}px` })
},
[
(_openBlock(true), _createElementBlock(
_Fragment,
null,
_renderList(_ctx.scrollColumns, (c) => {
return _openBlock(), _createElementBlock("div", {
key: c.id,
class: _normalizeClass(_ctx.n("column")),
onTouchstartPassive: ($event) => _ctx.handleTouchstart($event, c),
onTouchmove: _withModifiers(($event) => _ctx.handleTouchmove($event, c), ["prevent"]),
onTouchend: ($event) => _ctx.handleTouchend(c)
}, [
_createElementVNode("div", {
ref_for: true,
ref: (el) => _ctx.setScrollEl(el, c),
class: _normalizeClass(_ctx.n("scroller")),
style: _normalizeStyle({
transform: `translateY(${c.translate}px)`,
transitionDuration: `${c.duration}ms`,
transitionProperty: c.duration ? "transform" : "none"
}),
onTransitionend: ($event) => _ctx.handleTransitionend(c)
}, [
(_openBlock(true), _createElementBlock(
_Fragment,
null,
_renderList(c.column, (option, index) => {
return _openBlock(), _createElementBlock("div", {
key: _ctx.getValue(option),
class: _normalizeClass(_ctx.classes(_ctx.n("option"), option.className)),
style: _normalizeStyle({ height: `${_ctx.optionHeight}px` }),
onClick: ($event) => _ctx.handleClick(c, index)
}, [
_createElementVNode(
"div",
{
class: _normalizeClass(_ctx.classes(_ctx.n("text"), option.textClassName))
},
_toDisplayString(option[_ctx.getOptionKey("text")]),
3
/* TEXT, CLASS */
)
], 14, _hoisted_3);
}),
128
/* KEYED_FRAGMENT */
))
], 46, _hoisted_2)
], 42, _hoisted_1);
}),
128
/* KEYED_FRAGMENT */
)),
_createElementVNode(
"div",
{
class: _normalizeClass(_ctx.n("picked")),
style: _normalizeStyle({
top: `${_ctx.center}px`,
height: `${_ctx.optionHeight}px`
})
},
null,
6
/* CLASS, STYLE */
),
_createElementVNode(
"div",
{
class: _normalizeClass(_ctx.n("mask")),
style: _normalizeStyle({ backgroundSize: `100% ${(_ctx.columnHeight - _ctx.optionHeight) / 2}px` })
},
null,
6
/* CLASS, STYLE */
)
],
6
/* CLASS, STYLE */
)
],
16
/* FULL_PROPS */
)
]),
_: 3
/* FORWARDED */
},
16
/* FULL_PROPS */
);
}
const __sfc__ = defineComponent({
name,
components: {
VarButton,
VarPopup
},
inheritAttrs: false,
props,
setup(props2) {
const modelValue = useVModel(props2, "modelValue");
const scrollColumns = ref([]);
const visibleColumnsCount = computed(() => toNumber(props2.columnsCount));
const optionHeight = computed(() => toPxNum(props2.optionHeight));
const optionCount = computed(() => toPxNum(props2.optionCount));
const center = computed(() => optionCount.value * optionHeight.value / 2 - optionHeight.value / 2);
const columnHeight = computed(() => optionCount.value * optionHeight.value);
const { prevY, moveY, dragging, startTouch, moveTouch, endTouch } = useTouch();
const { t: pt } = injectLocaleProvider();
let prevIndexes = [];
initScrollColumns();
watch(() => props2.columns, initScrollColumns, { deep: true });
watch(() => modelValue.value, initScrollColumns);
function getOptionKey(key) {
const keyMap = {
text: props2.textKey,
value: props2.valueKey,
children: props2.childrenKey
};
return keyMap[key];
}
function getValue(option) {
var _a;
return (_a = option[getOptionKey("value")]) != null ? _a : option[getOptionKey("text")];
}
function setPrevIndexes(indexes) {
prevIndexes = [...indexes];
}
function normalizeNormalMode(columns) {
const visibleColumns = props2.columnsCount != null ? columns.slice(0, visibleColumnsCount.value) : columns;
return visibleColumns.map((column, idx) => {
const scrollColumn = {
id: sid++,
prevY: 0,
momentumPrevY: 0,
touching: false,
translate: center.value,
index: 0,
duration: 0,
momentumTime: 0,
column,
scrollEl: null,
scrolling: false
};
const value = modelValue.value[idx];
const index = scrollColumn.column.findIndex((option) => value === getValue(option));
scrollColumn.index = index === -1 ? 0 : index;
scrollTo(scrollColumn);
return scrollColumn;
});
}
function normalizeCascadeMode(column) {
const scrollColumns2 = [];
createChildren(scrollColumns2, column);
return scrollColumns2;
}
function createChildren(scrollColumns2, children, syncModelValue = true, depth = 1) {
var _a;
if (children.length && (props2.columnsCount == null || depth <= visibleColumnsCount.value)) {
const scrollColumn = {
id: sid++,
prevY: 0,
momentumPrevY: 0,
touching: false,
translate: center.value,
index: 0,
duration: 0,
momentumTime: 0,
column: children,
scrollEl: null,
scrolling: false
};
scrollColumns2.push(scrollColumn);
if (syncModelValue) {
const value = modelValue.value[scrollColumns2.length - 1];
const index = children.findIndex((option) => value === getValue(option));
scrollColumn.index = index === -1 ? 0 : index;
}
scrollTo(scrollColumn);
createChildren(
scrollColumns2,
(_a = scrollColumn.column[scrollColumn.index][getOptionKey("children")]) != null ? _a : [],
syncModelValue,
depth + 1
);
}
}
function rebuildChildren(scrollColumn) {
var _a;
scrollColumns.value.splice(scrollColumns.value.indexOf(scrollColumn) + 1);
createChildren(
scrollColumns.value,
(_a = scrollColumn.column[scrollColumn.index][getOptionKey("children")]) != null ? _a : [],
false,
scrollColumns.value.length + 1
);
}
function initScrollColumns() {
scrollColumns.value = props2.cascade ? normalizeCascadeMode(props2.columns) : normalizeNormalMode(props2.columns);
const { indexes } = getPicked();
setPrevIndexes(indexes);
}
function setScrollEl(el, scrollColumn) {
scrollColumn.scrollEl = el;
}
function handlePopupUpdateShow(value) {
call(props2["onUpdate:show"], value);
}
function clampTranslate(scrollColumn) {
const minTranslate = center.value - scrollColumn.column.length * optionHeight.value;
const maxTranslate = optionHeight.value + center.value;
scrollColumn.translate = clamp(scrollColumn.translate, minTranslate, maxTranslate);
}
function getTargetIndex(scrollColumn, viewTranslate) {
const index = Math.round((center.value - viewTranslate) / optionHeight.value);
return clampArrayRange(index, scrollColumn.column);
}
function updateTranslate(scrollColumn) {
scrollColumn.translate = center.value - scrollColumn.index * optionHeight.value;
return scrollColumn.translate;
}
function getPicked() {
const values = [];
const indexes = [];
const options = [];
scrollColumns.value.forEach(({ column, index }) => {
const option = column[index];
values.push(getValue(option));
indexes.push(index);
options.push(option);
});
return {
values,
indexes,
options
};
}
function scrollTo(scrollColumn, duration = 0) {
updateTranslate(scrollColumn);
scrollColumn.duration = duration;
}
function momentum(scrollColumn, distance, duration) {
scrollColumn.translate += Math.abs(distance / duration) / 3e-3 * (distance < 0 ? -1 : 1);
}
function handleClick(scrollColumn, index) {
if (dragging.value) {
return;
}
scrollColumn.index = index;
scrollTo(scrollColumn, TRANSITION_DURATION);
}
function handleTouchstart(event, scrollColumn) {
scrollColumn.touching = true;
scrollColumn.translate = getTranslateY(scrollColumn.scrollEl);
startTouch(event);
}
function handleTouchmove(event, scrollColumn) {
if (!scrollColumn.touching) {
return;
}
moveTouch(event);
scrollColumn.scrolling = false;
scrollColumn.duration = 0;
scrollColumn.prevY = prevY.value;
scrollColumn.translate += moveY.value;
clampTranslate(scrollColumn);
const now = performance.now();
if (now - scrollColumn.momentumTime > MOMENTUM_RECORD_TIME) {
scrollColumn.momentumTime = now;
scrollColumn.momentumPrevY = scrollColumn.translate;
}
}
function handleTouchend(scrollColumn) {
endTouch();
scrollColumn.touching = false;
scrollColumn.prevY = 0;
const distance = scrollColumn.translate - scrollColumn.momentumPrevY;
const duration = performance.now() - scrollColumn.momentumTime;
const shouldMomentum = Math.abs(distance) >= MOMENTUM_ALLOW_DISTANCE && duration <= MOMENTUM_RECORD_TIME;
const oldTranslate = scrollColumn.translate;
if (shouldMomentum) {
momentum(scrollColumn, distance, duration);
}
scrollColumn.index = getTargetIndex(scrollColumn, scrollColumn.translate);
scrollTo(scrollColumn, shouldMomentum ? MOMENTUM_TRANSITION_DURATION : TRANSITION_DURATION);
scrollColumn.scrolling = scrollColumn.translate !== oldTranslate;
if (!scrollColumn.scrolling) {
handleScrollColumnChange(scrollColumn);
}
}
function handleTransitionend(scrollColumn) {
scrollColumn.scrolling = false;
handleScrollColumnChange(scrollColumn);
}
function isSamePicked() {
const { indexes } = getPicked();
return indexes.every((index, idx) => index === prevIndexes[idx]);
}
function handleScrollColumnChange(scrollColumn) {
const { onChange, cascade } = props2;
if (isSamePicked()) {
return;
}
if (cascade) {
rebuildChildren(scrollColumn);
}
const hasScrolling = scrollColumns.value.some((scrollColumn2) => scrollColumn2.scrolling);
const hasTouching = scrollColumns.value.some((scrollColumn2) => scrollColumn2.touching);
if (hasScrolling || hasTouching) {
return;
}
const { values, indexes, options } = getPicked();
setPrevIndexes(indexes);
call(onChange, values, indexes, options);
modelValue.value = values;
}
function stopScroll() {
if (props2.cascade) {
const currentScrollColumn = scrollColumns.value.find((scrollColumn) => scrollColumn.scrolling);
if (currentScrollColumn) {
currentScrollColumn.index = getTargetIndex(currentScrollColumn, getTranslateY(currentScrollColumn.scrollEl));
currentScrollColumn.scrolling = false;
scrollTo(currentScrollColumn);
rebuildChildren(currentScrollColumn);
}
} else {
scrollColumns.value.forEach((scrollColumn) => {
scrollColumn.index = getTargetIndex(scrollColumn, getTranslateY(scrollColumn.scrollEl));
scrollTo(scrollColumn);
});
}
}
function confirm() {
stopScroll();
const { values, indexes, options } = getPicked();
setPrevIndexes(indexes);
call(props2.onConfirm, values, indexes, options);
}
function cancel() {
stopScroll();
const { values, indexes, options } = getPicked();
setPrevIndexes(indexes);
call(props2.onCancel, values, indexes, options);
}
return {
optionHeight,
optionCount,
scrollColumns,
columnHeight,
center,
Transition,
pt,
t,
n,
classes,
setScrollEl,
getOptionKey,
getValue,
handlePopupUpdateShow,
handleTouchstart,
handleTouchmove,
handleTouchend,
handleTransitionend,
confirm,
cancel,
handleClick
};
}
});
__sfc__.render = __render__;
var stdin_default = __sfc__;
export {
stdin_default as default
};