select.vue 27 KB

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