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 <template> 1 <template>
2 - <div  
3 - :class="classes"  
4 - @mousemove="handleMouseMove"> 2 + <div :class="classes">
5 <div :class="[prefixCls + '-header']"> 3 <div :class="[prefixCls + '-header']">
6 <span v-for="day in headerDays" :key="day"> 4 <span v-for="day in headerDays" :key="day">
7 {{day}} 5 {{day}}
8 </span> 6 </span>
9 </div> 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 </div> 16 </div>
12 </template> 17 </template>
13 <script> 18 <script>
14 - import { getFirstDayOfMonth, getDayCountOfMonth } from '../util'; 19 + import { getFirstDayOfMonth, getDayCountOfMonth, clearHours, isInRange } from '../util';
15 import { deepCopy } from '../../../utils/assist'; 20 import { deepCopy } from '../../../utils/assist';
16 import Locale from '../../../mixins/locale'; 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 export default { 27 export default {
27 - mixins: [ Locale ], 28 + mixins: [ Locale, mixin ],
  29 +
28 props: { 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 data () { 33 data () {
49 return { 34 return {
50 prefixCls: prefixCls, 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 computed: { 38 computed: {
87 classes () { 39 classes () {
88 return [ 40 return [
@@ -97,14 +49,17 @@ @@ -97,14 +49,17 @@
97 const weekDays = translatedDays.splice(weekStartDay, 7 - weekStartDay).concat(translatedDays.splice(0, weekStartDay)); 49 const weekDays = translatedDays.splice(weekStartDay, 7 - weekStartDay).concat(translatedDays.splice(0, weekStartDay));
98 return weekDays; 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 const weekStartDay = Number(this.t('i.datepicker.weekStartDay')); 56 const weekStartDay = Number(this.t('i.datepicker.weekStartDay'));
103 const day = (getFirstDayOfMonth(date) || 7) - weekStartDay; // day of first day 57 const day = (getFirstDayOfMonth(date) || 7) - weekStartDay; // day of first day
104 const today = clearHours(new Date()); // timestamp of today 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 const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth()); 64 const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth());
110 const dateCountOfLastMonth = getDayCountOfMonth(date.getFullYear(), (date.getMonth() === 0 ? 11 : date.getMonth() - 1)); 65 const dateCountOfLastMonth = getDayCountOfMonth(date.getFullYear(), (date.getMonth() === 0 ? 11 : date.getMonth() - 1));
@@ -127,7 +82,7 @@ @@ -127,7 +82,7 @@
127 const cell = deepCopy(cell_tmpl); 82 const cell = deepCopy(cell_tmpl);
128 cell.type = 'prev-month'; 83 cell.type = 'prev-month';
129 cell.text = dateCountOfLastMonth - (day - 1) + i; 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 const time = clearHours(cell.date); 86 const time = clearHours(cell.date);
132 cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time)); 87 cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
133 cells.push(cell); 88 cells.push(cell);
@@ -137,15 +92,16 @@ @@ -137,15 +92,16 @@
137 for (let i = 1; i <= dateCountOfMonth; i++) { 92 for (let i = 1; i <= dateCountOfMonth; i++) {
138 const cell = deepCopy(cell_tmpl); 93 const cell = deepCopy(cell_tmpl);
139 cell.text = i; 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 const time = clearHours(cell.date); 96 const time = clearHours(cell.date);
142 cell.type = time === today ? 'today' : 'normal'; 97 cell.type = time === today ? 'today' : 'normal';
143 - cell.selected = time === selectDay; 98 + cell.selected = selectedDays.includes(time);
144 cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time)); 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 cells.push(cell); 105 cells.push(cell);
150 } 106 }
151 107
@@ -154,7 +110,7 @@ @@ -154,7 +110,7 @@
154 const cell = deepCopy(cell_tmpl); 110 const cell = deepCopy(cell_tmpl);
155 cell.type = 'next-month'; 111 cell.type = 'next-month';
156 cell.text = i; 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 const time = clearHours(cell.date); 114 const time = clearHours(cell.date);
159 cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time)); 115 cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
160 cells.push(cell); 116 cells.push(cell);
@@ -164,74 +120,6 @@ @@ -164,74 +120,6 @@
164 } 120 }
165 }, 121 },
166 methods: { 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 getCellCls (cell) { 123 getCellCls (cell) {
236 return [ 124 return [
237 `${prefixCls}-cell`, 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 \ No newline at end of file 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 \ No newline at end of file 3 \ No newline at end of file
src/components/date-picker/picker.vue
@@ -29,12 +29,33 @@ @@ -29,12 +29,33 @@
29 ref="drop" 29 ref="drop"
30 :data-transfer="transfer" 30 :data-transfer="transfer"
31 v-transfer-dom> 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 </Drop> 52 </Drop>
34 </transition> 53 </transition>
35 </div> 54 </div>
36 </template> 55 </template>
37 <script> 56 <script>
  57 +
  58 +
38 import iInput from '../../components/input/input.vue'; 59 import iInput from '../../components/input/input.vue';
39 import Drop from '../../components/select/dropdown.vue'; 60 import Drop from '../../components/select/dropdown.vue';
40 import clickoutside from '../../directives/clickoutside'; 61 import clickoutside from '../../directives/clickoutside';
@@ -179,6 +200,10 @@ @@ -179,6 +200,10 @@
179 type: Boolean, 200 type: Boolean,
180 default: null 201 default: null
181 }, 202 },
  203 + multiple: {
  204 + type: Boolean,
  205 + default: false
  206 + },
182 size: { 207 size: {
183 validator (value) { 208 validator (value) {
184 return oneOf(value, ['small', 'large', 'default']); 209 return oneOf(value, ['small', 'large', 'default']);
@@ -206,21 +231,44 @@ @@ -206,21 +231,44 @@
206 }, 231 },
207 elementId: { 232 elementId: {
208 type: String 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 return { 256 return {
213 prefixCls: prefixCls, 257 prefixCls: prefixCls,
214 showClose: false, 258 showClose: false,
215 visible: false, 259 visible: false,
216 - picker: null,  
217 - internalValue: '', 260 + internalValue: initialValue,
218 disableClickOutSide: false, // fixed when click a date,trigger clickoutside to close picker 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 computed: { 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 opened () { 272 opened () {
225 return this.open === null ? this.visible : this.open; 273 return this.open === null ? this.visible : this.open;
226 }, 274 },
@@ -231,52 +279,31 @@ @@ -231,52 +279,31 @@
231 return icon; 279 return icon;
232 }, 280 },
233 transition () { 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 methods: { 300 methods: {
  301 + onSelectionModeChange(type){
  302 +
  303 + if (type.match(/^date/)) type = 'date';
  304 + this.selectionMode = type;
  305 + return type;
  306 + },
280 // ๅผ€ๅฏ transfer ๆ—ถ๏ผŒ็‚นๅ‡ป Drop ๅณไผšๅ…ณ้—ญ๏ผŒ่ฟ™้‡Œไธ่ฎฉๅ…ถๅ…ณ้—ญ 307 // ๅผ€ๅฏ transfer ๆ—ถ๏ผŒ็‚นๅ‡ป Drop ๅณไผšๅ…ณ้—ญ๏ผŒ่ฟ™้‡Œไธ่ฎฉๅ…ถๅ…ณ้—ญ
281 handleTransferClick () { 308 handleTransferClick () {
282 if (this.transfer) this.disableCloseUnderTransfer = true; 309 if (this.transfer) this.disableCloseUnderTransfer = true;
@@ -287,7 +314,7 @@ @@ -287,7 +314,7 @@
287 return false; 314 return false;
288 } 315 }
289 if (this.open !== null) return; 316 if (this.open !== null) return;
290 -// if (!this.disableClickOutSide) this.visible = false; 317 +
291 this.visible = false; 318 this.visible = false;
292 this.disableClickOutSide = false; 319 this.disableClickOutSide = false;
293 }, 320 },
@@ -300,87 +327,12 @@ @@ -300,87 +327,12 @@
300 }, 327 },
301 handleInputChange (event) { 328 handleInputChange (event) {
302 const oldValue = this.visualValue; 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 handleInputMouseenter () { 337 handleInputMouseenter () {
386 if (this.readonly || this.disabled) return; 338 if (this.readonly || this.disabled) return;
@@ -400,146 +352,96 @@ @@ -400,146 +352,96 @@
400 }, 352 },
401 handleClear () { 353 handleClear () {
402 this.visible = false; 354 this.visible = false;
403 - this.internalValue = '';  
404 - this.currentValue = ''; 355 + this.internalValue = this.internalValue.map(() => null);
405 this.$emit('on-clear'); 356 this.$emit('on-clear');
406 this.dispatch('FormItem', 'on-form-change', ''); 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 this.$nextTick(() => { 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 const type = this.type; 374 const type = this.type;
472 - const format = this.format || DEFAULT_FORMATS[type];  
473 - const formatter = ( 375 + const parser = (
474 TYPE_VALUE_RESOLVER_MAP[type] || 376 TYPE_VALUE_RESOLVER_MAP[type] ||
475 TYPE_VALUE_RESOLVER_MAP['default'] 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 watch: { 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 this.$refs.drop.destroy(); 415 this.$refs.drop.destroy();
494 - if (this.open === null) this.$emit('on-open-change', false);  
495 - // blur the input  
496 const input = this.$el.querySelector('input'); 416 const input = this.$el.querySelector('input');
497 if (input) input.blur(); 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 open (val) { 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 mounted () { 447 mounted () {
src/components/date-picker/picker/date-picker.js
1 -import Vue from 'vue';  
2 import Picker from '../picker.vue'; 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 import { oneOf } from '../../../utils/assist'; 5 import { oneOf } from '../../../utils/assist';
14 6
@@ -21,19 +13,18 @@ export default { @@ -21,19 +13,18 @@ export default {
21 }, 13 },
22 default: 'date' 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 created () { 28 created () {
38 if (!this.currentValue) { 29 if (!this.currentValue) {
39 if (this.type === 'daterange' || this.type === 'datetimerange') { 30 if (this.type === 'daterange' || this.type === 'datetimerange') {
@@ -42,8 +33,6 @@ export default { @@ -42,8 +33,6 @@ export default {
42 this.currentValue = ''; 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 import Picker from '../picker.vue'; 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 import Options from '../time-mixins'; 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 import { oneOf } from '../../../utils/assist'; 6 import { oneOf } from '../../../utils/assist';
15 7
16 export default { 8 export default {
17 mixins: [Picker, Options], 9 mixins: [Picker, Options],
  10 + components: { TimePickerPanel, RangeTimePickerPanel },
18 props: { 11 props: {
19 type: { 12 type: {
20 validator (value) { 13 validator (value) {
@@ -22,25 +15,19 @@ export default { @@ -22,25 +15,19 @@ export default {
22 }, 15 },
23 default: 'time' 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 };