UNPKG

element-plus

Version:

A Component Library for Vue 3

1 lines 11.5 kB
{"version":3,"file":"basic-time-spinner.mjs","names":[],"sources":["../../../../../../../packages/components/time-picker/src/time-picker-com/basic-time-spinner.vue"],"sourcesContent":["<template>\n <div :class=\"[ns.b('spinner'), { 'has-seconds': showSeconds }]\">\n <template v-if=\"!arrowControl\">\n <el-scrollbar\n v-for=\"item in spinnerItems\"\n :key=\"item\"\n :ref=\"(scrollbar: unknown) => setRef(scrollbar as any, item)\"\n :class=\"ns.be('spinner', 'wrapper')\"\n wrap-style=\"max-height: inherit;\"\n :view-class=\"ns.be('spinner', 'list')\"\n noresize\n tag=\"ul\"\n @mouseenter=\"emitSelectRange(item)\"\n @mousemove=\"adjustCurrentSpinner(item)\"\n >\n <li\n v-for=\"(disabled, key) in timeList[item]\"\n :key=\"key\"\n :class=\"[\n ns.be('spinner', 'item'),\n ns.is('active', key === timePartials[item]),\n ns.is('disabled', disabled),\n ]\"\n @click=\"handleClick(item, { value: key, disabled })\"\n >\n <template v-if=\"item === 'hours'\">\n {{ ('0' + (amPmMode ? key % 12 || 12 : key)).slice(-2)\n }}{{ getAmPmFlag(key) }}\n </template>\n <template v-else>\n {{ ('0' + key).slice(-2) }}\n </template>\n </li>\n </el-scrollbar>\n </template>\n <template v-if=\"arrowControl\">\n <div\n v-for=\"item in spinnerItems\"\n :key=\"item\"\n :class=\"[ns.be('spinner', 'wrapper'), ns.is('arrow')]\"\n @mouseenter=\"emitSelectRange(item)\"\n >\n <el-icon\n v-repeat-click=\"onDecrement\"\n :class=\"['arrow-up', ns.be('spinner', 'arrow')]\"\n >\n <arrow-up />\n </el-icon>\n <el-icon\n v-repeat-click=\"onIncrement\"\n :class=\"['arrow-down', ns.be('spinner', 'arrow')]\"\n >\n <arrow-down />\n </el-icon>\n <ul :class=\"ns.be('spinner', 'list')\">\n <li\n v-for=\"(time, key) in arrowControlTimeList[item]\"\n :key=\"key\"\n :class=\"[\n ns.be('spinner', 'item'),\n ns.is('active', time === timePartials[item]),\n ns.is('disabled', timeList[item][time!]),\n ]\"\n >\n <template v-if=\"isNumber(time)\">\n <template v-if=\"item === 'hours'\">\n {{ ('0' + (amPmMode ? time % 12 || 12 : time)).slice(-2)\n }}{{ getAmPmFlag(time) }}\n </template>\n <template v-else>\n {{ ('0' + time).slice(-2) }}\n </template>\n </template>\n </li>\n </ul>\n </div>\n </template>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, inject, nextTick, onMounted, ref, unref, watch } from 'vue'\nimport { debounce } from 'lodash-unified'\nimport { vRepeatClick } from '@element-plus/directives'\nimport ElScrollbar from '@element-plus/components/scrollbar'\nimport ElIcon from '@element-plus/components/icon'\nimport { ArrowDown, ArrowUp } from '@element-plus/icons-vue'\nimport { useNamespace } from '@element-plus/hooks'\nimport { getStyle, isNumber, rAF } from '@element-plus/utils'\nimport { CHANGE_EVENT } from '@element-plus/constants'\nimport {\n DEFAULT_FORMATS_TIME,\n PICKER_BASE_INJECTION_KEY,\n timeUnits,\n} from '../constants'\nimport { buildTimeList } from '../utils'\nimport { basicTimeSpinnerProps } from '../props/basic-time-spinner'\nimport { getTimeLists } from '../composables/use-time-picker'\n\nimport type { Ref } from 'vue'\nimport type { ScrollbarInstance } from '@element-plus/components/scrollbar'\nimport type { TimeUnit } from '../constants'\nimport type { TimeList } from '../utils'\n\nconst props = defineProps(basicTimeSpinnerProps)\nconst pickerBase = inject(PICKER_BASE_INJECTION_KEY) as any\nconst { isRange, format, saveOnBlur } = pickerBase.props\nconst emit = defineEmits([CHANGE_EVENT, 'select-range', 'set-option'])\n\nconst ns = useNamespace('time')\n\nconst { getHoursList, getMinutesList, getSecondsList } = getTimeLists(\n props.disabledHours,\n props.disabledMinutes,\n props.disabledSeconds\n)\n\n// data\nlet isScrolling = false\nconst ignoreScroll = {\n hours: false,\n minutes: false,\n seconds: false,\n}\n\nconst currentScrollbar = ref<TimeUnit>()\nconst listHoursRef = ref<ScrollbarInstance>()\nconst listMinutesRef = ref<ScrollbarInstance>()\nconst listSecondsRef = ref<ScrollbarInstance>()\nconst listRefsMap: Record<TimeUnit, Ref<ScrollbarInstance | undefined>> = {\n hours: listHoursRef,\n minutes: listMinutesRef,\n seconds: listSecondsRef,\n}\n\n// computed\nconst spinnerItems = computed(() => {\n return props.showSeconds ? timeUnits : timeUnits.slice(0, 2)\n})\n\nconst timePartials = computed<Record<TimeUnit, number>>(() => {\n const { spinnerDate } = props\n const hours = spinnerDate.hour()\n const minutes = spinnerDate.minute()\n const seconds = spinnerDate.second()\n return { hours, minutes, seconds }\n})\n\nconst timeList = computed(() => {\n const { hours, minutes } = unref(timePartials)\n const { role, spinnerDate } = props\n const compare = !isRange ? spinnerDate : undefined\n return {\n hours: getHoursList(role, compare),\n minutes: getMinutesList(hours, role, compare),\n seconds: getSecondsList(hours, minutes, role, compare),\n }\n})\n\nconst arrowControlTimeList = computed<Record<TimeUnit, TimeList>>(() => {\n const { hours, minutes, seconds } = unref(timePartials)\n\n return {\n hours: buildTimeList(hours, 23),\n minutes: buildTimeList(minutes, 59),\n seconds: buildTimeList(seconds, 59),\n }\n})\n\nconst debouncedResetScroll = debounce((type) => {\n isScrolling = false\n adjustCurrentSpinner(type)\n}, 200)\n\nconst getAmPmFlag = (hour: number) => {\n const shouldShowAmPm = !!props.amPmMode\n if (!shouldShowAmPm) return ''\n const isCapital = props.amPmMode === 'A'\n // todo locale\n let content = hour < 12 ? ' am' : ' pm'\n if (isCapital) content = content.toUpperCase()\n return content\n}\n\nconst emitSelectRange = (type: TimeUnit) => {\n let range = [0, 0]\n const actualFormat = format || DEFAULT_FORMATS_TIME\n const hourIndex = actualFormat.indexOf('HH')\n const minuteIndex = actualFormat.indexOf('mm')\n const secondIndex = actualFormat.indexOf('ss')\n switch (type) {\n case 'hours':\n if (hourIndex !== -1) {\n range = [hourIndex, hourIndex + 2]\n }\n break\n case 'minutes':\n if (minuteIndex !== -1) {\n range = [minuteIndex, minuteIndex + 2]\n }\n break\n case 'seconds':\n if (secondIndex !== -1) {\n range = [secondIndex, secondIndex + 2]\n }\n break\n }\n const [left, right] = range\n\n emit('select-range', left, right)\n currentScrollbar.value = type\n}\n\nconst adjustCurrentSpinner = (type: TimeUnit) => {\n adjustSpinner(type, unref(timePartials)[type])\n}\n\nconst adjustSpinners = () => {\n adjustCurrentSpinner('hours')\n adjustCurrentSpinner('minutes')\n adjustCurrentSpinner('seconds')\n}\n\nconst getScrollbarElement = (el: HTMLElement) =>\n el.querySelector(`.${ns.namespace.value}-scrollbar__wrap`) as HTMLElement\n\nconst adjustSpinner = (type: TimeUnit, value: number) => {\n if (props.arrowControl) return\n const scrollbar = unref(listRefsMap[type])\n if (scrollbar && scrollbar.$el) {\n if (!saveOnBlur) {\n ignoreScroll[type] = true\n rAF(() => {\n ignoreScroll[type] = false\n })\n }\n getScrollbarElement(scrollbar.$el).scrollTop = Math.max(\n 0,\n value * typeItemHeight(type)\n )\n }\n}\n\nconst typeItemHeight = (type: TimeUnit): number => {\n const scrollbar = unref(listRefsMap[type])\n const listItem = scrollbar?.$el.querySelector('li')\n if (listItem) {\n return Number.parseFloat(getStyle(listItem, 'height')) || 0\n }\n return 0\n}\n\nconst onIncrement = () => {\n scrollDown(1)\n}\n\nconst onDecrement = () => {\n scrollDown(-1)\n}\n\nconst scrollDown = (step: number) => {\n if (!currentScrollbar.value) {\n emitSelectRange('hours')\n }\n\n const label = currentScrollbar.value!\n const now = unref(timePartials)[label]\n const total = currentScrollbar.value === 'hours' ? 24 : 60\n const next = findNextUnDisabled(label, now, step, total)\n\n modifyDateField(label, next)\n adjustSpinner(label, next)\n nextTick(() => emitSelectRange(label))\n}\n\nconst findNextUnDisabled = (\n type: TimeUnit,\n now: number,\n step: number,\n total: number\n) => {\n let next = (now + step + total) % total\n const list = unref(timeList)[type]\n while (list[next] && next !== now) {\n next = (next + step + total) % total\n }\n return next\n}\n\nconst modifyDateField = (type: TimeUnit, value: number) => {\n const list = unref(timeList)[type]\n const isDisabled = list[value]\n if (isDisabled) return\n\n const { hours, minutes, seconds } = unref(timePartials)\n\n let changeTo\n switch (type) {\n case 'hours':\n changeTo = props.spinnerDate.hour(value).minute(minutes).second(seconds)\n break\n case 'minutes':\n changeTo = props.spinnerDate.hour(hours).minute(value).second(seconds)\n break\n case 'seconds':\n changeTo = props.spinnerDate.hour(hours).minute(minutes).second(value)\n break\n }\n emit(CHANGE_EVENT, changeTo)\n}\n\nconst handleClick = (\n type: TimeUnit,\n { value, disabled }: { value: number; disabled: boolean }\n) => {\n if (!disabled) {\n modifyDateField(type, value)\n emitSelectRange(type)\n adjustSpinner(type, value)\n }\n}\n\nconst handleScroll = (type: TimeUnit) => {\n if (!saveOnBlur && ignoreScroll[type]) return\n const scrollbar = unref(listRefsMap[type])\n if (!scrollbar) return\n\n isScrolling = true\n debouncedResetScroll(type)\n const value = Math.min(\n Math.round(\n (getScrollbarElement(scrollbar.$el).scrollTop -\n (scrollBarHeight(type) * 0.5 - 10) / typeItemHeight(type) +\n 3) /\n typeItemHeight(type)\n ),\n type === 'hours' ? 23 : 59\n )\n modifyDateField(type, value)\n}\n\nconst scrollBarHeight = (type: TimeUnit) => {\n return unref(listRefsMap[type])!.$el.offsetHeight\n}\n\nconst bindScrollEvent = () => {\n const bindFunction = (type: TimeUnit) => {\n const scrollbar = unref(listRefsMap[type])\n if (scrollbar && scrollbar.$el) {\n getScrollbarElement(scrollbar.$el).onscroll = () => {\n // TODO: scroll is emitted when set scrollTop programmatically\n // should find better solutions in the future!\n handleScroll(type)\n }\n }\n }\n bindFunction('hours')\n bindFunction('minutes')\n bindFunction('seconds')\n}\n\nonMounted(() => {\n nextTick(() => {\n !props.arrowControl && bindScrollEvent()\n adjustSpinners()\n // set selection on the first hour part\n if (props.role === 'start') emitSelectRange('hours')\n })\n})\n\nconst setRef = (scrollbar: ScrollbarInstance | null, type: TimeUnit) => {\n listRefsMap[type].value = scrollbar ?? undefined\n}\n\nemit('set-option', [`${props.role}_scrollDown`, scrollDown])\nemit('set-option', [`${props.role}_emitSelectRange`, emitSelectRange])\n\nwatch(\n () => props.spinnerDate,\n () => {\n if (isScrolling) return\n adjustSpinners()\n }\n)\n</script>\n"],"mappings":""}