Commit 95eae081bce373865b446f4070f275f5645250fb

Authored by Sergio Crisostomo
1 parent 0b51803b

refactor Datepicker

refactor Datepicker  to render subcomponents in template instead of
creating new Vue instances
src/components/date-picker/base/date-table.vue
1 1 <template>
2   - <div
3   - :class="classes"
4   - @mousemove="handleMouseMove">
  2 + <div :class="classes">
5 3 <div :class="[prefixCls + '-header']">
6 4 <span v-for="day in headerDays" :key="day">
7 5 {{day}}
8 6 </span>
9 7 </div>
10   - <span :class="getCellCls(cell)" v-for="(cell, index) in readCells"><em :index="index" @click="handleClick(cell)">{{ cell.text }}</em></span>
  8 + <span
  9 + :class="getCellCls(cell)"
  10 + v-for="cell in readCells"
  11 + @click="handleClick(cell)"
  12 + @mouseenter="handleMouseMove(cell)"
  13 + >
  14 + <em>{{ cell.text }}</em>
  15 + </span>
11 16 </div>
12 17 </template>
13 18 <script>
14   - import { getFirstDayOfMonth, getDayCountOfMonth } from '../util';
  19 + import { getFirstDayOfMonth, getDayCountOfMonth, clearHours, isInRange } from '../util';
15 20 import { deepCopy } from '../../../utils/assist';
16 21 import Locale from '../../../mixins/locale';
17 22  
18   - const prefixCls = 'ivu-date-picker-cells';
  23 + import mixin from './mixin';
  24 + import prefixCls from './prefixCls';
