UNPKG

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