basic-time-spinner.mjs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. import { defineComponent, inject, ref, computed, unref, onMounted, nextTick, watch, openBlock, createElementBlock, normalizeClass, Fragment, renderList, createBlock, withCtx, createTextVNode, toDisplayString, createCommentVNode, withDirectives, createVNode, createElementVNode } from 'vue';
  2. import { debounce } from 'lodash-unified';
  3. import { ElScrollbar } from '../../../scrollbar/index.mjs';
  4. import { ElIcon } from '../../../icon/index.mjs';
  5. import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
  6. import { PICKER_BASE_INJECTION_KEY, timeUnits, DEFAULT_FORMATS_TIME } from '../constants.mjs';
  7. import { buildTimeList } from '../utils.mjs';
  8. import { basicTimeSpinnerProps } from '../props/basic-time-spinner.mjs';
  9. import { getTimeLists } from '../composables/use-time-picker.mjs';
  10. import _export_sfc from '../../../../_virtual/plugin-vue_export-helper.mjs';
  11. import { vRepeatClick } from '../../../../directives/repeat-click/index.mjs';
  12. import { CHANGE_EVENT } from '../../../../constants/event.mjs';
  13. import { useNamespace } from '../../../../hooks/use-namespace/index.mjs';
  14. import { getStyle } from '../../../../utils/dom/style.mjs';
  15. import { isNumber } from '../../../../utils/types.mjs';
  16. const _sfc_main = /* @__PURE__ */ defineComponent({
  17. __name: "basic-time-spinner",
  18. props: basicTimeSpinnerProps,
  19. emits: [CHANGE_EVENT, "select-range", "set-option"],
  20. setup(__props, { emit }) {
  21. const props = __props;
  22. const pickerBase = inject(PICKER_BASE_INJECTION_KEY);
  23. const { isRange, format } = pickerBase.props;
  24. const ns = useNamespace("time");
  25. const { getHoursList, getMinutesList, getSecondsList } = getTimeLists(props.disabledHours, props.disabledMinutes, props.disabledSeconds);
  26. let isScrolling = false;
  27. const currentScrollbar = ref();
  28. const listHoursRef = ref();
  29. const listMinutesRef = ref();
  30. const listSecondsRef = ref();
  31. const listRefsMap = {
  32. hours: listHoursRef,
  33. minutes: listMinutesRef,
  34. seconds: listSecondsRef
  35. };
  36. const spinnerItems = computed(() => {
  37. return props.showSeconds ? timeUnits : timeUnits.slice(0, 2);
  38. });
  39. const timePartials = computed(() => {
  40. const { spinnerDate } = props;
  41. const hours = spinnerDate.hour();
  42. const minutes = spinnerDate.minute();
  43. const seconds = spinnerDate.second();
  44. return { hours, minutes, seconds };
  45. });
  46. const timeList = computed(() => {
  47. const { hours, minutes } = unref(timePartials);
  48. const { role, spinnerDate } = props;
  49. const compare = !isRange ? spinnerDate : void 0;
  50. return {
  51. hours: getHoursList(role, compare),
  52. minutes: getMinutesList(hours, role, compare),
  53. seconds: getSecondsList(hours, minutes, role, compare)
  54. };
  55. });
  56. const arrowControlTimeList = computed(() => {
  57. const { hours, minutes, seconds } = unref(timePartials);
  58. return {
  59. hours: buildTimeList(hours, 23),
  60. minutes: buildTimeList(minutes, 59),
  61. seconds: buildTimeList(seconds, 59)
  62. };
  63. });
  64. const debouncedResetScroll = debounce((type) => {
  65. isScrolling = false;
  66. adjustCurrentSpinner(type);
  67. }, 200);
  68. const getAmPmFlag = (hour) => {
  69. const shouldShowAmPm = !!props.amPmMode;
  70. if (!shouldShowAmPm)
  71. return "";
  72. const isCapital = props.amPmMode === "A";
  73. let content = hour < 12 ? " am" : " pm";
  74. if (isCapital)
  75. content = content.toUpperCase();
  76. return content;
  77. };
  78. const emitSelectRange = (type) => {
  79. let range = [0, 0];
  80. const actualFormat = format || DEFAULT_FORMATS_TIME;
  81. const hourIndex = actualFormat.indexOf("HH");
  82. const minuteIndex = actualFormat.indexOf("mm");
  83. const secondIndex = actualFormat.indexOf("ss");
  84. switch (type) {
  85. case "hours":
  86. if (hourIndex !== -1) {
  87. range = [hourIndex, hourIndex + 2];
  88. }
  89. break;
  90. case "minutes":
  91. if (minuteIndex !== -1) {
  92. range = [minuteIndex, minuteIndex + 2];
  93. }
  94. break;
  95. case "seconds":
  96. if (secondIndex !== -1) {
  97. range = [secondIndex, secondIndex + 2];
  98. }
  99. break;
  100. }
  101. const [left, right] = range;
  102. emit("select-range", left, right);
  103. currentScrollbar.value = type;
  104. };
  105. const adjustCurrentSpinner = (type) => {
  106. adjustSpinner(type, unref(timePartials)[type]);
  107. };
  108. const adjustSpinners = () => {
  109. adjustCurrentSpinner("hours");
  110. adjustCurrentSpinner("minutes");
  111. adjustCurrentSpinner("seconds");
  112. };
  113. const getScrollbarElement = (el) => el.querySelector(`.${ns.namespace.value}-scrollbar__wrap`);
  114. const adjustSpinner = (type, value) => {
  115. if (props.arrowControl)
  116. return;
  117. const scrollbar = unref(listRefsMap[type]);
  118. if (scrollbar && scrollbar.$el) {
  119. getScrollbarElement(scrollbar.$el).scrollTop = Math.max(0, value * typeItemHeight(type));
  120. }
  121. };
  122. const typeItemHeight = (type) => {
  123. const scrollbar = unref(listRefsMap[type]);
  124. const listItem = scrollbar == null ? void 0 : scrollbar.$el.querySelector("li");
  125. if (listItem) {
  126. return Number.parseFloat(getStyle(listItem, "height")) || 0;
  127. }
  128. return 0;
  129. };
  130. const onIncrement = () => {
  131. scrollDown(1);
  132. };
  133. const onDecrement = () => {
  134. scrollDown(-1);
  135. };
  136. const scrollDown = (step) => {
  137. if (!currentScrollbar.value) {
  138. emitSelectRange("hours");
  139. }
  140. const label = currentScrollbar.value;
  141. const now = unref(timePartials)[label];
  142. const total = currentScrollbar.value === "hours" ? 24 : 60;
  143. const next = findNextUnDisabled(label, now, step, total);
  144. modifyDateField(label, next);
  145. adjustSpinner(label, next);
  146. nextTick(() => emitSelectRange(label));
  147. };
  148. const findNextUnDisabled = (type, now, step, total) => {
  149. let next = (now + step + total) % total;
  150. const list = unref(timeList)[type];
  151. while (list[next] && next !== now) {
  152. next = (next + step + total) % total;
  153. }
  154. return next;
  155. };
  156. const modifyDateField = (type, value) => {
  157. const list = unref(timeList)[type];
  158. const isDisabled = list[value];
  159. if (isDisabled)
  160. return;
  161. const { hours, minutes, seconds } = unref(timePartials);
  162. let changeTo;
  163. switch (type) {
  164. case "hours":
  165. changeTo = props.spinnerDate.hour(value).minute(minutes).second(seconds);
  166. break;
  167. case "minutes":
  168. changeTo = props.spinnerDate.hour(hours).minute(value).second(seconds);
  169. break;
  170. case "seconds":
  171. changeTo = props.spinnerDate.hour(hours).minute(minutes).second(value);
  172. break;
  173. }
  174. emit(CHANGE_EVENT, changeTo);
  175. };
  176. const handleClick = (type, { value, disabled }) => {
  177. if (!disabled) {
  178. modifyDateField(type, value);
  179. emitSelectRange(type);
  180. adjustSpinner(type, value);
  181. }
  182. };
  183. const handleScroll = (type) => {
  184. const scrollbar = unref(listRefsMap[type]);
  185. if (!scrollbar)
  186. return;
  187. isScrolling = true;
  188. debouncedResetScroll(type);
  189. const value = Math.min(Math.round((getScrollbarElement(scrollbar.$el).scrollTop - (scrollBarHeight(type) * 0.5 - 10) / typeItemHeight(type) + 3) / typeItemHeight(type)), type === "hours" ? 23 : 59);
  190. modifyDateField(type, value);
  191. };
  192. const scrollBarHeight = (type) => {
  193. return unref(listRefsMap[type]).$el.offsetHeight;
  194. };
  195. const bindScrollEvent = () => {
  196. const bindFunction = (type) => {
  197. const scrollbar = unref(listRefsMap[type]);
  198. if (scrollbar && scrollbar.$el) {
  199. getScrollbarElement(scrollbar.$el).onscroll = () => {
  200. handleScroll(type);
  201. };
  202. }
  203. };
  204. bindFunction("hours");
  205. bindFunction("minutes");
  206. bindFunction("seconds");
  207. };
  208. onMounted(() => {
  209. nextTick(() => {
  210. !props.arrowControl && bindScrollEvent();
  211. adjustSpinners();
  212. if (props.role === "start")
  213. emitSelectRange("hours");
  214. });
  215. });
  216. const setRef = (scrollbar, type) => {
  217. listRefsMap[type].value = scrollbar != null ? scrollbar : void 0;
  218. };
  219. emit("set-option", [`${props.role}_scrollDown`, scrollDown]);
  220. emit("set-option", [`${props.role}_emitSelectRange`, emitSelectRange]);
  221. watch(() => props.spinnerDate, () => {
  222. if (isScrolling)
  223. return;
  224. adjustSpinners();
  225. });
  226. return (_ctx, _cache) => {
  227. return openBlock(), createElementBlock("div", {
  228. class: normalizeClass([unref(ns).b("spinner"), { "has-seconds": _ctx.showSeconds }])
  229. }, [
  230. !_ctx.arrowControl ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(unref(spinnerItems), (item) => {
  231. return openBlock(), createBlock(unref(ElScrollbar), {
  232. key: item,
  233. ref_for: true,
  234. ref: (scrollbar) => setRef(scrollbar, item),
  235. class: normalizeClass(unref(ns).be("spinner", "wrapper")),
  236. "wrap-style": "max-height: inherit;",
  237. "view-class": unref(ns).be("spinner", "list"),
  238. noresize: "",
  239. tag: "ul",
  240. onMouseenter: ($event) => emitSelectRange(item),
  241. onMousemove: ($event) => adjustCurrentSpinner(item)
  242. }, {
  243. default: withCtx(() => [
  244. (openBlock(true), createElementBlock(Fragment, null, renderList(unref(timeList)[item], (disabled, key) => {
  245. return openBlock(), createElementBlock("li", {
  246. key,
  247. class: normalizeClass([
  248. unref(ns).be("spinner", "item"),
  249. unref(ns).is("active", key === unref(timePartials)[item]),
  250. unref(ns).is("disabled", disabled)
  251. ]),
  252. onClick: ($event) => handleClick(item, { value: key, disabled })
  253. }, [
  254. item === "hours" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
  255. createTextVNode(toDisplayString(("0" + (_ctx.amPmMode ? key % 12 || 12 : key)).slice(-2)) + toDisplayString(getAmPmFlag(key)), 1)
  256. ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
  257. createTextVNode(toDisplayString(("0" + key).slice(-2)), 1)
  258. ], 64))
  259. ], 10, ["onClick"]);
  260. }), 128))
  261. ]),
  262. _: 2
  263. }, 1032, ["class", "view-class", "onMouseenter", "onMousemove"]);
  264. }), 128)) : createCommentVNode("v-if", true),
  265. _ctx.arrowControl ? (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(unref(spinnerItems), (item) => {
  266. return openBlock(), createElementBlock("div", {
  267. key: item,
  268. class: normalizeClass([unref(ns).be("spinner", "wrapper"), unref(ns).is("arrow")]),
  269. onMouseenter: ($event) => emitSelectRange(item)
  270. }, [
  271. withDirectives((openBlock(), createBlock(unref(ElIcon), {
  272. class: normalizeClass(["arrow-up", unref(ns).be("spinner", "arrow")])
  273. }, {
  274. default: withCtx(() => [
  275. createVNode(unref(ArrowUp))
  276. ]),
  277. _: 1
  278. }, 8, ["class"])), [
  279. [unref(vRepeatClick), onDecrement]
  280. ]),
  281. withDirectives((openBlock(), createBlock(unref(ElIcon), {
  282. class: normalizeClass(["arrow-down", unref(ns).be("spinner", "arrow")])
  283. }, {
  284. default: withCtx(() => [
  285. createVNode(unref(ArrowDown))
  286. ]),
  287. _: 1
  288. }, 8, ["class"])), [
  289. [unref(vRepeatClick), onIncrement]
  290. ]),
  291. createElementVNode("ul", {
  292. class: normalizeClass(unref(ns).be("spinner", "list"))
  293. }, [
  294. (openBlock(true), createElementBlock(Fragment, null, renderList(unref(arrowControlTimeList)[item], (time, key) => {
  295. return openBlock(), createElementBlock("li", {
  296. key,
  297. class: normalizeClass([
  298. unref(ns).be("spinner", "item"),
  299. unref(ns).is("active", time === unref(timePartials)[item]),
  300. unref(ns).is("disabled", unref(timeList)[item][time])
  301. ])
  302. }, [
  303. unref(isNumber)(time) ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
  304. item === "hours" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
  305. createTextVNode(toDisplayString(("0" + (_ctx.amPmMode ? time % 12 || 12 : time)).slice(-2)) + toDisplayString(getAmPmFlag(time)), 1)
  306. ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
  307. createTextVNode(toDisplayString(("0" + time).slice(-2)), 1)
  308. ], 64))
  309. ], 64)) : createCommentVNode("v-if", true)
  310. ], 2);
  311. }), 128))
  312. ], 2)
  313. ], 42, ["onMouseenter"]);
  314. }), 128)) : createCommentVNode("v-if", true)
  315. ], 2);
  316. };
  317. }
  318. });
  319. var TimeSpinner = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "basic-time-spinner.vue"]]);
  320. export { TimeSpinner as default };
  321. //# sourceMappingURL=basic-time-spinner.mjs.map