vui-design
Version:
A high quality UI Toolkit based on Vue.js
498 lines (447 loc) • 13.7 kB
JavaScript
import VuiCascaderSelection from "./cascader-selection";
import VuiCascaderDropdown from "./cascader-dropdown";
import VuiCascaderEmpty from "./cascader-empty";
import VuiCascaderMenuList from "./cascader-menu-list";
import VuiCascaderMenu from "./cascader-menu";
import Emitter from "../../mixins/emitter";
import PropTypes from "../../utils/prop-types";
import is from "../../utils/is";
import clone from "../../utils/clone";
import getClassNamePrefix from "../../utils/getClassNamePrefix";
import utils from "./utils";
export const createProps = () => {
return {
classNamePrefix: PropTypes.string,
size: PropTypes.oneOf(["small", "medium", "large"]),
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
value: PropTypes.array.def([]),
options: PropTypes.array.def([]),
expandTrigger: PropTypes.oneOf(["click", "hover"]).def("click"),
optionKeys: PropTypes.object.def(utils.optionKeys),
formatter: PropTypes.func.def((labels, options) => labels.join(" / ")),
changeOnSelect: PropTypes.bool.def(false),
bordered: PropTypes.bool.def(true),
searchable: PropTypes.bool.def(false),
filter: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]).def(true),
filterOptionProp: PropTypes.string.def("label"),
notFoundText: PropTypes.string,
clearable: PropTypes.bool.def(false),
disabled: PropTypes.bool.def(false),
placement: PropTypes.oneOf(["top", "top-start", "top-end", "bottom", "bottom-start", "bottom-end"]).def("bottom-start"),
animation: PropTypes.string.def("vui-cascader-dropdown-scale"),
dropdownClassName: PropTypes.string,
dropdownAutoWidth: PropTypes.bool.def(true),
getPopupContainer: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]).def(() => document.body),
beforeSelect: PropTypes.func,
validator: PropTypes.bool.def(true)
};
};
export default {
name: "vui-cascader",
inject: {
vuiForm: {
default: undefined
},
vuiInputGroup: {
default: undefined
}
},
provide() {
return {
vuiCascader: this
};
},
components: {
VuiCascaderSelection,
VuiCascaderDropdown,
VuiCascaderEmpty,
VuiCascaderMenuList,
VuiCascaderMenu
},
mixins: [
Emitter
],
model: {
prop: "value",
event: "input"
},
props: createProps(),
data() {
const { $props: props } = this;
const optionKeys = utils.getOptionKeys(props.optionKeys);
const state = {
hovered: false,
focused: false,
actived: false,
searching: false,
keyword: "",
value: this.getValue({
value: props.value,
options: props.options,
optionKeys: optionKeys
}),
options: []
};
return {
state
};
},
watch: {
value(value) {
const { $props: props } = this;
const optionKeys = utils.getOptionKeys(props.optionKeys);
this.state.value = this.getValue({
value: value,
options: props.options,
optionKeys: optionKeys
});
},
options(value) {
const { $props: props } = this;
const optionKeys = utils.getOptionKeys(props.optionKeys);
this.state.value = this.getValue({
value: props.value,
options: value,
optionKeys: optionKeys
});
}
},
methods: {
getValue(props) {
let value = clone(props.value);
let result = [];
if (!value.length) {
return result;
}
const { value: valueKey, children: childrenKey } = props.optionKeys;
const target = value.shift();
const option = props.options.find(option => option[valueKey] === target);
if (option) {
result = result.concat(clone(option));
if (option[childrenKey]) {
result = result.concat(this.getValue({
value: value,
options: option[childrenKey],
optionKeys: props.optionKeys
}));
}
}
return result;
},
getFilteredOptions(state, props) {
const optionKeys = utils.getOptionKeys(props.optionKeys);
const options = utils.flatten(null, props.options, optionKeys);
const predicate = is.function(props.filter) ? props.filter : utils.filter;
let list = [];
options.forEach(option => {
if (!props.changeOnSelect && !option.leaf) {
return;
}
if (!predicate(state.keyword, option, optionKeys[props.filterOptionProp])) {
return;
}
let item = {
...option
};
item[optionKeys.label] = item[optionKeys.label].replace(new RegExp(state.keyword, "g"), "<b>" + state.keyword + "</b>");
list.push(item);
});
return list;
},
getPopupReference() {
return this.$refs.selection.$el;
},
focus() {
this.$refs.selection && this.$refs.selection.focus();
},
blur() {
this.$refs.selection && this.$refs.selection.blur();
},
handleMouseenter(e) {
this.state.hovered = true;
this.$emit("mouseenter", e);
},
handleMouseleave(e) {
this.state.hovered = false;
this.$emit("mouseleave", e);
},
handleClick(e) {
const { $props: props, state } = this;
this.state.actived = props.searchable ? true : !state.actived;
},
handleFocus(e) {
this.state.focused = true;
this.$emit("focus", e);
},
handleBlur(e) {
const keyword = "";
this.state.focused = false;
this.state.actived = false;
this.state.keyword = keyword;
this.$emit("blur", e);
},
handleKeydown(e) {
const keyCode = e.keyCode;
if (!this.state.actived && [38, 40].indexOf(keyCode) > -1) {
e.preventDefault();
this.state.actived = true;
}
},
handleInput(e) {
if (/^composition(start|update)?$/g.test(e.type)) {
this.compositing = true;
}
else if (/^composition(end)?$/g.test(e.type)) {
this.compositing = false;
}
if (this.compositing) {
return;
}
const { $props: props, state } = this;
const keyword = e.target.value;
const searching = keyword !== "";
this.state.actived = true;
this.state.searching = searching;
this.state.keyword = keyword;
this.state.options = searching ? this.getFilteredOptions(state, props) : [];
},
handleClear(e) {
const { $props: props } = this;
const keyword = "";
const value = [];
this.state.searching = false;
this.state.keyword = keyword;
this.state.value = value;
this.state.options = [];
this.$emit("input", value);
this.$emit("change", value);
if (props.validator) {
this.dispatch("vui-form-item", "change", value);
}
},
handleResize(e) {
const callback = () => {
if (!this.$refs.dropdown) {
return;
}
this.$refs.dropdown.reregister();
}
this.$nextTick(callback);
},
handleBeforeOpen() {
this.$emit("beforeOpen");
},
handleAfterOpen() {
this.$emit("afterOpen");
},
handleBeforeClose() {
this.$emit("beforeClose");
},
handleAfterClose() {
this.state.searching = false;
this.state.options = [];
this.$emit("afterClose");
},
handleMenuListSelect(options) {
const { $props: props } = this;
const optionKeys = utils.getOptionKeys(props.optionKeys);
const option = options[options.length - 1];
const keyword = "";
const value = options.map(option => option[optionKeys.value]);
const callback = () => {
this.state.actived = option && option.children && option.children.length > 0;
this.state.keyword = keyword;
this.state.value = options;
this.$emit("input", value);
this.$emit("change", value);
if (props.validator) {
this.dispatch("vui-form-item", "change", value);
}
};
let hook = true;
if (is.function(props.beforeSelect)) {
hook = props.beforeSelect(value, options);
}
if (is.boolean(hook) && hook === false) {
return;
}
if (is.promise(hook)) {
hook.then(() => callback()).catch(error => {});
}
else {
callback();
}
},
handleMenuSelect(level, data) {
const { $props: props } = this;
const optionKeys = utils.getOptionKeys(props.optionKeys);
const keyword = "";
const value = data.path.map(option => option[optionKeys.value]);
const callback = () => {
this.state.actived = false;
this.state.keyword = keyword;
this.state.value = data.path;
this.$emit("input", value);
this.$emit("change", value);
if (props.validator) {
this.dispatch("vui-form-item", "change", value);
}
};
let hook = true;
if (is.function(props.beforeSelect)) {
hook = props.beforeSelect(value, data.path);
}
if (is.boolean(hook) && hook === false) {
return;
}
if (is.promise(hook)) {
hook.then(() => callback()).catch(error => {});
}
else {
callback();
}
}
},
render(h) {
const { vuiForm, vuiInputGroup, $props: props, state } = this;
const { handleMouseenter, handleMouseleave, handleClick, handleFocus, handleBlur, handleKeydown, handleInput, handleClear, handleResize } = this;
const { handleBeforeOpen, handleAfterOpen, handleBeforeClose, handleAfterClose } = this;
const { handleMenuListSelect, handleMenuSelect } = this;
// size: self > vuiInputGroup > vuiForm > vui
let size;
if (props.size) {
size = props.size;
}
else if (vuiInputGroup && vuiInputGroup.size) {
size = vuiInputGroup.size;
}
else if (vuiForm && vuiForm.size) {
size = vuiForm.size;
}
else {
size = "medium";
}
// disabled: vuiForm > vuiInputGroup > self
let disabled;
if (vuiForm && vuiForm.disabled) {
disabled = vuiForm.disabled;
}
else if (vuiInputGroup && vuiInputGroup.disabled) {
disabled = vuiInputGroup.disabled;
}
else {
disabled = props.disabled;
}
// optionKeys
const optionKeys = utils.getOptionKeys(props.optionKeys);
// options
let options = [];
if (state.searching) {
options = state.options;
}
else {
options = props.options;
}
// dropdownVisible
const dropdownVisible = state.actived;
// dropdownAutoWidth
let dropdownAutoWidth = props.dropdownAutoWidth;
if (options.length === 0) {
dropdownAutoWidth = false;
}
else if (state.searching === false) {
dropdownAutoWidth = true;
}
// class
const classNamePrefix = getClassNamePrefix(props.classNamePrefix, "cascader");
let classes = {};
classes.el = {
[`${classNamePrefix}`]: true,
[`${classNamePrefix}-${size}`]: size,
[`${classNamePrefix}-bordered`]: props.bordered,
[`${classNamePrefix}-hovered`]: state.hovered,
[`${classNamePrefix}-focused`]: state.focused,
[`${classNamePrefix}-actived`]: state.actived,
[`${classNamePrefix}-disabled`]: disabled
};
// render
let menu;
if (options.length === 0) {
menu = (
<VuiCascaderEmpty
classNamePrefix={classNamePrefix}
notFoundText={props.notFoundText}
/>
);
}
else if (state.searching) {
menu = (
<VuiCascaderMenu
classNamePrefix={classNamePrefix}
value={state.value[state.value.length - 1]}
options={options}
optionKeys={optionKeys}
dangerouslyUseHTMLString={true}
onSelect={handleMenuSelect}
/>
);
}
else {
menu = (
<VuiCascaderMenuList
classNamePrefix={classNamePrefix}
value={state.value}
options={options}
optionKeys={optionKeys}
expandTrigger={props.expandTrigger}
changeOnSelect={props.changeOnSelect}
onSelect={handleMenuListSelect}
/>
);
}
return (
<div class={classes.el}>
<VuiCascaderSelection
ref="selection"
classNamePrefix={classNamePrefix}
placeholder={props.placeholder}
value={state.value}
optionKeys={optionKeys}
formatter={props.formatter}
searchable={props.searchable}
keyword={state.keyword}
clearable={props.clearable}
hovered={state.hovered}
focused={props.searchable && state.actived}
disabled={disabled}
onMouseenter={handleMouseenter}
onMouseleave={handleMouseleave}
onClick={handleClick}
onFocus={handleFocus}
onBlur={handleBlur}
onKeydown={handleKeydown}
onInput={handleInput}
onClear={handleClear}
onResize={handleResize}
/>
<VuiCascaderDropdown
ref="dropdown"
classNamePrefix={classNamePrefix}
visible={dropdownVisible}
class={props.dropdownClassName}
placement={props.placement}
autoWidth={dropdownAutoWidth}
animation={props.animation}
getPopupReference={this.getPopupReference}
getPopupContainer={props.getPopupContainer}
onBeforeOpen={handleBeforeOpen}
onAfterOpen={handleAfterOpen}
onBeforeClose={handleBeforeClose}
onAfterClose={handleAfterClose}
>
{menu}
</VuiCascaderDropdown>
</div>
);
}
};