ivue-material-plus
Version:
A high quality UI components Library with Vue.js
1 lines • 17.5 kB
Source Map (JSON)
{"version":3,"file":"index2.mjs","sources":["../../../../../src/components/ivue-tabs/index.vue"],"sourcesContent":["<template>\r\n <div :class=\"prefixCls\">\r\n <!-- 头部 -->\r\n <div class=\"ivue-tabs-bar\" ref=\"bar\">\r\n <!-- 左边按钮 -->\r\n <transition name=\"fade-transition\">\r\n <ivue-icon\r\n :class=\"`ivue-tabs-icon ivue-tabs-icon--prev`\"\r\n v-show=\"hasArrows && data.prevIconVisible\"\r\n @click=\"handleScrollTo('prev')\"\r\n >{{ prevIcon }}</ivue-icon\r\n >\r\n </transition>\r\n <!-- 内容 -->\r\n <div\r\n :class=\"tabWrapperClasses\"\r\n :style=\"tabWrapperStyles\"\r\n v-touch=\"{\r\n start: (e) => overflowCheck(e, onTouchStart),\r\n move: (e) => overflowCheck(e, onTouchMove),\r\n end: (e) => overflowCheck(e, onTouchEnd),\r\n }\"\r\n ref=\"wrapper\"\r\n >\r\n <div\r\n :class=\"tabContainerClasses\"\r\n :style=\"containerStyles\"\r\n ref=\"container\"\r\n >\r\n <slider\r\n :sliderLeft=\"data.sliderLeft\"\r\n :sliderWidth=\"data.sliderWidth\"\r\n :color=\"sliderColor\"\r\n ></slider>\r\n <slot name=\"header\"></slot>\r\n </div>\r\n </div>\r\n <!-- 右边按钮 -->\r\n <transition name=\"fade-transition\">\r\n <ivue-icon\r\n :class=\"`ivue-tabs-icon ivue-tabs-icon--next`\"\r\n v-show=\"hasArrows && data.nextIconVisible\"\r\n @click=\"handleScrollTo('next')\"\r\n >{{ nextIcon }}</ivue-icon\r\n >\r\n </transition>\r\n </div>\r\n <!-- 内容 -->\r\n <div\r\n class=\"ivue-tabs-items\"\r\n v-touch=\"{\r\n left: () => handleSwipeItem('next'),\r\n right: () => handleSwipeItem('prev'),\r\n }\"\r\n >\r\n <slot name=\"content\"></slot>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\">\r\nimport {\r\n defineComponent,\r\n reactive,\r\n provide,\r\n computed,\r\n ref,\r\n watch,\r\n nextTick,\r\n onMounted,\r\n watchEffect,\r\n} from 'vue';\r\n\r\nimport tabsComputed from './tabs-computed';\r\nimport tabsTouch from './tabs-touch';\r\nimport IvueIcon from '../ivue-icon/index.vue';\r\nimport Slider from './slider.vue';\r\nimport Touch from '../../utils/directives/touch';\r\n\r\n// type\r\nimport { Props, Data, TabsContextKey } from './types/tabs';\r\n\r\nconst prefixCls = 'ivue-tabs';\r\n\r\nexport default defineComponent({\r\n name: prefixCls,\r\n directives: { Touch },\r\n // 声明事件\r\n emits: ['update:modelValue'],\r\n props: {\r\n /**\r\n * 当前激活 tab 面板的 name,可以使用 v-model 双向绑定数据\r\n *\r\n * @type {String,Number}\r\n */\r\n modelValue: {\r\n type: [String, Number],\r\n default: '',\r\n },\r\n /**\r\n * 导航内容居中\r\n *\r\n * @type {Boolean}\r\n */\r\n centered: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n /**\r\n * 导航内容向右\r\n *\r\n * @type {Boolean}\r\n */\r\n right: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n /**\r\n * 是否显示导航栏箭头\r\n *\r\n * @type {Boolean}\r\n */\r\n showArrows: {\r\n type: Boolean,\r\n default: true,\r\n },\r\n /**\r\n * 左边按钮\r\n *\r\n * @type {String}\r\n */\r\n prevIcon: {\r\n type: String,\r\n default: 'chevron_left',\r\n },\r\n /**\r\n * 右边按钮\r\n *\r\n * @type {String}\r\n */\r\n nextIcon: {\r\n type: String,\r\n default: 'chevron_right',\r\n },\r\n /**\r\n * 固定宽度标签\r\n *\r\n * @type {Boolean}\r\n */\r\n fixedWidth: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n /**\r\n * 自动适应宽度标签\r\n *\r\n * @type {Boolean}\r\n */\r\n autoWidth: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n /**\r\n * 导航高度\r\n *\r\n * @type {String,Number}\r\n */\r\n height: {\r\n type: [String, Number],\r\n },\r\n /**\r\n * 滑动条颜色\r\n *\r\n * @type {String}\r\n */\r\n sliderColor: {\r\n type: String,\r\n default: 'primary',\r\n },\r\n /**\r\n * 滑动条隐藏\r\n *\r\n * @type {Boolean}\r\n */\r\n hideSlider: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n /**\r\n * 调整显示箭头时设置的前后滚动的间距\r\n *\r\n * @type {Number | String}\r\n */\r\n arrowsMargin: {\r\n type: [Number, String],\r\n default: 40,\r\n },\r\n },\r\n setup(props: Props, { emit }) {\r\n // dom\r\n\r\n // tabs-container\r\n const container = ref<HTMLElement>(null);\r\n\r\n // tabs-wrapper\r\n const wrapper = ref<HTMLElement>(null);\r\n\r\n // tabs-bar\r\n const bar = ref<HTMLElement>(null);\r\n\r\n // data\r\n const data = reactive<Data>({\r\n // tab导航数组\r\n tabs: [],\r\n // tab内容数组\r\n tabsItem: [],\r\n // 导航栏是否需要滚动\r\n isOverflowing: false,\r\n // 下一个按钮显示\r\n nextIconVisible: false,\r\n // 上一个按钮显示\r\n prevIconVisible: false,\r\n // 滚动位置\r\n scrollOffset: 0,\r\n // 开始滑动x轴\r\n startX: 0,\r\n // 导航栏宽度\r\n widths: {\r\n bar: 0,\r\n container: 0,\r\n wrapper: 0,\r\n },\r\n // 重置时间函数\r\n resizeTimeout: null,\r\n // 滑动条位置\r\n sliderLeft: 0,\r\n // 滑动条宽度\r\n sliderWidth: 0,\r\n // 控制内容初始化动画效果\r\n isBooted: false,\r\n // 是否初始化完成\r\n isInit: false,\r\n });\r\n\r\n // computed\r\n const { hasArrows, containerStyles, activeTab, activeIndex } = tabsComputed(\r\n props,\r\n data\r\n );\r\n\r\n // tab外层样式\r\n const tabWrapperClasses = computed(() => {\r\n return {\r\n 'ivue-tabs-wrapper': true,\r\n };\r\n });\r\n\r\n // tab外层样式\r\n const tabWrapperStyles = computed(() => {\r\n const regexp = new RegExp(/[a-zA-Z]/g);\r\n\r\n // 是否有单位\r\n const isUnit = regexp.test(`${props.arrowsMargin}`);\r\n\r\n return {\r\n 'margin-left': hasArrows.value\r\n ? !isUnit\r\n ? `${props.arrowsMargin}px`\r\n : props.arrowsMargin\r\n : '',\r\n 'margin-right': hasArrows.value\r\n ? !isUnit\r\n ? `${props.arrowsMargin}px`\r\n : props.arrowsMargin\r\n : '',\r\n transition: data.isInit ? '' : 'none 0s',\r\n };\r\n });\r\n\r\n // tab样式\r\n const tabContainerClasses = computed(() => {\r\n return {\r\n [`${prefixCls}-container`]: true,\r\n [`${prefixCls}-container--centered`]: props.centered,\r\n [`${prefixCls}-container--right`]: props.right,\r\n [`${prefixCls}-container--overflow`]: data.isOverflowing,\r\n [`${prefixCls}-container--fixed-width`]: props.fixedWidth,\r\n [`${prefixCls}-container--auto-width`]: props.autoWidth,\r\n };\r\n });\r\n\r\n // methods\r\n\r\n const {\r\n onTouchStart,\r\n onTouchMove,\r\n onTouchEnd,\r\n newOffset,\r\n handleSwipeItem,\r\n } = tabsTouch(data, container, wrapper, activeIndex);\r\n\r\n // 初始化\r\n const initDate = () => {\r\n nextTick(() => {\r\n // 滚动导航栏\r\n scrollIntoView();\r\n });\r\n\r\n callSlider();\r\n };\r\n\r\n // 是否可以滚动\r\n const overflowCheck = (e, fn) => {\r\n data.isOverflowing && fn(e);\r\n };\r\n\r\n // 清除tab导航\r\n const unregister = (name: string) => {\r\n data.tabs = data.tabs.filter((o) => {\r\n return o.tabName !== name;\r\n });\r\n };\r\n\r\n // 清除tab item\r\n const unregisterItems = (name: string) => {\r\n data.tabsItem = data.tabsItem.filter((o) => {\r\n return o.tabName !== name;\r\n });\r\n };\r\n\r\n // 获取导航宽度\r\n const setWidths = () => {\r\n const _bar = bar.value ? bar.value?.clientWidth : 0;\r\n const _container = container.value ? container.value.clientWidth : 0;\r\n const _wrapper = wrapper.value ? wrapper.value.clientWidth : 0;\r\n\r\n data.widths = {\r\n bar: _bar,\r\n container: _container,\r\n wrapper: _wrapper,\r\n };\r\n\r\n setOverflow();\r\n };\r\n\r\n // 设置导航栏是否滚动\r\n const setOverflow = () => {\r\n data.isOverflowing = data.widths.bar < data.widths.container;\r\n };\r\n\r\n // 设置滑动条\r\n const callSlider = () => {\r\n const _activeTab = activeTab.value;\r\n\r\n if (props.hideSlider || !_activeTab) {\r\n return false;\r\n }\r\n\r\n nextTick(() => {\r\n if (!_activeTab || !_activeTab.$el || _activeTab.disabled) {\r\n return;\r\n }\r\n\r\n data.sliderLeft = _activeTab.$el.offsetLeft;\r\n data.sliderWidth = _activeTab.$el.clientWidth;\r\n });\r\n };\r\n\r\n // 滚动导航栏\r\n const scrollIntoView = () => {\r\n const _activeTab = activeTab.value;\r\n\r\n if (!_activeTab) {\r\n return;\r\n }\r\n if (!data.isOverflowing) {\r\n return (data.scrollOffset = 0);\r\n }\r\n\r\n const activeTabBounding = _activeTab.$el.getBoundingClientRect();\r\n const navScrollBounding = wrapper.value.getBoundingClientRect();\r\n const navBounding = container.value.getBoundingClientRect();\r\n\r\n const currentOffset = data.scrollOffset;\r\n let newOffset = currentOffset;\r\n\r\n if (\r\n parseInt(`${navBounding.right}`) <\r\n parseInt(`${navScrollBounding.right}`)\r\n ) {\r\n newOffset =\r\n container.value.offsetWidth - parseInt(`${navScrollBounding.width}`);\r\n }\r\n\r\n if (\r\n parseInt(activeTabBounding.left) < parseInt(`${navScrollBounding.left}`)\r\n ) {\r\n newOffset =\r\n currentOffset -\r\n (parseInt(`${navScrollBounding.left}`) -\r\n parseInt(activeTabBounding.left));\r\n } else if (\r\n parseInt(activeTabBounding.right) >\r\n parseInt(`${navScrollBounding.right}`)\r\n ) {\r\n newOffset =\r\n currentOffset +\r\n parseInt(activeTabBounding.right) -\r\n parseInt(`${navScrollBounding.right}`);\r\n }\r\n\r\n if (currentOffset !== newOffset) {\r\n const maxOffset = data.widths.container - data.widths.wrapper;\r\n const isOffset = newOffset + data.widths.wrapper;\r\n const widthsContainer = data.widths.container;\r\n\r\n if (isOffset > widthsContainer) {\r\n newOffset = maxOffset;\r\n }\r\n\r\n data.scrollOffset = newOffset;\r\n }\r\n };\r\n\r\n // 检查是否有icon\r\n const checkIcons = () => {\r\n data.prevIconVisible = checkPrevIcon();\r\n data.nextIconVisible = checkNextIcon();\r\n };\r\n\r\n // 检查右边按钮\r\n const checkNextIcon = () => {\r\n const offset = data.scrollOffset + data.widths.wrapper;\r\n\r\n if (offset === data.widths.container) {\r\n return false;\r\n }\r\n\r\n return (\r\n data.widths.container >\r\n (hasArrows.value ? offset - Number(props.arrowsMargin) : offset)\r\n );\r\n };\r\n\r\n // 检查左边按钮\r\n const checkPrevIcon = () => {\r\n return data.scrollOffset > 0;\r\n };\r\n\r\n // 点击箭头滚动\r\n const handleScrollTo = (direction) => {\r\n data.scrollOffset = newOffset(direction);\r\n };\r\n\r\n // 监听resize\r\n const onResize = () => {\r\n setWidths();\r\n\r\n clearTimeout(data.resizeTimeout);\r\n\r\n data.resizeTimeout = setTimeout(() => {\r\n // 设置滑动条\r\n callSlider();\r\n // 滚动导航栏\r\n scrollIntoView();\r\n // 检查是否有icon\r\n checkIcons();\r\n }, 300);\r\n };\r\n\r\n // 导航点击\r\n const tabNavClick = (tab) => {\r\n // 判断禁用\r\n if (tab.disabled) {\r\n return;\r\n }\r\n\r\n updateValue(tab.tabName);\r\n };\r\n\r\n // 更新当前激活的导航\r\n const updateValue = (value: string | number) => {\r\n // updated v-model\r\n emit('update:modelValue', value);\r\n };\r\n\r\n // 更新激活的item\r\n const updateItems = (reverse: boolean) => {\r\n for (let index = data.tabsItem.length; --index >= 0; ) {\r\n data.tabsItem[index].handleToggle(\r\n activeIndex.value === index,\r\n reverse,\r\n data.isBooted\r\n );\r\n }\r\n\r\n data.isBooted = true;\r\n };\r\n\r\n // watch\r\n\r\n // 监听导航滑动\r\n watch(\r\n () => data.scrollOffset,\r\n () => {\r\n // 是否开启按钮\r\n if (hasArrows.value) {\r\n data.nextIconVisible = checkNextIcon();\r\n data.prevIconVisible = checkPrevIcon();\r\n }\r\n }\r\n );\r\n\r\n // 监听激活项\r\n watch(\r\n () => props.modelValue,\r\n () => {\r\n initDate();\r\n }\r\n );\r\n\r\n // data\r\n watch(\r\n () => data.tabsItem,\r\n () => {\r\n nextTick(() => {\r\n onResize();\r\n });\r\n },\r\n {\r\n deep: true,\r\n }\r\n );\r\n\r\n // 监听固定标签\r\n watch(\r\n () => props.fixedWidth,\r\n () => {\r\n callSlider();\r\n }\r\n );\r\n\r\n // 监听激活的index\r\n watch(\r\n () => activeIndex.value,\r\n (current, previous) => {\r\n const reverse = current < previous;\r\n\r\n updateItems(reverse);\r\n }\r\n );\r\n\r\n // provide\r\n provide(\r\n TabsContextKey,\r\n reactive({\r\n props,\r\n data,\r\n unregister: unregister,\r\n tabNavClick: tabNavClick,\r\n unregisterItems: unregisterItems,\r\n })\r\n );\r\n\r\n // watch\r\n\r\n // 同步监听 tab导航数组\r\n watchEffect(() => {\r\n if (data.tabs) {\r\n onResize();\r\n }\r\n });\r\n\r\n // onMounted\r\n onMounted(() => {\r\n nextTick(() => {\r\n data.isInit = true;\r\n });\r\n\r\n initDate();\r\n });\r\n\r\n return {\r\n prefixCls,\r\n\r\n // dom\r\n container,\r\n bar,\r\n wrapper,\r\n\r\n // data\r\n data,\r\n\r\n // computed\r\n tabWrapperClasses,\r\n hasArrows,\r\n tabContainerClasses,\r\n containerStyles,\r\n tabWrapperStyles,\r\n\r\n // methods\r\n overflowCheck,\r\n onTouchStart,\r\n onTouchMove,\r\n onTouchEnd,\r\n handleScrollTo,\r\n unregister,\r\n handleSwipeItem,\r\n tabNavClick,\r\n };\r\n },\r\n components: {\r\n IvueIcon,\r\n Slider,\r\n },\r\n});\r\n</script>\r\n"],"names":["_resolveDirective","_normalizeClass","_createCommentVNode","_createElementVNode","_createVNode","_Transition","_withCtx","_withDirectives","_openBlock","_createElementBlock","_normalizeStyle"],"mappings":";;;;MAGkC,UAAM,GAAA;AAAA,EAAA,KAAA,EAAA,eAAA;AAAA;;;;;;2BAFtCA,iBAwDM,OAAA,CAAA,CAAA;;IAvDJ,KAAA,EAAWC,cAAA,CAAA,IAAA,CAAA,SAAA,CAAA;AAAA,GACX,EAAA;AAAA,IACEC,mBAAA,gBAAA,CAAA;AAAA,IAQaC,kBAAA,CAAA,OAAA,UAAA,EAAA;AAAA,MAAAD,mBAPI,4BAAiB,CAAA;AAAA,MAAAE,YAAlCC,UAOa,EAAA;AAAA,QAAA,IAAA,EAAA,iBAAA;AAAA;;QALH,OAAA,EAAAC,QAAA,MAAA;AAAA,UAAAC,cAAA,CAEAH,YAAE,oBAAA,EAAA;AAAA,YAAA,KAAA,EAAAH,eAAA,CAAA,mCAAA,CAAA,CAAA;AAAA,YACO,OAAA,EAAA,OAAA,CAAA,CAAA,KAAA,MAAA,CAAA,KAAA,CAAA,MAAA,KAAA,IAAA,CAAA,cAAA,CAAA,MAAA,CAAA,CAAA;AAAA,WAAA,EAAA;AAAA;;;;;;;;QAGnB,CAAA,EAAA,CAAA;AAAA,OAAA,CAAA;AAAA,yBAEQ,gBAAE,CAAA;AAAA,MAAAM,cACF,EAAAC,SAAA,EAAkB,EAAAC,kBAAA,CAAA,KAAA,EAAA;AAAA,QAMxB,KAAA,EAAIR,cAAS,CAAA,IAAA,CAAA,iBAAA,CAAA;AAAA,QAAA,KAAA,EAAAS,cAAA,CAAA,IAAA,CAAA,gBAAA,CAAA;AAAA,QAEb,GAAA,EAAA,SAAA;AAAA,OAAA,EAAA;AAAA,2BAEQ,KAAE,EAAA;AAAA,UACR,KAAA,EAAIT,cAAW,CAAA,IAAA,CAAA,mBAAA,CAAA;AAAA,UAAA,KAAA,EAAAS,cAAA,CAAA,IAAA,CAAA,eAAA,CAAA;AAAA,UAEf,GAAA,EAAA,WAAA;AAAA,SAAA,EAAA;AAAA,sBAEc,iBAAO,EAAA;AAAA,YAClB,UAAA,EAAO,KAAA,IAAA,CAAA,UAAA;AAAA,YAAA,WAAA,EAAA,KAAA,IAAA,CAAA,WAAA;AAAA,YAEV,OAA2B,IAAA,CAAA,WAAA;AAAA,aAAA,IAAA,EAAA,CAAA,EAAA,CAAA,YAAA,EAAA,aAAA,EAAA,OAAA,CAAA,CAAA;AAAA;;OAjBO,EAAA,CAAA,CAAA,GAAA;AAAA,QAAwD,CAAA,gBAAA,EAAA;AAAA,UAAiD,OAAA,CAAA,CAAA,KAAK,KAAe,aAAA,CAAA,CAAA,EAAE,KAAU,YAAA,CAAA;AAAA,UAAA,MAAA,CAAA,CAAA,KAAA,KAAA,aAAA,CAAA,CAAA,EAAA,KAAA,WAAA,CAAA;AAAA;SAoB/K,CAAA;AAAA,OACA,CAAA;AAAA,MAAAR,mBAAiB,4BAAiB,CAAA;AAAA,MAAAE,YAAlCC,UAOa,EAAA;AAAA,QAAA,IAAA,EAAA,iBAAA;AAAA;;QALH,OAAA,EAAAC,QAAA,MAAA;AAAA,UAAAC,cAAA,CAEAH,YAAE,oBAAA,EAAA;AAAA,YAAA,KAAA,EAAAH,eAAA,CAAA,mCAAA,CAAA,CAAA;AAAA,YACO,OAAA,EAAA,OAAA,CAAA,CAAA,KAAA,MAAA,CAAA,KAAA,CAAA,MAAA,KAAA,IAAA,CAAA,cAAA,CAAA,MAAA,CAAA,CAAA;AAAA,WAAA,EAAA;AAAA;;;;;;;;;OAIrB,CAAA;AAAA,OAAA,GAAA,CAAA;AAAA,IAQ8BC,mBAAA,gBAAA,CAAA;AAAA,IAAAK,cAAA,EAAAC,SAAA,EAAA,EAAAC,kBAAA,CAAA,OAAA,UAAA,EAAA;AAAA;;MALmD,CAAA,gBAAA,EAAA;AAAA,QAAA,IAAA,EAAA,MAAA,IAAA,CAAA,eAAA,CAAA,MAAA,CAAA;AAAA;;;;;;;;;"}