vui-design
Version:
A high quality UI Toolkit based on Vue.js
240 lines (215 loc) • 6.8 kB
JavaScript
import VuiSelectMenuItemGroup from "./select-menu-item-group";
import VuiSelectMenuItem from "./select-menu-item";
import PropTypes from "../../utils/prop-types";
import getStyle from "../../utils/getStyle";
import getClassNamePrefix from "../../utils/getClassNamePrefix";
export const createProps = () => {
return {
classNamePrefix: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
options: PropTypes.array.def([]),
multiple: PropTypes.bool.def(false),
visible: PropTypes.bool.def(false)
};
};
export default {
name: "vui-select-menu",
inject: {
vuiSelect: {
default: undefined
}
},
provide() {
return {
vuiSelectMenu: this
};
},
components: {
VuiSelectMenuItemGroup,
VuiSelectMenuItem
},
props: createProps(),
data() {
return {
pagination: {
pageSize: 8,
averageSize: 34,
range: [0, 16],
preventScrolling: false
}
};
},
computed: {
getPageListParameters() {
return {
options: this.options,
visible: this.visible,
activedMenuItemIndex: this.vuiSelect.state.activedMenuItemIndex
};
}
},
watch: {
getPageListParameters: {
immediate: true,
deep: true,
handler(value) {
this.$nextTick(() => this.getPageList());
}
}
},
methods: {
getPageList() {
//
const { vuiSelect, $props: props, pagination } = this;
const { state: vuiSelectState } = vuiSelect;
//
const paddingTop = parseInt(getStyle(this.$refs.wrapper, "paddingTop"));
const itemRectTop = vuiSelectState.activedMenuItemIndex * pagination.averageSize + paddingTop;
const itemRectBottom = itemRectTop + pagination.averageSize;
const viewRectTop = this.$refs.wrapper.scrollTop;
const viewRectBottom = viewRectTop + this.$refs.wrapper.clientHeight;
let scrollTop = vuiSelectState.activedMenuItemIndex * pagination.averageSize + paddingTop;
if (itemRectTop < viewRectTop) {
scrollTop = itemRectTop - paddingTop;
}
else if (itemRectBottom > viewRectBottom) {
scrollTop = itemRectBottom - this.$refs.wrapper.clientHeight + paddingTop;
}
else {
scrollTop = this.$refs.wrapper.scrollTop;
}
//
const reserve = pagination.pageSize / 2;
const scrollSize = scrollTop < paddingTop ? 0 : (scrollTop - paddingTop);
let startIndex = Math.floor(scrollSize / pagination.averageSize);
let endIndex = 0;
if (startIndex < reserve) {
startIndex = 0;
}
else {
startIndex = startIndex - reserve;
}
let options = props.options.slice(startIndex, props.options.length);
if (options.length < pagination.pageSize * 2) {
startIndex = Math.max(props.options.length - pagination.pageSize * 2, 0);
endIndex = props.options.length;
}
else {
endIndex = startIndex + pagination.pageSize * 2;
}
//
if (vuiSelectState.activedEventType === "navigate") {
if ((itemRectTop < viewRectTop) || (itemRectBottom > viewRectBottom)) {
this.$refs.wrapper.scrollTop = scrollTop;
}
}
//
this.pagination.range = [startIndex, endIndex];
this.pagination.preventScrolling = true;
},
handleScroll(e) {
const { $props: props, pagination } = this;
if (pagination.preventScrolling) {
this.pagination.preventScrolling = false;
}
else {
//
const paddingTop = parseInt(getStyle(e.target, "paddingTop"));
let scrollTop = e.target.scrollTop;
//
const reserve = pagination.pageSize / 2;
const scrollSize = scrollTop < paddingTop ? 0 : (scrollTop - paddingTop);
let startIndex = Math.floor(scrollSize / pagination.averageSize);
let endIndex = 0;
if (startIndex < reserve) {
startIndex = 0;
}
else {
startIndex = startIndex - reserve;
}
let options = props.options.slice(startIndex, props.options.length);
if (options.length < pagination.pageSize * 2) {
startIndex = Math.max(props.options.length - pagination.pageSize * 2, 0);
endIndex = props.options.length;
}
else {
endIndex = startIndex + pagination.pageSize * 2;
}
//
this.pagination.range = [startIndex, endIndex];
this.pagination.preventScrolling = false;
}
},
handleActiveMenuItem(option) {
if (option.disabled) {
return;
}
this.$emit("active", option);
},
handleClickMenuItem(option) {
const { $props: props } = this;
if (option.disabled) {
return;
}
if (props.multiple) {
const index = props.value.findIndex(target => target.value === option.value);
if (index === -1) {
this.$emit("select", option);
}
else {
this.$emit("deselect", option);
}
}
else {
this.$emit("select", option);
}
}
},
render(h) {
const { $props: props, state, pagination } = this;
const { handleScroll, handleActiveMenuItem, handleClickMenuItem } = this;
const options = props.options.slice(pagination.range[0], pagination.range[1]);
// class
const classNamePrefix = getClassNamePrefix(props.classNamePrefix, "menu");
let classes = {};
classes.elWrapper = `${classNamePrefix}-wrapper`;
classes.el = `${classNamePrefix}`;
// style
let styles = {};
styles.el = {
height: (props.options.length * pagination.averageSize) + "px",
paddingTop: (pagination.range[0] * pagination.averageSize) + "px"
};
// render
return (
<div ref="wrapper" class={classes.elWrapper} onScroll={handleScroll}>
<div class={classes.el} style={styles.el}>
{
options.map(option => {
if (option.type === "option-group") {
return (
<VuiSelectMenuItemGroup
key={option.key}
classNamePrefix={classNamePrefix}
data={option}
/>
);
}
else if (option.type === "option" || option.type === "keyword") {
return (
<VuiSelectMenuItem
key={option.key}
classNamePrefix={classNamePrefix}
data={option}
onMouseenter={handleActiveMenuItem}
onClick={handleClickMenuItem}
/>
);
}
})
}
</div>
</div>
);
}
};