7b1de6298fd061c13bf2efa8d63763c679de6765280b36916749680d7cfea03f8a17dec57ed61815d6c748fc885af6e1fd8ce1e66392d87ef2150f7caa1015 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. <template>
  2. <div
  3. class="el-select"
  4. :class="[selectSize ? 'el-select--' + selectSize : '']"
  5. @click.stop="toggleMenu"
  6. v-clickoutside="handleClose">
  7. <div
  8. class="el-select__tags"
  9. v-if="multiple"
  10. ref="tags"
  11. :style="{ 'max-width': inputWidth - 32 + 'px', width: '100%' }">
  12. <span v-if="collapseTags && selected.length">
  13. <el-tag
  14. :closable="!selectDisabled"
  15. :size="collapseTagSize"
  16. :hit="selected[0].hitState"
  17. type="info"
  18. @close="deleteTag($event, selected[0])"
  19. disable-transitions>
  20. <span class="el-select__tags-text">{{ selected[0].currentLabel }}</span>
  21. </el-tag>
  22. <el-tag
  23. v-if="selected.length > 1"
  24. :closable="false"
  25. :size="collapseTagSize"
  26. type="info"
  27. disable-transitions>
  28. <span class="el-select__tags-text">+ {{ selected.length - 1 }}</span>
  29. </el-tag>
  30. </span>
  31. <transition-group @after-leave="resetInputHeight" v-if="!collapseTags">
  32. <el-tag
  33. v-for="item in selected"
  34. :key="getValueKey(item)"
  35. :closable="!selectDisabled"
  36. :size="collapseTagSize"
  37. :hit="item.hitState"
  38. type="info"
  39. @close="deleteTag($event, item)"
  40. disable-transitions>
  41. <span class="el-select__tags-text">{{ item.currentLabel }}</span>
  42. </el-tag>
  43. </transition-group>
  44. <input
  45. type="text"
  46. class="el-select__input"
  47. :class="[selectSize ? `is-${ selectSize }` : '']"
  48. :disabled="selectDisabled"
  49. :autocomplete="autoComplete || autocomplete"
  50. @focus="handleFocus"
  51. @blur="softFocus = false"
  52. @keyup="managePlaceholder"
  53. @keydown="resetInputState"
  54. @keydown.down.prevent="handleNavigate('next')"
  55. @keydown.up.prevent="handleNavigate('prev')"
  56. @keydown.enter.prevent="selectOption"
  57. @keydown.esc.stop.prevent="visible = false"
  58. @keydown.delete="deletePrevTag"
  59. @keydown.tab="visible = false"
  60. @compositionstart="handleComposition"
  61. @compositionupdate="handleComposition"
  62. @compositionend="handleComposition"
  63. v-model="query"
  64. @input="debouncedQueryChange"
  65. v-if="filterable"
  66. :style="{ 'flex-grow': '1', width: inputLength / (inputWidth - 32) + '%', 'max-width': inputWidth - 42 + 'px' }"
  67. ref="input">
  68. </div>
  69. <el-input
  70. ref="reference"
  71. v-model="selectedLabel"
  72. type="text"
  73. :placeholder="currentPlaceholder"
  74. :name="name"
  75. :id="id"
  76. :autocomplete="autoComplete || autocomplete"
  77. :size="selectSize"
  78. :disabled="selectDisabled"
  79. :readonly="readonly"
  80. :validate-event="false"
  81. :class="{ 'is-focus': visible }"
  82. :tabindex="(multiple && filterable) ? '-1' : null"
  83. @focus="handleFocus"
  84. @blur="handleBlur"
  85. @input="debouncedOnInputChange"
  86. @keydown.native.down.stop.prevent="handleNavigate('next')"
  87. @keydown.native.up.stop.prevent="handleNavigate('prev')"
  88. @keydown.native.enter.prevent="selectOption"
  89. @keydown.native.esc.stop.prevent="visible = false"
  90. @keydown.native.tab="visible = false"
  91. @compositionstart="handleComposition"
  92. @compositionupdate="handleComposition"
  93. @compositionend="handleComposition"
  94. @mouseenter.native="inputHovering = true"
  95. @mouseleave.native="inputHovering = false">
  96. <template slot="prefix" v-if="$slots.prefix">
  97. <slot name="prefix"></slot>
  98. </template>
  99. <template slot="suffix">
  100. <i v-show="!showClose" :class="['el-select__caret', 'el-input__icon', 'el-icon-' + iconClass]"></i>
  101. <i v-if="showClose" class="el-select__caret el-input__icon el-icon-circle-close" @click="handleClearClick"></i>
  102. </template>
  103. </el-input>
  104. <transition
  105. name="el-zoom-in-top"
  106. @before-enter="handleMenuEnter"
  107. @after-leave="doDestroy">
  108. <el-select-menu
  109. ref="popper"
  110. :append-to-body="popperAppendToBody"
  111. v-show="visible && emptyText !== false">
  112. <el-scrollbar
  113. tag="ul"
  114. wrap-class="el-select-dropdown__wrap"
  115. view-class="el-select-dropdown__list"
  116. ref="scrollbar"
  117. :class="{ 'is-empty': !allowCreate && query && filteredOptionsCount === 0 }"
  118. v-show="options.length > 0 && !loading">
  119. <el-option
  120. :value="query"
  121. created
  122. v-if="showNewOption">
  123. </el-option>
  124. <slot></slot>
  125. </el-scrollbar>
  126. <template v-if="emptyText && (!allowCreate || loading || (allowCreate && options.length === 0 ))">
  127. <slot name="empty" v-if="$slots.empty"></slot>
  128. <p class="el-select-dropdown__empty" v-else>
  129. {{ emptyText }}
  130. </p>
  131. </template>
  132. </el-select-menu>
  133. </transition>
  134. </div>
  135. </template>
  136. <script type="text/babel">
  137. import Emitter from 'element-ui/src/mixins/emitter';
  138. import Focus from 'element-ui/src/mixins/focus';
  139. import Locale from 'element-ui/src/mixins/locale';
  140. import ElInput from 'element-ui/packages/input';
  141. import ElSelectMenu from './select-dropdown.vue';
  142. import ElOption from './option.vue';
  143. import ElTag from 'element-ui/packages/tag';
  144. import ElScrollbar from 'element-ui/packages/scrollbar';
  145. import debounce from 'throttle-debounce/debounce';
  146. import Clickoutside from 'element-ui/src/utils/clickoutside';
  147. import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event';
  148. import scrollIntoView from 'element-ui/src/utils/scroll-into-view';
  149. import { getValueByPath, valueEquals, isIE, isEdge } from 'element-ui/src/utils/util';
  150. import NavigationMixin from './navigation-mixin';
  151. import { isKorean } from 'element-ui/src/utils/shared';
  152. export default {
  153. mixins: [Emitter, Locale, Focus('reference'), NavigationMixin],
  154. name: 'ElSelect',
  155. componentName: 'ElSelect',
  156. inject: {
  157. elForm: {
  158. default: ''
  159. },
  160. elFormItem: {
  161. default: ''
  162. }
  163. },
  164. provide() {
  165. return {
  166. 'select': this
  167. };
  168. },
  169. computed: {
  170. _elFormItemSize() {
  171. return (this.elFormItem || {}).elFormItemSize;
  172. },
  173. readonly() {
  174. return !this.filterable || this.multiple || (!isIE() && !isEdge() && !this.visible);
  175. },
  176. showClose() {
  177. let hasValue = this.multiple
  178. ? Array.isArray(this.value) && this.value.length > 0
  179. : this.value !== undefined && this.value !== null && this.value !== '';
  180. let criteria = this.clearable &&
  181. !this.selectDisabled &&
  182. this.inputHovering &&
  183. hasValue;
  184. return criteria;
  185. },
  186. iconClass() {
  187. return this.remote && this.filterable ? '' : (this.visible ? 'arrow-up is-reverse' : 'arrow-up');
  188. },
  189. debounce() {
  190. return this.remote ? 300 : 0;
  191. },
  192. emptyText() {
  193. if (this.loading) {
  194. return this.loadingText || this.t('el.select.loading');
  195. } else {
  196. if (this.remote && this.query === '' && this.options.length === 0) return false;
  197. if (this.filterable && this.query && this.options.length > 0 && this.filteredOptionsCount === 0) {
  198. return this.noMatchText || this.t('el.select.noMatch');
  199. }
  200. if (this.options.length === 0) {
  201. return this.noDataText || this.t('el.select.noData');
  202. }
  203. }
  204. return null;
  205. },
  206. showNewOption() {
  207. let hasExistingOption = this.options.filter(option => !option.created)
  208. .some(option => option.currentLabel === this.query);
  209. return this.filterable && this.allowCreate && this.query !== '' && !hasExistingOption;
  210. },
  211. selectSize() {
  212. return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
  213. },
  214. selectDisabled() {
  215. return this.disabled || (this.elForm || {}).disabled;
  216. },
  217. collapseTagSize() {
  218. return ['small', 'mini'].indexOf(this.selectSize) > -1
  219. ? 'mini'
  220. : 'small';
  221. },
  222. propPlaceholder() {
  223. return typeof this.placeholder !== 'undefined' ? this.placeholder : this.t('el.select.placeholder');
  224. }
  225. },
  226. components: {
  227. ElInput,
  228. ElSelectMenu,
  229. ElOption,
  230. ElTag,
  231. ElScrollbar
  232. },
  233. directives: { Clickoutside },
  234. props: {
  235. name: String,
  236. id: String,
  237. value: {
  238. required: true
  239. },
  240. autocomplete: {
  241. type: String,
  242. default: 'off'
  243. },
  244. /** @Deprecated in next major version */
  245. autoComplete: {
  246. type: String,
  247. validator(val) {
  248. process.env.NODE_ENV !== 'production' &&
  249. console.warn('[Element Warn][Select]\'auto-complete\' property will be deprecated in next major version. please use \'autocomplete\' instead.');
  250. return true;
  251. }
  252. },
  253. automaticDropdown: Boolean,
  254. size: String,
  255. disabled: Boolean,
  256. clearable: Boolean,
  257. filterable: Boolean,
  258. allowCreate: Boolean,
  259. loading: Boolean,
  260. popperClass: String,
  261. remote: Boolean,
  262. loadingText: String,
  263. noMatchText: String,
  264. noDataText: String,
  265. remoteMethod: Function,
  266. filterMethod: Function,
  267. multiple: Boolean,
  268. multipleLimit: {
  269. type: Number,
  270. default: 0
  271. },
  272. placeholder: {
  273. type: String,
  274. required: false
  275. },
  276. defaultFirstOption: Boolean,
  277. reserveKeyword: Boolean,
  278. valueKey: {
  279. type: String,
  280. default: 'value'
  281. },
  282. collapseTags: Boolean,
  283. popperAppendToBody: {
  284. type: Boolean,
  285. default: true
  286. }
  287. },
  288. data() {
  289. return {
  290. options: [],
  291. cachedOptions: [],
  292. createdLabel: null,
  293. createdSelected: false,
  294. selected: this.multiple ? [] : {},
  295. inputLength: 20,
  296. inputWidth: 0,
  297. initialInputHeight: 0,
  298. cachedPlaceHolder: '',
  299. optionsCount: 0,
  300. filteredOptionsCount: 0,
  301. visible: false,
  302. softFocus: false,
  303. selectedLabel: '',
  304. hoverIndex: -1,
  305. query: '',
  306. previousQuery: null,
  307. inputHovering: false,
  308. currentPlaceholder: '',
  309. menuVisibleOnFocus: false,
  310. isOnComposition: false,
  311. isSilentBlur: false
  312. };
  313. },
  314. watch: {
  315. selectDisabled() {
  316. this.$nextTick(() => {
  317. this.resetInputHeight();
  318. });
  319. },
  320. propPlaceholder(val) {
  321. this.cachedPlaceHolder = this.currentPlaceholder = val;
  322. },
  323. value(val, oldVal) {
  324. if (this.multiple) {
  325. this.resetInputHeight();
  326. if ((val && val.length > 0) || (this.$refs.input && this.query !== '')) {
  327. this.currentPlaceholder = '';
  328. } else {
  329. this.currentPlaceholder = this.cachedPlaceHolder;
  330. }
  331. if (this.filterable && !this.reserveKeyword) {
  332. this.query = '';
  333. this.handleQueryChange(this.query);
  334. }
  335. }
  336. this.setSelected();
  337. if (this.filterable && !this.multiple) {
  338. this.inputLength = 20;
  339. }
  340. if (!valueEquals(val, oldVal)) {
  341. this.dispatch('ElFormItem', 'el.form.change', val);
  342. }
  343. },
  344. visible(val) {
  345. if (!val) {
  346. this.broadcast('ElSelectDropdown', 'destroyPopper');
  347. if (this.$refs.input) {
  348. this.$refs.input.blur();
  349. }
  350. this.query = '';
  351. this.previousQuery = null;
  352. this.selectedLabel = '';
  353. this.inputLength = 20;
  354. this.menuVisibleOnFocus = false;
  355. this.resetHoverIndex();
  356. this.$nextTick(() => {
  357. if (this.$refs.input &&
  358. this.$refs.input.value === '' &&
  359. this.selected.length === 0) {
  360. this.currentPlaceholder = this.cachedPlaceHolder;
  361. }
  362. });
  363. if (!this.multiple) {
  364. if (this.selected) {
  365. if (this.filterable && this.allowCreate &&
  366. this.createdSelected && this.createdLabel) {
  367. this.selectedLabel = this.createdLabel;
  368. } else {
  369. this.selectedLabel = this.selected.currentLabel;
  370. }
  371. if (this.filterable) this.query = this.selectedLabel;
  372. }
  373. if (this.filterable) {
  374. this.currentPlaceholder = this.cachedPlaceHolder;
  375. }
  376. }
  377. } else {
  378. this.broadcast('ElSelectDropdown', 'updatePopper');
  379. if (this.filterable) {
  380. this.query = this.remote ? '' : this.selectedLabel;
  381. this.handleQueryChange(this.query);
  382. if (this.multiple) {
  383. this.$refs.input.focus();
  384. } else {
  385. if (!this.remote) {
  386. this.broadcast('ElOption', 'queryChange', '');
  387. this.broadcast('ElOptionGroup', 'queryChange');
  388. }
  389. if (this.selectedLabel) {
  390. this.currentPlaceholder = this.selectedLabel;
  391. this.selectedLabel = '';
  392. }
  393. }
  394. }
  395. }
  396. this.$emit('visible-change', val);
  397. },
  398. options() {
  399. if (this.$isServer) return;
  400. this.$nextTick(() => {
  401. this.broadcast('ElSelectDropdown', 'updatePopper');
  402. });
  403. if (this.multiple) {
  404. this.resetInputHeight();
  405. }
  406. let inputs = this.$el.querySelectorAll('input');
  407. if ([].indexOf.call(inputs, document.activeElement) === -1) {
  408. this.setSelected();
  409. }
  410. if (this.defaultFirstOption && (this.filterable || this.remote) && this.filteredOptionsCount) {
  411. this.checkDefaultFirstOption();
  412. }
  413. }
  414. },
  415. methods: {
  416. handleNavigate(direction) {
  417. if (this.isOnComposition) return;
  418. this.navigateOptions(direction);
  419. },
  420. handleComposition(event) {
  421. const text = event.target.value;
  422. if (event.type === 'compositionend') {
  423. this.isOnComposition = false;
  424. this.$nextTick(_ => this.handleQueryChange(text));
  425. } else {
  426. const lastCharacter = text[text.length - 1] || '';
  427. this.isOnComposition = !isKorean(lastCharacter);
  428. }
  429. },
  430. handleQueryChange(val) {
  431. if (this.previousQuery === val || this.isOnComposition) return;
  432. if (
  433. this.previousQuery === null &&
  434. (typeof this.filterMethod === 'function' || typeof this.remoteMethod === 'function')
  435. ) {
  436. this.previousQuery = val;
  437. return;
  438. }
  439. this.previousQuery = val;
  440. this.$nextTick(() => {
  441. if (this.visible) this.broadcast('ElSelectDropdown', 'updatePopper');
  442. });
  443. this.hoverIndex = -1;
  444. if (this.multiple && this.filterable) {
  445. this.$nextTick(() => {
  446. const length = this.$refs.input.value.length * 15 + 20;
  447. this.inputLength = this.collapseTags ? Math.min(50, length) : length;
  448. this.managePlaceholder();
  449. this.resetInputHeight();
  450. });
  451. }
  452. if (this.remote && typeof this.remoteMethod === 'function') {
  453. this.hoverIndex = -1;
  454. this.remoteMethod(val);
  455. } else if (typeof this.filterMethod === 'function') {
  456. this.filterMethod(val);
  457. this.broadcast('ElOptionGroup', 'queryChange');
  458. } else {
  459. this.filteredOptionsCount = this.optionsCount;
  460. this.broadcast('ElOption', 'queryChange', val);
  461. this.broadcast('ElOptionGroup', 'queryChange');
  462. }
  463. if (this.defaultFirstOption && (this.filterable || this.remote) && this.filteredOptionsCount) {
  464. this.checkDefaultFirstOption();
  465. }
  466. },
  467. scrollToOption(option) {
  468. const target = Array.isArray(option) && option[0] ? option[0].$el : option.$el;
  469. if (this.$refs.popper && target) {
  470. const menu = this.$refs.popper.$el.querySelector('.el-select-dropdown__wrap');
  471. scrollIntoView(menu, target);
  472. }
  473. this.$refs.scrollbar && this.$refs.scrollbar.handleScroll();
  474. },
  475. handleMenuEnter() {
  476. this.$nextTick(() => this.scrollToOption(this.selected));
  477. },
  478. emitChange(val) {
  479. if (!valueEquals(this.value, val)) {
  480. this.$emit('change', val);
  481. }
  482. },
  483. getOption(value) {
  484. let option;
  485. const isObject = Object.prototype.toString.call(value).toLowerCase() === '[object object]';
  486. const isNull = Object.prototype.toString.call(value).toLowerCase() === '[object null]';
  487. const isUndefined = Object.prototype.toString.call(value).toLowerCase() === '[object undefined]';
  488. for (let i = this.cachedOptions.length - 1; i >= 0; i--) {
  489. const cachedOption = this.cachedOptions[i];
  490. const isEqual = isObject
  491. ? getValueByPath(cachedOption.value, this.valueKey) === getValueByPath(value, this.valueKey)
  492. : cachedOption.value === value;
  493. if (isEqual) {
  494. option = cachedOption;
  495. break;
  496. }
  497. }
  498. if (option) return option;
  499. const label = (!isObject && !isNull && !isUndefined)
  500. ? String(value) : '';
  501. let newOption = {
  502. value: value,
  503. currentLabel: label
  504. };
  505. if (this.multiple) {
  506. newOption.hitState = false;
  507. }
  508. return newOption;
  509. },
  510. setSelected() {
  511. if (!this.multiple) {
  512. let option = this.getOption(this.value);
  513. if (option.created) {
  514. this.createdLabel = option.currentLabel;
  515. this.createdSelected = true;
  516. } else {
  517. this.createdSelected = false;
  518. }
  519. this.selectedLabel = option.currentLabel;
  520. this.selected = option;
  521. if (this.filterable) this.query = this.selectedLabel;
  522. return;
  523. }
  524. let result = [];
  525. if (Array.isArray(this.value)) {
  526. this.value.forEach(value => {
  527. result.push(this.getOption(value));
  528. });
  529. }
  530. this.selected = result;
  531. this.$nextTick(() => {
  532. this.resetInputHeight();
  533. });
  534. },
  535. handleFocus(event) {
  536. if (!this.softFocus) {
  537. if (this.automaticDropdown || this.filterable) {
  538. if (this.filterable && !this.visible) {
  539. this.menuVisibleOnFocus = true;
  540. }
  541. this.visible = true;
  542. }
  543. this.$emit('focus', event);
  544. } else {
  545. this.softFocus = false;
  546. }
  547. },
  548. blur() {
  549. this.visible = false;
  550. this.$refs.reference.blur();
  551. },
  552. handleBlur(event) {
  553. setTimeout(() => {
  554. if (this.isSilentBlur) {
  555. this.isSilentBlur = false;
  556. } else {
  557. this.$emit('blur', event);
  558. }
  559. }, 50);
  560. this.softFocus = false;
  561. },
  562. handleClearClick(event) {
  563. this.deleteSelected(event);
  564. },
  565. doDestroy() {
  566. this.$refs.popper && this.$refs.popper.doDestroy();
  567. },
  568. handleClose() {
  569. this.visible = false;
  570. },
  571. toggleLastOptionHitState(hit) {
  572. if (!Array.isArray(this.selected)) return;
  573. const option = this.selected[this.selected.length - 1];
  574. if (!option) return;
  575. if (hit === true || hit === false) {
  576. option.hitState = hit;
  577. return hit;
  578. }
  579. option.hitState = !option.hitState;
  580. return option.hitState;
  581. },
  582. deletePrevTag(e) {
  583. if (e.target.value.length <= 0 && !this.toggleLastOptionHitState()) {
  584. const value = this.value.slice();
  585. value.pop();
  586. this.$emit('input', value);
  587. this.emitChange(value);
  588. }
  589. },
  590. managePlaceholder() {
  591. if (this.currentPlaceholder !== '') {
  592. this.currentPlaceholder = this.$refs.input.value ? '' : this.cachedPlaceHolder;
  593. }
  594. },
  595. resetInputState(e) {
  596. if (e.keyCode !== 8) this.toggleLastOptionHitState(false);
  597. this.inputLength = this.$refs.input.value.length * 15 + 20;
  598. this.resetInputHeight();
  599. },
  600. resetInputHeight() {
  601. if (this.collapseTags && !this.filterable) return;
  602. this.$nextTick(() => {
  603. if (!this.$refs.reference) return;
  604. let inputChildNodes = this.$refs.reference.$el.childNodes;
  605. let input = [].filter.call(inputChildNodes, item => item.tagName === 'INPUT')[0];
  606. const tags = this.$refs.tags;
  607. const tagsHeight = tags ? Math.round(tags.getBoundingClientRect().height) : 0;
  608. const sizeInMap = this.initialInputHeight || 40;
  609. input.style.height = this.selected.length === 0
  610. ? sizeInMap + 'px'
  611. : Math.max(
  612. tags ? (tagsHeight + (tagsHeight > sizeInMap ? 6 : 0)) : 0,
  613. sizeInMap
  614. ) + 'px';
  615. if (this.visible && this.emptyText !== false) {
  616. this.broadcast('ElSelectDropdown', 'updatePopper');
  617. }
  618. });
  619. },
  620. resetHoverIndex() {
  621. setTimeout(() => {
  622. if (!this.multiple) {
  623. this.hoverIndex = this.options.indexOf(this.selected);
  624. } else {
  625. if (this.selected.length > 0) {
  626. this.hoverIndex = Math.min.apply(null, this.selected.map(item => this.options.indexOf(item)));
  627. } else {
  628. this.hoverIndex = -1;
  629. }
  630. }
  631. }, 300);
  632. },
  633. handleOptionSelect(option, byClick) {
  634. if (this.multiple) {
  635. const value = (this.value || []).slice();
  636. const optionIndex = this.getValueIndex(value, option.value);
  637. if (optionIndex > -1) {
  638. value.splice(optionIndex, 1);
  639. } else if (this.multipleLimit <= 0 || value.length < this.multipleLimit) {
  640. value.push(option.value);
  641. }
  642. this.$emit('input', value);
  643. this.emitChange(value);
  644. if (option.created) {
  645. this.query = '';
  646. this.handleQueryChange('');
  647. this.inputLength = 20;
  648. }
  649. if (this.filterable) this.$refs.input.focus();
  650. } else {
  651. this.$emit('input', option.value);
  652. this.emitChange(option.value);
  653. this.visible = false;
  654. }
  655. this.isSilentBlur = byClick;
  656. this.setSoftFocus();
  657. if (this.visible) return;
  658. this.$nextTick(() => {
  659. this.scrollToOption(option);
  660. });
  661. },
  662. setSoftFocus() {
  663. this.softFocus = true;
  664. const input = this.$refs.input || this.$refs.reference;
  665. if (input) {
  666. input.focus();
  667. }
  668. },
  669. getValueIndex(arr = [], value) {
  670. const isObject = Object.prototype.toString.call(value).toLowerCase() === '[object object]';
  671. if (!isObject) {
  672. return arr.indexOf(value);
  673. } else {
  674. const valueKey = this.valueKey;
  675. let index = -1;
  676. arr.some((item, i) => {
  677. if (getValueByPath(item, valueKey) === getValueByPath(value, valueKey)) {
  678. index = i;
  679. return true;
  680. }
  681. return false;
  682. });
  683. return index;
  684. }
  685. },
  686. toggleMenu() {
  687. if (!this.selectDisabled) {
  688. if (this.menuVisibleOnFocus) {
  689. this.menuVisibleOnFocus = false;
  690. } else {
  691. this.visible = !this.visible;
  692. }
  693. if (this.visible) {
  694. (this.$refs.input || this.$refs.reference).focus();
  695. }
  696. }
  697. },
  698. selectOption() {
  699. if (!this.visible) {
  700. this.toggleMenu();
  701. } else {
  702. if (this.options[this.hoverIndex]) {
  703. this.handleOptionSelect(this.options[this.hoverIndex]);
  704. }
  705. }
  706. },
  707. deleteSelected(event) {
  708. event.stopPropagation();
  709. const value = this.multiple ? [] : '';
  710. this.$emit('input', value);
  711. this.emitChange(value);
  712. this.visible = false;
  713. this.$emit('clear');
  714. },
  715. deleteTag(event, tag) {
  716. let index = this.selected.indexOf(tag);
  717. if (index > -1 && !this.selectDisabled) {
  718. const value = this.value.slice();
  719. value.splice(index, 1);
  720. this.$emit('input', value);
  721. this.emitChange(value);
  722. this.$emit('remove-tag', tag.value);
  723. }
  724. event.stopPropagation();
  725. },
  726. onInputChange() {
  727. if (this.filterable && this.query !== this.selectedLabel) {
  728. this.query = this.selectedLabel;
  729. this.handleQueryChange(this.query);
  730. }
  731. },
  732. onOptionDestroy(index) {
  733. if (index > -1) {
  734. this.optionsCount--;
  735. this.filteredOptionsCount--;
  736. this.options.splice(index, 1);
  737. }
  738. },
  739. resetInputWidth() {
  740. this.inputWidth = this.$refs.reference.$el.getBoundingClientRect().width;
  741. },
  742. handleResize() {
  743. this.resetInputWidth();
  744. if (this.multiple) this.resetInputHeight();
  745. },
  746. checkDefaultFirstOption() {
  747. this.hoverIndex = -1;
  748. // highlight the created option
  749. let hasCreated = false;
  750. for (let i = this.options.length - 1; i >= 0; i--) {
  751. if (this.options[i].created) {
  752. hasCreated = true;
  753. this.hoverIndex = i;
  754. break;
  755. }
  756. }
  757. if (hasCreated) return;
  758. for (let i = 0; i !== this.options.length; ++i) {
  759. const option = this.options[i];
  760. if (this.query) {
  761. // highlight first options that passes the filter
  762. if (!option.disabled && !option.groupDisabled && option.visible) {
  763. this.hoverIndex = i;
  764. break;
  765. }
  766. } else {
  767. // highlight currently selected option
  768. if (option.itemSelected) {
  769. this.hoverIndex = i;
  770. break;
  771. }
  772. }
  773. }
  774. },
  775. getValueKey(item) {
  776. if (Object.prototype.toString.call(item.value).toLowerCase() !== '[object object]') {
  777. return item.value;
  778. } else {
  779. return getValueByPath(item.value, this.valueKey);
  780. }
  781. }
  782. },
  783. created() {
  784. this.cachedPlaceHolder = this.currentPlaceholder = this.propPlaceholder;
  785. if (this.multiple && !Array.isArray(this.value)) {
  786. this.$emit('input', []);
  787. }
  788. if (!this.multiple && Array.isArray(this.value)) {
  789. this.$emit('input', '');
  790. }
  791. this.debouncedOnInputChange = debounce(this.debounce, () => {
  792. this.onInputChange();
  793. });
  794. this.debouncedQueryChange = debounce(this.debounce, (e) => {
  795. this.handleQueryChange(e.target.value);
  796. });
  797. this.$on('handleOptionClick', this.handleOptionSelect);
  798. this.$on('setSelected', this.setSelected);
  799. },
  800. mounted() {
  801. if (this.multiple && Array.isArray(this.value) && this.value.length > 0) {
  802. this.currentPlaceholder = '';
  803. }
  804. addResizeListener(this.$el, this.handleResize);
  805. const reference = this.$refs.reference;
  806. if (reference && reference.$el) {
  807. const sizeMap = {
  808. medium: 36,
  809. small: 32,
  810. mini: 28
  811. };
  812. const input = reference.$el.querySelector('input');
  813. this.initialInputHeight = input.getBoundingClientRect().height || sizeMap[this.selectSize];
  814. }
  815. if (this.remote && this.multiple) {
  816. this.resetInputHeight();
  817. }
  818. this.$nextTick(() => {
  819. if (reference && reference.$el) {
  820. this.inputWidth = reference.$el.getBoundingClientRect().width;
  821. }
  822. });
  823. this.setSelected();
  824. },
  825. beforeDestroy() {
  826. if (this.$el && this.handleResize) removeResizeListener(this.$el, this.handleResize);
  827. }
  828. };
  829. </script>