picker.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  1. <template>
  2. <el-input
  3. class="el-date-editor"
  4. :class="'el-date-editor--' + type"
  5. :readonly="!editable || readonly || type === 'dates' || type === 'week'"
  6. :disabled="pickerDisabled"
  7. :size="pickerSize"
  8. :name="name"
  9. v-bind="firstInputId"
  10. v-if="!ranged"
  11. v-clickoutside="handleClose"
  12. :placeholder="placeholder"
  13. @focus="handleFocus"
  14. @keydown.native="handleKeydown"
  15. :value="displayValue"
  16. @input="value => userInput = value"
  17. @change="handleChange"
  18. @mouseenter.native="handleMouseEnter"
  19. @mouseleave.native="showClose = false"
  20. :validateEvent="false"
  21. ref="reference">
  22. <i slot="prefix"
  23. class="el-input__icon"
  24. :class="triggerClass"
  25. @click="handleFocus">
  26. </i>
  27. <i slot="suffix"
  28. class="el-input__icon"
  29. @click="handleClickIcon"
  30. :class="[showClose ? '' + clearIcon : '']"
  31. v-if="haveTrigger">
  32. </i>
  33. </el-input>
  34. <div
  35. class="el-date-editor el-range-editor el-input__inner"
  36. :class="[
  37. 'el-date-editor--' + type,
  38. pickerSize ? `el-range-editor--${ pickerSize }` : '',
  39. pickerDisabled ? 'is-disabled' : '',
  40. pickerVisible ? 'is-active' : ''
  41. ]"
  42. @click="handleRangeClick"
  43. @mouseenter="handleMouseEnter"
  44. @mouseleave="showClose = false"
  45. @keydown="handleKeydown"
  46. ref="reference"
  47. v-clickoutside="handleClose"
  48. v-else>
  49. <i :class="['el-input__icon', 'el-range__icon', triggerClass]"></i>
  50. <input
  51. autocomplete="off"
  52. :placeholder="startPlaceholder"
  53. :value="displayValue && displayValue[0]"
  54. :disabled="pickerDisabled"
  55. v-bind="firstInputId"
  56. :readonly="!editable || readonly"
  57. :name="name && name[0]"
  58. @input="handleStartInput"
  59. @change="handleStartChange"
  60. @focus="handleFocus"
  61. class="el-range-input">
  62. <slot name="range-separator">
  63. <span class="el-range-separator">{{ rangeSeparator }}</span>
  64. </slot>
  65. <input
  66. autocomplete="off"
  67. :placeholder="endPlaceholder"
  68. :value="displayValue && displayValue[1]"
  69. :disabled="pickerDisabled"
  70. v-bind="secondInputId"
  71. :readonly="!editable || readonly"
  72. :name="name && name[1]"
  73. @input="handleEndInput"
  74. @change="handleEndChange"
  75. @focus="handleFocus"
  76. class="el-range-input">
  77. <i
  78. @click="handleClickIcon"
  79. v-if="haveTrigger"
  80. :class="[showClose ? '' + clearIcon : '']"
  81. class="el-input__icon el-range__close-icon">
  82. </i>
  83. </div>
  84. </template>
  85. <script>
  86. import Vue from 'vue';
  87. import Clickoutside from 'element-ui/src/utils/clickoutside';
  88. import { formatDate, parseDate, isDateObject, getWeekNumber } from 'element-ui/src/utils/date-util';
  89. import Popper from 'element-ui/src/utils/vue-popper';
  90. import Emitter from 'element-ui/src/mixins/emitter';
  91. import ElInput from 'element-ui/packages/input';
  92. import merge from 'element-ui/src/utils/merge';
  93. const NewPopper = {
  94. props: {
  95. appendToBody: Popper.props.appendToBody,
  96. offset: Popper.props.offset,
  97. boundariesPadding: Popper.props.boundariesPadding,
  98. arrowOffset: Popper.props.arrowOffset
  99. },
  100. methods: Popper.methods,
  101. data() {
  102. return merge({ visibleArrow: true }, Popper.data);
  103. },
  104. beforeDestroy: Popper.beforeDestroy
  105. };
  106. const DEFAULT_FORMATS = {
  107. date: 'yyyy-MM-dd',
  108. month: 'yyyy-MM',
  109. datetime: 'yyyy-MM-dd HH:mm:ss',
  110. time: 'HH:mm:ss',
  111. week: 'yyyywWW',
  112. timerange: 'HH:mm:ss',
  113. daterange: 'yyyy-MM-dd',
  114. monthrange: 'yyyy-MM',
  115. datetimerange: 'yyyy-MM-dd HH:mm:ss',
  116. year: 'yyyy'
  117. };
  118. const HAVE_TRIGGER_TYPES = [
  119. 'date',
  120. 'datetime',
  121. 'time',
  122. 'time-select',
  123. 'week',
  124. 'month',
  125. 'year',
  126. 'daterange',
  127. 'monthrange',
  128. 'timerange',
  129. 'datetimerange',
  130. 'dates'
  131. ];
  132. const DATE_FORMATTER = function(value, format) {
  133. if (format === 'timestamp') return value.getTime();
  134. return formatDate(value, format);
  135. };
  136. const DATE_PARSER = function(text, format) {
  137. if (format === 'timestamp') return new Date(Number(text));
  138. return parseDate(text, format);
  139. };
  140. const RANGE_FORMATTER = function(value, format) {
  141. if (Array.isArray(value) && value.length === 2) {
  142. const start = value[0];
  143. const end = value[1];
  144. if (start && end) {
  145. return [DATE_FORMATTER(start, format), DATE_FORMATTER(end, format)];
  146. }
  147. }
  148. return '';
  149. };
  150. const RANGE_PARSER = function(array, format, separator) {
  151. if (!Array.isArray(array)) {
  152. array = array.split(separator);
  153. }
  154. if (array.length === 2) {
  155. const range1 = array[0];
  156. const range2 = array[1];
  157. return [DATE_PARSER(range1, format), DATE_PARSER(range2, format)];
  158. }
  159. return [];
  160. };
  161. const TYPE_VALUE_RESOLVER_MAP = {
  162. default: {
  163. formatter(value) {
  164. if (!value) return '';
  165. return '' + value;
  166. },
  167. parser(text) {
  168. if (text === undefined || text === '') return null;
  169. return text;
  170. }
  171. },
  172. week: {
  173. formatter(value, format) {
  174. let week = getWeekNumber(value);
  175. let month = value.getMonth();
  176. const trueDate = new Date(value);
  177. if (week === 1 && month === 11) {
  178. trueDate.setHours(0, 0, 0, 0);
  179. trueDate.setDate(trueDate.getDate() + 3 - (trueDate.getDay() + 6) % 7);
  180. }
  181. let date = formatDate(trueDate, format);
  182. date = /WW/.test(date)
  183. ? date.replace(/WW/, week < 10 ? '0' + week : week)
  184. : date.replace(/W/, week);
  185. return date;
  186. },
  187. parser(text, format) {
  188. // parse as if a normal date
  189. return TYPE_VALUE_RESOLVER_MAP.date.parser(text, format);
  190. }
  191. },
  192. date: {
  193. formatter: DATE_FORMATTER,
  194. parser: DATE_PARSER
  195. },
  196. datetime: {
  197. formatter: DATE_FORMATTER,
  198. parser: DATE_PARSER
  199. },
  200. daterange: {
  201. formatter: RANGE_FORMATTER,
  202. parser: RANGE_PARSER
  203. },
  204. monthrange: {
  205. formatter: RANGE_FORMATTER,
  206. parser: RANGE_PARSER
  207. },
  208. datetimerange: {
  209. formatter: RANGE_FORMATTER,
  210. parser: RANGE_PARSER
  211. },
  212. timerange: {
  213. formatter: RANGE_FORMATTER,
  214. parser: RANGE_PARSER
  215. },
  216. time: {
  217. formatter: DATE_FORMATTER,
  218. parser: DATE_PARSER
  219. },
  220. month: {
  221. formatter: DATE_FORMATTER,
  222. parser: DATE_PARSER
  223. },
  224. year: {
  225. formatter: DATE_FORMATTER,
  226. parser: DATE_PARSER
  227. },
  228. number: {
  229. formatter(value) {
  230. if (!value) return '';
  231. return '' + value;
  232. },
  233. parser(text) {
  234. let result = Number(text);
  235. if (!isNaN(text)) {
  236. return result;
  237. } else {
  238. return null;
  239. }
  240. }
  241. },
  242. dates: {
  243. formatter(value, format) {
  244. return value.map(date => DATE_FORMATTER(date, format));
  245. },
  246. parser(value, format) {
  247. return (typeof value === 'string' ? value.split(', ') : value)
  248. .map(date => date instanceof Date ? date : DATE_PARSER(date, format));
  249. }
  250. }
  251. };
  252. const PLACEMENT_MAP = {
  253. left: 'bottom-start',
  254. center: 'bottom',
  255. right: 'bottom-end'
  256. };
  257. const parseAsFormatAndType = (value, customFormat, type, rangeSeparator = '-') => {
  258. if (!value) return null;
  259. const parser = (
  260. TYPE_VALUE_RESOLVER_MAP[type] ||
  261. TYPE_VALUE_RESOLVER_MAP['default']
  262. ).parser;
  263. const format = customFormat || DEFAULT_FORMATS[type];
  264. return parser(value, format, rangeSeparator);
  265. };
  266. const formatAsFormatAndType = (value, customFormat, type) => {
  267. if (!value) return null;
  268. const formatter = (
  269. TYPE_VALUE_RESOLVER_MAP[type] ||
  270. TYPE_VALUE_RESOLVER_MAP['default']
  271. ).formatter;
  272. const format = customFormat || DEFAULT_FORMATS[type];
  273. return formatter(value, format);
  274. };
  275. /*
  276. * Considers:
  277. * 1. Date object
  278. * 2. date string
  279. * 3. array of 1 or 2
  280. */
  281. const valueEquals = function(a, b) {
  282. // considers Date object and string
  283. const dateEquals = function(a, b) {
  284. const aIsDate = a instanceof Date;
  285. const bIsDate = b instanceof Date;
  286. if (aIsDate && bIsDate) {
  287. return a.getTime() === b.getTime();
  288. }
  289. if (!aIsDate && !bIsDate) {
  290. return a === b;
  291. }
  292. return false;
  293. };
  294. const aIsArray = a instanceof Array;
  295. const bIsArray = b instanceof Array;
  296. if (aIsArray && bIsArray) {
  297. if (a.length !== b.length) {
  298. return false;
  299. }
  300. return a.every((item, index) => dateEquals(item, b[index]));
  301. }
  302. if (!aIsArray && !bIsArray) {
  303. return dateEquals(a, b);
  304. }
  305. return false;
  306. };
  307. const isString = function(val) {
  308. return typeof val === 'string' || val instanceof String;
  309. };
  310. const validator = function(val) {
  311. // either: String, Array of String, null / undefined
  312. return (
  313. val === null ||
  314. val === undefined ||
  315. isString(val) ||
  316. (Array.isArray(val) && val.length === 2 && val.every(isString))
  317. );
  318. };
  319. export default {
  320. mixins: [Emitter, NewPopper],
  321. inject: {
  322. elForm: {
  323. default: ''
  324. },
  325. elFormItem: {
  326. default: ''
  327. }
  328. },
  329. props: {
  330. size: String,
  331. format: String,
  332. valueFormat: String,
  333. readonly: Boolean,
  334. placeholder: String,
  335. startPlaceholder: String,
  336. endPlaceholder: String,
  337. prefixIcon: String,
  338. clearIcon: {
  339. type: String,
  340. default: 'el-icon-circle-close'
  341. },
  342. name: {
  343. default: '',
  344. validator
  345. },
  346. disabled: Boolean,
  347. clearable: {
  348. type: Boolean,
  349. default: true
  350. },
  351. id: {
  352. default: '',
  353. validator
  354. },
  355. popperClass: String,
  356. editable: {
  357. type: Boolean,
  358. default: true
  359. },
  360. align: {
  361. type: String,
  362. default: 'left'
  363. },
  364. value: {},
  365. defaultValue: {},
  366. defaultTime: {},
  367. rangeSeparator: {
  368. default: '-'
  369. },
  370. pickerOptions: {},
  371. unlinkPanels: Boolean,
  372. validateEvent: {
  373. type: Boolean,
  374. default: true
  375. }
  376. },
  377. components: { ElInput },
  378. directives: { Clickoutside },
  379. data() {
  380. return {
  381. pickerVisible: false,
  382. showClose: false,
  383. userInput: null,
  384. valueOnOpen: null, // value when picker opens, used to determine whether to emit change
  385. unwatchPickerOptions: null
  386. };
  387. },
  388. watch: {
  389. pickerVisible(val) {
  390. if (this.readonly || this.pickerDisabled) return;
  391. if (val) {
  392. this.showPicker();
  393. this.valueOnOpen = Array.isArray(this.value) ? [...this.value] : this.value;
  394. } else {
  395. this.hidePicker();
  396. this.emitChange(this.value);
  397. this.userInput = null;
  398. if (this.validateEvent) {
  399. this.dispatch('ElFormItem', 'el.form.blur');
  400. }
  401. this.$emit('blur', this);
  402. this.blur();
  403. }
  404. },
  405. parsedValue: {
  406. immediate: true,
  407. handler(val) {
  408. if (this.picker) {
  409. this.picker.value = val;
  410. }
  411. }
  412. },
  413. defaultValue(val) {
  414. // NOTE: should eventually move to jsx style picker + panel ?
  415. if (this.picker) {
  416. this.picker.defaultValue = val;
  417. }
  418. },
  419. value(val, oldVal) {
  420. if (!valueEquals(val, oldVal) && !this.pickerVisible && this.validateEvent) {
  421. this.dispatch('ElFormItem', 'el.form.change', val);
  422. }
  423. }
  424. },
  425. computed: {
  426. ranged() {
  427. return this.type.indexOf('range') > -1;
  428. },
  429. reference() {
  430. const reference = this.$refs.reference;
  431. return reference.$el || reference;
  432. },
  433. refInput() {
  434. if (this.reference) {
  435. return [].slice.call(this.reference.querySelectorAll('input'));
  436. }
  437. return [];
  438. },
  439. valueIsEmpty() {
  440. const val = this.value;
  441. if (Array.isArray(val)) {
  442. for (let i = 0, len = val.length; i < len; i++) {
  443. if (val[i]) {
  444. return false;
  445. }
  446. }
  447. } else {
  448. if (val) {
  449. return false;
  450. }
  451. }
  452. return true;
  453. },
  454. triggerClass() {
  455. return this.prefixIcon || (this.type.indexOf('time') !== -1 ? 'el-icon-time' : 'el-icon-date');
  456. },
  457. selectionMode() {
  458. if (this.type === 'week') {
  459. return 'week';
  460. } else if (this.type === 'month') {
  461. return 'month';
  462. } else if (this.type === 'year') {
  463. return 'year';
  464. } else if (this.type === 'dates') {
  465. return 'dates';
  466. }
  467. return 'day';
  468. },
  469. haveTrigger() {
  470. if (typeof this.showTrigger !== 'undefined') {
  471. return this.showTrigger;
  472. }
  473. return HAVE_TRIGGER_TYPES.indexOf(this.type) !== -1;
  474. },
  475. displayValue() {
  476. const formattedValue = formatAsFormatAndType(this.parsedValue, this.format, this.type, this.rangeSeparator);
  477. if (Array.isArray(this.userInput)) {
  478. return [
  479. this.userInput[0] || (formattedValue && formattedValue[0]) || '',
  480. this.userInput[1] || (formattedValue && formattedValue[1]) || ''
  481. ];
  482. } else if (this.userInput !== null) {
  483. return this.userInput;
  484. } else if (formattedValue) {
  485. return this.type === 'dates'
  486. ? formattedValue.join(', ')
  487. : formattedValue;
  488. } else {
  489. return '';
  490. }
  491. },
  492. parsedValue() {
  493. if (!this.value) return this.value; // component value is not set
  494. if (this.type === 'time-select') return this.value; // time-select does not require parsing, this might change in next major version
  495. const valueIsDateObject = isDateObject(this.value) || (Array.isArray(this.value) && this.value.every(isDateObject));
  496. if (valueIsDateObject) {
  497. return this.value;
  498. }
  499. if (this.valueFormat) {
  500. return parseAsFormatAndType(this.value, this.valueFormat, this.type, this.rangeSeparator) || this.value;
  501. }
  502. // NOTE: deal with common but incorrect usage, should remove in next major version
  503. // user might provide string / timestamp without value-format, coerce them into date (or array of date)
  504. return Array.isArray(this.value) ? this.value.map(val => new Date(val)) : new Date(this.value);
  505. },
  506. _elFormItemSize() {
  507. return (this.elFormItem || {}).elFormItemSize;
  508. },
  509. pickerSize() {
  510. return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
  511. },
  512. pickerDisabled() {
  513. return this.disabled || (this.elForm || {}).disabled;
  514. },
  515. firstInputId() {
  516. const obj = {};
  517. let id;
  518. if (this.ranged) {
  519. id = this.id && this.id[0];
  520. } else {
  521. id = this.id;
  522. }
  523. if (id) obj.id = id;
  524. return obj;
  525. },
  526. secondInputId() {
  527. const obj = {};
  528. let id;
  529. if (this.ranged) {
  530. id = this.id && this.id[1];
  531. }
  532. if (id) obj.id = id;
  533. return obj;
  534. }
  535. },
  536. created() {
  537. // vue-popper
  538. this.popperOptions = {
  539. boundariesPadding: 0,
  540. gpuAcceleration: false
  541. };
  542. this.placement = PLACEMENT_MAP[this.align] || PLACEMENT_MAP.left;
  543. this.$on('fieldReset', this.handleFieldReset);
  544. },
  545. methods: {
  546. focus() {
  547. if (!this.ranged) {
  548. this.$refs.reference.focus();
  549. } else {
  550. this.handleFocus();
  551. }
  552. },
  553. blur() {
  554. this.refInput.forEach(input => input.blur());
  555. },
  556. // {parse, formatTo} Value deals maps component value with internal Date
  557. parseValue(value) {
  558. const isParsed = isDateObject(value) || (Array.isArray(value) && value.every(isDateObject));
  559. if (this.valueFormat && !isParsed) {
  560. return parseAsFormatAndType(value, this.valueFormat, this.type, this.rangeSeparator) || value;
  561. } else {
  562. return value;
  563. }
  564. },
  565. formatToValue(date) {
  566. const isFormattable = isDateObject(date) || (Array.isArray(date) && date.every(isDateObject));
  567. if (this.valueFormat && isFormattable) {
  568. return formatAsFormatAndType(date, this.valueFormat, this.type, this.rangeSeparator);
  569. } else {
  570. return date;
  571. }
  572. },
  573. // {parse, formatTo} String deals with user input
  574. parseString(value) {
  575. const type = Array.isArray(value) ? this.type : this.type.replace('range', '');
  576. return parseAsFormatAndType(value, this.format, type);
  577. },
  578. formatToString(value) {
  579. const type = Array.isArray(value) ? this.type : this.type.replace('range', '');
  580. return formatAsFormatAndType(value, this.format, type);
  581. },
  582. handleMouseEnter() {
  583. if (this.readonly || this.pickerDisabled) return;
  584. if (!this.valueIsEmpty && this.clearable) {
  585. this.showClose = true;
  586. }
  587. },
  588. handleChange() {
  589. if (this.userInput) {
  590. const value = this.parseString(this.displayValue);
  591. if (value) {
  592. this.picker.value = value;
  593. if (this.isValidValue(value)) {
  594. this.emitInput(value);
  595. this.userInput = null;
  596. }
  597. }
  598. }
  599. if (this.userInput === '') {
  600. this.emitInput(null);
  601. this.emitChange(null);
  602. this.userInput = null;
  603. }
  604. },
  605. handleStartInput(event) {
  606. if (this.userInput) {
  607. this.userInput = [event.target.value, this.userInput[1]];
  608. } else {
  609. this.userInput = [event.target.value, null];
  610. }
  611. },
  612. handleEndInput(event) {
  613. if (this.userInput) {
  614. this.userInput = [this.userInput[0], event.target.value];
  615. } else {
  616. this.userInput = [null, event.target.value];
  617. }
  618. },
  619. handleStartChange(event) {
  620. const value = this.parseString(this.userInput && this.userInput[0]);
  621. if (value) {
  622. this.userInput = [this.formatToString(value), this.displayValue[1]];
  623. const newValue = [value, this.picker.value && this.picker.value[1]];
  624. this.picker.value = newValue;
  625. if (this.isValidValue(newValue)) {
  626. this.emitInput(newValue);
  627. this.userInput = null;
  628. }
  629. }
  630. },
  631. handleEndChange(event) {
  632. const value = this.parseString(this.userInput && this.userInput[1]);
  633. if (value) {
  634. this.userInput = [this.displayValue[0], this.formatToString(value)];
  635. const newValue = [this.picker.value && this.picker.value[0], value];
  636. this.picker.value = newValue;
  637. if (this.isValidValue(newValue)) {
  638. this.emitInput(newValue);
  639. this.userInput = null;
  640. }
  641. }
  642. },
  643. handleClickIcon(event) {
  644. if (this.readonly || this.pickerDisabled) return;
  645. if (this.showClose) {
  646. this.valueOnOpen = this.value;
  647. event.stopPropagation();
  648. this.emitInput(null);
  649. this.emitChange(null);
  650. this.showClose = false;
  651. if (this.picker && typeof this.picker.handleClear === 'function') {
  652. this.picker.handleClear();
  653. }
  654. } else {
  655. this.pickerVisible = !this.pickerVisible;
  656. }
  657. },
  658. handleClose() {
  659. if (!this.pickerVisible) return;
  660. this.pickerVisible = false;
  661. if (this.type === 'dates') {
  662. // restore to former value
  663. const oldValue = parseAsFormatAndType(this.valueOnOpen, this.valueFormat, this.type, this.rangeSeparator) || this.valueOnOpen;
  664. this.emitInput(oldValue);
  665. }
  666. },
  667. handleFieldReset(initialValue) {
  668. this.userInput = initialValue === '' ? null : initialValue;
  669. },
  670. handleFocus() {
  671. const type = this.type;
  672. if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
  673. this.pickerVisible = true;
  674. }
  675. this.$emit('focus', this);
  676. },
  677. handleKeydown(event) {
  678. const keyCode = event.keyCode;
  679. // ESC
  680. if (keyCode === 27) {
  681. this.pickerVisible = false;
  682. event.stopPropagation();
  683. return;
  684. }
  685. // Tab
  686. if (keyCode === 9) {
  687. if (!this.ranged) {
  688. this.handleChange();
  689. this.pickerVisible = this.picker.visible = false;
  690. this.blur();
  691. event.stopPropagation();
  692. } else {
  693. // user may change focus between two input
  694. setTimeout(() => {
  695. if (this.refInput.indexOf(document.activeElement) === -1) {
  696. this.pickerVisible = false;
  697. this.blur();
  698. event.stopPropagation();
  699. }
  700. }, 0);
  701. }
  702. return;
  703. }
  704. // Enter
  705. if (keyCode === 13) {
  706. if (this.userInput === '' || this.isValidValue(this.parseString(this.displayValue))) {
  707. this.handleChange();
  708. this.pickerVisible = this.picker.visible = false;
  709. this.blur();
  710. }
  711. event.stopPropagation();
  712. return;
  713. }
  714. // if user is typing, do not let picker handle key input
  715. if (this.userInput) {
  716. event.stopPropagation();
  717. return;
  718. }
  719. // delegate other keys to panel
  720. if (this.picker && this.picker.handleKeydown) {
  721. this.picker.handleKeydown(event);
  722. }
  723. },
  724. handleRangeClick() {
  725. const type = this.type;
  726. if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
  727. this.pickerVisible = true;
  728. }
  729. this.$emit('focus', this);
  730. },
  731. hidePicker() {
  732. if (this.picker) {
  733. this.picker.resetView && this.picker.resetView();
  734. this.pickerVisible = this.picker.visible = false;
  735. this.destroyPopper();
  736. }
  737. },
  738. showPicker() {
  739. if (this.$isServer) return;
  740. if (!this.picker) {
  741. this.mountPicker();
  742. }
  743. this.pickerVisible = this.picker.visible = true;
  744. this.updatePopper();
  745. this.picker.value = this.parsedValue;
  746. this.picker.resetView && this.picker.resetView();
  747. this.$nextTick(() => {
  748. this.picker.adjustSpinners && this.picker.adjustSpinners();
  749. });
  750. },
  751. mountPicker() {
  752. this.picker = new Vue(this.panel).$mount();
  753. this.picker.defaultValue = this.defaultValue;
  754. this.picker.defaultTime = this.defaultTime;
  755. this.picker.popperClass = this.popperClass;
  756. this.popperElm = this.picker.$el;
  757. this.picker.width = this.reference.getBoundingClientRect().width;
  758. this.picker.showTime = this.type === 'datetime' || this.type === 'datetimerange';
  759. this.picker.selectionMode = this.selectionMode;
  760. this.picker.unlinkPanels = this.unlinkPanels;
  761. this.picker.arrowControl = this.arrowControl || this.timeArrowControl || false;
  762. this.$watch('format', (format) => {
  763. this.picker.format = format;
  764. });
  765. const updateOptions = () => {
  766. const options = this.pickerOptions;
  767. if (options && options.selectableRange) {
  768. let ranges = options.selectableRange;
  769. const parser = TYPE_VALUE_RESOLVER_MAP.datetimerange.parser;
  770. const format = DEFAULT_FORMATS.timerange;
  771. ranges = Array.isArray(ranges) ? ranges : [ranges];
  772. this.picker.selectableRange = ranges.map(range => parser(range, format, this.rangeSeparator));
  773. }
  774. for (const option in options) {
  775. if (options.hasOwnProperty(option) &&
  776. // 忽略 time-picker 的该配置项
  777. option !== 'selectableRange') {
  778. this.picker[option] = options[option];
  779. }
  780. }
  781. // main format must prevail over undocumented pickerOptions.format
  782. if (this.format) {
  783. this.picker.format = this.format;
  784. }
  785. };
  786. updateOptions();
  787. this.unwatchPickerOptions = this.$watch('pickerOptions', () => updateOptions(), { deep: true });
  788. this.$el.appendChild(this.picker.$el);
  789. this.picker.resetView && this.picker.resetView();
  790. this.picker.$on('dodestroy', this.doDestroy);
  791. this.picker.$on('pick', (date = '', visible = false) => {
  792. this.userInput = null;
  793. this.pickerVisible = this.picker.visible = visible;
  794. this.emitInput(date);
  795. this.picker.resetView && this.picker.resetView();
  796. });
  797. this.picker.$on('select-range', (start, end, pos) => {
  798. if (this.refInput.length === 0) return;
  799. if (!pos || pos === 'min') {
  800. this.refInput[0].setSelectionRange(start, end);
  801. this.refInput[0].focus();
  802. } else if (pos === 'max') {
  803. this.refInput[1].setSelectionRange(start, end);
  804. this.refInput[1].focus();
  805. }
  806. });
  807. },
  808. unmountPicker() {
  809. if (this.picker) {
  810. this.picker.$destroy();
  811. this.picker.$off();
  812. if (typeof this.unwatchPickerOptions === 'function') {
  813. this.unwatchPickerOptions();
  814. }
  815. this.picker.$el.parentNode.removeChild(this.picker.$el);
  816. }
  817. },
  818. emitChange(val) {
  819. // determine user real change only
  820. if (!valueEquals(val, this.valueOnOpen)) {
  821. this.$emit('change', val);
  822. this.valueOnOpen = val;
  823. if (this.validateEvent) {
  824. this.dispatch('ElFormItem', 'el.form.change', val);
  825. }
  826. }
  827. },
  828. emitInput(val) {
  829. const formatted = this.formatToValue(val);
  830. if (!valueEquals(this.value, formatted)) {
  831. this.$emit('input', formatted);
  832. }
  833. },
  834. isValidValue(value) {
  835. if (!this.picker) {
  836. this.mountPicker();
  837. }
  838. if (this.picker.isValidValue) {
  839. return value && this.picker.isValidValue(value);
  840. } else {
  841. return true;
  842. }
  843. }
  844. }
  845. };
  846. </script>