UNPKG

22.3 kBJavaScriptView Raw
1import { r as registerInstance, c as createEvent, h, H as Host, g as getElement } from './index-8809c729.js';
2import { c as classnames } from './index-1d8e8acd.js';
3
4function getTimeRange(begin, end) {
5 const range = [];
6 for (let i = begin; i <= end; i++) {
7 range.push(`${i < 10 ? '0' : ''}${i}`);
8 }
9 return range;
10}
11const hoursRange = [
12 '20',
13 '21',
14 '22',
15 '23',
16 ...getTimeRange(0, 23),
17 '00',
18 '01',
19 '02',
20 '03'
21];
22const minutesRange = [
23 '56',
24 '57',
25 '58',
26 '59',
27 ...getTimeRange(0, 59),
28 '00',
29 '01',
30 '02',
31 '03'
32];
33/**
34 * 校验传入的 value 是否合法
35 */
36function verifyValue(value, range) {
37 if (!isNaN(+value) && value >= 0 && value < range.length)
38 return true;
39 return false;
40}
41/**
42 * 检验传入的 time value 是否合法
43 */
44function verifyTime(value) {
45 if (!/^\d{1,2}:\d{1,2}$/.test(value))
46 return false;
47 const time = value.split(':').map(num => +num);
48 if (time[0] < 0 || time[0] > 23)
49 return false;
50 if (time[1] < 0 || time[1] > 59)
51 return false;
52 return true;
53}
54/**
55 * 比较时间
56 * return t1 <= t2
57 */
58function compareTime(t1, t2) {
59 const t1List = t1.split(':').map(i => +i);
60 const t2List = t2.split(':').map(i => +i);
61 if (t1List[0] < t2List[0])
62 return true;
63 if (t1List[0] === t2List[0] && t1List[1] <= t2List[1])
64 return true;
65 return false;
66}
67/**
68 * 校验日期合法性,返回合法性和日期数组
69 */
70function verifyDate(dateStr) {
71 if (!dateStr)
72 return false;
73 const date = new Date(dateStr.replace(/-/g, '/'));
74 return isNaN(date.getMonth()) ? false : date;
75}
76/**
77 * 获取当月最大天数
78 */
79function getMaxDay(year, month) {
80 if (month === 4 || month === 6 || month === 9 || month === 11)
81 return 30;
82 if (month === 2) {
83 if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0)
84 return 29;
85 else
86 return 28;
87 }
88 return 31;
89}
90function formatValue(value) {
91 let res;
92 if (Array.isArray(value)) {
93 res = value.map(item => String(item));
94 }
95 else {
96 res = value;
97 }
98 return res;
99}
100/**
101 * 获取时间数组
102 */
103function getDateRange(start, end) {
104 const range = [];
105 for (let i = start; i <= end; i++) {
106 range.push(i);
107 }
108 return range;
109}
110/**
111 * 获取年份区间数组
112 */
113function getYearRange(start, end) {
114 return getDateRange(start, end);
115}
116/**
117 * 获取月份区间数组
118 */
119function getMonthRange(start, end, year) {
120 let rangeStart = 1;
121 let rangeEnd = 12;
122 // 当前年份等于开始年份,由开始对应的月份约束开始值
123 if (start.getFullYear() === year) {
124 rangeStart = start.getMonth() + 1;
125 }
126 // 当前年份等于结束年份,由结束对应的月份约束结束值
127 if (end.getFullYear() === year) {
128 rangeEnd = end.getMonth() + 1;
129 }
130 return getDateRange(rangeStart, rangeEnd);
131}
132/**
133 * 获取日期区间数组
134 */
135function getDayRange(start, end, year, month) {
136 let rangeStart = 1;
137 let rangeEnd = getMaxDay(year, month);
138 if (start.getFullYear() === year && start.getMonth() + 1 === month) {
139 rangeStart = start.getDate();
140 }
141 if (end.getFullYear() === year && end.getMonth() + 1 === month) {
142 rangeEnd = end.getDate();
143 }
144 return getDateRange(rangeStart, rangeEnd);
145}
146
147const TOP = 102;
148const LINE_HEIGHT = 34;
149const MASK_HEIGHT = LINE_HEIGHT * 7;
150
151const indexCss = ".weui-picker,.weui-picker__hd{font-size:12px}";
152
153let Picker = class {
154 constructor(hostRef) {
155 registerInstance(this, hostRef);
156 this.onChange = createEvent(this, "change", 7);
157 this.onColumnChange = createEvent(this, "columnchange", 7);
158 this.onCancel = createEvent(this, "cancel", 7);
159 this.index = [];
160 this.mode = 'selector';
161 this.disabled = false;
162 this.range = [];
163 this.start = '';
164 this.end = '';
165 this.fields = 'day';
166 this.name = '';
167 this.pickerValue = [];
168 this.height = [];
169 this.hidden = true;
170 this.fadeOut = false;
171 this.isWillLoadCalled = false;
172 // 展示 Picker
173 this.showPicker = () => {
174 if (this.disabled)
175 return;
176 this.height = this.getHeightByIndex();
177 this.hidden = false;
178 };
179 this.getHeightByIndex = () => {
180 const height = this.index.map(i => {
181 let factor = 0;
182 if (this.mode === 'time') {
183 factor = LINE_HEIGHT * 4;
184 }
185 return TOP - LINE_HEIGHT * i - factor;
186 });
187 return height;
188 };
189 // 隐藏 picker
190 this.hidePicker = () => {
191 this.fadeOut = true;
192 setTimeout(() => {
193 this.hidden = true;
194 this.fadeOut = false;
195 }, 350);
196 };
197 // 点击确定按钮
198 this.handleChange = () => {
199 this.hidePicker();
200 this.index = this.height.map(h => (TOP - h) / LINE_HEIGHT);
201 let value = this.index.length && this.mode !== 'selector'
202 ? this.index
203 : this.index[0];
204 if (this.mode === 'time') {
205 const range = [hoursRange.slice(), minutesRange.slice()];
206 const timeArr = this.index.map((n, i) => range[i][n]);
207 this.index = timeArr.map(item => parseInt(item));
208 value = timeArr.join(':');
209 }
210 if (this.mode === 'date') {
211 const { _start, _end, _updateValue } = this.pickerDate;
212 const currentYear = _updateValue[0];
213 const currentMonth = _updateValue[1];
214 const yearRange = getYearRange(_start.getFullYear(), _end.getFullYear());
215 const monthRange = getMonthRange(_start, _end, currentYear);
216 const dayRange = getDayRange(_start, _end, currentYear, currentMonth);
217 const year = yearRange[this.index[0]];
218 const month = monthRange[this.index[1]];
219 const day = dayRange[this.index[2]];
220 if (this.fields === 'year') {
221 value = [year];
222 }
223 else if (this.fields === 'month') {
224 value = [year, month];
225 }
226 else {
227 value = [year, month, day];
228 }
229 value = value
230 .map(item => {
231 return item < 10 ? `0${item}` : item;
232 })
233 .join('-');
234 }
235 this.pickerValue = value;
236 this.onChange.emit({
237 value
238 });
239 };
240 // 点击取消按钮或蒙层
241 this.handleCancel = () => {
242 this.hidePicker();
243 this.onCancel.emit();
244 };
245 this.updateHeight = (height, columnId, needRevise = false) => {
246 const temp = [...this.height];
247 temp[columnId] = height;
248 this.height = temp;
249 // time picker 必须在规定时间范围内,因此需要在 touchEnd 做修正
250 if (needRevise) {
251 let { start, end } = this;
252 if (!verifyTime(start))
253 start = '00:00';
254 if (!verifyTime(end))
255 end = '23:59';
256 if (!compareTime(start, end))
257 return;
258 const range = [hoursRange.slice(), minutesRange.slice()];
259 const timeList = this.height.map(h => (TOP - h) / LINE_HEIGHT);
260 const timeStr = timeList.map((n, i) => range[i][n]).join(':');
261 if (!compareTime(start, timeStr)) {
262 // 修正到 start
263 const height = start
264 .split(':')
265 .map(i => TOP - LINE_HEIGHT * (+i + 4));
266 requestAnimationFrame(() => (this.height = height));
267 }
268 else if (!compareTime(timeStr, end)) {
269 // 修正到 end
270 const height = end
271 .split(':')
272 .map(i => TOP - LINE_HEIGHT * (+i + 4));
273 requestAnimationFrame(() => (this.height = height));
274 }
275 }
276 };
277 this.handleColumnChange = (height, columnId) => {
278 this.onColumnChange.emit({
279 column: Number(columnId),
280 value: (TOP - height) / LINE_HEIGHT
281 });
282 };
283 this.updateDay = (value, fields) => {
284 const { _start, _end, _updateValue } = this.pickerDate;
285 _updateValue[fields] = value;
286 const currentYear = _updateValue[0];
287 const currentMonth = _updateValue[1];
288 const currentDay = _updateValue[2];
289 // 滚动年份
290 if (fields === 0) {
291 const monthRange = getMonthRange(_start, _end, currentYear);
292 const max = monthRange[monthRange.length - 1];
293 const min = monthRange[0];
294 if (currentMonth > max)
295 _updateValue[1] = max;
296 if (currentMonth < min)
297 _updateValue[1] = min;
298 const index = monthRange.indexOf(_updateValue[1]);
299 const height = TOP - LINE_HEIGHT * index;
300 this.updateDay(_updateValue[1], 1);
301 this.updateHeight(height, '1');
302 }
303 else if (fields === 1) {
304 const dayRange = getDayRange(_start, _end, currentYear, currentMonth);
305 const max = dayRange[dayRange.length - 1];
306 const min = dayRange[0];
307 if (currentDay > max)
308 _updateValue[2] = max;
309 if (currentDay < min)
310 _updateValue[2] = min;
311 const index = dayRange.indexOf(_updateValue[2]);
312 const height = TOP - LINE_HEIGHT * index;
313 this.updateDay(_updateValue[2], 2);
314 this.updateHeight(height, '2');
315 }
316 };
317 // 单列选择器
318 this.getSelector = () => {
319 return (h("taro-picker-group", { range: this.range, rangeKey: this.rangeKey, height: this.height[0], updateHeight: this.updateHeight, columnId: '0' }));
320 };
321 // 多列选择器
322 this.getMultiSelector = () => {
323 return this.range.map((range, index) => {
324 return (h("taro-picker-group", { range: range, rangeKey: this.rangeKey, height: this.height[index], updateHeight: this.updateHeight, onColumnChange: this.handleColumnChange, columnId: String(index) }));
325 });
326 };
327 // 时间选择器
328 this.getTimeSelector = () => {
329 const hourRange = hoursRange.slice();
330 const minRange = minutesRange.slice();
331 return [
332 h("taro-picker-group", { mode: 'time', range: hourRange, height: this.height[0], updateHeight: this.updateHeight, columnId: '0' }),
333 h("taro-picker-group", { mode: 'time', range: minRange, height: this.height[1], updateHeight: this.updateHeight, columnId: '1' })
334 ];
335 };
336 // 日期选择器
337 this.getDateSelector = () => {
338 const { fields, height } = this;
339 const { _start, _end, _updateValue } = this.pickerDate;
340 const currentYear = _updateValue[0];
341 const currentMonth = _updateValue[1];
342 const yearRange = getYearRange(_start.getFullYear(), _end.getFullYear())
343 .map(item => `${item}年`);
344 const monthRange = getMonthRange(_start, _end, currentYear)
345 .map(item => `${item < 10 ? `0${item}` : item}月`);
346 const dayRange = getDayRange(_start, _end, currentYear, currentMonth)
347 .map(item => `${item < 10 ? `0${item}` : item}日`);
348 const renderView = [
349 h("taro-picker-group", { mode: 'date', range: yearRange, height: height[0], updateDay: this.updateDay, updateHeight: this.updateHeight, columnId: '0' })
350 ];
351 if (fields === 'month' || fields === 'day') {
352 renderView.push(h("taro-picker-group", { mode: 'date', range: monthRange, height: height[1], updateDay: this.updateDay, updateHeight: this.updateHeight, columnId: '1' }));
353 }
354 if (fields === 'day') {
355 renderView.push(h("taro-picker-group", { mode: 'date', range: dayRange, height: height[2], updateDay: this.updateDay, updateHeight: this.updateHeight, columnId: '2' }));
356 }
357 return renderView;
358 };
359 }
360 componentWillLoad() {
361 this.isWillLoadCalled = true;
362 this.handleProps();
363 }
364 componentDidLoad() {
365 Object.defineProperty(this.el, 'value', {
366 get: () => this.pickerValue,
367 set: val => (this.value = val),
368 configurable: true
369 });
370 if (this.overlay) {
371 document.body.appendChild(this.overlay);
372 }
373 }
374 disconnectedCallback() {
375 var _a;
376 if (this.overlay) {
377 (_a = this.overlay.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(this.overlay);
378 }
379 }
380 onPropsChange() {
381 if (!this.isWillLoadCalled)
382 return;
383 this.handleProps();
384 }
385 handleProps() {
386 const { mode, start, end } = this;
387 if (mode === 'selector') {
388 const value = this.value;
389 this.index = [verifyValue(value, this.range) ? Math.floor(value) : 0];
390 }
391 else if (mode === 'multiSelector') {
392 const value = this.value;
393 this.index = [];
394 this.range.forEach((range, index) => {
395 const val = value === null || value === void 0 ? void 0 : value[index];
396 const item = verifyValue(val, range) ? Math.floor(val) : 0;
397 this.index.push(item);
398 });
399 }
400 else if (mode === 'time') {
401 let value = this.value;
402 // check value...
403 if (!verifyTime(value)) {
404 console.warn('time picker value illegal');
405 value = '0:0';
406 }
407 const time = value.split(':').map(n => +n);
408 this.index = time;
409 }
410 else if (mode === 'date') {
411 const value = this.value;
412 const _value = verifyDate(value) || new Date(new Date().setHours(0, 0, 0, 0)); // 没传值或值的合法性错误默认今天时间
413 const _start = verifyDate(start) || new Date('1970/01/01');
414 const _end = verifyDate(end) || new Date('2999/01/01');
415 // 时间区间有效性
416 if (_value >= _start && _value <= _end) {
417 const currentYear = _value.getFullYear();
418 const currentMonth = _value.getMonth() + 1;
419 const currentDay = _value.getDate();
420 const yearRange = getYearRange(_start.getFullYear(), _end.getFullYear());
421 const monthRange = getMonthRange(_start, _end, currentYear);
422 const dayRange = getDayRange(_start, _end, currentYear, currentMonth);
423 this.index = [
424 yearRange.indexOf(currentYear),
425 monthRange.indexOf(currentMonth),
426 dayRange.indexOf(currentDay)
427 ];
428 if (!this.pickerDate ||
429 this.pickerDate._value.getTime() !== _value.getTime() ||
430 this.pickerDate._start.getTime() !== _start.getTime() ||
431 this.pickerDate._end.getTime() !== _end.getTime()) {
432 this.pickerDate = {
433 _value,
434 _start,
435 _end,
436 _updateValue: [
437 currentYear,
438 currentMonth,
439 currentDay
440 ]
441 };
442 }
443 }
444 else {
445 throw new Error('Date Interval Error');
446 }
447 }
448 // Prop 变化时,无论是否正在显示弹层,都更新 height 值
449 this.height = this.getHeightByIndex();
450 // 同步表单 value 值,用于 form submit
451 this.pickerValue = this.value;
452 if (mode === 'date') {
453 const val = this.pickerValue;
454 if (this.fields === 'month') {
455 this.pickerValue = val.split('-').slice(0, 2).join('-');
456 }
457 else if (this.fields === 'year') {
458 this.pickerValue = val.split('-')[0];
459 }
460 }
461 }
462 render() {
463 const { name, mode, fadeOut, hidden } = this;
464 // 选项条
465 let pickerGroup;
466 switch (mode) {
467 case 'multiSelector':
468 pickerGroup = this.getMultiSelector();
469 break;
470 case 'time':
471 pickerGroup = this.getTimeSelector();
472 break;
473 case 'date':
474 pickerGroup = this.getDateSelector();
475 break;
476 default:
477 pickerGroup = this.getSelector();
478 }
479 // 动画类名控制逻辑
480 const clsMask = classnames('weui-mask', 'weui-animate-fade-in', {
481 'weui-animate-fade-out': fadeOut
482 });
483 const clsSlider = classnames('weui-picker', 'weui-animate-slide-up', {
484 'weui-animate-slide-down': fadeOut
485 });
486 const shouldDivHidden = hidden ? { display: 'none' } : {};
487 return (h(Host, null, h("div", { onClick: this.showPicker }, h("slot", null), h("input", { type: 'hidden', name: name, value: formatValue(this.pickerValue) })), h("div", { class: 'weui-picker__overlay', style: shouldDivHidden, ref: el => { this.overlay = el; } }, h("div", { class: clsMask, onClick: this.handleCancel }), h("div", { class: clsSlider }, h("div", { class: 'weui-picker__hd' }, h("div", { class: 'weui-picker__action', onClick: this.handleCancel }, "\u53D6\u6D88"), h("div", { class: 'weui-picker__action', onClick: this.handleChange }, "\u786E\u5B9A")), h("div", { class: 'weui-picker__bd' }, pickerGroup), h("input", { type: 'hidden', name: name, value: formatValue(this.pickerValue) })))));
488 }
489 get el() { return getElement(this); }
490 static get watchers() { return {
491 "mode": ["onPropsChange"],
492 "value": ["onPropsChange"],
493 "range": ["onPropsChange"],
494 "start": ["onPropsChange"],
495 "end": ["onPropsChange"]
496 }; }
497};
498Picker.style = indexCss;
499
500let TaroPickerGroup = class {
501 constructor(hostRef) {
502 registerInstance(this, hostRef);
503 this.range = [];
504 }
505 getPosition() {
506 const transition = this.touchEnd ? 0.3 : 0;
507 const transformValue = `translate3d(0, ${this.height}px, 0)`;
508 const transitionValue = `transform ${transition}s`;
509 return {
510 transform: transformValue,
511 '-webkit-transform': transformValue,
512 transition: transitionValue,
513 '-webkit-transition': transitionValue
514 };
515 }
516 formulaUnlimitedScroll(range, absoluteHeight, direction) {
517 const { height, updateHeight, columnId } = this;
518 const factor = direction === 'up' ? 1 : -1;
519 this.touchEnd = false;
520 // 点击超过范围,点击到补帧时,先跳到另一端的补帧
521 updateHeight(-range * factor * LINE_HEIGHT + height, columnId);
522 // 再做过渡动画
523 requestAnimationFrame(() => {
524 this.touchEnd = true;
525 const index = Math.round(absoluteHeight / -LINE_HEIGHT) + range * factor;
526 const relativeHeight = TOP - LINE_HEIGHT * index;
527 updateHeight(relativeHeight, columnId, true);
528 });
529 }
530 handleMoveStart(clientY) {
531 // 记录第一次的点击位置
532 this.startY = clientY;
533 this.preY = clientY;
534 this.hadMove = false;
535 }
536 handleMoving(clientY) {
537 const y = clientY;
538 const deltaY = y - this.preY;
539 this.preY = y;
540 this.touchEnd = false;
541 if (Math.abs(y - this.startY) > 10)
542 this.hadMove = true;
543 let newPos = this.height + deltaY;
544 // 处理时间选择器的无限滚动
545 if (this.mode === 'time') {
546 if (this.columnId === '0') {
547 // 数字 28 来自于 4 格补帧 + 0 ~ 23 的 24 格,共 28 格
548 if (newPos > TOP - LINE_HEIGHT * 3) {
549 newPos = TOP - LINE_HEIGHT * 27 + deltaY;
550 }
551 if (newPos < TOP - LINE_HEIGHT * 28) {
552 newPos = TOP - LINE_HEIGHT * 4 + deltaY;
553 }
554 }
555 else if (this.columnId === '1') {
556 if (newPos > TOP - LINE_HEIGHT * 3) {
557 newPos = TOP - LINE_HEIGHT * 63 + deltaY;
558 }
559 if (newPos < TOP - LINE_HEIGHT * 64) {
560 newPos = TOP - LINE_HEIGHT * 4 + deltaY;
561 }
562 }
563 }
564 this.updateHeight(newPos, this.columnId);
565 }
566 handleMoveEnd(clientY) {
567 const { mode, range, height, updateHeight, onColumnChange, columnId } = this;
568 const max = 0;
569 const min = -LINE_HEIGHT * (range.length - 1);
570 const endY = clientY;
571 this.touchEnd = true;
572 // touchEnd 时的高度,可能带小数点,需要再处理
573 let absoluteHeight;
574 if (!this.hadMove) {
575 /** 点击 */
576 // 屏幕高度
577 const windowHeight = window.innerHeight;
578 // picker__mask 垂直方向距离屏幕顶部的高度
579 const relativeY = windowHeight - MASK_HEIGHT / 2;
580 absoluteHeight = height - TOP - (endY - relativeY);
581 // 处理时间选择器的无限滚动
582 if (mode === 'time') {
583 if (columnId === '0') {
584 // 点击上溢出
585 // absoluteHeight 是相对模块中点来算的,所以会算多半行,这时要减去这半行,即2.5行
586 if (absoluteHeight > -LINE_HEIGHT * 2.5) {
587 return this.formulaUnlimitedScroll(24, absoluteHeight, 'up');
588 }
589 // 点击下溢出
590 if (absoluteHeight < -LINE_HEIGHT * 28.5) {
591 return this.formulaUnlimitedScroll(24, absoluteHeight, 'down');
592 }
593 }
594 else if (columnId === '1') {
595 // 点击上溢出
596 if (absoluteHeight > -LINE_HEIGHT * 2.5) {
597 return this.formulaUnlimitedScroll(60, absoluteHeight, 'up');
598 }
599 // 点击下溢出
600 if (absoluteHeight < -LINE_HEIGHT * 64.5) {
601 return this.formulaUnlimitedScroll(60, absoluteHeight, 'down');
602 }
603 }
604 }
605 }
606 else {
607 /** 滚动 */
608 absoluteHeight = height - TOP;
609 }
610 // 边界情况处理
611 if (absoluteHeight > max)
612 absoluteHeight = 0;
613 if (absoluteHeight < min)
614 absoluteHeight = min;
615 // 先按公式算出 index, 再用此 index 算出一个整数高度
616 const index = Math.round(absoluteHeight / -LINE_HEIGHT);
617 const relativeHeight = TOP - LINE_HEIGHT * index;
618 if (this.mode === 'date') {
619 if (this.columnId === '0') {
620 this.updateDay(+this.range[index].replace(/[^0-9]/gi, ''), 0);
621 }
622 if (this.columnId === '1') {
623 this.updateDay(+this.range[index].replace(/[^0-9]/gi, ''), 1);
624 }
625 if (this.columnId === '2') {
626 this.updateDay(+this.range[index].replace(/[^0-9]/gi, ''), 2);
627 }
628 }
629 updateHeight(relativeHeight, columnId, mode === 'time');
630 onColumnChange && onColumnChange(relativeHeight, columnId);
631 }
632 onMouseDown(e) {
633 this.isMove = true;
634 this.handleMoveStart(e.clientY);
635 }
636 onMouseMove(e) {
637 e.preventDefault();
638 if (!this.isMove)
639 return;
640 this.handleMoving(e.clientY);
641 }
642 onMouseMoveEnd(e) {
643 if (!this.isMove)
644 return;
645 this.isMove = false;
646 this.handleMoveEnd(e.clientY);
647 }
648 onTouchStart(e) {
649 this.handleMoveStart(e.changedTouches[0].clientY);
650 }
651 onTouchMove(e) {
652 e.preventDefault();
653 this.handleMoving(e.changedTouches[0].clientY);
654 }
655 onTouchEnd(e) {
656 this.handleMoveEnd(e.changedTouches[0].clientY);
657 }
658 render() {
659 const { range, rangeKey } = this;
660 const pickerItem = range.map(item => {
661 const content = rangeKey ? item[rangeKey] : item;
662 return (h("div", { class: 'weui-picker__item' }, content));
663 });
664 return (h(Host, { class: 'weui-picker__group' }, h("div", { class: 'weui-picker__mask' }), h("div", { class: 'weui-picker__indicator' }), h("div", { class: 'weui-picker__content', style: this.getPosition() }, pickerItem)));
665 }
666};
667
668export { Picker as taro_picker_core, TaroPickerGroup as taro_picker_group };