UNPKG

kui-vue

Version:

A high quality UI Toolkit built on Vue.js 2.0

401 lines (379 loc) 11 kB
import Option from "./option"; import Icon from "../icon"; import Empty from "../empty"; import { hasProp, getChild, isNotEmpty } from "../_tool/utils"; import Drop from "../base/drop"; import { t } from "../locale"; import { Sync, Close, CloseCircle, ChevronDown } from "kui-icons"; export default { name: "Select", props: { placeholder: String, size: { default: "default", validator(value) { return ["small", "large", "default"].indexOf(value) >= 0; }, }, transfer: { type: Boolean, default: true }, width: Number, value: [String, Number, Array], clearable: Boolean, filterable: Boolean, disabled: Boolean, multiple: Boolean, loading: Boolean, bordered: { type: Boolean, default: true }, showArrow: { type: Boolean, default: true }, options: Array, theme: String, emptyText: String, icon: [String, Array], shape: String, arrowIcon: [String, Array], }, provide() { return { Select: this, }; }, data() { return { label: "", opened: false, currentValue: this.value || "", showSearch: false, queryKey: "", selectWidth: this.width, isFocus: false, }; }, watch: { value(value) { if (isNotEmpty(value)) { this.currentValue = value; } else { this.currentValue = this.multiple ? [] : ""; } }, }, methods: { hideDrop() { if (this.showSearch) { this.queryKey = ""; this.$refs.search.value = ""; this.$refs.search.style.width = ""; } this.showSearch = false; }, getLabel(childs, labelValue) { let Label = null; childs.forEach((c) => { let { value, label } = c.componentOptions.propsData; if (labelValue === value) { Label = label || (c.componentOptions.children[0].text || "").trim(); return; } }); return Label; }, clear(e) { let label = this.multiple ? [] : ""; let value = this.multiple ? [] : ""; this.label = label; this.currentValue = value; this.$emit("input", value); this.$emit("change", value); e.stopPropagation(); }, showDrops() { let isSearch = "search" in this.$listeners; this.opened = !this.opened; if (this.filterable || isSearch) { this.showSearch = this.opened; if (this.opened) { this.$nextTick((e) => { this.isFocus = true; this.$refs.search.focus(); }); } else { this.$refs.search.blur(); this.isFocus = false; setTimeout(() => { this.queryKey = ""; this.$refs.search.value = ""; }, 200); } } // this.$nextTick(e => this.setPosition()) }, toggleDrop() { if (this.disabled) { return false; } let isSearch = "search" in this.$listeners; if (isSearch) { this.showSearch = true; this.$nextTick((e) => { this.$refs.search.focus(); this.isFocus = true; }); return; } this.showDrops(); }, setPosition() { if (this.opened) { this.$refs.overlay.setPosition(); } }, change(item) { let { multiple, value, currentValue } = this; // console.log(value, currentValue) if (this.showSearch) { this.queryKey = ""; this.$refs.search.value = ""; this.$refs.search.style.width = ""; } if (!multiple) { this.opened = false; this.showSearch = false; } else if ("search" in this.$listeners || this.filterable) { this.$nextTick((e) => { this.$refs.search.focus(); this.isFocus = true; }); } let hasValue = hasProp(this, "value"); //set value if (multiple) { if (!hasValue) { value = currentValue || []; } let index = value.indexOf(item.value); if (index === -1) { value.push(item.value); } else { value.splice(index, 1); } } else { value = item.value; } this.currentValue = value; //set label this.$nextTick((e) => this.setPosition()); this.$emit("input", value); this.$emit("change", item); }, removeTag(e, c) { if (this.disabled) return; this.change({ value: c.value, label: c.label }); e.stopPropagation(); }, searchInput(e) { this.queryKey = e.target.value; //todo: this.$nextTick((k) => { // let max = this.selectWidth - 15 - (this.showArrow ? 25 : 0) e.target.style.width = this.$refs.mirror.offsetWidth + "px"; this.setPosition(); }); if ("search" in this.$listeners) { clearTimeout(this.timer); this.timer = setTimeout(() => { this.opened = true; this.$emit("search", e); }, 500); } }, emptyClick(e) { if (this.showSearch) { this.$nextTick((e) => { this.$refs.search.focus(); this.isFocus = true; }); } }, getOptions(filterable) { let { queryKey, options, $slots } = this; let childs = null; if (Array.isArray(options)) { childs = options.map((k, i) => { let prop = { props: { ...k }, key: k.key || k.label + k.value, }; return <Option {...prop} />; }); } else { childs = getChild($slots.default); } if (!filterable) return childs; if (this.filterable && queryKey && !this.$listeners.search) { let parsedQuery = String(queryKey).replace(/(\^|\(|\)|\[|\]|\$|\*|\+|\.|\?|\\|\{|\}|\|)/g, "\\$1"); let Reg = new RegExp(parsedQuery, "i"); childs = childs.filter((c) => { let label = c.componentOptions.propsData.label || c.componentOptions.children[0].text; return Reg.test(label); }); } return childs; }, }, render() { let { disabled, size, multiple, opened, placeholder, currentValue, showArrow, bordered, clear, removeTag, queryKey, theme, arrowIcon, icon, shape, filterable, clearable, toggleDrop, isFocus } = this; let childNode = []; if (arrowIcon == undefined) { arrowIcon = ChevronDown; } let childs = this.getOptions(); let label = null; let values = isNotEmpty(currentValue) ? currentValue : multiple ? [] : ""; if (multiple) { if (currentValue.length) { let labels = []; values.forEach((value) => { let label = this.getLabel(childs, value); if (!label) return; labels.push({ label, key: `label_${value}`, value }); }); label = labels; } else { label = []; } } else { label = this.getLabel(childs, values); } const classes = [ "k-select", { "k-select-disabled": disabled, "k-select-open": opened, "k-select-borderless": bordered === false, "k-select-lg": size == "large", "k-select-sm": size == "small", "k-select-light": theme == "light", "k-select-has-icon": !!icon, "k-select-circle": shape == "circle" && !multiple, "k-select-multiple": multiple, "k-select-show-search": isFocus, "k-select-show-tags": multiple && (label || []).length, }, ]; const queryProps = { on: { input: this.searchInput, blur: () => { if (!this.opened) this.showSearch = false; this.isFocus = false; }, }, ref: "search", class: "k-select-search", attrs: { autoComplete: "off", }, }; const queryNode = ( <div v-show={this.showSearch} key="search" class="k-select-search-wrap"> <input {...queryProps} /> <span class="k-select-search-mirror" ref="mirror"> {queryKey} </span> </div> ); const loadingNode = ( <div class="k-select-loading"> <Icon type={Sync} spin /> <span>{t("k.select.loading")}</span> </div> ); const props = { ref: "overlay", props: { width: this.selectWidth, value: opened, selection: this.$el, transfer: true, extendWidth: true, transitionName: "k-select", className: [ "k-select-dropdown", { "k-select-dropdown-multiple": this.multiple, "k-select-dropdown-sm": size == "small", }, ], }, on: { input: (e) => { this.opened = e; }, hide: () => { this.opened = false; setTimeout(() => { this.hideDrop(); }, 300); }, }, }; childs = this.getOptions(true); let overlay = <Drop {...props}>{this.loading ? loadingNode : !childs.length ? <Empty onClick={this.emptyClick} description={this.emptyText} /> : <ul>{childs}</ul>}</Drop>; label = multiple ? label || [] : label; placeholder = placeholder || t("k.select.placeholder"); const placeNode = placeholder && !isNotEmpty(label) && !queryKey ? <div class="k-select-placeholder">{placeholder}</div> : null; const tags = multiple ? label.map((c, i) => { return ( <span class="k-select-tag" key={c.key}> {c.label} <Icon type={Close} onClick={(e) => removeTag(e, c)} /> </span> ); }) : null; const labelStyle = { // opacity: this.showSearch ? .4 : 1, display: queryKey.length ? "none" : "", }; const labelsNode = multiple ? ( [ <div class="k-select-labels" name="k-select-tag"> {tags} {queryNode} </div>, ] ) : label ? ( <div class="k-select-label" style={labelStyle}> {label} </div> ) : null; let isSearch = "search" in this.$listeners; childNode.push(labelsNode); placeNode && childNode.push(placeNode); if ((filterable || isSearch) && !multiple) { childNode.push(queryNode); } // label = "1" const styles = { width: `${this.width}px` }; let showClear = !disabled && clearable && isNotEmpty(label) && label.length > 0; classes[1]["k-select-has-clear"] = showClear; const iconNodes = []; if (!isSearch && showArrow) { iconNodes.push(<Icon class="k-select-arrow" type={arrowIcon} />); } if (showClear) { iconNodes.push(<Icon class="k-select-clearable" type={CloseCircle} onClick={clear} />); } // console.log(iconNodes) iconNodes.push(" "); return ( <div tabIndex="0" class={classes} style={styles} onClick={toggleDrop} ref="rel"> {icon ? <Icon type={icon} class="k-select-icon" /> : null} <div class="k-select-selection">{childNode}</div> {iconNodes} {overlay} </div> ); }, };