VTimePicker.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import { resolveDirective as _resolveDirective, mergeProps as _mergeProps, createVNode as _createVNode } from "vue";
  2. // Styles
  3. import "./VTimePicker.css";
  4. // Components
  5. import { pad } from "./util.mjs";
  6. import { VTimePickerClock } from "./VTimePickerClock.mjs";
  7. import { VTimePickerControls } from "./VTimePickerControls.mjs";
  8. import { makeVPickerProps, VPicker } from "../VPicker/VPicker.mjs"; // Composables
  9. import { useLocale } from "../../composables/locale.mjs"; // Utilities
  10. import { computed, onMounted, ref, watch } from 'vue';
  11. import { SelectingTimes } from "./SelectingTimes.mjs";
  12. import { createRange, genericComponent, omit, propsFactory, useRender } from "../../util/index.mjs"; // Types
  13. const rangeHours24 = createRange(24);
  14. const rangeHours12am = createRange(12);
  15. const rangeHours12pm = rangeHours12am.map(v => v + 12);
  16. const range60 = createRange(60);
  17. const selectingNames = {
  18. 1: 'hour',
  19. 2: 'minute',
  20. 3: 'second'
  21. };
  22. export { SelectingTimes };
  23. export const makeVTimePickerProps = propsFactory({
  24. allowedHours: [Function, Array],
  25. allowedMinutes: [Function, Array],
  26. allowedSeconds: [Function, Array],
  27. ampmInTitle: Boolean,
  28. disabled: Boolean,
  29. format: {
  30. type: String,
  31. default: 'ampm'
  32. },
  33. max: String,
  34. min: String,
  35. modelValue: null,
  36. readonly: Boolean,
  37. scrollable: Boolean,
  38. useSeconds: Boolean,
  39. ...omit(makeVPickerProps({
  40. title: '$vuetify.timePicker.title'
  41. }), ['landscape'])
  42. }, 'VTimePicker');
  43. export const VTimePicker = genericComponent()({
  44. name: 'VTimePicker',
  45. props: makeVTimePickerProps(),
  46. emits: {
  47. 'update:hour': val => true,
  48. 'update:minute': val => true,
  49. 'update:period': val => true,
  50. 'update:second': val => true,
  51. 'update:modelValue': val => true
  52. },
  53. setup(props, _ref) {
  54. let {
  55. emit,
  56. slots
  57. } = _ref;
  58. const {
  59. t
  60. } = useLocale();
  61. const inputHour = ref(null);
  62. const inputMinute = ref(null);
  63. const inputSecond = ref(null);
  64. const lazyInputHour = ref(null);
  65. const lazyInputMinute = ref(null);
  66. const lazyInputSecond = ref(null);
  67. const period = ref('am');
  68. const selecting = ref(SelectingTimes.Hour);
  69. const controlsRef = ref(null);
  70. const clockRef = ref(null);
  71. const isAllowedHourCb = computed(() => {
  72. let cb;
  73. if (props.allowedHours instanceof Array) {
  74. cb = val => props.allowedHours.includes(val);
  75. } else {
  76. cb = props.allowedHours;
  77. }
  78. if (!props.min && !props.max) return cb;
  79. const minHour = props.min ? Number(props.min.split(':')[0]) : 0;
  80. const maxHour = props.max ? Number(props.max.split(':')[0]) : 23;
  81. return val => {
  82. return val >= minHour * 1 && val <= maxHour * 1 && (!cb || cb(val));
  83. };
  84. });
  85. const isAllowedMinuteCb = computed(() => {
  86. let cb;
  87. const isHourAllowed = !isAllowedHourCb.value || inputHour.value === null || isAllowedHourCb.value(inputHour.value);
  88. if (props.allowedMinutes instanceof Array) {
  89. cb = val => props.allowedMinutes.includes(val);
  90. } else {
  91. cb = props.allowedMinutes;
  92. }
  93. if (!props.min && !props.max) {
  94. return isHourAllowed ? cb : () => false;
  95. }
  96. const [minHour, minMinute] = props.min ? props.min.split(':').map(Number) : [0, 0];
  97. const [maxHour, maxMinute] = props.max ? props.max.split(':').map(Number) : [23, 59];
  98. const minTime = minHour * 60 + minMinute * 1;
  99. const maxTime = maxHour * 60 + maxMinute * 1;
  100. return val => {
  101. const time = 60 * inputHour.value + val;
  102. return time >= minTime && time <= maxTime && isHourAllowed && (!cb || cb(val));
  103. };
  104. });
  105. const isAllowedSecondCb = computed(() => {
  106. let cb;
  107. const isHourAllowed = !isAllowedHourCb.value || inputHour.value === null || isAllowedHourCb.value(inputHour.value);
  108. const isMinuteAllowed = isHourAllowed && (!isAllowedMinuteCb.value || inputMinute.value === null || isAllowedMinuteCb.value(inputMinute.value));
  109. if (props.allowedSeconds instanceof Array) {
  110. cb = val => props.allowedSeconds.includes(val);
  111. } else {
  112. cb = props.allowedSeconds;
  113. }
  114. if (!props.min && !props.max) {
  115. return isMinuteAllowed ? cb : () => false;
  116. }
  117. const [minHour, minMinute, minSecond] = props.min ? props.min.split(':').map(Number) : [0, 0, 0];
  118. const [maxHour, maxMinute, maxSecond] = props.max ? props.max.split(':').map(Number) : [23, 59, 59];
  119. const minTime = minHour * 3600 + minMinute * 60 + (minSecond || 0) * 1;
  120. const maxTime = maxHour * 3600 + maxMinute * 60 + (maxSecond || 0) * 1;
  121. return val => {
  122. const time = 3600 * inputHour.value + 60 * inputMinute.value + val;
  123. return time >= minTime && time <= maxTime && isMinuteAllowed && (!cb || cb(val));
  124. };
  125. });
  126. const isAmPm = computed(() => {
  127. return props.format === 'ampm';
  128. });
  129. watch(() => props.modelValue, val => setInputData(val));
  130. onMounted(() => {
  131. setInputData(props.modelValue);
  132. });
  133. function genValue() {
  134. if (inputHour.value != null && inputMinute.value != null && (!props.useSeconds || inputSecond.value != null)) {
  135. return `${pad(inputHour.value)}:${pad(inputMinute.value)}` + (props.useSeconds ? `:${pad(inputSecond.value)}` : '');
  136. }
  137. return null;
  138. }
  139. function emitValue() {
  140. const value = genValue();
  141. if (value !== null) emit('update:modelValue', value);
  142. }
  143. function convert24to12(hour) {
  144. return hour ? (hour - 1) % 12 + 1 : 12;
  145. }
  146. function convert12to24(hour, period) {
  147. return hour % 12 + (period === 'pm' ? 12 : 0);
  148. }
  149. function setInputData(value) {
  150. if (value == null || value === '') {
  151. inputHour.value = null;
  152. inputMinute.value = null;
  153. inputSecond.value = null;
  154. } else if (value instanceof Date) {
  155. inputHour.value = value.getHours();
  156. inputMinute.value = value.getMinutes();
  157. inputSecond.value = value.getSeconds();
  158. } else {
  159. const [hour,, minute,, second, period] = value.trim().toLowerCase().match(/^(\d+):(\d+)(:(\d+))?([ap]m)?$/) || new Array(6);
  160. inputHour.value = period ? convert12to24(parseInt(hour, 10), period) : parseInt(hour, 10);
  161. inputMinute.value = parseInt(minute, 10);
  162. inputSecond.value = parseInt(second || 0, 10);
  163. }
  164. period.value = inputHour.value == null || inputHour.value < 12 ? 'am' : 'pm';
  165. }
  166. function firstAllowed(type, value) {
  167. const allowedFn = type === 'hour' ? isAllowedHourCb.value : type === 'minute' ? isAllowedMinuteCb.value : isAllowedSecondCb.value;
  168. if (!allowedFn) return value;
  169. // TODO: clean up (Note from V2 code)
  170. const range = type === 'minute' ? range60 : type === 'second' ? range60 : isAmPm.value ? value < 12 ? rangeHours12am : rangeHours12pm : rangeHours24;
  171. const first = range.find(v => allowedFn((v + value) % range.length + range[0]));
  172. return ((first || 0) + value) % range.length + range[0];
  173. }
  174. function setPeriod(val) {
  175. period.value = val;
  176. if (inputHour.value != null) {
  177. const newHour = inputHour.value + (period.value === 'am' ? -12 : 12);
  178. inputHour.value = firstAllowed('hour', newHour);
  179. }
  180. emit('update:period', val);
  181. emitValue();
  182. return true;
  183. }
  184. function onInput(value) {
  185. if (selecting.value === SelectingTimes.Hour) {
  186. inputHour.value = isAmPm.value ? convert12to24(value, period.value) : value;
  187. } else if (selecting.value === SelectingTimes.Minute) {
  188. inputMinute.value = value;
  189. } else {
  190. inputSecond.value = value;
  191. }
  192. }
  193. function onChange(value) {
  194. switch (selectingNames[selecting.value]) {
  195. case 'hour':
  196. emit('update:hour', value);
  197. break;
  198. case 'minute':
  199. emit('update:minute', value);
  200. break;
  201. case 'second':
  202. emit('update:second', value);
  203. break;
  204. default:
  205. break;
  206. }
  207. const emitChange = selecting.value === (props.useSeconds ? SelectingTimes.Second : SelectingTimes.Minute);
  208. if (selecting.value === SelectingTimes.Hour) {
  209. selecting.value = SelectingTimes.Minute;
  210. } else if (props.useSeconds && selecting.value === SelectingTimes.Minute) {
  211. selecting.value = SelectingTimes.Second;
  212. }
  213. if (inputHour.value === lazyInputHour.value && inputMinute.value === lazyInputMinute.value && (!props.useSeconds || inputSecond.value === lazyInputSecond.value)) return;
  214. const time = genValue();
  215. if (time === null) return;
  216. lazyInputHour.value = inputHour.value;
  217. lazyInputMinute.value = inputMinute.value;
  218. props.useSeconds && (lazyInputSecond.value = inputSecond.value);
  219. emitChange && emitValue();
  220. }
  221. useRender(() => {
  222. const pickerProps = VPicker.filterProps(props);
  223. const timePickerControlsProps = VTimePickerControls.filterProps(props);
  224. const timePickerClockProps = VTimePickerClock.filterProps(omit(props, ['format', 'modelValue', 'min', 'max']));
  225. return _createVNode(VPicker, _mergeProps(pickerProps, {
  226. "color": undefined,
  227. "class": ['v-time-picker', props.class],
  228. "style": props.style
  229. }), {
  230. title: () => slots.title?.() ?? _createVNode("div", {
  231. "class": "v-time-picker__title"
  232. }, [t(props.title)]),
  233. header: () => _createVNode(VTimePickerControls, _mergeProps(timePickerControlsProps, {
  234. "ampm": isAmPm.value || props.ampmInTitle,
  235. "ampmReadonly": isAmPm.value && !props.ampmInTitle,
  236. "hour": inputHour.value,
  237. "minute": inputMinute.value,
  238. "period": period.value,
  239. "second": inputSecond.value,
  240. "selecting": selecting.value,
  241. "onUpdate:period": val => setPeriod(val),
  242. "onUpdate:selecting": value => selecting.value = value,
  243. "ref": controlsRef
  244. }), null),
  245. default: () => _createVNode(VTimePickerClock, _mergeProps(timePickerClockProps, {
  246. "allowedValues": selecting.value === SelectingTimes.Hour ? isAllowedHourCb.value : selecting.value === SelectingTimes.Minute ? isAllowedMinuteCb.value : isAllowedSecondCb.value,
  247. "double": selecting.value === SelectingTimes.Hour && !isAmPm.value,
  248. "format": selecting.value === SelectingTimes.Hour ? isAmPm.value ? convert24to12 : val => val : val => pad(val, 2),
  249. "max": selecting.value === SelectingTimes.Hour ? isAmPm.value && period.value === 'am' ? 11 : 23 : 59,
  250. "min": selecting.value === SelectingTimes.Hour && isAmPm.value && period.value === 'pm' ? 12 : 0,
  251. "size": 20,
  252. "step": selecting.value === SelectingTimes.Hour ? 1 : 5,
  253. "modelValue": selecting.value === SelectingTimes.Hour ? inputHour.value : selecting.value === SelectingTimes.Minute ? inputMinute.value : inputSecond.value,
  254. "onChange": onChange,
  255. "onInput": onInput,
  256. "ref": clockRef
  257. }), null),
  258. actions: slots.actions
  259. });
  260. });
  261. }
  262. });
  263. //# sourceMappingURL=VTimePicker.mjs.map