19 25  
20   - const clearHours = function (time) {
21   - const cloneDate = new Date(time);
22   - cloneDate.setHours(0, 0, 0, 0);
23   - return cloneDate.getTime();
24   - };
25 26  
26 27 export default {
27   - mixins: [ Locale ],
  28 + mixins: [ Locale, mixin ],
  29 +
28 30 props: {
29   - date: {},
30   - year: {},
31   - month: {},
32   - selectionMode: {
33   - default: 'day'
34   - },
35   - disabledDate: {},
36   - minDate: {},
37   - maxDate: {},
38   - rangeState: {
39   - default () {
40   - return {
41   - endDate: null,
42   - selecting: false
43   - };
44   - }
45   - },
46   - value: ''
  31 + /* more props in mixin */
47 32 },
48 33 data () {
49 34 return {
50 35 prefixCls: prefixCls,
51   - readCells: []
52 36 };
53 37 },
54   - watch: {
55   - 'rangeState.endDate' (newVal) {
56   - this.markRange(newVal);
57   - },
58   - minDate(newVal, oldVal) {
59   - if (newVal && !oldVal) {
60   - this.rangeState.selecting = true;
61   - this.markRange(newVal);
62   - } else if (!newVal) {
63   - this.rangeState.selecting = false;
64   - this.markRange(newVal);
65   - } else {
66   - this.markRange();
67   - }
68   - },
69   - maxDate(newVal, oldVal) {
70   - if (newVal && !oldVal) {
71   - this.rangeState.selecting = false;
72   - this.markRange(newVal);
73   -// this.$emit('on-pick', {
74   -// minDate: this.minDate,
75   -// maxDate: this.maxDate
76   -// });
77   - }
78   - },
79   - cells: {
80   - handler (cells) {
81   - this.readCells = cells;
82   - },
83   - immediate: true
84   - }
85   - },
86 38 computed: {
87 39 classes () {
88 40 return [
... ... @@ -97,14 +49,17 @@
97 49 const weekDays = translatedDays.splice(weekStartDay, 7 - weekStartDay).concat(translatedDays.splice(0, weekStartDay));
98 50 return weekDays;
99 51 },
100   - cells () {
101   - const date = new Date(this.year, this.month, 1);
  52 + readCells () {
  53 + const tableYear = this.tableDate.getFullYear();
  54 + const tableMonth = this.tableDate.getMonth();
  55 + const date = new Date(tableYear, tableMonth, 1);
102 56 const weekStartDay = Number(this.t('i.datepicker.weekStartDay'));
103 57 const day = (getFirstDayOfMonth(date) || 7) - weekStartDay; // day of first day
104 58 const today = clearHours(new Date()); // timestamp of today
105   - const selectDay = clearHours(new Date(this.value)); // timestamp of selected day
106   - const minDay = clearHours(new Date(this.minDate));
107   - const maxDay = clearHours(new Date(this.maxDate));
  59 + const selectedDays = this.dates.filter(Boolean).map(clearHours); // timestamp of selected days
  60 + const [minDay, maxDay] = this.dates.map(clearHours);
  61 + const rangeStart = this.rangeState.from && clearHours(this.rangeState.from);
  62 + const rangeEnd = this.rangeState.to && clearHours(this.rangeState.to);
108 63  
109 64 const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth());
110 65 const dateCountOfLastMonth = getDayCountOfMonth(date.getFullYear(), (date.getMonth() === 0 ? 11 : date.getMonth() - 1));
... ... @@ -127,7 +82,7 @@
127 82 const cell = deepCopy(cell_tmpl);
128 83 cell.type = 'prev-month';
129 84 cell.text = dateCountOfLastMonth - (day - 1) + i;
130   - cell.date = new Date(this.year, this.month - 1, cell.text);
  85 + cell.date = new Date(tableYear, tableMonth - 1, cell.text);
131 86 const time = clearHours(cell.date);
132 87 cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
133 88 cells.push(cell);
... ... @@ -137,15 +92,16 @@
137 92 for (let i = 1; i <= dateCountOfMonth; i++) {
138 93 const cell = deepCopy(cell_tmpl);
139 94 cell.text = i;
140   - cell.date = new Date(this.year, this.month, cell.text);
  95 + cell.date = new Date(tableYear, tableMonth, cell.text);
141 96 const time = clearHours(cell.date);
142 97 cell.type = time === today ? 'today' : 'normal';
143   - cell.selected = time === selectDay;
  98 + cell.selected = selectedDays.includes(time);
144 99 cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
145   - cell.range = time >= minDay && time <= maxDay;
146   - cell.start = this.minDate && time === minDay;
147   - cell.end = this.maxDate && time === maxDay;
148   -
  100 + if (this.selectionMode === 'range'){
  101 + cell.range = isInRange(time, rangeStart, rangeEnd);
  102 + cell.start = time === minDay;
  103 + cell.end = time === maxDay;
  104 + }
149 105 cells.push(cell);
150 106 }
151 107  
... ... @@ -154,7 +110,7 @@
154 110 const cell = deepCopy(cell_tmpl);
155 111 cell.type = 'next-month';
156 112 cell.text = i;
157   - cell.date = new Date(this.year, this.month + 1, cell.text);
  113 + cell.date = new Date(tableYear, tableMonth + 1, cell.text);
158 114 const time = clearHours(cell.date);
159 115 cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
160 116 cells.push(cell);
... ... @@ -164,74 +120,6 @@
164 120 }
165 121 },
166 122 methods: {
167   - handleClick (cell) {
168   -
169   - if (cell.disabled) return;
170   - const newDate = cell.date;
171   -
172   - if (this.selectionMode === 'range') {
173   - if (this.minDate && this.maxDate) {
174   - const minDate = new Date(newDate.getTime());
175   - const maxDate = null;
176   - this.rangeState.selecting = true;
177   - this.markRange(this.minDate);
178   -
179   - this.$emit('on-pick', {minDate, maxDate}, false);
180   - } else if (this.minDate && !this.maxDate) {
181   - if (newDate >= this.minDate) {
182   - const maxDate = new Date(newDate.getTime());
183   - this.rangeState.selecting = false;
184   -
185   - this.$emit('on-pick', {minDate: this.minDate, maxDate});
186   - } else {
187   - const minDate = new Date(newDate.getTime());
188   -
189   - this.$emit('on-pick', {minDate, maxDate: this.maxDate}, false);
190   - }
191   - } else if (!this.minDate) {
192   - const minDate = new Date(newDate.getTime());
193   - this.rangeState.selecting = true;
194   - this.markRange(this.minDate);
195   -
196   - this.$emit('on-pick', {minDate, maxDate: this.maxDate}, false);
197   - }
198   - } else {
199   - this.$emit('on-pick', newDate);
200   - }
201   - this.$emit('on-pick-click');
202   - },
203   - handleMouseMove (event) {
204   - if (!this.rangeState.selecting) return;
205   -
206   - this.$emit('on-changerange', {
207   - minDate: this.minDate,
208   - maxDate: this.maxDate,
209   - rangeState: this.rangeState
210   - });
211   -
212   - const target = event.target;
213   - if (target.tagName === 'EM') {
214   - const cell = this.cells[parseInt(event.target.getAttribute('index'))];
215   -// if (cell.disabled) return; // todo ๅพ…็กฎๅฎš
216   - this.rangeState.endDate = cell.date;
217   - }
218   - },
219   - markRange (maxDate) {
220   - const minDate = this.minDate;
221   - if (!maxDate) maxDate = this.maxDate;
222   -
223   - const minDay = clearHours(new Date(minDate));
224   - const maxDay = clearHours(new Date(maxDate));
225   -
226   - this.cells.forEach(cell => {
227   - if (cell.type === 'today' || cell.type === 'normal') {
228   - const time = clearHours(new Date(this.year, this.month, cell.text));
229   - cell.range = time >= minDay && time <= maxDay;
230   - cell.start = minDate && time === minDay;
231   - cell.end = maxDate && time === maxDay;
232   - }
233   - });
234   - },
235 123 getCellCls (cell) {
236 124 return [
237 125 `${prefixCls}-cell`,
... ...
src/components/date-picker/base/mixin.js 0 โ†’ 100644
  1 +
  2 +export default {
  3 + props: {
  4 + tableDate: {
  5 + type: Date,
  6 + required: true
  7 + },
  8 + disabledDate: {
  9 + type: Function
  10 + },
  11 + selectionMode: {
  12 + type: String,
  13 + required: true
  14 + },
  15 + value: {
  16 + type: Array,
  17 + required: true
  18 + },
  19 + rangeState: {
  20 + type: Object,
  21 + default: () => ({
  22 + from: null,
  23 + to: null,
  24 + selecting: false
  25 + })
  26 + },
  27 +
  28 + },
  29 + computed: {
  30 + dates(){
  31 + const {selectionMode, value, rangeState} = this;
  32 + const rangeSelecting = selectionMode === 'range' && rangeState.selecting;
  33 + return rangeSelecting ? [rangeState.from] : value;
  34 + },
  35 + },
  36 + methods: {
  37 + handleClick (cell) {
  38 + if (cell.disabled) return;
  39 + const newDate = cell.date;
  40 +
  41 + this.$emit('on-pick', newDate);
  42 + this.$emit('on-pick-click');
  43 + },
  44 + handleMouseMove (cell) {
  45 + if (!this.rangeState.selecting) return;
  46 + if (cell.disabled) return;
  47 + const newDate = cell.date;
  48 + this.$emit('on-change-range', newDate);
  49 + },
  50 + }
  51 +};
0 52 \ No newline at end of file
... ...
src/components/date-picker/base/month-table.vue 0 โ†’ 100644
  1 +<template>
  2 + <div :class="classes">
  3 + <span
  4 + :class="getCellCls(cell)"
  5 + v-for="cell in cells"
  6 + @click="handleClick(cell)"
  7 + @mouseenter="handleMouseMove(cell)"
  8 +
  9 + >
  10 + <em>{{ cell.text }}</em>
  11 + </span>
  12 + </div>
  13 +</template>
  14 +<script>
  15 + import { clearHours, isInRange } from '../util';
  16 + import { deepCopy } from '../../../utils/assist';
  17 + import Locale from '../../../mixins/locale';
  18 + import mixin from './mixin';
  19 + import prefixCls from './prefixCls';
  20 +
  21 + export default {
  22 + mixins: [ Locale, mixin ],
  23 + props: {/* in mixin */},
  24 + computed: {
  25 + classes() {
  26 + return [
  27 + `${prefixCls}`,
  28 + `${prefixCls}-month`
  29 + ];
  30 + },
  31 + cells () {
  32 + let cells = [];
  33 + const cell_tmpl = {
  34 + text: '',
  35 + selected: false,
  36 + disabled: false
  37 + };
  38 +
  39 + const tableYear = this.tableDate.getFullYear();
  40 + const rangeStart = this.rangeState.from && clearHours(new Date(this.rangeState.from.getFullYear(), this.rangeState.from.getMonth(), 1));
  41 + const rangeEnd = this.rangeState.to && clearHours(new Date(this.rangeState.to.getFullYear(), this.rangeState.to.getMonth(), 1));
  42 + const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), date.getMonth(), 1)));
  43 +
  44 + for (let i = 0; i < 12; i++) {
  45 + const cell = deepCopy(cell_tmpl);
  46 + cell.date = new Date(tableYear, i, 1);
  47 + cell.text = this.tCell(i + 1);
  48 + const time = clearHours(cell.date);
  49 + cell.range = isInRange(time, rangeStart, rangeEnd);
  50 + cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'month';
  51 + cell.selected = selectedDays.includes(time);
  52 + cells.push(cell);
  53 + }
  54 +
  55 + return cells;
  56 + }
  57 + },
  58 + methods: {
  59 + getCellCls (cell) {
  60 + return [
  61 + `${prefixCls}-cell`,
  62 + {
  63 + [`${prefixCls}-cell-selected`]: cell.selected,
  64 + [`${prefixCls}-cell-disabled`]: cell.disabled,
  65 + [`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
  66 + }
  67 + ];
  68 + },
  69 + tCell (nr) {
  70 + return this.t(`i.datepicker.months.m${nr}`);
  71 + }
  72 + }
  73 + };
  74 +</script>
... ...
src/components/date-picker/base/prefixCls.js 0 โ†’ 100644
  1 +
  2 +export default 'ivu-date-picker-cells';
0 3 \ No newline at end of file
... ...
src/components/date-picker/picker.vue
... ... @@ -29,12 +29,33 @@
29 29 ref="drop"
30 30 :data-transfer="transfer"
31 31 v-transfer-dom>
32   - <div ref="picker"></div>
  32 + <div>
  33 + <component
  34 + :is="panel"
  35 + :visible="visible"
  36 + :showTime="type === 'datetime' || type === 'datetimerange'"
  37 + :confirm="isConfirm"
  38 + :selectionMode="selectionMode"
  39 + :steps="steps"
  40 + :format="format"
  41 + :value="internalValue"
  42 +
  43 + v-bind="ownPickerProps"
  44 +
  45 + @on-pick="onPick"
  46 + @on-pick-clear="handleClear"
  47 + @on-pick-success="onPickSuccess"
  48 + @on-pick-click="disableClickOutSide = true"
  49 + @on-selection-mode-change="onSelectionModeChange"
  50 + ></component>
  51 + </div>
33 52 </Drop>
34 53 </transition>
35 54 </div>
36 55 </template>
37 56 <script>
  57 +
  58 +
38 59 import iInput from '../../components/input/input.vue';
39 60 import Drop from '../../components/select/dropdown.vue';
40 61 import clickoutside from '../../directives/clickoutside';
... ... @@ -179,6 +200,10 @@
179 200 type: Boolean,
180 201 default: null
181 202 },
  203 + multiple: {
  204 + type: Boolean,
  205 + default: false
  206 + },
182 207 size: {
183 208 validator (value) {
184 209 return oneOf(value, ['small', 'large', 'default']);
... ... @@ -206,21 +231,44 @@
206 231 },
207 232 elementId: {
208 233 type: String
  234 + },
  235 + steps: {
  236 + type: Array,
  237 + default: () => []
  238 + },
  239 + value: {
  240 + type: [Date, String, Array],
  241 + validator(val){
  242 + if (Array.isArray(val)){
  243 + const [start, end] = val.map(v => new Date(v));
  244 + return !isNaN(start.getTime()) && !isNaN(end.getTime());
  245 + } else {
  246 + if (typeof val === 'string') val = val.trim();
  247 + const date = new Date(val);
  248 + return val === '' || val === null || !isNaN(date.getTime());
  249 + }
  250 + }
209 251 }
210 252 },
211   - data () {
  253 + data(){
  254 + const initialValue = this.formatDate(this.value);
  255 +
212 256 return {
213 257 prefixCls: prefixCls,
214 258 showClose: false,
215 259 visible: false,
216   - picker: null,
217   - internalValue: '',
  260 + internalValue: initialValue,
218 261 disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker
219   - disableCloseUnderTransfer: false, // transfer ๆจกๅผไธ‹๏ผŒ็‚นๅ‡ปDropไนŸไผš่งฆๅ‘ๅ…ณ้—ญ
220   - currentValue: this.value
  262 + disableCloseUnderTransfer: false, // transfer ๆจกๅผไธ‹๏ผŒ็‚นๅ‡ปDropไนŸไผš่งฆๅ‘ๅ…ณ้—ญ,
  263 + selectionMode: this.onSelectionModeChange(this.type)
221 264 };
222 265 },
223 266 computed: {
  267 + publicValue(){
  268 + const isRange = this.type.includes('range');
  269 + return isRange ? this.formatDate(this.internalValue) : this.formatDate(this.internalValue[0]);
  270 + },
  271 +
224 272 opened () {
225 273 return this.open === null ? this.visible : this.open;
226 274 },
... ... @@ -231,52 +279,31 @@
231 279 return icon;
232 280 },
233 281 transition () {
234   - if (this.placement === 'bottom-start' || this.placement === 'bottom' || this.placement === 'bottom-end') {
235   - return 'slide-up';
236   - } else {
237   - return 'slide-down';
238   - }
  282 + const bottomPlaced = this.placement.match(/^bottom/);
  283 + return bottomPlaced ? 'slide-up' : 'slide-down';
239 284 },
240   - selectionMode() {
241   - if (this.type === 'month') {
242   - return 'month';
243   - } else if (this.type === 'year') {
244   - return 'year';
245   - }
  285 + visualValue() {
  286 + const value = this.internalValue;
246 287  
247   - return 'day';
248   - },
249   - visualValue: {
250   - get () {
251   - const value = this.internalValue;
252   - if (!value) return;
253   - const formatter = (
254   - TYPE_VALUE_RESOLVER_MAP[this.type] ||
255   - TYPE_VALUE_RESOLVER_MAP['default']
256   - ).formatter;
257   - const format = DEFAULT_FORMATS[this.type];
258   -
259   - return formatter(value, this.format || format);
260   - },
261   -
262   - set (value) {
263   - if (value) {
264   - const type = this.type;
265   - const parser = (
266   - TYPE_VALUE_RESOLVER_MAP[type] ||
267   - TYPE_VALUE_RESOLVER_MAP['default']
268   - ).parser;
269   - const parsedValue = parser(value, this.format || DEFAULT_FORMATS[type]);
270   - if (parsedValue) {
271   - if (this.picker) this.picker.value = parsedValue;
272   - }
273   - return;
274   - }
275   - if (this.picker) this.picker.value = value;
276   - }
  288 + if (!value) return;
  289 + const formatter = (
  290 + TYPE_VALUE_RESOLVER_MAP[this.type] ||
  291 + TYPE_VALUE_RESOLVER_MAP['default']
  292 + ).formatter;
  293 + const format = DEFAULT_FORMATS[this.type];
  294 + return formatter(value, this.format || format);
  295 + },
  296 + isConfirm(){
  297 + return this.confirm || this.type === 'datetime' || this.type === 'datetimerange';
277 298 }
278 299 },
279 300 methods: {
  301 + onSelectionModeChange(type){
  302 +
  303 + if (type.match(/^date/)) type = 'date';
  304 + this.selectionMode = type;
  305 + return type;
  306 + },
280 307 // ๅผ€ๅฏ transfer ๆ—ถ๏ผŒ็‚นๅ‡ป Drop ๅณไผšๅ…ณ้—ญ๏ผŒ่ฟ™้‡Œไธ่ฎฉๅ…ถๅ…ณ้—ญ
281 308 handleTransferClick () {
282 309 if (this.transfer) this.disableCloseUnderTransfer = true;
... ... @@ -287,7 +314,7 @@
287 314 return false;
288 315 }
289 316 if (this.open !== null) return;
290   -// if (!this.disableClickOutSide) this.visible = false;
  317 +
291 318 this.visible = false;
292 319 this.disableClickOutSide = false;
293 320 },
... ... @@ -300,87 +327,12 @@
300 327 },
301 328 handleInputChange (event) {
302 329 const oldValue = this.visualValue;
303   - const value = event.target.value;
304   -
305   - let correctValue = '';
306   - let correctDate = '';
307   - const type = this.type;
308   - const format = this.format || DEFAULT_FORMATS[type];
309   -
310   - if (type === 'daterange' || type === 'timerange' || type === 'datetimerange') {
311   - const parser = (
312   - TYPE_VALUE_RESOLVER_MAP[type] ||
313   - TYPE_VALUE_RESOLVER_MAP['default']
314   - ).parser;
315   -
316   - const formatter = (
317   - TYPE_VALUE_RESOLVER_MAP[type] ||
318   - TYPE_VALUE_RESOLVER_MAP['default']
319   - ).formatter;
320   -
321   - const parsedValue = parser(value, format);
322   -
323   - if (parsedValue[0] instanceof Date && parsedValue[1] instanceof Date) {
324   - if (parsedValue[0].getTime() > parsedValue[1].getTime()) {
325   - correctValue = oldValue;
326   - } else {
327   - correctValue = formatter(parsedValue, format);
328   - }
329   - // todo ๅˆคๆ–ญdisabledDate
330   - } else {
331   - correctValue = oldValue;
332   - }
333   -
334   - correctDate = parser(correctValue, format);
335   - } else if (type === 'time') {
336   - const parsedDate = parseDate(value, format);
337   -
338   - if (parsedDate instanceof Date) {
339   - if (this.disabledHours.length || this.disabledMinutes.length || this.disabledSeconds.length) {
340   - const hours = parsedDate.getHours();
341   - const minutes = parsedDate.getMinutes();
342   - const seconds = parsedDate.getSeconds();
343   -
344   - if ((this.disabledHours.length && this.disabledHours.indexOf(hours) > -1) ||
345   - (this.disabledMinutes.length && this.disabledMinutes.indexOf(minutes) > -1) ||
346   - (this.disabledSeconds.length && this.disabledSeconds.indexOf(seconds) > -1)) {
347   - correctValue = oldValue;
348   - } else {
349   - correctValue = formatDate(parsedDate, format);
350   - }
351   - } else {
352   - correctValue = formatDate(parsedDate, format);
353   - }
354   - } else {
355   - correctValue = oldValue;
356   - }
357   -
358   - correctDate = parseDate(correctValue, format);
359   - } else {
360   - const parsedDate = parseDate(value, format);
361   -
362   - if (parsedDate instanceof Date) {
363   - const options = this.options || false;
364   - if (options && options.disabledDate && typeof options.disabledDate === 'function' && options.disabledDate(new Date(parsedDate))) {
365   - correctValue = oldValue;
366   - } else {
367   - correctValue = formatDate(parsedDate, format);
368   - }
369   - } else if (!parsedDate) {
370   - correctValue = '';
371   - } else {
372   - correctValue = oldValue;
373   - }
  330 + const newValue = event.target.value;
374 331  
375   - correctDate = parseDate(correctValue, format);
  332 + if (newValue !== oldValue) {
  333 + this.emitChange();
  334 + this.internalValue = this.formatDate(newValue);
376 335 }
377   -
378   - this.visualValue = correctValue;
379   - event.target.value = correctValue;
380   - this.internalValue = correctDate;
381   - this.currentValue = correctDate;
382   -
383   - if (correctValue !== oldValue) this.emitChange(correctDate);
384 336 },
385 337 handleInputMouseenter () {
386 338 if (this.readonly || this.disabled) return;
... ... @@ -400,146 +352,96 @@
400 352 },
401 353 handleClear () {
402 354 this.visible = false;
403   - this.internalValue = '';
404   - this.currentValue = '';
  355 + this.internalValue = this.internalValue.map(() => null);
405 356 this.$emit('on-clear');
406 357 this.dispatch('FormItem', 'on-form-change', '');
407   - // #2215๏ผŒๅฝ“ๅˆๅง‹่ฎพ็ฝฎไบ† value๏ผŒ็›ดๆŽฅ็‚น clear๏ผŒ่ฟ™ๆ—ถ this.picker ่ฟ˜ๆฒกๆœ‰ๅŠ ่ฝฝ
408   - if (!this.picker) {
409   - this.emitChange('');
410   - }
411   - },
412   - showPicker () {
413   - if (!this.picker) {
414   - let isConfirm = this.confirm;
415   - const type = this.type;
416   -
417   - this.picker = this.Panel.$mount(this.$refs.picker);
418   - if (type === 'datetime' || type === 'datetimerange') {
419   - isConfirm = true;
420   - this.picker.showTime = true;
421   - }
422   - this.picker.value = this.internalValue;
423   - this.picker.confirm = isConfirm;
424   - this.picker.selectionMode = this.selectionMode;
425   - if (this.format) this.picker.format = this.format;
426   -
427   - // TimePicker
428   - if (this.disabledHours) this.picker.disabledHours = this.disabledHours;
429   - if (this.disabledMinutes) this.picker.disabledMinutes = this.disabledMinutes;
430   - if (this.disabledSeconds) this.picker.disabledSeconds = this.disabledSeconds;
431   - if (this.hideDisabledOptions) this.picker.hideDisabledOptions = this.hideDisabledOptions;
  358 + this.emitChange();
432 359  
433   - const options = this.options;
434   - for (const option in options) {
435   - this.picker[option] = options[option];
436   - }
437   -
438   - this.picker.$on('on-pick', (date, visible = false) => {
439   - if (!isConfirm) this.visible = visible;
440   - this.currentValue = date;
441   - this.picker.value = date;
442   - this.picker.resetView && this.picker.resetView();
443   - this.emitChange(date);
444   - });
445   -
446   - this.picker.$on('on-pick-clear', () => {
447   - this.handleClear();
448   - });
449   - this.picker.$on('on-pick-success', () => {
450   - this.visible = false;
451   - this.$emit('on-ok');
452   - });
453   - this.picker.$on('on-pick-click', () => this.disableClickOutSide = true);
454   - }
455   - if (this.internalValue instanceof Date) {
456   - this.picker.date = new Date(this.internalValue.getTime());
457   - } else {
458   - this.picker.value = this.internalValue;
459   - }
460   - this.picker.resetView && this.picker.resetView();
  360 + setTimeout(
  361 + () => this.onSelectionModeChange(this.type),
  362 + 500 // delay to improve dropdown close visual effect
  363 + );
461 364 },
462   - emitChange (date) {
463   - const newDate = this.formattingDate(date);
464   -
465   - this.$emit('on-change', newDate);
  365 + emitChange () {
  366 + this.$emit('on-change', this.publicValue);
466 367 this.$nextTick(() => {
467   - this.dispatch('FormItem', 'on-form-change', newDate);
  368 + this.dispatch('FormItem', 'on-form-change', this.publicValue);
468 369 });
469 370 },
470   - formattingDate (date) {
  371 + formatDate (val) {
  372 + const isRange = this.type.includes('range');
  373 +
471 374 const type = this.type;
472   - const format = this.format || DEFAULT_FORMATS[type];
473   - const formatter = (
  375 + const parser = (
474 376 TYPE_VALUE_RESOLVER_MAP[type] ||
475 377 TYPE_VALUE_RESOLVER_MAP['default']
476   - ).formatter;
  378 + ).parser;
477 379  
478   - let newDate = formatter(date, format);
479   - if (type === 'daterange' || type === 'timerange' || type === 'datetimerange') {
480   - newDate = [newDate.split(RANGE_SEPARATOR)[0], newDate.split(RANGE_SEPARATOR)[1]];
  380 + if (val && type === 'time' && !(val instanceof Date)) {
  381 + val = parser(val, this.format || DEFAULT_FORMATS[type]);
  382 + } else if (type.match(/range$/)) {
  383 + if (!val){
  384 + val = [null, null];
  385 + } else {
  386 + val = val.map(date => new Date(date)); // try to parse
  387 + val = val.map(date => isNaN(date.getTime()) ? null : date); // check if parse passed
  388 + }
  389 + } else if (typeof val === 'string' && type.indexOf('time') !== 0 ){
  390 + val = parser(val, this.format || DEFAULT_FORMATS[type]) || val;
  391 + }
  392 + return isRange ? val : [val];
  393 + },
  394 + onPick(dates, visible = false) {
  395 +
  396 + if (this.type === 'multiple'){
  397 + this.internalValue = [...this.internalValue, dates]; // TODO: filter multiple date duplicates
  398 + } else {
  399 + this.internalValue = Array.isArray(dates) ? dates : [dates];
481 400 }
482   - return newDate;
  401 +
  402 + if (!this.isConfirm) this.onSelectionModeChange(this.type); // reset the selectionMode
  403 + if (!this.isConfirm) this.visible = visible;
  404 + this.emitChange();
  405 + },
  406 + onPickSuccess(){
  407 + this.visible = false;
  408 + this.$emit('on-ok');
483 409 }
  410 +
484 411 },
485 412 watch: {
486   - visible (val) {
487   - if (val) {
488   - this.showPicker();
489   - this.$refs.drop.update();
490   - if (this.open === null) this.$emit('on-open-change', true);
491   - } else {
492   - if (this.picker) this.picker.resetView && this.picker.resetView(true);
  413 + visible (state) {
  414 + if (state === false){
493 415 this.$refs.drop.destroy();
494   - if (this.open === null) this.$emit('on-open-change', false);
495   - // blur the input
496 416 const input = this.$el.querySelector('input');
497 417 if (input) input.blur();
498 418 }
  419 + this.$emit('on-open-change', state);
499 420 },
500   - internalValue(val) {
501   - if (!val && this.picker && typeof this.picker.handleClear === 'function') {
502   - this.picker.handleClear();
  421 + value(val) {
  422 + const type = this.type;
  423 + const parser = (
  424 + TYPE_VALUE_RESOLVER_MAP[type] ||
  425 + TYPE_VALUE_RESOLVER_MAP['default']
  426 + ).parser;
  427 +
  428 + if (val && type === 'time' && !(val instanceof Date)) {
  429 + val = parser(val, this.format || DEFAULT_FORMATS[type]);
  430 + } else if (val && type.match(/range$/) && Array.isArray(val) && val.filter(Boolean).length === 2 && !(val[0] instanceof Date) && !(val[1] instanceof Date)) {
  431 + val = val.join(RANGE_SEPARATOR);
  432 + val = parser(val, this.format || DEFAULT_FORMATS[type]);
  433 + } else if (typeof val === 'string' && type.indexOf('time') !== 0 ){
  434 + val = parser(val, this.format || DEFAULT_FORMATS[type]) || val;
503 435 }
504   -// this.$emit('input', val);
505   - },
506   - value (val) {
507   - this.currentValue = val;
508   - },
509   - currentValue: {
510   - immediate: true,
511   - handler (val) {
512   - const type = this.type;
513   - const parser = (
514   - TYPE_VALUE_RESOLVER_MAP[type] ||
515   - TYPE_VALUE_RESOLVER_MAP['default']
516   - ).parser;
517   -
518   - if (val && type === 'time' && !(val instanceof Date)) {
519   - val = parser(val, this.format || DEFAULT_FORMATS[type]);
520   - } else if (val && type.match(/range$/) && Array.isArray(val) && val.filter(Boolean).length === 2 && !(val[0] instanceof Date) && !(val[1] instanceof Date)) {
521   - val = val.join(RANGE_SEPARATOR);
522   - val = parser(val, this.format || DEFAULT_FORMATS[type]);
523   - } else if (typeof val === 'string' && type.indexOf('time') !== 0 ){
524   - val = parser(val, this.format || DEFAULT_FORMATS[type]) || val;
525   - }
526 436  
527   - this.internalValue = val;
528   - this.$emit('input', val);
529   - }
  437 + this.internalValue = val;
  438 + this.$emit('input', val);
530 439 },
531 440 open (val) {
532   - if (val === true) {
533   - this.visible = val;
534   - this.$emit('on-open-change', true);
535   - } else if (val === false) {
536   - this.$emit('on-open-change', false);
537   - }
538   - }
539   - },
540   - beforeDestroy () {
541   - if (this.picker) {
542   - this.picker.$destroy();
  441 + this.visible = val === true;
  442 + },
  443 + type(type){
  444 + this.onSelectionModeChange(type);
543 445 }
544 446 },
545 447 mounted () {
... ...
src/components/date-picker/picker/date-picker.js
1   -import Vue from 'vue';
2 1 import Picker from '../picker.vue';
3   -import DatePanel from '../panel/date.vue';
4   -import DateRangePanel from '../panel/date-range.vue';
5   -
6   -const getPanel = function (type) {
7   - if (type === 'daterange' || type === 'datetimerange') {
8   - return DateRangePanel;
9   - }
10   - return DatePanel;
11   -};
  2 +import DatePickerPanel from '../panel/Date/date.vue';
  3 +import RangeDatePickerPanel from '../panel/Date/date-range.vue';
12 4  
13 5 import { oneOf } from '../../../utils/assist';
14 6  
... ... @@ -21,19 +13,18 @@ export default {
21 13 },
22 14 default: 'date'
23 15 },
24   - value: {}
25 16 },
26   - watch: {
27   - type(value){
28   - const typeMap = {
29   - year: 'year',
30   - month: 'month',
31   - date: 'day'
32   - };
33   - const validType = oneOf(value, Object.keys(typeMap));
34   - if (validType) this.Panel.selectionMode = typeMap[value];
  17 + components: { DatePickerPanel, RangeDatePickerPanel },
  18 + computed: {
  19 + panel(){
  20 + const isRange = this.type === 'daterange' || this.type === 'datetimerange';
  21 + return isRange ? 'RangeDatePickerPanel' : 'DatePickerPanel';
  22 + },
  23 + ownPickerProps(){
  24 + return {};
35 25 }
36 26 },
  27 +/*
37 28 created () {
38 29 if (!this.currentValue) {
39 30 if (this.type === 'daterange' || this.type === 'datetimerange') {
... ... @@ -42,8 +33,6 @@ export default {
42 33 this.currentValue = '';
43 34 }
44 35 }
45   -
46   - const panel = getPanel(this.type);
47   - this.Panel = new Vue(panel);
48 36 }
  37 +*/
49 38 };
... ...
src/components/date-picker/picker/time-picker.js
1   -import Vue from 'vue';
2 1 import Picker from '../picker.vue';
3   -import TimePanel from '../panel/time.vue';
4   -import TimeRangePanel from '../panel/time-range.vue';
  2 +import TimePickerPanel from '../panel/Time/time.vue';
  3 +import RangeTimePickerPanel from '../panel/Time/time-range.vue';
5 4 import Options from '../time-mixins';
6 5  
7   -const getPanel = function (type) {
8   - if (type === 'timerange') {
9   - return TimeRangePanel;
10   - }
11   - return TimePanel;
12   -};
13   -
14 6 import { oneOf } from '../../../utils/assist';
15 7  
16 8 export default {
17 9 mixins: [Picker, Options],
  10 + components: { TimePickerPanel, RangeTimePickerPanel },
18 11 props: {
19 12 type: {
20 13 validator (value) {
... ... @@ -22,25 +15,19 @@ export default {
22 15 },
23 16 default: 'time'
24 17 },
25   - steps: {
26   - type: Array,
27   - default: () => []
28   - },
29   - value: {}
30 18 },
31   - created () {
32   - if (!this.currentValue) {
33   - if (this.type === 'timerange') {
34   - this.currentValue = ['',''];
35   - } else {
36   - this.currentValue = '';
37   - }
  19 + computed: {
  20 + panel(){
  21 + const isRange = this.type === 'timerange';
  22 + return isRange ? 'RangeTimePickerPanel' : 'TimePickerPanel';
  23 + },
  24 + ownPickerProps(){
  25 + return {
  26 + ...this.disabledHours,
  27 + ...this.disabledMinutes,
  28 + ...this.disabledSeconds,
  29 + ...this.hideDisabledOptions,
  30 + };
38 31 }
39   - const Panel = Vue.extend(getPanel(this.type));
40   - this.Panel = new Panel({
41   - propsData: {
42   - steps: this.steps
43   - }
44   - });
45   - }
  32 + },
46 33 };
... ...