picker.vue 25 KB

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