123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- <template>
- <div
- class="el-autocomplete"
- v-clickoutside="close"
- aria-haspopup="listbox"
- role="combobox"
- :aria-expanded="suggestionVisible"
- :aria-owns="id"
- >
- <el-input
- ref="input"
- v-bind="[$props, $attrs]"
- @input="handleInput"
- @change="handleChange"
- @focus="handleFocus"
- @blur="handleBlur"
- @clear="handleClear"
- @keydown.up.native.prevent="highlight(highlightedIndex - 1)"
- @keydown.down.native.prevent="highlight(highlightedIndex + 1)"
- @keydown.enter.native="handleKeyEnter"
- @keydown.native.tab="close"
- >
- <template slot="prepend" v-if="$slots.prepend">
- <slot name="prepend"></slot>
- </template>
- <template slot="append" v-if="$slots.append">
- <slot name="append"></slot>
- </template>
- <template slot="prefix" v-if="$slots.prefix">
- <slot name="prefix"></slot>
- </template>
- <template slot="suffix" v-if="$slots.suffix">
- <slot name="suffix"></slot>
- </template>
- </el-input>
- <el-autocomplete-suggestions
- visible-arrow
- :class="[popperClass ? popperClass : '']"
- :popper-options="popperOptions"
- :append-to-body="popperAppendToBody"
- ref="suggestions"
- :placement="placement"
- :id="id"
- >
- <li
- v-for="(item, index) in suggestions"
- :key="index"
- :class="{ highlighted: highlightedIndex === index }"
- @click="select(item)"
- :id="`${id}-item-${index}`"
- role="option"
- :aria-selected="highlightedIndex === index"
- >
- <!-- {{ item[valueKey] }} -->
- <slot :item="item">
- {{ item[valueKey] }}
- </slot>
- </li>
- </el-autocomplete-suggestions>
- </div>
- </template>
- <script>
- import debounce from "throttle-debounce/debounce";
- import ElInput from "element-ui/packages/input";
- import Clickoutside from "element-ui/src/utils/clickoutside";
- import ElAutocompleteSuggestions from "./autocomplete-suggestions.vue";
- import Emitter from "element-ui/src/mixins/emitter";
- import Migrating from "element-ui/src/mixins/migrating";
- import { generateId } from "element-ui/src/utils/util";
- import Focus from "element-ui/src/mixins/focus";
- export default {
- name: "ElAutocomplete",
- mixins: [Emitter, Focus("input"), Migrating],
- inheritAttrs: false,
- componentName: "ElAutocomplete",
- components: {
- ElInput,
- ElAutocompleteSuggestions,
- },
- directives: { Clickoutside },
- props: {
- valueKey: {
- type: String,
- default: "value",
- },
- popperClass: String,
- popperOptions: Object,
- placeholder: String,
- clearable: {
- type: Boolean,
- default: false,
- },
- disabled: Boolean,
- name: String,
- size: String,
- value: String,
- maxlength: Number,
- minlength: Number,
- autofocus: Boolean,
- fetchSuggestions: Function,
- triggerOnFocus: {
- type: Boolean,
- default: true,
- },
- customItem: String,
- selectWhenUnmatched: {
- type: Boolean,
- default: false,
- },
- prefixIcon: String,
- suffixIcon: String,
- label: String,
- debounce: {
- type: Number,
- default: 300,
- },
- placement: {
- type: String,
- default: "bottom-start",
- },
- hideLoading: Boolean,
- popperAppendToBody: {
- type: Boolean,
- default: true,
- },
- highlightFirstItem: {
- type: Boolean,
- default: false,
- },
- },
- data() {
- return {
- activated: false,
- suggestions: [],
- loading: false,
- highlightedIndex: -1,
- suggestionDisabled: false,
- };
- },
- computed: {
- suggestionVisible() {
- const suggestions = this.suggestions;
- let isValidData = Array.isArray(suggestions) && suggestions.length > 0;
- return (isValidData || this.loading) && this.activated;
- },
- id() {
- return `el-autocomplete-${generateId()}`;
- },
- },
- watch: {
- suggestionVisible(val) {
- let $input = this.getInput();
- if ($input) {
- this.broadcast("ElAutocompleteSuggestions", "visible", [
- val,
- $input.offsetWidth,
- ]);
- }
- },
- },
- methods: {
- getMigratingConfig() {
- return {
- props: {
- "custom-item": "custom-item is removed, use scoped slot instead.",
- props: "props is removed, use value-key instead.",
- },
- };
- },
- getData(queryString) {
- if (this.suggestionDisabled) {
- return;
- }
- this.loading = true;
- this.fetchSuggestions(queryString, (suggestions) => {
- this.loading = false;
- if (this.suggestionDisabled) {
- return;
- }
- if (Array.isArray(suggestions)) {
- this.suggestions = suggestions;
- this.highlightedIndex = this.highlightFirstItem ? 0 : -1;
- } else {
- console.error(
- "[Element Error][Autocomplete]autocomplete suggestions must be an array"
- );
- }
- });
- },
- handleInput(value) {
- this.$emit("input", value);
-
- // this.suggestionDisabled = false;
- // if (!this.triggerOnFocus && !value) {
- // this.suggestionDisabled = true;
- // this.suggestions = [];
- // return;
- // }
- // this.debouncedGetData(value);
- },
- search(value) {
- this.activated = true
- this.suggestionDisabled = false;
- if (!this.triggerOnFocus && !value) {
- this.suggestionDisabled = true;
- this.suggestions = [];
- return;
- }
- this.debouncedGetData(value);
- },
- handleChange(value) {
- this.$emit("change", value);
- },
- handleFocus(event) {
- // this.activated = true;
- this.$emit("focus", event);
- // if (this.triggerOnFocus) {
- // this.debouncedGetData(this.value);
- // }
- },
- handleBlur(event) {
- this.$emit("blur", event);
- },
- handleClear() {
- this.activated = false;
- this.$emit("clear");
- },
- close(e) {
- this.activated = false;
- },
- handleKeyEnter(e) {
- // if (
- // this.suggestionVisible &&
- // this.highlightedIndex >= 0 &&
- // this.highlightedIndex < this.suggestions.length
- // ) {
- // e.preventDefault();
- // this.select(this.suggestions[this.highlightedIndex]);
- // } else if (this.selectWhenUnmatched) {
- // this.$emit("select", { value: this.value });
- // this.$nextTick((_) => {
- // this.suggestions = [];
- // this.highlightedIndex = -1;
- // });
- // }
- },
- select(item) {
- this.$emit("input", item[this.valueKey]);
- this.$emit("select", item);
- this.$nextTick((_) => {
- this.suggestions = [];
- this.highlightedIndex = -1;
- });
- },
- highlight(index) {
- if (!this.suggestionVisible || this.loading) {
- return;
- }
- if (index < 0) {
- this.highlightedIndex = -1;
- return;
- }
- if (index >= this.suggestions.length) {
- index = this.suggestions.length - 1;
- }
- const suggestion = this.$refs.suggestions.$el.querySelector(
- ".el-autocomplete-suggestion__wrap"
- );
- const suggestionList = suggestion.querySelectorAll(
- ".el-autocomplete-suggestion__list li"
- );
- let highlightItem = suggestionList[index];
- let scrollTop = suggestion.scrollTop;
- let offsetTop = highlightItem.offsetTop;
- if (
- offsetTop + highlightItem.scrollHeight >
- scrollTop + suggestion.clientHeight
- ) {
- suggestion.scrollTop += highlightItem.scrollHeight;
- }
- if (offsetTop < scrollTop) {
- suggestion.scrollTop -= highlightItem.scrollHeight;
- }
- this.highlightedIndex = index;
- let $input = this.getInput();
- $input.setAttribute(
- "aria-activedescendant",
- `${this.id}-item-${this.highlightedIndex}`
- );
- },
- getInput() {
- return this.$refs.input.getInput();
- },
- },
- mounted() {
- this.debouncedGetData = debounce(this.debounce, this.getData);
- this.$on("item-click", (item) => {
- this.select(item);
- });
- let $input = this.getInput();
- $input.setAttribute("role", "textbox");
- $input.setAttribute("aria-autocomplete", "list");
- $input.setAttribute("aria-controls", "id");
- $input.setAttribute(
- "aria-activedescendant",
- `${this.id}-item-${this.highlightedIndex}`
- );
- },
- beforeDestroy() {
- this.$refs.suggestions.$destroy();
- },
- };
- </script>
|