add vc-cascader
parent
dd1266ee61
commit
de23e4de11
|
@ -62,13 +62,18 @@ export function cloneElement (n, nodeProps, deep) {
|
|||
const node = cloneVNode(ele, deep)
|
||||
const { props = {}, key, on = {}, children } = nodeProps
|
||||
const data = node.data || {}
|
||||
const { style = data.style,
|
||||
class: cls = data.class,
|
||||
attrs = data.attrs,
|
||||
const { style = {},
|
||||
class: cls = {},
|
||||
attrs = {},
|
||||
ref,
|
||||
domProps = data.domProps,
|
||||
domProps = {},
|
||||
} = nodeProps
|
||||
node.data = Object.assign({}, data, { style, attrs, class: cls, domProps })
|
||||
node.data = Object.assign({}, data, {
|
||||
style: { ...data.style, ...style },
|
||||
attrs: { ...data.attrs, ...attrs },
|
||||
class: { ...data.class, ...cls },
|
||||
domProps: { ...data.domProps, ...domProps },
|
||||
})
|
||||
if (node.componentOptions) {
|
||||
node.componentOptions.propsData = node.componentOptions.propsData || {}
|
||||
node.componentOptions.listeners = node.componentOptions.listeners || {}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<script>
|
||||
import React, { Component, cloneElement } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Trigger from 'rc-trigger';
|
||||
import Menus from './Menus';
|
||||
import KeyCode from 'rc-util/lib/KeyCode';
|
||||
import arrayTreeFilter from 'array-tree-filter';
|
||||
import shallowEqualArrays from 'shallow-equal/arrays';
|
||||
import PropTypes from '../_util/vue-types'
|
||||
import Trigger from '../trigger'
|
||||
import Menus from './Menus'
|
||||
import KeyCode from '../_util/KeyCode'
|
||||
import arrayTreeFilter from 'array-tree-filter'
|
||||
import shallowEqualArrays from 'shallow-equal/arrays'
|
||||
import { hasProp, getEvents } from '../_util/props-util'
|
||||
import BaseMixin from '../_util/BaseMixin'
|
||||
import { cloneElement } from '../_util/vnode'
|
||||
|
||||
const BUILT_IN_PLACEMENTS = {
|
||||
bottomLeft: {
|
||||
|
@ -40,274 +42,278 @@ const BUILT_IN_PLACEMENTS = {
|
|||
adjustY: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
class Cascader extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
let initialValue = [];
|
||||
if ('value' in props) {
|
||||
initialValue = props.value || [];
|
||||
} else if ('defaultValue' in props) {
|
||||
initialValue = props.defaultValue || [];
|
||||
export default {
|
||||
props: {
|
||||
value: PropTypes.array,
|
||||
defaultValue: PropTypes.array,
|
||||
options: PropTypes.array.def([]).isRequired,
|
||||
// onChange: PropTypes.func,
|
||||
// onPopupVisibleChange: PropTypes.func,
|
||||
popupVisible: PropTypes.bool,
|
||||
disabled: PropTypes.bool.def(false),
|
||||
transitionName: PropTypes.string.def(''),
|
||||
popupClassName: PropTypes.string.def(''),
|
||||
popupStyle: PropTypes.object.def({}),
|
||||
popupPlacement: PropTypes.string.def('bottomLeft'),
|
||||
prefixCls: PropTypes.string.def('rc-cascader'),
|
||||
dropdownMenuColumnStyle: PropTypes.object,
|
||||
builtinPlacements: PropTypes.object.def(BUILT_IN_PLACEMENTS),
|
||||
loadData: PropTypes.func,
|
||||
changeOnSelect: PropTypes.bool,
|
||||
// onKeyDown: PropTypes.func,
|
||||
expandTrigger: PropTypes.string.def('click'),
|
||||
},
|
||||
mixins: [BaseMixin],
|
||||
data () {
|
||||
let initialValue = []
|
||||
const { value, defaultValue, popupVisible } = this
|
||||
if (hasProp(this, 'value')) {
|
||||
initialValue = value || []
|
||||
} else if (hasProp(this, 'defaultValue')) {
|
||||
initialValue = defaultValue || []
|
||||
}
|
||||
|
||||
this.state = {
|
||||
popupVisible: props.popupVisible,
|
||||
activeValue: initialValue,
|
||||
value: initialValue,
|
||||
};
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if ('value' in nextProps && !shallowEqualArrays(this.props.value, nextProps.value)) {
|
||||
const newValues = {
|
||||
value: nextProps.value || [],
|
||||
activeValue: nextProps.value || [],
|
||||
};
|
||||
// allow activeValue diff from value
|
||||
// https://github.com/ant-design/ant-design/issues/2767
|
||||
if ('loadData' in nextProps) {
|
||||
delete newValues.activeValue;
|
||||
return {
|
||||
sPopupVisible: popupVisible,
|
||||
sActiveValue: initialValue,
|
||||
sValue: initialValue,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val, oldValue) {
|
||||
if (!shallowEqualArrays(val, oldValue)) {
|
||||
const newValues = {
|
||||
value: oldValue || [],
|
||||
activeValue: oldValue || [],
|
||||
}
|
||||
// allow activeValue diff from value
|
||||
// https://github.com/ant-design/ant-design/issues/2767
|
||||
if (hasProp(this, 'loadData')) {
|
||||
delete newValues.activeValue
|
||||
}
|
||||
this.setState(newValues)
|
||||
}
|
||||
this.setState(newValues);
|
||||
}
|
||||
if ('popupVisible' in nextProps) {
|
||||
},
|
||||
popupVisible (val) {
|
||||
this.setState({
|
||||
popupVisible: nextProps.popupVisible,
|
||||
});
|
||||
}
|
||||
}
|
||||
getPopupDOMNode() {
|
||||
return this.trigger.getPopupDomNode();
|
||||
}
|
||||
getCurrentLevelOptions() {
|
||||
const { options } = this.props;
|
||||
const { activeValue = [] } = this.state;
|
||||
const result = arrayTreeFilter(options, (o, level) => o.value === activeValue[level]);
|
||||
if (result[result.length - 2]) {
|
||||
return result[result.length - 2].children;
|
||||
}
|
||||
return [...options].filter(o => !o.disabled);
|
||||
}
|
||||
getActiveOptions(activeValue) {
|
||||
return arrayTreeFilter(this.props.options, (o, level) => o.value === activeValue[level]);
|
||||
}
|
||||
setPopupVisible = (popupVisible) => {
|
||||
if (!('popupVisible' in this.props)) {
|
||||
this.setState({ popupVisible });
|
||||
}
|
||||
// sync activeValue with value when panel open
|
||||
if (popupVisible && !this.state.visible) {
|
||||
this.setState({
|
||||
activeValue: this.state.value,
|
||||
});
|
||||
}
|
||||
this.props.onPopupVisibleChange(popupVisible);
|
||||
}
|
||||
handleChange = (options, setProps, e) => {
|
||||
if (e.type !== 'keydown' || e.keyCode === KeyCode.ENTER) {
|
||||
this.props.onChange(options.map(o => o.value), options);
|
||||
this.setPopupVisible(setProps.visible);
|
||||
}
|
||||
}
|
||||
handlePopupVisibleChange = (popupVisible) => {
|
||||
this.setPopupVisible(popupVisible);
|
||||
}
|
||||
handleMenuSelect = (targetOption, menuIndex, e) => {
|
||||
sPopupVisible: val,
|
||||
})
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPopupDOMNode () {
|
||||
return this.$refs.trigger.getPopupDomNode()
|
||||
},
|
||||
getCurrentLevelOptions () {
|
||||
const { options, sActiveValue = [] } = this
|
||||
const result = arrayTreeFilter(options, (o, level) => o.value === sActiveValue[level])
|
||||
if (result[result.length - 2]) {
|
||||
return result[result.length - 2].children
|
||||
}
|
||||
return [...options].filter(o => !o.disabled)
|
||||
},
|
||||
getActiveOptions (activeValue) {
|
||||
return arrayTreeFilter(this.options, (o, level) => o.value === activeValue[level])
|
||||
},
|
||||
setPopupVisible (popupVisible) {
|
||||
if (!hasProp(this, 'popupVisible')) {
|
||||
this.setState({ sPopupVisible: popupVisible })
|
||||
}
|
||||
// sync activeValue with value when panel open
|
||||
if (popupVisible && !this.sVisible) {
|
||||
this.setState({
|
||||
sActiveValue: this.sValue,
|
||||
})
|
||||
}
|
||||
this.__emit('popupVisibleChange', popupVisible)
|
||||
},
|
||||
handleChange (options, setProps, e) {
|
||||
if (e.type !== 'keydown' || e.keyCode === KeyCode.ENTER) {
|
||||
this.__emit('change', options.map(o => o.value), options)
|
||||
this.setPopupVisible(setProps.visible)
|
||||
}
|
||||
},
|
||||
handlePopupVisibleChange (popupVisible) {
|
||||
this.setPopupVisible(popupVisible)
|
||||
},
|
||||
handleMenuSelect (targetOption, menuIndex, e) {
|
||||
// Keep focused state for keyboard support
|
||||
const triggerNode = this.trigger.getRootDomNode();
|
||||
if (triggerNode && triggerNode.focus) {
|
||||
triggerNode.focus();
|
||||
}
|
||||
const { changeOnSelect, loadData, expandTrigger } = this.props;
|
||||
if (!targetOption || targetOption.disabled) {
|
||||
return;
|
||||
}
|
||||
let { activeValue } = this.state;
|
||||
activeValue = activeValue.slice(0, menuIndex + 1);
|
||||
activeValue[menuIndex] = targetOption.value;
|
||||
const activeOptions = this.getActiveOptions(activeValue);
|
||||
if (targetOption.isLeaf === false && !targetOption.children && loadData) {
|
||||
if (changeOnSelect) {
|
||||
this.handleChange(activeOptions, { visible: true }, e);
|
||||
const triggerNode = this.$refs.trigger.getRootDomNode()
|
||||
if (triggerNode && triggerNode.focus) {
|
||||
triggerNode.focus()
|
||||
}
|
||||
this.setState({ activeValue });
|
||||
loadData(activeOptions);
|
||||
return;
|
||||
}
|
||||
const newState = {};
|
||||
if (!targetOption.children || !targetOption.children.length) {
|
||||
this.handleChange(activeOptions, { visible: false }, e);
|
||||
// set value to activeValue when select leaf option
|
||||
newState.value = activeValue;
|
||||
const { changeOnSelect, loadData, expandTrigger } = this
|
||||
if (!targetOption || targetOption.disabled) {
|
||||
return
|
||||
}
|
||||
let { sActiveValue } = this
|
||||
sActiveValue = sActiveValue.slice(0, menuIndex + 1)
|
||||
sActiveValue[menuIndex] = targetOption.value
|
||||
const activeOptions = this.getActiveOptions(sActiveValue)
|
||||
if (targetOption.isLeaf === false && !targetOption.children && loadData) {
|
||||
if (changeOnSelect) {
|
||||
this.handleChange(activeOptions, { visible: true }, e)
|
||||
}
|
||||
this.setState({ sActiveValue })
|
||||
loadData(activeOptions)
|
||||
return
|
||||
}
|
||||
const newState = {}
|
||||
if (!targetOption.children || !targetOption.children.length) {
|
||||
this.handleChange(activeOptions, { visible: false }, e)
|
||||
// set value to activeValue when select leaf option
|
||||
newState.sValue = sActiveValue
|
||||
// add e.type judgement to prevent `onChange` being triggered by mouseEnter
|
||||
} else if (changeOnSelect && (e.type === 'click' || e.type === 'keydown')) {
|
||||
if (expandTrigger === 'hover') {
|
||||
this.handleChange(activeOptions, { visible: false }, e);
|
||||
} else {
|
||||
this.handleChange(activeOptions, { visible: true }, e);
|
||||
} else if (changeOnSelect && (e.type === 'click' || e.type === 'keydown')) {
|
||||
if (expandTrigger === 'hover') {
|
||||
this.handleChange(activeOptions, { visible: false }, e)
|
||||
} else {
|
||||
this.handleChange(activeOptions, { visible: true }, e)
|
||||
}
|
||||
// set value to activeValue on every select
|
||||
newState.sValue = sActiveValue
|
||||
}
|
||||
// set value to activeValue on every select
|
||||
newState.value = activeValue;
|
||||
}
|
||||
newState.activeValue = activeValue;
|
||||
// not change the value by keyboard
|
||||
if ('value' in this.props ||
|
||||
newState.sActiveValue = sActiveValue
|
||||
// not change the value by keyboard
|
||||
if (hasProp(this, 'value') ||
|
||||
(e.type === 'keydown' && e.keyCode !== KeyCode.ENTER)) {
|
||||
delete newState.value;
|
||||
}
|
||||
this.setState(newState);
|
||||
}
|
||||
handleKeyDown = (e) => {
|
||||
const { children } = this.props;
|
||||
// https://github.com/ant-design/ant-design/issues/6717
|
||||
// Don't bind keyboard support when children specify the onKeyDown
|
||||
if (children && children.props.onKeyDown) {
|
||||
children.props.onKeyDown(e);
|
||||
return;
|
||||
}
|
||||
const activeValue = [...this.state.activeValue];
|
||||
const currentLevel = activeValue.length - 1 < 0 ? 0 : activeValue.length - 1;
|
||||
const currentOptions = this.getCurrentLevelOptions();
|
||||
const currentIndex = currentOptions.map(o => o.value).indexOf(activeValue[currentLevel]);
|
||||
if (e.keyCode !== KeyCode.DOWN &&
|
||||
delete newState.sValue
|
||||
}
|
||||
this.setState(newState)
|
||||
},
|
||||
handleKeyDown (e) {
|
||||
const { $slots } = this
|
||||
const children = $slots.default && $slots.default[0]
|
||||
// https://github.com/ant-design/ant-design/issues/6717
|
||||
// Don't bind keyboard support when children specify the onKeyDown
|
||||
if (children) {
|
||||
const keydown = getEvents(children).keydown
|
||||
if (keydown) {
|
||||
keydown(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
const activeValue = [...this.sActiveValue]
|
||||
const currentLevel = activeValue.length - 1 < 0 ? 0 : activeValue.length - 1
|
||||
const currentOptions = this.getCurrentLevelOptions()
|
||||
const currentIndex = currentOptions.map(o => o.value).indexOf(activeValue[currentLevel])
|
||||
if (e.keyCode !== KeyCode.DOWN &&
|
||||
e.keyCode !== KeyCode.UP &&
|
||||
e.keyCode !== KeyCode.LEFT &&
|
||||
e.keyCode !== KeyCode.RIGHT &&
|
||||
e.keyCode !== KeyCode.ENTER &&
|
||||
e.keyCode !== KeyCode.BACKSPACE &&
|
||||
e.keyCode !== KeyCode.ESC) {
|
||||
return;
|
||||
}
|
||||
// Press any keys above to reopen menu
|
||||
if (!this.state.popupVisible &&
|
||||
return
|
||||
}
|
||||
// Press any keys above to reopen menu
|
||||
if (!this.sPopupVisible &&
|
||||
e.keyCode !== KeyCode.BACKSPACE &&
|
||||
e.keyCode !== KeyCode.LEFT &&
|
||||
e.keyCode !== KeyCode.RIGHT &&
|
||||
e.keyCode !== KeyCode.ESC) {
|
||||
this.setPopupVisible(true);
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === KeyCode.DOWN || e.keyCode === KeyCode.UP) {
|
||||
let nextIndex = currentIndex;
|
||||
if (nextIndex !== -1) {
|
||||
if (e.keyCode === KeyCode.DOWN) {
|
||||
nextIndex += 1;
|
||||
nextIndex = nextIndex >= currentOptions.length ? 0 : nextIndex;
|
||||
this.setPopupVisible(true)
|
||||
return
|
||||
}
|
||||
if (e.keyCode === KeyCode.DOWN || e.keyCode === KeyCode.UP) {
|
||||
let nextIndex = currentIndex
|
||||
if (nextIndex !== -1) {
|
||||
if (e.keyCode === KeyCode.DOWN) {
|
||||
nextIndex += 1
|
||||
nextIndex = nextIndex >= currentOptions.length ? 0 : nextIndex
|
||||
} else {
|
||||
nextIndex -= 1
|
||||
nextIndex = nextIndex < 0 ? currentOptions.length - 1 : nextIndex
|
||||
}
|
||||
} else {
|
||||
nextIndex -= 1;
|
||||
nextIndex = nextIndex < 0 ? currentOptions.length - 1 : nextIndex;
|
||||
nextIndex = 0
|
||||
}
|
||||
} else {
|
||||
nextIndex = 0;
|
||||
activeValue[currentLevel] = currentOptions[nextIndex].value
|
||||
} else if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.BACKSPACE) {
|
||||
activeValue.splice(activeValue.length - 1, 1)
|
||||
} else if (e.keyCode === KeyCode.RIGHT) {
|
||||
if (currentOptions[currentIndex] && currentOptions[currentIndex].children) {
|
||||
activeValue.push(currentOptions[currentIndex].children[0].value)
|
||||
}
|
||||
} else if (e.keyCode === KeyCode.ESC) {
|
||||
this.setPopupVisible(false)
|
||||
return
|
||||
}
|
||||
activeValue[currentLevel] = currentOptions[nextIndex].value;
|
||||
} else if (e.keyCode === KeyCode.LEFT || e.keyCode === KeyCode.BACKSPACE) {
|
||||
activeValue.splice(activeValue.length - 1, 1);
|
||||
} else if (e.keyCode === KeyCode.RIGHT) {
|
||||
if (currentOptions[currentIndex] && currentOptions[currentIndex].children) {
|
||||
activeValue.push(currentOptions[currentIndex].children[0].value);
|
||||
if (!activeValue || activeValue.length === 0) {
|
||||
this.setPopupVisible(false)
|
||||
}
|
||||
} else if (e.keyCode === KeyCode.ESC) {
|
||||
this.setPopupVisible(false);
|
||||
return;
|
||||
}
|
||||
if (!activeValue || activeValue.length === 0) {
|
||||
this.setPopupVisible(false);
|
||||
}
|
||||
const activeOptions = this.getActiveOptions(activeValue);
|
||||
const targetOption = activeOptions[activeOptions.length - 1];
|
||||
this.handleMenuSelect(targetOption, activeOptions.length - 1, e);
|
||||
const activeOptions = this.getActiveOptions(activeValue)
|
||||
const targetOption = activeOptions[activeOptions.length - 1]
|
||||
this.handleMenuSelect(targetOption, activeOptions.length - 1, e)
|
||||
this.__emit('keydown', e)
|
||||
},
|
||||
},
|
||||
|
||||
if (this.props.onKeyDown) {
|
||||
this.props.onKeyDown(e);
|
||||
}
|
||||
}
|
||||
|
||||
saveTrigger = (node) => {
|
||||
this.trigger = node;
|
||||
}
|
||||
|
||||
render() {
|
||||
render () {
|
||||
const {
|
||||
$props, $slots, sValue, sActiveValue, handleMenuSelect,
|
||||
sPopupVisible, handlePopupVisibleChange, handleKeyDown,
|
||||
} = this
|
||||
const {
|
||||
prefixCls, transitionName, popupClassName, options, disabled,
|
||||
builtinPlacements, popupPlacement, children, ...restProps,
|
||||
} = this.props;
|
||||
builtinPlacements, popupPlacement, ...restProps } = $props
|
||||
// Did not show popup when there is no options
|
||||
let menus = <div />;
|
||||
let emptyMenuClassName = '';
|
||||
let menus = <div />
|
||||
let emptyMenuClassName = ''
|
||||
if (options && options.length > 0) {
|
||||
const menusProps = {
|
||||
props: {
|
||||
...$props,
|
||||
value: sValue,
|
||||
activeValue: sActiveValue,
|
||||
visible: sPopupVisible,
|
||||
},
|
||||
on: {
|
||||
select: handleMenuSelect,
|
||||
},
|
||||
}
|
||||
menus = (
|
||||
<Menus
|
||||
{...this.props}
|
||||
value={this.state.value}
|
||||
activeValue={this.state.activeValue}
|
||||
onSelect={this.handleMenuSelect}
|
||||
visible={this.state.popupVisible}
|
||||
{...menusProps}
|
||||
/>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
emptyMenuClassName = ` ${prefixCls}-menus-empty`;
|
||||
emptyMenuClassName = ` ${prefixCls}-menus-empty`
|
||||
}
|
||||
const triggerProps = {
|
||||
props: {
|
||||
...restProps,
|
||||
disabled: disabled,
|
||||
popupPlacement: popupPlacement,
|
||||
builtinPlacements: builtinPlacements,
|
||||
popupTransitionName: transitionName,
|
||||
action: disabled ? [] : ['click'],
|
||||
popupVisible: disabled ? false : sPopupVisible,
|
||||
prefixCls: `${prefixCls}-menus`,
|
||||
popupClassName: popupClassName + emptyMenuClassName,
|
||||
},
|
||||
on: {
|
||||
popupVisibleChange: handlePopupVisibleChange,
|
||||
},
|
||||
ref: 'trigger',
|
||||
}
|
||||
return (
|
||||
<Trigger
|
||||
ref={this.saveTrigger}
|
||||
{...restProps}
|
||||
options={options}
|
||||
disabled={disabled}
|
||||
popupPlacement={popupPlacement}
|
||||
builtinPlacements={builtinPlacements}
|
||||
popupTransitionName={transitionName}
|
||||
action={disabled ? [] : ['click']}
|
||||
popupVisible={disabled ? false : this.state.popupVisible}
|
||||
onPopupVisibleChange={this.handlePopupVisibleChange}
|
||||
prefixCls={`${prefixCls}-menus`}
|
||||
popupClassName={popupClassName + emptyMenuClassName}
|
||||
popup={menus}
|
||||
>
|
||||
{cloneElement(children, {
|
||||
onKeyDown: this.handleKeyDown,
|
||||
tabIndex: disabled ? undefined : 0,
|
||||
<Trigger {...triggerProps}>
|
||||
{$slots.default && cloneElement($slots.default[0], {
|
||||
on: {
|
||||
keydown: handleKeyDown,
|
||||
},
|
||||
attrs: {
|
||||
tabIndex: disabled ? undefined : 0,
|
||||
},
|
||||
})}
|
||||
<template slot='popup'>
|
||||
{menus}
|
||||
</template>
|
||||
</Trigger>
|
||||
);
|
||||
}
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
Cascader.defaultProps = {
|
||||
options: [],
|
||||
onChange() {},
|
||||
onPopupVisibleChange() {},
|
||||
disabled: false,
|
||||
transitionName: '',
|
||||
prefixCls: 'rc-cascader',
|
||||
popupClassName: '',
|
||||
popupPlacement: 'bottomLeft',
|
||||
builtinPlacements: BUILT_IN_PLACEMENTS,
|
||||
expandTrigger: 'click',
|
||||
};
|
||||
|
||||
Cascader.propTypes = {
|
||||
value: PropTypes.array,
|
||||
defaultValue: PropTypes.array,
|
||||
options: PropTypes.array.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
onPopupVisibleChange: PropTypes.func,
|
||||
popupVisible: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
transitionName: PropTypes.string,
|
||||
popupClassName: PropTypes.string,
|
||||
popupPlacement: PropTypes.string,
|
||||
prefixCls: PropTypes.string,
|
||||
dropdownMenuColumnStyle: PropTypes.object,
|
||||
builtinPlacements: PropTypes.object,
|
||||
loadData: PropTypes.func,
|
||||
changeOnSelect: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
onKeyDown: PropTypes.func,
|
||||
expandTrigger: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Cascader;
|
||||
|
||||
</script>
|
||||
|
|
|
@ -1,156 +1,150 @@
|
|||
<script>
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import PropTypes from '../_util/vue-types'
|
||||
import arrayTreeFilter from 'array-tree-filter'
|
||||
import { findDOMNode } from 'react-dom'
|
||||
|
||||
class Menus extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
import BaseMixin from '../_util/BaseMixin'
|
||||
|
||||
export default {
|
||||
mixins: [BaseMixin],
|
||||
props: {
|
||||
value: PropTypes.array.def([]),
|
||||
activeValue: PropTypes.array.def([]),
|
||||
options: PropTypes.array.isRequired,
|
||||
prefixCls: PropTypes.string.def('rc-cascader-menus'),
|
||||
expandTrigger: PropTypes.string.def('click'),
|
||||
// onSelect: PropTypes.func,
|
||||
visible: PropTypes.bool.def(false),
|
||||
dropdownMenuColumnStyle: PropTypes.object,
|
||||
},
|
||||
data () {
|
||||
this.menuItems = {}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.scrollActiveItemToView()
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (!prevProps.visible && this.props.visible) {
|
||||
return {}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.scrollActiveItemToView()
|
||||
}
|
||||
}
|
||||
|
||||
getOption (option, menuIndex) {
|
||||
const { prefixCls, expandTrigger } = this.props
|
||||
const onSelect = this.props.onSelect.bind(this, option, menuIndex)
|
||||
let expandProps = {
|
||||
onClick: onSelect,
|
||||
}
|
||||
let menuItemCls = `${prefixCls}-menu-item`
|
||||
const hasChildren = option.children && option.children.length > 0
|
||||
if (hasChildren || option.isLeaf === false) {
|
||||
menuItemCls += ` ${prefixCls}-menu-item-expand`
|
||||
}
|
||||
if (expandTrigger === 'hover' && hasChildren) {
|
||||
expandProps = {
|
||||
onMouseEnter: this.delayOnSelect.bind(this, onSelect),
|
||||
onMouseLeave: this.delayOnSelect.bind(this),
|
||||
onClick: onSelect,
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
visible (val) {
|
||||
if (val) {
|
||||
this.$nextTick(() => {
|
||||
this.scrollActiveItemToView()
|
||||
})
|
||||
}
|
||||
}
|
||||
if (this.isActiveOption(option, menuIndex)) {
|
||||
menuItemCls += ` ${prefixCls}-menu-item-active`
|
||||
expandProps.ref = this.saveMenuItem(menuIndex)
|
||||
}
|
||||
if (option.disabled) {
|
||||
menuItemCls += ` ${prefixCls}-menu-item-disabled`
|
||||
}
|
||||
if (option.loading) {
|
||||
menuItemCls += ` ${prefixCls}-menu-item-loading`
|
||||
}
|
||||
let title = ''
|
||||
if (option.title) {
|
||||
title = option.title
|
||||
} else if (typeof option.label === 'string') {
|
||||
title = option.label
|
||||
}
|
||||
return (
|
||||
<li
|
||||
key={option.value}
|
||||
className={menuItemCls}
|
||||
title={title}
|
||||
{...expandProps}
|
||||
>
|
||||
{option.label}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getOption (option, menuIndex) {
|
||||
const { prefixCls, expandTrigger } = this
|
||||
const onSelect = (e) => {
|
||||
this.__emit('select', option, menuIndex, e)
|
||||
}
|
||||
const expandProps = {
|
||||
attrs: {
|
||||
},
|
||||
on: {
|
||||
click: onSelect,
|
||||
},
|
||||
key: option.value,
|
||||
}
|
||||
let menuItemCls = `${prefixCls}-menu-item`
|
||||
const hasChildren = option.children && option.children.length > 0
|
||||
if (hasChildren || option.isLeaf === false) {
|
||||
menuItemCls += ` ${prefixCls}-menu-item-expand`
|
||||
}
|
||||
if (expandTrigger === 'hover' && hasChildren) {
|
||||
expandProps.on = {
|
||||
mouseenter: this.delayOnSelect.bind(this, onSelect),
|
||||
mouseleave: this.delayOnSelect.bind(this),
|
||||
click: onSelect,
|
||||
}
|
||||
}
|
||||
if (this.isActiveOption(option, menuIndex)) {
|
||||
menuItemCls += ` ${prefixCls}-menu-item-active`
|
||||
expandProps.ref = this.getMenuItemRef(menuIndex)
|
||||
}
|
||||
if (option.disabled) {
|
||||
menuItemCls += ` ${prefixCls}-menu-item-disabled`
|
||||
}
|
||||
if (option.loading) {
|
||||
menuItemCls += ` ${prefixCls}-menu-item-loading`
|
||||
}
|
||||
let title = ''
|
||||
if (option.title) {
|
||||
title = option.title
|
||||
} else if (typeof option.label === 'string') {
|
||||
title = option.label
|
||||
}
|
||||
expandProps.attrs.title = title
|
||||
expandProps.class = menuItemCls
|
||||
return (
|
||||
<li {...expandProps}>
|
||||
{option.label}
|
||||
</li>
|
||||
)
|
||||
},
|
||||
|
||||
getActiveOptions (values) {
|
||||
const activeValue = values || this.props.activeValue
|
||||
const options = this.props.options
|
||||
return arrayTreeFilter(options, (o, level) => o.value === activeValue[level])
|
||||
}
|
||||
getActiveOptions (values) {
|
||||
const activeValue = values || this.activeValue
|
||||
const options = this.options
|
||||
return arrayTreeFilter(options, (o, level) => o.value === activeValue[level])
|
||||
},
|
||||
|
||||
getShowOptions () {
|
||||
const { options } = this.props
|
||||
const result = this.getActiveOptions()
|
||||
.map(activeOption => activeOption.children)
|
||||
.filter(activeOption => !!activeOption)
|
||||
result.unshift(options)
|
||||
return result
|
||||
}
|
||||
getShowOptions () {
|
||||
const { options } = this
|
||||
const result = this.getActiveOptions()
|
||||
.map(activeOption => activeOption.children)
|
||||
.filter(activeOption => !!activeOption)
|
||||
result.unshift(options)
|
||||
return result
|
||||
},
|
||||
|
||||
delayOnSelect (onSelect, ...args) {
|
||||
if (this.delayTimer) {
|
||||
clearTimeout(this.delayTimer)
|
||||
this.delayTimer = null
|
||||
}
|
||||
if (typeof onSelect === 'function') {
|
||||
this.delayTimer = setTimeout(() => {
|
||||
onSelect(args)
|
||||
delayOnSelect (onSelect, ...args) {
|
||||
if (this.delayTimer) {
|
||||
clearTimeout(this.delayTimer)
|
||||
this.delayTimer = null
|
||||
}, 150)
|
||||
}
|
||||
}
|
||||
|
||||
scrollActiveItemToView () {
|
||||
// scroll into view
|
||||
const optionsLength = this.getShowOptions().length
|
||||
for (let i = 0; i < optionsLength; i++) {
|
||||
const itemComponent = this.menuItems[i]
|
||||
if (itemComponent) {
|
||||
const target = findDOMNode(itemComponent)
|
||||
target.parentNode.scrollTop = target.offsetTop
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof onSelect === 'function') {
|
||||
this.delayTimer = setTimeout(() => {
|
||||
onSelect(args)
|
||||
this.delayTimer = null
|
||||
}, 150)
|
||||
}
|
||||
},
|
||||
|
||||
isActiveOption (option, menuIndex) {
|
||||
const { activeValue = [] } = this.props
|
||||
return activeValue[menuIndex] === option.value
|
||||
}
|
||||
scrollActiveItemToView () {
|
||||
// scroll into view
|
||||
const optionsLength = this.getShowOptions().length
|
||||
for (let i = 0; i < optionsLength; i++) {
|
||||
const itemComponent = this.$refs[`menuItems_${i}`]
|
||||
if (itemComponent) {
|
||||
const target = itemComponent
|
||||
target.parentNode.scrollTop = target.offsetTop
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
saveMenuItem = (index) => (node) => {
|
||||
this.menuItems[index] = node
|
||||
}
|
||||
isActiveOption (option, menuIndex) {
|
||||
const { activeValue = [] } = this
|
||||
return activeValue[menuIndex] === option.value
|
||||
},
|
||||
|
||||
getMenuItemRef (index) {
|
||||
return `menuItems_${index}`
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
const { prefixCls, dropdownMenuColumnStyle } = this.props
|
||||
const { prefixCls, dropdownMenuColumnStyle } = this
|
||||
return (
|
||||
<div>
|
||||
{this.getShowOptions().map((options, menuIndex) =>
|
||||
<ul className={`${prefixCls}-menu`} key={menuIndex} style={dropdownMenuColumnStyle}>
|
||||
<ul class={`${prefixCls}-menu`} key={menuIndex} style={dropdownMenuColumnStyle}>
|
||||
{options.map(option => this.getOption(option, menuIndex))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Menus.defaultProps = {
|
||||
options: [],
|
||||
value: [],
|
||||
activeValue: [],
|
||||
onSelect () {},
|
||||
prefixCls: 'rc-cascader-menus',
|
||||
visible: false,
|
||||
expandTrigger: 'click',
|
||||
}
|
||||
|
||||
Menus.propTypes = {
|
||||
value: PropTypes.array,
|
||||
activeValue: PropTypes.array,
|
||||
options: PropTypes.array.isRequired,
|
||||
prefixCls: PropTypes.string,
|
||||
expandTrigger: PropTypes.string,
|
||||
onSelect: PropTypes.func,
|
||||
visible: PropTypes.bool,
|
||||
dropdownMenuColumnStyle: PropTypes.object,
|
||||
}
|
||||
|
||||
export default Menus
|
||||
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<script>
|
||||
/* eslint-disable no-console */
|
||||
import '../assets/index.less'
|
||||
import Cascader from '../index'
|
||||
|
||||
const addressOptions = [{
|
||||
label: '福建',
|
||||
value: 'fj',
|
||||
children: [{
|
||||
label: '福州',
|
||||
value: 'fuzhou',
|
||||
children: [{
|
||||
label: '马尾',
|
||||
value: 'mawei',
|
||||
}],
|
||||
}, {
|
||||
label: '泉州',
|
||||
value: 'quanzhou',
|
||||
}],
|
||||
}, {
|
||||
label: '浙江',
|
||||
value: 'zj',
|
||||
children: [{
|
||||
label: '杭州',
|
||||
value: 'hangzhou',
|
||||
children: [{
|
||||
label: '余杭',
|
||||
value: 'yuhang',
|
||||
}],
|
||||
}],
|
||||
}, {
|
||||
label: '北京',
|
||||
value: 'bj',
|
||||
children: [{
|
||||
label: '朝阳区',
|
||||
value: 'chaoyang',
|
||||
}, {
|
||||
label: '海淀区',
|
||||
value: 'haidian',
|
||||
}],
|
||||
}]
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
inputValue: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (value, selectedOptions) {
|
||||
console.log(value, selectedOptions)
|
||||
this.inputValue = selectedOptions.map(o => o.label).join(', ')
|
||||
},
|
||||
},
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Cascader options={addressOptions} onChange={this.onChange} transitionName='slide-up'>
|
||||
<input value={this.inputValue}/>
|
||||
</Cascader>
|
||||
)
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -3,7 +3,7 @@ const AsyncComp = () => {
|
|||
const hashs = window.location.hash.split('/')
|
||||
const d = hashs[hashs.length - 1]
|
||||
return {
|
||||
component: import(`../components/affix/demo/${d}`),
|
||||
component: import(`../components/vc-cascader/demo/${d}`),
|
||||
}
|
||||
}
|
||||
export default [
|
||||
|
|
|
@ -416,6 +416,11 @@
|
|||
"integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=",
|
||||
"dev": true
|
||||
},
|
||||
"array-tree-filter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "http://registry.npm.taobao.org/array-tree-filter/download/array-tree-filter-2.1.0.tgz",
|
||||
"integrity": "sha1-hzrAD+yDdJ8lWsjdCDgUtPYykZA="
|
||||
},
|
||||
"array-union": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "http://10.134.27.134:7001/array-union/download/array-union-1.0.2.tgz",
|
||||
|
@ -10935,6 +10940,16 @@
|
|||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"shallow-equal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "http://registry.npm.taobao.org/shallow-equal/download/shallow-equal-1.0.0.tgz",
|
||||
"integrity": "sha1-UI0YOLPeWQq4dXsBGyXkMJAJRfc="
|
||||
},
|
||||
"shallowequal": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "http://registry.npm.taobao.org/shallowequal/download/shallowequal-1.0.2.tgz",
|
||||
"integrity": "sha1-FWHb3vuMAUCBADGQhXZNo/z4P48="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npm.taobao.org/shebang-command/download/shebang-command-1.2.0.tgz",
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"add-dom-event-listener": "^1.0.2",
|
||||
"array-tree-filter": "^2.1.0",
|
||||
"classnames": "^2.2.5",
|
||||
"component-classes": "^1.2.6",
|
||||
"css-animation": "^1.4.1",
|
||||
|
@ -100,6 +101,7 @@
|
|||
"lodash.isplainobject": "^4.0.6",
|
||||
"moment": "^2.20.1",
|
||||
"omit.js": "^1.0.0",
|
||||
"shallow-equal": "^1.0.0",
|
||||
"shallowequal": "^1.0.2",
|
||||
"vue": "^2.5.13",
|
||||
"vue-clipboard2": "0.0.8",
|
||||
|
|
Loading…
Reference in New Issue